JavaScriptでCSSパーサーを書くための情報を収集中(4日目)

4日目です。今日は、

  • レガシースタイル(<<font size="+1" color="red"> や <strike> など)を収集する。 ⇒ _collectLegacyStyle()
  • インラインスタイルを収集する。 ⇒ _collectInlineStyle()
  • body要素からターゲット要素までの絶対パスを生成する。⇒ _createPath()

といった機能を実装しています。

レガシースタイル

レガシーなスタイルを収集し、重み付けゼロ(spec=0) で Hash に追加します。

<font color="" face="" size="">
<b>, <i>, <em>, <strong>, <big>, <small>, <s>, <strike>, <u>
<body text="" link="" vlink="" alink="" bgcolor="" background="" background="">

インラインスタイル

インラインスタイル(<div style="...">) を収集し、重み付け1000(spec=1000) で Hash に追加します。
要素の絶対パスを生成し、ルールを合成します。

例:
<body>
  <div>
     <p style="color: red">hoge</p>
  </div>
</body>

p要素のインラインスタイルを収集し、以下のルールを合成します。パスのルートは body になります。

body>div:nth-child(0)>p:nth-child(0) { color: red }

実装の抜粋( http://uupaa-js-spinoff.googlecode.com/svn/trunk/uuCSSParser.js/uuCSSParser.js )

今回から、uuMeta.js や uuQuery.js に依存するようになりました。uuMeta.createXHR() が new XMLHttpRequest() 、uuQuery() が querySelectorAll(), uuQuery.tag() が getElementsByTagName() 相当です。

(function() {
var _cssp, // inner namespace
    _mm = uuMeta,
    _doc = document,
    _ie = _mm.ie,
    _int = parseInt,
    /* 略 */
    // for Legacy Style
    SIZE = { 1: 0.64, 2: 0.8, 3: 1, 4: 1.2, 5: 1.44, 6: 1.73, 7: 2.07 },
    TAGS = { b: "font-weight:bold",
             i: "font-style:italic",
             s: "text-decoration:line-through",
             u: "text-decoration:underline",
             em: "font-style:italic", // fuzzy
             big: "font-size:larger",
             small: "font-size:smaller",
             strike: "text-decoration:line-through",
             strong: "font-weight:bold" }; // fuzzy

_cssp = {
  parse:
      function(css) { // @param CSSString(= ""):
                      // @return Hash: { specs: [spec-num1, spec-num2, ...],
                      //                 data[spec-num1]: { rule, sele, decl },
                      //                 data[spec-num2]: ... }
    var specs = [], data = {};

    _ie && _cssp._memento();
    if (!css) {
      _cssp._collectLegacyStyle(specs, data); // spec = 0
      _cssp._collectInlineStyle(specs, data); // spec = 1000
      css = _cssp._importStyleSheets();
    }
    _cssp._collectStyleSheets(specs, data, css);
    specs.sort(function(a, b) {
      return a - b; // sort of number order
    });
    return { specs: specs, data: data };
  },

  _memento: function() { /* 略 */ },
  _collectStyleSheets: function(specs, data, css) { /* 略 */ },
  _importStyleSheets: function() { /* 略 */ },

  // collect legacy styles
  _collectLegacyStyle: function(specs, data) {
    function add(expr, decl) {
      data[0].push({ rule: expr + "{" + decl + "}",
                     sele: expr, decl: [decl] });
    }

    specs.push(0), data[0] = []; // add
    var node, v, w, n, nz, path;

    // <font color="" face="" size="">
    node = uuQuery("font", _doc.body);
    for (n = 0, nz = node.length; n < nz; ++n) {
      v = node[n];
      path = _cssp._createPath(v);
      v.color && add(path, "color:" + v.color);
      v.face  && add(path, 'font-family:"' + v.face.split(",").join('","')
                                           + '"');
      if (v.size) {
        w = 1;
        v.size.replace(/\-\d+/, function(m) { w = Math.pow(0.8, _int(m)); }).
               replace(/\+\d+/, function(m) { w = Math.pow(1.2, _int(m)); }).
               replace(/\d+/,   function(m) { w = SIZE[_int(m)]; });
        add(path, "font-size:" + (w * 100).toFixed(0) + "%");
      }
    }

    // <b>, <i>, <em>, <strong>, <big>, <small>, <s>, <strike>, <u>
    node = uuQuery("b,i,em,strong,big,small,s,strike,u", _doc.body);
    for (n = 0, nz = node.length; n < nz; ++n) {
      v = node[n];
      add(_cssp._createPath(v), TAGS[v.tagName.toLowerCase()]);
    }

    // <body text="" link="" vlink="" alink=""
    //       bgcolor="" background="">
    v = _doc.body;
    w = "color:";
    v.text    && add("body",      w + v.text);
    v.link    && add("a:link",    w + v.link);
    v.vlink   && add("a:visited", w + v.vlink);
    v.alink   && add("a:active",  w + v.alink);
    v.bgcolor && add("body", "background-" + w + v.bgcolor);
    v.background &&
                 add("body", "background-image:url(" + v.background + ")");
    !data[0].length && (specs.pop(), delete data[0]); // undo
  },

  // collect inline styles
  _collectInlineStyle: function(specs, data) {
    function add(expr, decl) {
      data[1000].push({ rule: expr + "{" + decl + "}",
                        sele: expr, decl: [decl] });
    }

    specs.push(1000), data[1000] = []; // add
    var node, n, nz;

    node = uuQuery("html[style]");
    for (n = 0, nz = node.length; n < nz; ++n) {
      add("html", node[n].style.cssText);
    }
    node = uuQuery("body[style]");
    for (n = 0, nz = node.length; n < nz; ++n) {
      add("body", node[n].style.cssText);
    }
    node = uuQuery("[style]", _doc.body);
    for (n = 0, nz = node.length; n < nz; ++n) {
      add(_cssp._createPath(node[n]), node[n].style.cssText);
    }
    !data[1000].length && (specs.pop(), delete data[1000]); // undo
  },

  _createPath: function(elm) {
    var rv = [], e = elm, body = _doc.body, v, i;

    for (; e !== body; e = e.parentNode) {
      for (v = e.parentNode.firstChild, i = 0; v !== e; v = v.nextSibling) {
        (v.nodeType === 1) && ++i;
      }
      rv.push(e.tagName + ":nth-child(" + i + ")");
    }
    rv.reverse();
    return "body>" + rv.join(">");
  }
};

window.uuCSSParser = _cssp; // export 
})(); // uuCSSParser scope
align と center要素について

align(<align="left">) や <center> に関してはノータッチです。
CSS には幅が定義されていない要素のセンタリングを的確に行う方法が存在せず、align は古いブラウザでもそれなりにサポートされているため、JavaScriptレベルではフォローしません。


次回は、

  • 収集したルールをアレコレして、要素にスタイルを適用する

です。