typeOf 'aki_mana'


結果、2003 - 2005 に提案された 継承に関するページが上方に表示された。

ECMAScript のバージョンが上がって、JavaScript OOP における疑似クラスの作り方も豊富になってるんだけど、特にレガシーなブラウザに対応する必要があるときは使えそうなのでメモ。


Function.prototype.inheritsFrom = function( parentClassOrObject ){ 
	if ( parentClassOrObject.constructor == Function ) 
	{ 
		//Normal Inheritance 
		this.prototype = new parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject.prototype;
	} 
	else 
	{ 
		//Pure Virtual Inheritance 
		this.prototype = parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject;
	} 
	return this;
};

ネームド関数を new 演算子で具象化する書き方なら instanceof も意図したとおりに利用できそう。
関数名=グローバルスコープ上で変数となるので、多重定義で嵌りそうなことが注意点かなと。

Object.create() で継承するパターンを取り入れるならこんな感じ?


Function.prototype.inheritsFrom = function( parentClassOrObject ){ 
	if( Object.create && typeof parentClassOrObject==="function" ){
		this.prototype = Object.create(parentClassOrObject.prototype, {
			constructor: {
				value: this,
				enumerable: false,
				writable: true,
				configurable: true
			}
		});
	}
	else if ( parentClassOrObject.constructor == Function ) 
	{ 
		//Normal Inheritance 
		this.prototype = new parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject.prototype;
	} 
	else 
	{ 
		//Pure Virtual Inheritance 
		this.prototype = parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject;
	} 
	return this;
};
webを漁ると、Object.create() は使うな。 __proto__ おススメ。といった具合でいろんな情報であふれてるんだけど。古いブラウザで取りあえずは継承の状態を作れるのが Function.prototype.inheritsFrom() の特徴。



JsonML は何度もネタにしてるんだけど、使いづらさを改善すれば簡便になるのではないかと。

JsonML のダメなところは、
  1. XMLに完全対応していない点
  2. オブジェクト解析環境の不備がある(JSON.stringify() に依存してる)点。
たとえば、こんな XML ファイルがあって、JsonML本家のライブラリでは DTD関連が処理できないのだけど


