はじめに
あのDan Kogai 氏にも好評な噂の名著「Jythonプログラミング」がついに発売されるわけですが、皆様いかがお過ごしでしょうか。今日は Jython にちなんで、 CSS の構文解析のお話をしたいと思います。(関係ない)
話は変わって
最近、僕の中では CSS (の実装)ブームです。
前回のパーサブームでは JavaScript で XPath を作ったわけですが、そのときはパーサを手で書いていました。
じゃあ、今回は yacc でも使ってみるかと鼻息荒めで挑んでみたのですが、少し困ったことがありました。
手書きパーサだと、パーサ全体の一部分だけを使ったパーサを簡単に作ることができるじゃないですか><
たとえば、
// ↓ こういうケースで、文法全体の一部分だけでパースしたいってことあるよねん>< // 宣言だけパースしたい>< element.style.cssText = 'background: red; color: blue'; // Media Query だけパースしたい>< linkElement.media.appendMedium('print and (min-device-width: 800px)'); // 色だけパースしたい>< element.style.color = 'rgb(100, 0, 100)';
でも、 yacc を使うとそれができないんです>< 困った/(^o^)\
じゃあ、何個もパーサ作りますか?
やだやだ!めんどいめどい><!!
1つの文法定義ファイルでぜーんぶやりたいです><
じゃあ、実際ブラウザはどうやってるのですか><
WebKit のソースを見てみました。
まず、 yacc の定義を探してみます。
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSGrammar.y
これ一個しか yacc の文法定義っぽいファイルはないなあ><
じゃあ WebKit では、この 1 ファイルだけを使ってやってるのかなあ。
さらに追いかけてみる
JS から CSS の値が設定された時、何が起こるか
element.style.color = 'red';
まずは、 CSSStyleDeclaration の setProperty が呼ばれる
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSStyleDeclaration.cpp
void CSSStyleDeclaration::setProperty(const String& propertyName, const String& value, ExceptionCode& ec) { int important = value.find("!important", 0, false); if (important == -1) setProperty(propertyName, value, "", ec); else setProperty(propertyName, value.left(important - 1), "important", ec); } void CSSStyleDeclaration::setProperty(const String& propertyName, const String& value, const String& priority, ExceptionCode& ec) { int propID = cssPropertyID(propertyName); if (!propID) { // FIXME: Should we raise an exception here? return; } bool important = priority.find("important", 0, false) != -1; setProperty(propID, value, important, ec); }
さらに、今度は CSSStyleDeclaration#setProperty からサブクラスの CSSMutableStyleDeclaration#setProperty が呼び出される
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSMutableStyleDeclaration.cpp
void CSSMutableStyleDeclaration::setProperty(int propertyID, const String& value, bool important, ExceptionCode& ec) { setProperty(propertyID, value, important, true, ec); } bool CSSMutableStyleDeclaration::setProperty(int propertyID, const String& value, bool important, bool notifyChanged, Exceptio nCode& ec) { ec = 0; if (value.isEmpty()) { removeProperty(propertyID, notifyChanged, false, ec); return ec == 0; } // ■■ わー!ここで与えられた文字列をパースしようとしてる>< CSSParser parser(useStrictParsing()); bool success = parser.parseValue(this, propertyID, value, important); if (!success) { } else if (notifyChanged) setChanged(); ASSERT(!ec); return success; }
というわけで、 CSSParser オブジェクトの中身に答えがありそうですね。
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSParser.cpp
bool CSSParser::parseValue(CSSMutableStyleDeclaration *declaration, int _id, const String &string, bool _important) { styleElement = declaration->stylesheet(); // ■■ これが答えだ!! setupParser("@-webkit-value{", string, "} "); id = _id; important = _important; CSSParser* old = currentParser; currentParser = this; // ここで yacc で作った関数を呼ぶ cssyyparse(this); currentParser = old; rule = 0; bool ok = false; if (numParsedProperties) { ok = true; declaration->addParsedProperties(parsedProperties, numParsedProperties); clearProperties(); } return ok; }
きた!そういうことか!と思った!
つまり、 JS で
element.style.color = 'red';
とすると
@-webkit-value { red }
がパースされるってことか!!
まとめ
すっきりした!
すっきりした!