forked from: ポッドキャスト(enclosure要素)取得のテスト
forked from ポッドキャスト(enclosure要素)取得のテスト (diff: 70)
ActionScript3 source code
/**
* Copyright ohisama ( http://wonderfl.net/user/ohisama )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/qcBp
*/
// forked from riafeed's ポッドキャスト(enclosure要素)取得のテスト
/**
* ポッドキャストで使われているRSS2.0のenclosure要素およびATOMのlink rel="enclosure"要素を取得して、
* その要素に定義されているメディアファイルのURLを列挙するテストです。
*/
package
{
import com.adobe.serialization.json.JSON;
import flash.display.Sprite;
import flash.display.Sprite;
import com.bit101.components.PushButton;
import flash.events.*;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.text.TextField;
import flash.text.TextFieldType;
public class FlashTest extends Sprite
{
private var _text:TextField;
private var _input:TextField;
private var _btn:PushButton;
public static var dbg:TextField;
public function FlashTest():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
_input = new TextField();
_input.type = TextFieldType.INPUT;
_input.border = true;
_input.borderColor = 0x999999;
_input.height = 20;
_input.width = 300;
addChild(_input);
_btn = new PushButton(stage, 305, 0, "load", PushEventHandler);
_text = new TextField();
_text.width = _text.height = 465;
_text.wordWrap = true;
_text.y = 20;
addChild(_text);
}
public function PushEventHandler(e:Event):void
{
_text.text = "";
dbg = _text;
Feed.load(
_input.text,
function(feed:String):void
{
_text.appendText("enclosure一覧\r\n");
//_text.appendText(feed + "\r\n");
var feedobj:Object = Feed.parse(feed);
var entry:Feed;
var enclosure:Object;
for each(entry in feedobj.entry)
{
//_text.appendText(entry.content + "\r\n");
for each(enclosure in entry.enclosure)
{
_text.appendText(enclosure.href + "\r\n");
}
}
},
function(e:IOErrorEvent):void
{
_text.text = e.toString();
},
10
);
}
}
}
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLLoaderDataFormat;
import flash.utils.escapeMultiByte;
import com.adobe.serialization.json.JSON;
/**
* フィード解析クラス
*
* ActionScript Syndication Library
* http://www.libspark.org/wiki/Syndication
*
* を扱いやすいように一つのクラスにまとめたものです
*/
class Feed
{
public namespace atom = "http://www.w3.org/2005/Atom";
public namespace atom03 = "http://purl.org/atom/ns#";
public namespace dc = "http://purl.org/dc/elements/1.1/";
public namespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
public namespace rss = "http://purl.org/rss/1.0/";
public namespace syndication = "http://purl.org/rss/1.0/modules/syndication/";
public static var contentns:Namespace = new Namespace("content", "http://purl.org/rss/1.0/modules/content/");
public static var contentqn:QName = new QName(contentns, "encoded");
use namespace rss;
use namespace rdf;
use namespace dc;
use namespace atom;
use namespace atom03;
public static const NONE:Number = -1;
public static const ATOM:Number = 0;
public static const RSS:Number = 1;
public static const RSS2:Number = 2;
public static const ATOM03:Number = 3;
/**
* コンストラクタ
*
* 直接オブジェクトを生成することは非推奨です、Feed.parseメソッドを使ってください
*/
public function Feed(type:Number = NONE, data:XML = null)
{
_type = type;
_data = data;
if (_data != null)
{
switch (_type)
{
case RSS:
case RSS2:
{
if (_data.descendants(contentqn).length() > 0)
{
_content = _data.descendants(contentqn).toString();
}
else
{
_content = _data.description.toString();
}
break;
}
case ATOM:
case ATOM03:
{
_content = _data.content.toString();
}
}
var temp:String = "<body>" + _content + "</body>";
//エントリの内容をXMLオブジェクトに変換します
//HTMLとしては正しいがXMLでは整形式にならないHTMLをある程度変換しますが、
//それでも変換できない場合はnullになります
try
{
_contentXML = new XML(temp);
//エントリの内容のテキスト表現を取得します
_contentText = getText(_content);
}
catch (e:Error)
{
temp = convertHtml(temp);
try
{
_contentXML = new XML(temp);
_contentText = getText(temp);
}
catch (e:Error)
{
_contentXML = null;
_contentText = getText(_content);
}
}
}
}
/**
* エントリーのデータを保持します。
*/
private var _data:XML = null;
/**
* フィードのタイプを保持します
*/
private var _type:Number = NONE;
/**
* エントリ内容を保持します
*/
private var _content:String = "";
/**
* エントリ内容のテキスト表現を保持します
*/
private var _contentText:String = "";
/**
* エントリ内容を解析したXMLオブジェクトを保持します
*/
private var _contentXML:XML = null;
/**
* 作成者の情報を取得します
*/
public function get author():String
{
switch (_type)
{
case RSS:
case RSS2:
{
return _data.dc::creator.toString();
}
case ATOM:
case ATOM03:
{
return _data.author.name.toString();
}
}
return "";
}
/**
* エントリのタイトルを取得します
*/
public function get title():String {
switch (_type) {
case RSS:
case RSS2:
case ATOM:
case ATOM03: {
return _data.title.toString();
}
}
return "";
}
/**
* エントリのURLを取得します
*/
public function get link():String {
switch (_type) {
case RSS:
case RSS2: {
return _data.link.toString();
}
case ATOM:
case ATOM03: {
return _data.link.(@type.match(/^text\/html$/)).@href.toString();
}
}
return "";
}
/**
* エントリの概要を取得します
*/
public function get summary():String {
switch (_type) {
case RSS:
case RSS2: {
return _data.description.toString();
}
case ATOM:
case ATOM03: {
return _data.summary.toString();
}
}
return "";
}
/**
* エントリの内容を取得します
*/
public function get content():String {
if (_contentXML != null) {
return _contentXML.toXMLString();
}
return _content;
}
/**
* XML化していないエントリの内容を取得します
*/
public function get contentRaw():String {
return _content;
}
/**
* エントリ文章のXMLオブジェクトです
*/
public function get contentXML():XML {
return _contentXML;
}
/**
* エントリ文章のテキスト表現です
*/
public function get contentText():String {
return _contentText;
}
/**
* 公開日の情報を保持します。
*/
private var _published:Date;
/**
* 公開日を取得します
*/
public function get published():Date {
if (_published == null) {
switch (_type) {
case RSS: {
if (!!_data.dc::date.toString()) {
_published = parseW3C(_data.dc::date.toString());
}
break;
}
case RSS2: {
if (!!_data.pubDate.toString()) {
_published = parseRFC822(_data.pubDate.toString());
}
break;
}
case ATOM: {
if (!!_data.published.toString()) {
_published = parseW3C(_data.published.toString());
}
break;
}
case ATOM03: {
if (!!_data.created.toString()) {
_published = parseW3C(_data.created.toString());
}
break;
}
}
}
return _published;
}
/**
* カテゴリーの情報を保持します。
*/
private var _categories:Array;
/**
* エントリーのカテゴリー情報を取得します
*/
public function get categories():Array {
var nodeData:XML;
if (_categories == null) {
_categories = [];
switch (_type) {
case RSS: {
for each (nodeData in _data.dc::subject) {
_categories.push(nodeData.toString());
}
break;
}
case RSS2: {
for each (nodeData in _data.category) {
_categories.push(nodeData.toString());
}
break;
}
case ATOM: {
for each (nodeData in _data.category) {
_categories.push(nodeData.@term.toString());
}
break;
}
case ATOM03: {
for each (nodeData in _data.dc::subject) {
_categories.push(nodeData.toString());
}
break;
}
}
}
return _categories;
}
/**
* エンクロージャーの情報を保持します
*/
private var _enclosure:Array;
/**
* エンクロージャーを取得します
*/
public function get enclosure():Array {
var nodeData:XML;
var encdata:Object;
if (_enclosure == null) {
_enclosure = [];
switch (_type) {
case RSS2: {
if (_data..enclosure.length() == 1) {
encdata = new Object();
encdata.type = _data.enclosure.@type.toString();
encdata.length = _data.enclosure.@length.toString();
encdata.href = _data.enclosure.@url.toString();
encdata.title = "";
_enclosure.push(encdata);
}
break;
}
case ATOM:
case ATOM03: {
for each (nodeData in _data..link.(@rel == "enclosure")) {
encdata = new Object();
encdata.type = nodeData.@type.toString();
encdata.length = nodeData.@length.toString();
encdata.href = nodeData.@href.toString();
encdata.title = nodeData.@title.toString();
_enclosure.push(encdata);
}
break;
}
}
}
return _enclosure;
}
/**
* エントリの文字列表現を取得します
*/
public function toString():String {
return title;
}
/**
* エントリに含まれているimgタグのURLを取得するヘルパーメソッドです
*
* @id imgタグに設定されているid属性を指定、指定しない場合は一番始めにあるimgタグを指定したとみなす
* return 指定したimgタグのURL、存在しない場合は空文字列
*/
public function getImageURL(id:String = null):String {
if (_contentXML != null) {
if (_contentXML..img.length() > 0) {
if (id == null) {
return _contentXML..img[0].@src.toString();
} else {
var list:XMLList = _contentXML..img.(@id == id);
if (list.length() > 0) {
return list[0].@src.toString();
}
}
}
} else {
var reg:RegExp;
var dat:Object = null;
if (id == null) {
reg = /<img .*?src=[\"']?(.*?)[\"']?[ >]/i;
} else {
reg = new RegExp("<img .*?id=[\"']" + id + "[\"'] .*?src=[\"']?(.*?)[\"']?[ >]", "i");
}
dat = reg.exec(_content);
if (dat != null) {
return dat[1];
}
}
return "";
}
/**
* エントリに含まれているimgタグのURLを全て取得するヘルパーメソッドです
*
* return エントリにあるimgタグのURLを格納した配列、存在しない場合は空配列
*/
public function getImageURLs():Array {
var ret:Array = [];
if (_contentXML != null) {
if (_contentXML..img.length() > 0) {
for each (var node:XML in _contentXML..img) {
ret.push(node.@src.toString());
}
}
} else {
var reg:RegExp;
var dat:Object = null;
reg = /<img .*?src=[\"']?(.*?)[\"']?[ >]/gi;
dat = reg.exec(_content);
while (dat != null) {
ret.push(dat[1]);
dat = reg.exec(_content);
}
}
return ret;
}
/**
* エントリに含まれているaタグのURLを全て取得するヘルパーメソッドです
*
* return エントリにあるaタグのURLを格納した配列、存在しない場合は空配列
*/
public function getAnchorURLs():Array {
var ret:Array = [];
if (_contentXML != null) {
if (_contentXML..a.length() > 0) {
for each (var node:XML in _contentXML..a) {
ret.push(node.@href.toString());
}
}
} else {
var reg:RegExp;
var dat:Object = null;
reg = /<a .*?href=[\"']?(.*?)[\"']?[ >]/gi;
dat = reg.exec(_content);
while (dat != null) {
ret.push(dat[1]);
dat = reg.exec(_content);
}
}
return ret;
}
/**
* フィードをパースする
*
* @param data Feedの文字列かXML
* @return Feedオブジェクト
* @throws
* IllegalOperationError
* UnknownFeedError
*/
public static function parse(data:*):Object {
// 文字列ならばXMLへキャスト
if (data is String) {
data = XML(data);
}
// dataがXMLで無ければExceptionを投げる
if (data is XML === false) {
throw new Error('first argument must be String or XML.');
}
var localName:String = data.localName();
var version:String = data.@version.toString();
var nsList:Array = data.namespaceDeclarations();
nsList = nsList.map(function(... a):String {
return a[0].uri;
});
var feed:Object = new Object();
var adapter:int;
var base:XMLList;
var nodeData:XML;
feed.type = NONE;
feed.title = "";
feed.link = "";
feed.entry = [];
if (localName == 'rss' && !!version) {
feed.type = RSS2;
feed.title = data.channel.title.toString();
feed.link = data.channel.link.toString();
base = data.channel.item;
} else if (nsList.indexOf(rss.uri) !== -1) {
feed.type = RSS;
feed.title = data.channel.title.toString();
feed.link = data.channel.link.toString();
base = data.item;
} else if (nsList.indexOf(atom.uri) !== -1) {
feed.type = ATOM;
feed.title = data.title.toString();
feed.link = data.link.(@type.match(/^text\/html$/)).@href.toString();
base = data.entry;
} else if (nsList.indexOf(atom03.uri) !== -1) {
feed.type = ATOM03;
feed.title = data.title.toString();
feed.link = data.link.(@type.match(/^text\/html$/)).@href.toString();
base = data.entry;
}
if (feed.type != NONE) {
for each (nodeData in base) {
feed.entry.push(new Feed(feed.type, nodeData));
}
}
return feed;
}
public static const MILLISECOND:Number = 1;
public static const SECOND:Number = MILLISECOND * 1000;
public static const MINUTE:Number = SECOND * 60;
public static const HOUR:Number = MINUTE * 60;
public static const DAY:Number = HOUR * 24;
/**
* day names map.
*/
public static const DAY_NAMES_SHORT:Array = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
/**
* month name map. (short)
*/
public static const MONTH_NAMES_SHORT:Array = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
/**
* month name map. (long)
*/
public static const MONTH_NAMES_LONG:Array = [ 'January', 'Febrary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
/**
* timezone map.
*/
public static const TIMEZONES:Object = { 'ADT': -3 * HOUR, 'AST': -4 * HOUR, 'CDT': -5 * HOUR, 'CST': -6 * HOUR, 'EDT': -4 * HOUR, 'EST': -5 * HOUR, 'GMT': 0, 'MDT': -6 * HOUR, 'MST': -7 * HOUR, 'PDT': -7 * HOUR, 'PST': -8 * HOUR, 'UT': 0, 'UTC': 0, 'Z': 0, 'A': -1 * HOUR, 'M': -12 * HOUR, 'N': 1 * HOUR, 'Y': 12 * HOUR }
/**
* RFC822形式の日付文字列ををDateオブジェクトに変換します
*/
public static function parseRFC822(dateString:String):Date {
var parts:Array = dateString.split(/\s+/);
var dayNames:Array = DAY_NAMES_SHORT.map(function(... a):* {
return a[0].toLowerCase();
});
var dn:String = parts[0].toLowerCase();
var dl:int = dn.length - 1;
if ([ ',', '.' ].indexOf(dn.charAt(dl)) !== -1 || dayNames.indexOf(dn) !== -1) {
parts.shift();
}
var Y:int, m:int, d:int;
d = int(parts.shift());
m = MONTH_NAMES_SHORT.indexOf(parts.shift());
Y = int(parts.shift());
var H:int, M:int, S:int, times:Array, tzInfo:String;
// check format.
if (parts.length) {
times = parts.shift().split(':');
H = int(times.shift());
M = int(times.shift());
S = int(times.shift());
} else {
H = 0;
M = 0;
S = 0;
}
tzInfo = parts.shift() || 'GMT';
var op:int = 1, offset:Number = 0, utc:Number = Date.UTC(Y, m, d, H, M, S);
if (tzInfo.search(/\d/) === -1) {
offset = TIMEZONES[tzInfo];
} else {
if (tzInfo.length > 4) {
if (tzInfo.charAt(0) == '-') {
op = -1;
}
tzInfo = tzInfo.substr(1, 4);
}
offset = (int(tzInfo.substr(0, 2)) * HOUR + int(tzInfo.substr(2, 2)) * MINUTE) * op;
}
return new Date(utc - offset);
}
/**
* W3C形式の日付文字列をDateオブジェクトに変換します
*/
public static function parseW3C(dateString:String):Date {
var parts:Array = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([-+](\d{2})(?::?(\d{2}))|Z)$/.exec(dateString);
var utc:Number = Date.UTC(parts[1], int(parts[2]) - 1, parts[3], parts[4], parts[5], parts[6]);
var offset:Number = 0;
var tzInfo:String = parts[7];
var hour:int = int(parts[8]);
var minutes:int = int(parts[9]);
if (tzInfo && tzInfo != 'Z') {
offset = hour * HOUR + minutes * MINUTE;
if (tzInfo.charAt(0) == '-') {
offset *= -1;
}
}
return new Date(utc - offset);
}
/**
* フィードを取得するヘルパーメソッドです
*
* @url 取得したいフィードのURL
* @success 取得に成功した時に呼ばれる関数
* @error 取得に失敗したときに呼ばれる関数
* @num Google Ajax Feed APIで取得するエントリー数
* @useproxy クロスドメイン対策としてGoogle Ajax Feed APIを使ってフィードを取得するかどうかを指定
* @usecache Google Ajax Feed APIを使う際にキャッシュを有効にするどうかを指定
* @loaderobj Feedの取得に使用するローダーオブジェクト(途中停止用)
*/
public static function load(url:String, success:Function, error:Function = null, num:Number = 10, useproxy:Boolean = true, usecache:Boolean = true, loaderobj:URLLoader = null):void {
if (url == "") {
if (error != null) {
error(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "URL is Empty"));
}
return;
}
var proxystr:String = "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=" + num.toString() + "&output=xml&q=";
var seturl:String;
if (useproxy) {
var useurl:String;
if (!usecache) {
if (url.lastIndexOf("?") == -1) {
useurl = url + "?" + new Date().time.toString();
} else {
useurl = url + "&" + new Date().time.toString();
}
} else {
useurl = url;
}
seturl = proxystr + escapeMultiByte(useurl);
} else {
seturl = url;
}
var loader:URLLoader;
if (loaderobj == null) {
loader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
} else {
loader = loaderobj;
}
var listener:Object = {
//成功
_complete: function():void {
var data:String;
if (useproxy) {
var jsobj:Object = JSON.decode(loader.data);
if (jsobj.responseStatus != 200) {
if (error != null) {
error(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "FeedAPI Error Responce:" + jsobj.responseStatus));
}
return;
} else {
data = jsobj.responseData.xmlString;
}
} else {
data = loader.data;
}
success(data);
listener._destroy();
},
//IOエラー
_ioerror: function(e:IOErrorEvent):void {
if (error != null) {
error(e);
listener._destroy();
}
},
//セキュリティエラー
_securityerror: function(e:SecurityErrorEvent):void {
if (error != null) {
error(IOErrorEvent.IO_ERROR, false, false, e.text);
listener._destroy();
}
},
//イベント終了
_destroy: function():void {
loader.removeEventListener(Event.COMPLETE, listener._complete);
loader.removeEventListener(IOErrorEvent.IO_ERROR, listener._ioerror);
loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, listener._securityerror);
}//
};
loader.addEventListener(Event.COMPLETE, listener._complete);
loader.addEventListener(IOErrorEvent.IO_ERROR, listener._ioerror);
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, listener._securityerror);
loader.load(new URLRequest(seturl));
}
/**
* HTMLタグを除去するヘルパーメソッドです
*
* @val HTMLを除去する文字列
* return HTMLタグを除去した文字列
*/
public static function getText(val:String):String {
var ret:String;
ret = val.replace(/(<p( .*?)?>|<br( .*?)?>|<li( .*?)?>)/ig, "\r\n");
ret = ret.replace(/<(script|style).*?<\/(script|style)>/ig, "");
ret = ret.replace(/<.*?>/g, "");
return ret;
}
/**
* XMLとして正しくないHTMLを可能な限りXMLで扱える形式に変換します
*
* @src 変換するHTML
* return 変換したHTML
*/
public static function convertHtml(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 = {
//関係タグのネスト数をカウント
p: 0, //pタグ
uol: 0, //ulタグとolタグはまとめてカウント
li: 0, //liタグ
table: 0, //tableタグ
tr: 0, //trタグ
tdh: 0, //tdタグとthタグはまとめてカウント
tbody: 0, //tbodyタグ
thead: 0, //theadタグ
tfoot: 0, //tfootタグ
colgroup: 0, //colgroupタグ
option: 0, //optionタグ
dl: 0, //dlタグ
dd: 0, //ddタグ
tdhnest: [], //tdタグとthタグはあえてまとめてカウントしているのでどっちの終了タグを補完するかを記憶するための配列
dtnest: [], //dtタグは仕様上ネストごとに出てきたり出てこなかったりする場合がありネストごとに個別にカウントする必要があるので専用の配列を使う
stack: [], //スタック
//開始タグの処理
pushtag: function(tagname:String):void {
var nopush:Boolean = false;
//空要素をXML形式に変換(<img src=""> → <img src="" />)
switch (tagname) {
case "hr":
case "br":
case "img":
case "input":
case "param":
case "col":
case "area":
case "base":
case "link":
case "isindex":
case "meta":
case "basefont": {
tag = tag.replace(/<(.*?)>/, "<$1 />");
nopush = true;
break;
}
case "head":
case "body": {
//headタグ内でhead,bodyが出てきたらheadの閉じタグを補完
if (tagnest.stack.indexOf("head")) {
tagnest.poptag("head");
}
//bodyタグ内でhead,bodyが出てきたらbodyの閉じタグを補完(文法違反だけど一応)
if (tagnest.stack.indexOf("body")) {
tagnest.poptag("body");
}
break;
}
//段落タグ
case "p": {
if (tagnest["p"] == 1) {
tagnest.poptag("p");
}
tagnest["p"] = 1;
break;
}
//リストタグ
case "ul":
case "ol": {
tagnest["uol"]++;
break;
}
case "li": {
if (tagnest["li"] == tagnest["uol"]) {
tagnest.poptag("li");
}
tagnest["li"]++;
break;
}
//フォームの選択タグ
case "option": {
if (tagnest["option"] == 1) {
tagnest.poptag("option");
}
tagnest["option"] = 1;
break;
}
case "optgroup": {
if (tagnest["option"] == 1) {
tagnest.poptag("option");
}
break;
}
//構造化リストタグ
case "dl": {
tagnest["dl"]++;
break;
}
case "dt":
case "dd": {
if (tagnest["dd"] == tagnest["dl"]) {
tagnest.poptag("dd");
} else if (tagnest.dtnest[tagnest["dl"]] == 1) {
tagnest.poptag("dt");
}
if (tagname == "dt") {
tagnest.dtnest[tagnest["dl"]] = 1;
} else {
tagnest["dd"]++;
}
break;
}
//テーブルタグ
case "table": {
tagnest["table"]++;
break;
}
case "tr": {
if (tagnest["colgroup"] == tagnest["table"]) {
tagnest.poptag("colgroup");
}
if (tagnest["tdh"] == tagnest["table"]) {
tagnest.poptag(tagnest.tdhnest[tagnest["tdh"]]);
}
if (tagnest["tr"] == tagnest["table"]) {
tagnest.poptag("tr");
}
if (tagnest["tbody"] < tagnest["table"] && tagnest["thead"] < tagnest["table"] && tagnest["tfoot"] < tagnest["table"]) {
buf += "<tbody>";
tagnest.stack.push("tbody");
tagnest["tbody"]++;
}
tagnest["tr"]++;
break;
}
case "td":
case "th": {
if (tagnest["tdh"] == tagnest["table"]) {
tagnest.poptag(tagnest.tdhnest[tagnest["tdh"]]);
}
tagnest["tdh"]++;
tagnest.tdhnest[tagnest["tdh"]] = tagname;
break;
}
case "thead":
case "tfoot":
case "tbody": {
if (tagnest["colgroup"] == tagnest["table"]) {
tagnest.poptag("colgroup");
}
if (tagnest["tbody"] == tagnest["table"]) {
tagnest.poptag("tbody");
}
if (tagnest["thead"] == tagnest["table"]) {
tagnest.poptag("thead");
}
if (tagnest["tfoot"] == tagnest["table"]) {
tagnest.poptag("tfoot");
}
tagnest[tagname]++;
break;
}
}
if (!nopush) {
this.stack.push(tagname);
}
},
//閉じタグの処理
poptag: function(tagname:String = "", closetag:Boolean = true):String {
if (tagname != "" && tagnest.stack.indexOf(tagname) == -1) {
return "";
}
var noloop:Boolean = false;
if (tagname == "") {
noloop = true;
}
var ret:String = "";
while (this.stack.length > 0) {
ret = this.stack.pop();
switch (ret) {
case "p":
case "option": {
tagnest[ret] = 0;
break;
}
case "li":
case "dl":
case "dd":
case "table":
case "thead":
case "tfoot":
case "tbody":
case "tr": {
tagnest[ret]--;
break;
}
case "ul":
case "ol": {
tagnest["uol"]--;
break;
}
case "dt": {
tagnest.dtnest[tagnest["dl"]] = 0;
break;
}
case "td":
case "th": {
tagnest.tdhnest[tagnest["tdh"]] = "";
tagnest["tdh"]--;
break;
}
}
if (ret == tagname || noloop) {
break;
}
buf += "</" + ret + ">";
}
if (closetag) {
buf += "</" + ret + ">";
}
return ret;
},
//タグ終了文字の検索
searchclose: function():int {
var pos:int = pin;
var isquot:Boolean = false;
var isdquot:Boolean = false;
var isapos:Boolean = false;
var char:String;
while (pos < temp.length) {
char = temp.charAt(pos);
switch (char) {
case ">": {
if (!isquot) {
return pos;
}
break;
}
case "'": {
if (!isdquot) {
isquot = !isquot;
}
isapos = !isapos;
break;
}
case "\"": {
if (!isapos) {
isquot = !isquot;
}
isdquot = !isdquot;
break;
}
}
pos++;
}
return -1;
},
//タグ開始文字の検索
searchopen: function():Boolean {
var pos:int = 1;
var isquot:Boolean = false;
var isdquot:Boolean = false;
var isapos:Boolean = false;
var char:String;
while (pos < tag.length) {
char = tag.charAt(pos);
switch (char) {
case "<": {
if (!isquot) {
return true;
}
break;
}
case "'": {
if (!isdquot) {
isquot = !isquot;
}
isapos = !isapos;
break;
}
case "\"": {
if (!isapos) {
isquot = !isquot;
}
isdquot = !isdquot;
break;
}
}
pos++;
}
return false;
},
//特定の文字列の間を読み飛ばす
skip: function(str:String, term:String):Boolean {
if (temp.substr(pin, str.length) == str) {
var end:int = temp.indexOf(term, pin + 1);
if (end == -1) {
buf += temp.substring(pin);
buf += term;
idx = temp.length;
return true;
}
buf += temp.substring(pin, end + term.length);
idx = end + term.length;
return true;
}
return false;
},
//変換の終了処理
terminate: function():void {
text = temp.substring(idx);
text = text.replace(/>/g, ">");
text = text.replace(/</g, "<");
buf += text;
while (tagnest.stack.length > 0) {
tagnest.poptag();
}
}
//
}
var pop:String = "";
var text:String;
while (true) {
//タグの開始を検索
pin = temp.indexOf("<", idx);
//なかったら終了
if (pin == -1) {
tagnest.terminate();
break;
}
//あったらひとまずタグの前までの文字列を追加
buf += temp.substring(idx, pin).replace(/>/g, ">");
//XML宣言などを読み飛ばす
if (tagnest.skip("<?", "?>")) {
continue;
}
//コメントを読み飛ばす
if (tagnest.skip("<!--", "-->")) {
continue;
}
//CDATAセクションを読み飛ばす
if (tagnest.skip("<![CDATA[", "]]>")) {
continue;
}
//タグの取得
var closepos:int = tagnest.searchclose();
if (closepos == -1) {
tagnest.terminate();
break;
}
tag = temp.substring(pin, closepos + 1);
var taglength:int = tag.length;
//タグの中に<が含まれている場合は変換
if (tagnest.searchopen()) {
buf += "<";
idx += 1;
continue;
}
//タグ内から改行を取り除く
tag = tag.replace(/[\r\n]/ig, "");
//タグ名の取得
var end:int = tag.indexOf(" ");
if (end == -1) {
end = tag.length - 1;
}
tagname = tag.substring(1, end).toLowerCase();
var tagbuf:String = tag.substring(1, tag.length - 1);
tagbuf = tagbuf.replace(/</g, "<");
tagbuf = tagbuf.replace(/>/g, ">");
if (!tagname.match(/[a-z]+/)) {
buf += "<" + tagbuf + ">";
idx = pin + taglength;
continue;
}
tag = "<" + tagbuf + ">";
//引用符のない属性値に引用符を付加(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");
}
if (tagname.charAt(0) == '/') {
if (tagnest.stack.indexOf(tagname.substring(1)) == -1) {
//構造的に正しくない閉じタグは無視する
idx = pin + taglength;
continue;
}
tagnest.poptag(tagname.substring(1), false);
} else {
if (!tag.match(/\/>$/)) {
//空要素タグでなければスタックに追加
tagnest.pushtag(tagname);
}
}
//タグ名を強制的に小文字にする
buf += "<" + tagname + tag.substring(end);
idx = pin + taglength;
}
temp = buf;
return temp;
}
}