<?xml version="1.0" ?>
<!DOCTYPE members [
  <!ELEMENT members (member*)>
  <!ELEMENT member (name,addr,contact? )>
  <!ATTLIST member num CDATA #REQUIRED>
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT addr (#PCDATA)>
  <!ELEMENT contact EMPTY>
  <!ATTLIST contact tel CDATA #REQUIRED>
  <!ATTLIST contact e-mail CDATA #IMPLIED>
] >
<!-- コメント1 -->
<members>
  <member num="01">
    <name>山田太郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="xx@xxxxx" />
  </member>
  <member num="02">
    <name>山田花子</name>
    <addr>大阪府XXXX</addr>
    <contact tel="xx-xxxx-xxxx" />
  </member>
  <!--
  <member num="03">
    <name>山田次郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="yy@xxxxx" />
  </member>
  -->
</members>


これを、開発中のライブラリでJsonMLに変換->JSON出力してみるとこうなる。


[""
, ["?", {
      "target":"xml"
      "version":"1.0"
    }
  ]
, ["!"
  , " コメント1 "
  ]
, ["!DOCTYPE", {
      "rootElement":"members"
    }
  , ["!ELEMENT", {
        "name":"members"
        "content":"(member*)"
      }
    ]
  , ["!ATTLIST", {
        "targetElement":"member"
      }
    , [{"name":"num","dataType":"CDATA"}]
    ]
  , ["!ELEMENT", {
        "name":"name"
        "content":"(#PCDATA)"
      }
    ]
  , ["!ELEMENT", {
        "name":"addr"
        "content":"(#PCDATA)"
      }
    ]
  , ["!ELEMENT", {
        "name":"contact"
        "content":"EMPTY"
      }
    ]
  , ["!ATTLIST", {
        "targetElement":"contact"
      }
    , [{"name":"tel","dataType":"CDATA"}]
    ]
  , ["!ATTLIST", {
        "targetElement":"contact"
      }
    , [{"name":"e-mail","dataType":"CDATA"}]
    ]
  ]
, ["members"
  , ["member", {
        "num":"01"
      }
    , ["name"
      , "山田太郎"
      ]
    , ["addr"
      , "東京都XXXXX"
      ]
    , ["contact", {
          "tel":"xx-xxxx-xxxx"
          "e-mail":"xx@xxxxx"
        }
      ]
    ]
  , ["member", {
        "num":"02"
      }
    , ["name"
      , "山田花子"
      ]
    , ["addr"
      , "大阪府XXXX"
      ]
    , ["contact", {
          "tel":"xx-xxxx-xxxx"
        }
      ]
    ]
  , ["!"
    , "
  <member num="03">
    <name>山田次郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="yy@xxxxx" />
  </member>
  "
    ]
  ]
]
JSON.stringify() の結果に比べて、マークアップ構造が確認しやすいはず。

マークアップ言語として出力してみるとこう。


<?xml version="1.0"?>
<!-- コメント1 -->
<!-- コメント2 -->
<!DOCTYPE members[
  <!ELEMENT members (member*)>
  <!ATTLIST member num CDATA>
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT addr (#PCDATA)>
  <!ELEMENT contact EMPTY>
  <!ATTLIST contact tel CDATA>
  <!ATTLIST contact e-mail CDATA>
]>
<members>
  <member num="01">
    <name>山田太郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="xx@xxxxx" />
  </member>
  <member num="02">
    <name>山田花子</name>
    <addr>大阪府XXXX</addr>
    <contact tel="xx-xxxx-xxxx" />
  </member>
  <!-- <member num="03">
    <name>山田次郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="yy@xxxxx" />
  </member> -->
</members>

まぁ、妥協できる範囲で相互変換できるようになった。

XMLへの対応ってことで、HTMLとは違い、< と > とで囲まれた部分は文書要素でなくてもタグと考える。
処理命令やDTD周辺も文書要素のように考え、処理を行うのが特徴。

特に骨が折れたのは、DTD(文書型定義)用タグの扱い。
XMLの基本機能なんだけど、文書ツリーの書き方と違うシンタックスになるので、文字列処理するしかなかった。
文書型定義にはXML文書のツリーで行う代替手法を用る時代だけど、XML基本機能のDTDは対応すべきかなと考えてる。



DTDチャンク(DTD用タグ)となる <!ATTLIST > には子要素として、Attr(ATTRIBUTE_NODE)を与えることにした。
JsonMLには ATTRIBUTE_NODE を扱う文法がなかったので、element の文法に '[' attribute-list ']' を追加して対応。


XML宣言だと 要素としてタグ名が "?", 属性は { target:"xml", version:"1.0" } という具合にライブラリ内で予約する。DTD関係のタグも同様にして、JsonMLの要素のようにふるまうことができれば、DOMライクなAPIでビルドすることもできそう。

node はもちろん、WebWorker 内や onDOMContentLoaded 発生前のシングルページアプリケーションでも動く。Built-Inオブジェクトの Array 構造だからね。
JsonML関連で DOMライクなAPI実装は FakeElement ってのを作ってたので、リファクタ中。
dom ライクなAPIに セレクタ関連のAPIは未実装だったけど、この辺も実装すれば、JsonMLは文字列化した交換用データというより、JavaScriptアプリケーションで高速処理できる内部処理用データとして便利なのは変わらないと思ってる。


巷では変なObject ベースの変なデータ構造が流行ってるなと思ったら、Node.js でXMLを使いたいニーズがあって、 xml2js が流行ってたんですねぇ。あのデータ構造は、個人的に腑に落ちないので、頑張って開発します。



もう古い?時事ニュースからのネタ

誤解されないためにも最初に申し上げますが、
あくまでもネタですのであしからず。

地球の円周距離は 40,000 km と定められてて、5週は 200,000km
1年間に一人が一睡もせずに使用できる時間は 365 x 24 = 8760[h]

ここで、小学校でも習う「距離・速さ・時間」の計算をすると、
200,000 ÷ 8760 = 22 [km/h] の速度で移動する計算
一睡もせずにという条件は無理。

プロの運転手で1日6時間を移動に使うと考えると、88[km/h]
まぁ、高速道路も利用していると考えれば、お咎めのない安全運転といえるでしょう。

でも、移動に時間を割くことができない人のケースを考える。
例えば、1日あたり3時間までしか移動しない場合は176[km/h]
もっと少ない時間しか移動していない場合、速さだって増えます。

冒頭でも挙げてるけど、複数の車両を組織として運用する場合は問題もなくなる。

ただ、運行する車両ごとに時間、走行メーター(出発地、目的地)の記録を付けていれば、
堂々と「必要だった」と言えたのに。ダンマリになると、こういう風にくだらないネタを考えてしまう。

「業務上の記録って大事」だと思った。


Pure JavaScript で マークアップ言語を解析する際、JsonMLにコンバートするのが手っ取り早い。

けど、サーバーから取得したばかりのマークアップ言語のソースって、
Processing Instruction (XML宣言などの処理命令)や DTD用の <!doctype> 要素があって、
上手に無視してルート要素から解析したいのが本音。

で、表題の通り。
<!DOCTYPE> って、おまじないのように単純に考えていいのは、HTMLコーダー限定で、
本来は、マークアップの整合性を確認するためのルールをXML解析器に知らせる内容が列挙されてる。

<!DOCTYPE> 要素を用いてDTDを有効化するパターンは5種類かな。


1)[] の間に定義を書く(基本)
<!DOCTYPE rootElem [
    <-- 文書型定義 -->
]>

2) <-- 文書型定義 --> 部分をファイルにしてアクセスする
<!DOCTYPE rootElem SYSTEM "システム識別子(URI)">

