JSON形式でHTMLのDOMツリーを書く
JSON形式でツリー状のオブジェクトとか書いていると、HTMLもこれでいいじゃんっていう気になった。例えばこんな感じ。
{ tagName: "table", body: { tagName: "tbody", body: { tagName: "tr", style: "font-size:small", body: [ { tagName: "td", body: "一列目", style: "text-align:right" }, { tagName: "td", body: "2列目" }, { tagName: "td", body: "3列目" }, { tagName: "td", body: ["ヨン列目", "マジで"] } ] } } }
これをHTMLの
<table> <tbody> <tr style="font-size:small"> <td style="text-align:right">一列目</td> <td>2列目</td> <td>3列目</td> <td>ヨン列目マジで</td> </tr> </tbody> </table>
というようなDOMに変換。タグの名前をtagNameプロパティ、
以下のようなテストケースをパスすれば、一応できたっぽいと言えると思ってjsUnitで書いてみたっす。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Test loading a local HTML Document</title> <link rel="stylesheet" type="text/css" href="../css/jsUnitStyle.css"> <script language="JavaScript" type="text/javascript" src="jsUnitCore.js"></script> <script language="JavaScript" type="text/javascript" src="../js/prototype.js"></script> <script language="JavaScript" type="text/javascript" src="../js/brownie-json-dom.js"></script> <script language="JavaScript" type="text/javascript"> <!-- function test_BuildTable() { var builder = new JsonDomBuilder("body", document); var tableElement = builder.execute( { tagName: "table", body: { tagName: "tbody", body: { tagName: "tr", style: "font-size:small", body: [ { tagName: "td", body: "一列目", style: "text-align:right" }, { tagName: "td", body: "2列目" }, { tagName: "td", body: "3列目" }, { tagName: "td", body: ["ヨン列目", "マジで"] } ] } } } ); assertEquals("TABLE", tableElement.tagName); assertEquals(1, tableElement.childNodes.length); var tbody = tableElement.childNodes[0]; assertEquals("TBODY", tbody.tagName); assertEquals(1, tbody.childNodes.length); var tr1 = tbody.childNodes[0]; assertEquals("TR", tr1.tagName); assertEquals("small", tr1.style.fontSize); assertEquals(4, tr1.childNodes.length); var td1 = tr1.childNodes[0]; var td2 = tr1.childNodes[1]; var td3 = tr1.childNodes[2]; var td4 = tr1.childNodes[3]; assertEquals("TD", td1.tagName); assertEquals("right", td1.style.textAlign); assertEquals(1, td1.childNodes.length); assertEquals("#text", td1.childNodes[0].nodeName); assertEquals("一列目", td1.childNodes[0].nodeValue); assertEquals("TD", td2.tagName); assertEquals(1, td2.childNodes.length); assertEquals("#text", td2.childNodes[0].nodeName); assertEquals("2列目", td2.childNodes[0].nodeValue); assertEquals("TD", td3.tagName); assertEquals(1, td3.childNodes.length); assertEquals("#text", td3.childNodes[0].nodeName); assertEquals("3列目", td3.childNodes[0].nodeValue); assertEquals("TD", td4.tagName); assertEquals(2, td4.childNodes.length); assertEquals("#text", td4.childNodes[0].nodeName); assertEquals("ヨン列目", td4.childNodes[0].nodeValue); assertEquals("#text", td4.childNodes[1].nodeName); assertEquals("マジで", td4.childNodes[1].nodeValue); } --></script> </head> <body> <h1>JSON-DOM Tests</h1> <p>This page tests loading data documents asynchronously. To see them, take a look at the source.</p> </body> </html>
で、これをパスするクラスはこんな感じで作ってみた。
/** * brownie-json-dom.js * * uses * prototype.js * * @author T.Akima * @copyright T.Akima * @license LGPL */ JsonDomBuilder = Class.create(); JsonDomBuilder.prototype = { initialize: function( bodyPropertyName, baseDocument ) { this.bodyPropertyName = bodyPropertyName || "body"; this.baseDocument = baseDocument || document; }, execute: function( obj ) { return this.dispatchBuild(obj); }, dispatchBuild: function( obj ) { if (obj.constructor == Array) { return this.buildNodes( obj ); } else if (obj.tagName) { return this.buildNode( obj ); } else if (obj.constructor == String) { return this.buildText( obj ); } else { //throw new Error("obj have no tagName "); return this.buildText( obj.toString() ); } }, buildText: function( string ) { return this.baseDocument.createTextNode( string ); }, buildNode: function( obj ) { var result = this.baseDocument.createElement( obj.tagName ); this.applyAttributes( result, obj, ["tagName", this.bodyPropertyName] ); var body = obj[ this.bodyPropertyName ]; if (body) { var children = this.dispatchBuild( body ); if (children) { if (children.constructor != Array) children = [ children ]; for( var i = 0; i < children.length; i++ ) { result.appendChild( children[i] ); } } } return result; }, buildNodes: function( arrayObj ) { var result = new Array(); for(var i = 0; i < arrayObj.length; i++) { if (arrayObj[i]) result.push( this.dispatchBuild( arrayObj[i] ) ); } return result; }, applyAttributes: function( node, attributeObj, ignoreProperties ) { if (!attributeObj) return; for(var prop in attributeObj) { if (ignoreProperties && this._array_contains( ignoreProperties, prop )) continue; if (prop == "style" && (navigator.appVersion.indexOf("MSIE") > -1)) { var style = attributeObj[prop]; var styleObj = Style.toStyleObject( style ); for(var styleItemName in styleObj) node.style[styleItemName] = styleObj[styleItemName]; continue; } else if (prop == "className" || prop == "class") { node.className = attributeObj[prop]; } else { node.setAttribute(prop, attributeObj[prop]); } } }, _array_contains: function( arrayObj, obj ) { for(var i = 0; i < arrayObj.length; i++) { if (arrayObj[i] == obj) return true; } return false; } } Style = { /** * "text-align:right; display:block; " というようなstyleの文字列を * { textAlign: "right", display: "block" } という オブジェクトに直す。 * IEがsetAttributeでstyleを設定できないから必要になった。 */ toStyleObject: function( styleString ) { if (!styleString) return null; var result = {}; var entries = styleString.split( ";" ); for(var i = 0; i < entries.length; i++) { var items = entries[i].split( ":", 2 ); if (items.length < 1) continue; var key = Style.toJsStyleName( items[0] ); var value = items.length < 2 ? null : items[1]; result[key] = value; } return result; }, /** * text-align -> textAlign * というような変換を行う。 */ toJsStyleName: function( cssStyleName, delimeter ) { delimeter = delimeter || "-"; if (!cssStyleName) return null; var items = cssStyleName.split( delimeter ); if (items.length < 1) return ""; var result = items[0]; for(var i = 1; i < items.length; i++) { var s = items[i]; result = result + s.charAt(0).toUpperCase() + s.substring(1).toLowerCase(); } return result; } }
executeメソッドの戻り値のelementは、どのelementにもappendChildされてないので、どこかにappendChildしてやればそれなりに表示されるっていう寸法です。
一応、IE6とFirefox1.0.7でパスしたのを確認しましたが、まあきっと何か抜けてんじゃないかなと思って、ツッコミを頂くために公開してみたわけです。何か気付いたら教えてくださいませ。
・・・と、ここまで書いて今更、もう誰か作ってんじゃねーの?とか思った。既にこんなのがあったら教えてください・・・・