HTML→XHTML変換
フィードを解析するときに内容が(整形式でない)HTMLだとAS3のXMLクラスで扱えないので
HTMLを正規表現でXHTMLに変換するテスト
ネストに対応してみた
/**
* Copyright riafeed ( http://wonderfl.net/user/riafeed )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/dmfG
*/
/*
フィードを解析するときに内容が(整形式でない)HTMLだとAS3のXMLクラスで扱えないので
HTMLを正規表現でXHTMLに変換するテスト
ネストに対応してみた
*/
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.display.Sprite;
import flash.events.Event;
import com.bit101.components.PushButton;
public class FlashTest extends Sprite {
private var _text:TextField;
private var _text2:TextField;
private var _btn:PushButton;
public function FlashTest() {
// write as3 code here..
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
public function init(e:Event = null):void {
_text = new TextField();
_text.type = TextFieldType.INPUT;
_text.border = true;
_text.multiline = true
_text.width = 465;
_text.height = 120;
_text.wordWrap = true;
_text.y = 0;
addChild(_text);
_btn = new PushButton(stage, 0, 120, "Convert", PushEventHandler);
_text2 = new TextField();
_text2.width = _text2.height = 465;
_text2.wordWrap = true;
_text2.y = 150;
addChild(_text2);
}
private function PushEventHandler(e:Event):void {
var ret:String = convHTML(_text.text);
_text2.text = ret;
try {
var xmltest:XML = new XML(ret);
_text2.textColor = Number(0x000000);
}catch(e:Error){
_text2.textColor = Number(0xff0000);
_text2.appendText("\nこのXMLは整形式になっていません:" + e);
}
}
public function convHTML(src:String):String {
var temp:String = src;
//HTMLでは終了タグの省略が認められていた要素に終了タグを付加(<li>~ → <li>~</li>)
/*
考え方:
終了タグが省略できる要素だけでネストすることは物理的にありえない(判別できないので)ことを利用して
ベースとなる親要素(tr要素のベースはtbody要素といった感じで)と共にネストの深さをカウントし、
ネストの深さが同じ時にだけ終了タグを補完することで間違った位置に終了タグを補完する現象を避ける
*/
var buf:String = "";
var tag:String;
var tagname:String;
var idx:int = 0;
var pin:int = 0;
//関係タグのネスト数
var tagnest:Object =
{
html:0,
head:0,
body:-1, //bodyタグの自動補完は最初の1回だけ行うようにするために初期値が-1
p:0,
uol:0, //ulタグとolタグはまとめてカウント
li:0,
table:0,
tr:0,
tdh:0, //tdタグとthタグはまとめてカウント
tbody:0,
thead:0,
tfoot:0,
colgroup:0,
option:0,
dl:0,
dd:0
}
//tdタグとthタグはあえてまとめてカウントしているのでどっちの終了タグを補完するかを記憶するための配列
var tdhnest:Array = [];
//dtタグは仕様上ネストごとに出てきたり出てこなかったりする場合がありネストごとに個別にカウントする必要があるので専用の配列を使う
var dtnest:Array = [];
var stack:Array = [];
while(true){
//タグの開始を検索
pin = temp.indexOf("<", idx);
//なかったら終了
if(pin == -1) {
buf += temp.substring(idx);
if(tagnest["p"] == 1){
buf += "</p>";
}
if(tagnest["head"] == 1){
buf += "</head>";
}
if(tagnest["body"] == 1){
buf += "</body>";
}
if(tagnest["html"] == 1){
buf += "</html>";
}
break;
}
//あったらひとまずタグの前までの文字列を追加
buf += temp.substring(idx, pin);
//XML宣言などを読み飛ばす
if(temp.substr(pin, 2) == "<?") {
var qend:int = temp.indexOf("?>", pin + 1);
if(qend == -1) {
buf += temp.substring(pin);
break;
}
buf += temp.substring(pin, qend + 2);
idx = comend + 3;
continue;
}
//コメントを読み飛ばす
if(temp.substr(pin, 4) == "<!--") {
var comend:int = temp.indexOf("-->", pin + 1);
if(comend == -1) {
buf += temp.substring(pin);
break;
}
buf += temp.substring(pin, comend + 3);
idx = comend + 3;
continue;
}
//CDATAセクションなどを読み飛ばす
if(temp.substr(pin, 3) == "<![") {
var secend:int = temp.indexOf("]>", pin + 1);
if(secend == -1) {
buf += temp.substring(pin);
break;
}
buf += temp.substring(pin, secend + 2);
idx = secend + 2;
continue;
}
//タグの取得
tag = temp.substring(pin, temp.indexOf(">", pin) + 1);
var taglength:int = tag.length;
//タグ内から改行を取り除く
tag = tag.replace(/[\r\n]/ig, "");
//タグ名の取得
var end:int = tag.indexOf(" ");
if(end == -1) end = tag.length - 1;
tagname = tag.substring(1, end).toLowerCase();
if(!tagname.match(/[a-z]+/)) {
buf += "<" + tag.substring(1, tag.length - 1) + ">";
idx = pin + taglength;
continue;
}
//htmlタグの自動補完
if(tagnest["html"] == 0 && tagname != "html") {
if(tagname != "body" && tagname != "head") {
buf = "<html><body>" + buf;
tagnest["body"] = 1;
} else {
buf = "<html>" + buf;
}
tagnest["html"] = 1;
}
//引用符のない属性値に引用符を付加(width=200 → width="200")
while (tag.match(/(<[^>]*? [a-z]+=)([^"'][^ >]*?)([ >])/i)) {
tag = tag.replace(/(<[^>]*? [a-z]+=)([^"'][^ >]*?)([ >])/i, "$1\"$2\"$3");
}
//短縮された属性値をXML形式に変換(checked → checked="checked")
while (tag.match(/(<[^>]*? )([a-z]+?)([ >])/i)) {
tag = tag.replace(/(<[^>]*? )([a-z]+?)([ >])/i, "$1$2=\"$2\"$3");
}
//タグごとに処理
switch(tagname) {
//空要素をXML形式に変換(<img src=""> → <img src="" />)
case "hr":
case "br":
case "img":
case "input":
case "param":
case "col":
case "area":
case "base":
case "link":
case "isindex":
case "meta":
case "basefont":{
if(tag.charAt(tag.length-2) != '/'){
tag = tag.replace(/<(.*?)>/ig, "<$1 />");
}
break;
}
case "head":
case "body":
//headタグ内でhead,bodyが出てきたらheadの閉じタグを補完
if(tagnest["head"] == 1) {
buf += "</head>";
tagnest["head"] = 0;
}
//bodyタグ内でhead,bodyが出てきたらbodyの閉じタグを補完(文法違反だけど一応)
if(tagnest["body"] == 1) {
buf += "</body>";
tagnest["body"] = 0;
}
case "html":
tagnest[tagname] = 1;
break;
case "/html":
if(tagnest["p"] == 1) {
buf += "</p>";
tagnest["p"] = 0;
}
if(tagnest["head"] == 1) {
buf += "</head>";
tagnest["head"] = 0;
}
if(tagnest["body"] == 1) {
buf += "</body>";
tagnest["body"] = 0;
}
tagnest["html"] = 0;
break;
case "/body":
if(tagnest["p"] == 1) {
buf += "</p>";
tagnest["p"] = 0;
}
case "/head":
tagnest[tagname.substring(1)] = 0;
break;
//段落タグ
case "p":
if(tagnest["p"] == 1) {
buf += "</p>";
} else {
tagnest["p"] = 1;
}
break;
case "/p":
tagnest["p"] = 0;
break;
//リストタグ
case "ul":
case "ol":
tagnest["uol"]++;
break;
case "/ul":
case "/ol":
if(tagnest["li"] == tagnest["uol"]) {
buf += "</li>";
tagnest["li"]--;
}
tagnest["uol"]--;
break;
case "li":
if(tagnest["li"] == tagnest["uol"]) {
buf += "</li>";
} else {
tagnest["li"]++;
}
break;
case "/li":
tagnest["li"]--;
break;
//フォームの選択タグ
case "option":
if(tagnest["option"] == 1) {
buf += "</option>";
} else {
tagnest["option"] = 1;
}
break;
case "/option":
tagnest["option"] = 0;
break;
case "optgroup":
case "/select":
case "/optgroup":
if(tagnest["option"] == 1) {
buf += "</option>";
tagnest["option"] = 0;
}
break;
//構造化リストタグ
case "dl":
tagnest["dl"]++;
break;
case "dt":
case "dd":
case "/dl":
if(tagnest["dd"] == tagnest["dl"]) {
buf += "</dd>"
tagnest["dd"]--;
}else if(dtnest[tagnest["dl"]] == 1) {
buf += "</dt>"
dtnest[tagnest["dl"]] = 0;
}
if(tagname == "/dl"){
tagnest["dl"]--;
}else if(tagname == "dt"){
dtnest[tagnest["dl"]] = 1;
}else{
tagnest["dd"]++;
}
break;
//テーブルタグ
case "table":
tagnest["table"]++;
break;
case "/table":
if(tagnest["table"] > 0) {
if(tagnest["colgroup"] == tagnest["table"]) {
buf += "</colgroup>";
tagnest["colgroup"]--;
}
if(tagnest["tdh"] == tagnest["table"]) {
buf += "</"+ tdhnest[tagnest["tdh"]] +">";
tagnest["tdh"]--;
}
if(tagnest["tr"] == tagnest["table"]) {
buf += "</tr>";
tagnest["tr"]--;
}
if(tagnest["tbody"] == tagnest["table"]) {
buf += "</tbody>";
tagnest["tbody"]--;
}
if(tagnest["thead"] == tagnest["table"]) {
buf += "</thead>";
tagnest["thead"]--;
}
if(tagnest["tfoot"] == tagnest["table"]) {
buf += "</tfoot>";
tagnest["tfoot"]--;
}
tagnest["table"]--;
}
break;
case "tr":
if(tagnest["colgroup"] == tagnest["table"]) {
buf += "</colgroup>";
tagnest["colgroup"]--;
}
if(tagnest["tdh"] == tagnest["table"]) {
buf += "</"+ tdhnest[tagnest["tdh"]] +">";
tagnest["tdh"]--;
}
if(tagnest["tr"] == tagnest["table"]) {
buf += "</tr>";
} else {
if(tagnest["tbody"] < tagnest["table"] && tagnest["thead"] < tagnest["table"] && tagnest["tfoot"] < tagnest["table"]) {
buf += "<tbody>";
tagnest["tbody"]++;
}
tagnest["tr"]++;
}
break;
case "/tr":
if(tagnest["tdh"] == tagnest["table"]) {
buf += "</"+ tdhnest[tagnest["tdh"]] +">";
tagnest["tdh"]--;
}
tagnest["tr"]--;
break;
case "td":
case "th":
if(tagnest["tdh"] == tagnest["table"]) {
buf += "</"+ tdhnest[tagnest["tdh"]] +">";
tdhnest[tagnest["tdh"]] = tagname;
} else {
tagnest["tdh"]++;
tdhnest[tagnest["tdh"]] = tagname;
}
break;
case "/td":
case "/th":
tagnest["tdh"]--;
break;
case "thead":
case "tfoot":
case "tbody":
if(tagnest["colgroup"] == tagnest["table"]) {
buf += "</colgroup>";
tagnest["colgroup"]--;
}
if(tagnest["tbody"] == tagnest["table"]) {
buf += "</tbody>";
tagnest["tbody"]--;
}
if(tagnest["thead"] == tagnest["table"]) {
buf += "</thead>";
tagnest["thead"]--;
}
if(tagnest["tfoot"] == tagnest["table"]) {
buf += "</tfoot>";
tagnest["tfoot"]--;
}
tagnest[tagname]++;
break;
case "/thead":
case "/tfoot":
case "/tbody":
if(tagnest["tdh"] == tagnest["table"]) {
buf += "</"+ tdhnest[tagnest["tdh"]] +">";
tagnest["tdh"]--;
}
if(tagnest["tr"] == tagnest["table"]) {
buf += "</tr>";
tagnest["tr"]--;
}
if(tagnest[tagname.substring(1)] == tagnest["table"]) tagnest[tagname.substring(1)]--;
break;
}
//タグ名を強制的に小文字にする
buf += "<" + tagname + tag.substring(end);
idx = pin + taglength;
}
temp = buf;
/*
これだとネストに対応できない...
if(temp.match(/<\/p>/i) == null) {
temp = temp.replace(/(<p.*?>)/ig, "</p>$1");
temp = temp.replace(/(<p.*?>.*?)(<\/body>)/isg, "$1</p>$2");
temp = temp.replace(/(<body>.*?)<\/p>/isg, "$1");
}
if(temp.match(/<\/li>/i) == null) {
temp = temp.replace(/(<li.*?>)/ig, "</li>$1");
temp = temp.replace(/(<li.*?>.*?)(<\/ul>|<\/ol>)/isg, "$1</li>$2");
temp = temp.replace(/(<ul.*?>|<ol.*?>)(.*?)<\/li>/isg, "$1$2");
temp = temp.replace(/(<\/ul>|<\/ol>)([^<>]*?)<\/li>/isg, "$1$2");
}
if(temp.match(/<\/tr>/i) == null) {
temp = temp.replace(/(<tr.*?>)/ig, "</tr>$1");
temp = temp.replace(/(<tr.*?>.*?)(<\/thead>|<\/tfoot>|<\/tbody>|<\/table>)/isg, "$1</tr>$2");
temp = temp.replace(/(<thead.*?>|<tfoot.*?>|<tbody.*?>|<table.*?>)(.*?)<\/tr>/isg, "$1$2");
temp = temp.replace(/(<\/thead>|<\/tfoot>|<\/tbody>|<\/table>)([^<>]*?)<\/tr>/isg, "$1$2");
}
if(temp.match(/<\/td>/i) == null) {
temp = temp.replace(/(<td.*?>)/ig, "</td>$1");
temp = temp.replace(/(<td.*?>.*?)(<\/tr>)/isg, "$1</td>$2");
temp = temp.replace(/(<tr.*?>.*?)<\/td>/isg, "$1");
temp = temp.replace(/(<\/tr>[^<>]*?)<\/td>/isg, "$1");
}
if(temp.match(/<\/th>/i) == null) {
temp = temp.replace(/(<th.*?>)/ig, "</th>$1");
temp = temp.replace(/(<th.*?>.*?)(<\/tr>)/isg, "$1</th>$2");
temp = temp.replace(/(<tr.*?>.*?)<\/th>/isg, "$1");
temp = temp.replace(/(<\/tr>[^<>]*?)<\/th>/isg, "$1");
}
if(temp.match(/<\/colgroup>/i) == null) {
temp = temp.replace(/(<colgroup.*?>)/ig, "</colgroup>$1");
temp = temp.replace(/(<colgroup.*?>.*?)(<tr.*?>|<thead.*?>|<tfoot.*?>|<tbody.*?>|<\/table>)/isg, "$1</colgroup>$2");
temp = temp.replace(/(<table.*?>)(.*?)<\/colgroup>/isg, "$1$2");
}
if(temp.match(/<\/option>/i) == null) {
temp = temp.replace(/(<option.*?>)/ig, "</option>$1");
temp = temp.replace(/(<option.*?>.*?)(<\/select>|<\/optgroup>)/isg, "$1</option>$2");
temp = temp.replace(/(<select.*?>|<optgroup.*?>)(.*?)<\/option>/isg, "$1$2");
temp = temp.replace(/(<\/optgroup>)([^<>]*?)<\/option>/isg, "$1$2");
}
if(temp.match(/<\/(dt|dd)>/i) == null) {
temp = temp.replace(/(<dt.*?>|<dd.*?>)/ig, "</dt></dd>$1");
temp = temp.replace(/(<dt.*?>|<dd.*?>)(.*?)(<\/dl>)/isg, "$1$2</dt></dd>$3");
temp = temp.replace(/(<dl.*?>)(.*?)<\/dt><\/dd>/isg, "$1$2");
temp = temp.replace(/(<dt.*?>)(.*?)(<\/dt>)<\/dd>/isg, "$1$2$3");
temp = temp.replace(/(<dd.*?>)(.*?)<\/dt>(<\/dd>)/isg, "$1$2$3");
temp = temp.replace(/(<\/dl>)([^<>]*?)<\/dt><\/dd>/isg, "$1$2");
}
*/
return temp;
}
}
}