3)広く使われる外部DTDはこんな感じに
<!DOCTYPE rootElem PUBLIC "公開識別子">

4)広く使われる外部DTDでも詳細な内容を外部ファイルとして用意するとき
<!DOCTYPE rootElem PUBLIC "公開識別子" "システム識別子(URI)">

5)外部ファイルに、ちょこっとだけ文書型定義を加えるとき。
<!DOCTYPE rootElem SYSTEM "システム識別子(URI)" [
    <-- 文書型定義 -->
]>


ここで、1)と5)に着目。
<!DOCTYPE >要素の内部に直書きしちゃうパターンでも上手に除去しなくちゃならんということ。

考えたのがこれ。


var parseDocTypeElement = function( ml ){
  var RE_TAG = /\x3c|\x3e/g
-   , RE_DTD = /\x21(DOCTYPE|ATTLIST|ELEMENT|ENTITY)/i
+   , RE_DTD = /\x21(DOCTYPE|ATTLIST|ELEMENT|ENTITY|NOTATION)/i
    , ptrs = [] // length === depth
    , inComment = false
    , doctypeElement // doctype tag
    , rootElement
    , dtdBegin = 0
    , dtdEnd = -1
  ;

  ml.replace(RE_TAG, function($0, offset, src){
    var m, str;
    switch($0){
    case '<':
      str = src.substring(offset);
      if( src.substr(offset,2)==="--" ) inComment = true;
      else if( m = str.match(RE_DTD) ){
        if(m[1].toLowerCase() === "doctype") {
          dtdBegin = offset +  m.index + m[1].length + 1;
          if(m=src.substring( dtdBegin ).match(/\s*([^\s]+)\s*/)){
            rootElement = m[1];
            dtdBegin += m[0].length;
          }
        }
        ptrs.push({ dtdTag: m[1], offset: offset + m.index + m[1].length + 1 });
      }
      break;
    case '>':
      str = src.substring(offset);
      if( inComment && src.substr(offset-2,2)!=="--" ) inComment = false;
      else { ptrs.pop(); if( !ptrs.length ) dtdEnd = offset; }
      break;
    }
  });
  doctypeElement = ml.substring(0, dtdEnd+1);
  return {
     ml : rootElement && ml.replace(doctypeElement,'') || ml
    , doctypeElement : rootElement && doctypeElement || null
    , rootElement : rootElement || null
    , dtd : rootElement && ml.substring(dtdBegin, dtdEnd) || null
  }
};

返却される オブジェクトの ml プロパティは <!DOCTYPE> 要素の記述を除去した内容。
で、ルート要素やDTDの定義部分も取得しておけば、使いやすいかも。

コメントの書式がネストするとバグると思う(この辺は、しっかりテストしていない)。

追記)2016-03-21
nodeType で dtd chunk となるタグに NOTATION があったので修正。
JavaScript 関連書籍において DOM の nodeType を詳説するものがなかったので調べてみた。

NODE TYPE タグ
CDATA_SECTION_NODE <![CDATA[CDATAテキスト]]>
DOCUMENT_TYPE_NODE <!DOCTYPE target [resource]>
ENTITY_NODE <!ENTITY [%] name [SYSTEM|PUBLIC publicID] resource [NDATA notation] >
NOTATION_NODE <!NOTATION name [SYSTEM|PUBLIC publicID] resource";>
PROCESSING_INSTRUCTION_NODE <?xml version="1.0"?> など

ENTITY_REFERENCE_NODE は &~; で表現される文字列実体参照っぽい。
ATTRIBUTE_NODE は DTD内の <!ATTLIST > で定義されたものやDOM APIで任意に追加/参照する属性の文字列っぽい。


コンピュータで内部実装される Unicode が UTF-16 であることを考えると、
2文字分(またはそれ以上)の文字コードで1文字を表現する代用対、結合文字があるため、
Stringオブジェクトは使いづらい側面がある。

配列化して処理するというアイディアは前回のエントリでも示した通り。

でも、単純な配列化では物足りないことがある。
エディタ画面上における文字(カーソル)位置の算出。

各要素に {width:, string:, codepoint: , escaped: } なオブジェクトを格納しつつ、String オブジェクトのように substring, substr を扱えるオブジェクトを考えてみた。

- width : 0: エディタに表現されない制御文字, 1: halfWidth文字, 2:fullWidth文字
- string: 1文字を示す String オブジェクト。前述のとおり、代用対、結合文字は length が2以上になる。
- codepoint: "U+HHHH" な表記。結合文字は "U+HHHH+HHHH ..."
- escaped: ソースコードとして見た時に エスケープシーケンスの適用を受けた halfWidth 文字かどうか。


因みに、正規表現は前回内容から変更した。理由は2つ。
1)コード体系から代用対に対する結合文字の存在も考えられること
2)JavaScript の正規表現で Unicode を示すとき、必ず \uHHHH と4文字構成の UCS を記述する必要があることを失念していた。




var TextSourcePrototype = {
  init: function( src ){
    if(!src) return; // 適当実装:継承時(サブクラスの prototype に代入時)に無視できるように
    var r, lineBreaks;
    this.lineBreaks = lineBreaks = [];
    this.source = r = [];
    src.replace(/([\uD800-\uDBFF][\uDC00-\uDFFF])|([\1\w\d][\u3099-\u309A\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F])|([^\1\2])/g, function($0, $1, $2, $3){
      var  f, h, l, len;
      if($0==="\r") return "";
      f=function(s){return (s.charCodeAt(0)).toString(16).toUpperCase()};
      r.push({
        width: $0.match(/^[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]$/) ? 0 :
          $3&&$3.match(/[\u0000-\u00ff\uFF61-\uFF9F]/) ? 1: 2,
        string: $0, 
        codepoint: "U+" + ($1
          ? (h=$1.charCodeAt(0)&0x3FF,
              l=$1.charCodeAt(1)&0x2B2,
              ((h<<10)|l)+0x10000
            ).toString(16).toUpperCase()
          : $2
            ? ($2.match(/./g).map(f).join("+"))
            : f($0))
      });
      r[r.length-1].escaped = Boolean(r.length>1 && !r[r.length-1].escaped && r[r.length-1].width===1 && r[r.length-2].string === ”\”);
      if( r[r.length-1].string === "\n" ) lineBreaks.push(r.length-1);
      if( this.offset ) this.offset( r[r.length-1], r.length-1 );// サブクラスで実装する。
    }.bind(this));
  },
  // オフセットからエディタ上の行と桁を取得する
  pos: function( offset, flag ){
    var r, s, b, l, c, i;
    for( s=this.source, b=this.lineBreaks, l=c=0, i=offset; 0<=i; i-- ) {
      c+=s[i].width;
      if(!b.length&&i==0) break;
      else if(offset>i&&s[i].string==="\n"){
        c--;l=b.indexOf(i)+1;break;
      }
    }
    r = { L: l + 1, C: c };
    return flag ? r: "[L="+(r.L)+":C="+(r.C)+"]";
  },
  substring: function( from, to ){
    var r=[], i;
    if( from < 0 ) from = 0;
    to || (to=this.source.length);
    if( from < to ) for(i=from; i<to; i++) r.push( this.source[i].string );
    else if(from == to) r.push( this.source[from].string )
    return r.join("")
  },
  substr: function( from, cnt ){
    cnt || (cnt=this.source.length-from);
    return this.substring( from, from+cnt );
  }
};
function TextSource( txt ){
  this.source = [];
  //this.length = 0;// this.source.length のgetter にするのが利便性高そう。
  this.lineBreaks = [];
  this.init( txt );
}
TextSource.prototype = TextSourcePrototype;


init() 関数内では、正規表現で配列化した後で走査してオブジェクト化する方法を採用せず、replace() の第二引数に与える関数で一気に処理することに。こうすることで、文字列の総当たり回数が抑制できる。



エディタ上の文字位置算出なんて、JavaScript製アプリケーションに必要なのか?
たぶん、役に立つことはある ― その程度の代物。

けど、Wiki, Markdown のソースとか 日本語が含まれる場合には使い道があると思う。

改行コードが CRLF に矯正されるWindowsでは、LF のみを解釈するようにした。
個人的に利用する環境用(Win, MacOSX)なので、CR のみとなる他の環境 OSは考慮してない。







このページのトップヘ