Creak: Swift の HTML 解析ライブラリ

Home PDF

Creak は、HTML ドキュメントを効率的に解析し、ドキュメントの要素を表すツリー構造を構築するために設計されています。解析プロセスには、この目標を達成するために協力して動作する複数の重要なステップとコンポーネントが含まれています。以下に、Creak が HTML を解析する詳細な説明を示します:

解析プロセスの概要

  1. 初期化:HTML文字列を読み込み、クリーンアップします。
  2. トークン化:HTML文字列を、タグやテキストなど、HTMLの異なる部分を表すトークンに分解します。
  3. ツリー構造の構築:トークンを使用して、HTMLドキュメントの要素とテキストを表すツリー構造を構築します。

主要コンポーネント

詳細な解析手順

1. 初期化

Dom クラスは、解析プロセスの初期化を担当します。loadStr メソッドは、生の HTML 文字列を受け取り、それをクリーンアップして Content オブジェクトを初期化します。

public func loadStr(str: String) -> Dom {
    raw = str
    let html = clean(str)
    content = Content(content: html)
    parse()
    return self
}

上記のSwiftコードは、文字列を読み込んでDOM(Document Object Model)を生成する関数です。この関数は、与えられた文字列をクリーンアップし、その内容を解析してDOMを構築します。最終的に、自身のインスタンスを返します。

2. トークン化

Content クラスは、HTML文字列をトークン化するためのユーティリティ関数を提供します。これには、現在の文字位置から文字をコピーする、文字をスキップする、タグや属性などのトークンを処理するメソッドが含まれます。

これらのメソッドは、HTMLのさまざまな部分(例えば、タグ、属性、テキストコンテンツなど)を識別し、抽出するために使用されます。

3. ツリー構造の構築

Dom クラスの parse メソッドは、HTML 文字列を走査し、タグとテキストを識別して、HtmlNodeTextNode で構成されるツリー構造を構築します。

private func parse() {
    root = HtmlNode(tag: "root")
    var activeNode: InnerNode? = root
    while activeNode != nil {
        let str = content.copyUntil("<")
        if (str == "") {
            let info = parseTag()
            if !info.status {
                activeNode = nil
                continue
            }
            
            if info.closing {
                let originalNode = activeNode
                while activeNode?.tag.name != info.tag {
                    activeNode = activeNode?.parent
                    if activeNode == nil {
                        activeNode = originalNode
                        break
                    }
                }
                if activeNode != nil {
                    activeNode = activeNode?.parent
                }
                continue
            }
            
            if info.node == nil {
                continue
            }
            
            let node = info.node!
            activeNode!.addChild(node)
            if !node.tag.selfClosing {
                activeNode = node
            }
        } else if (trim(str) != "") {
            let textNode = TextNode(text: str)
            activeNode?.addChild(textNode)
        }
    }
}

このSwiftコードは、HTMLコンテンツを解析するためのparse関数を定義しています。以下にその動作を説明します。

  1. 初期化: rootノードを作成し、activeNoderootに設定します。
  2. ループ: activeNodenilでない限り、ループを続けます。
  3. 文字列の取得: <までの文字列を取得します。
  4. タグの解析:
    • 文字列が空の場合、parseTag関数を呼び出してタグ情報を取得します。
    • タグが閉じタグの場合、対応する開始タグを見つけるまで親ノードを遡ります。
    • タグが開始タグの場合、新しいノードを作成し、activeNodeの子ノードとして追加します。自己終了タグでない場合、activeNodeを新しいノードに更新します。
  5. テキストノードの追加: 文字列が空でない場合、テキストノードを作成し、activeNodeの子ノードとして追加します。

この関数は、HTMLの構造を解析し、ノードツリーを構築するために使用されます。

タグ解析

parseTag メソッドは、タグの識別と処理を行います。

private func parseTag() -> ParseInfo {
    var result = ParseInfo()
    if content.char() != ("<" as Character) {
        return result
    }
    
    if content.fastForward(1).char() == "/" {
        var tag = content.fastForward(1).copyByToken(Content.Token.Slash, char: true)
        content.copyUntil(">")
        content.fastForward(1)
        
        tag = tag.lowercaseString
        if selfClosing.contains(tag) {
            result.status = true
            return result
        } else {
            result.status = true
            result.closing = true
            result.tag = tag
            return result
        }
    }
    
    let tag = content.copyByToken(Content.Token.Slash, char: true).lowercaseString
    let node = HtmlNode(tag: tag)
    
    while content.char() != ">" &&
       content.char() != "/" {
        let space = content.skipByToken(Content.Token.Blank, copy: true)
        if space?.characters.count == 0 {
            content.fastForward(1)
            continue
        }
        
        let name = content.copyByToken(Content.Token.Equal, char: true)
        if name == "/" {
            break
        }
        
        if name == "" {
            content.fastForward(1)
            continue
        }
        
        content.skipByToken(Content.Token.Blank)
        if content.char() == "=" {
            content.fastForward(1).skipByToken(Content.Token.Blank)
            var attr = AttrValue()
            let quote: Character? = content.char()
            if quote != nil {
                if quote == "\"" {
                    attr.doubleQuote = true
                } else {
                    attr.doubleQuote = false
                }
                content.fastForward(1)
                var string = content.copyUntil(String(quote!), char: true, escape: true)
                var moreString = ""
                repeat {
                    moreString = content.copyUntilUnless(String(quote!), unless: "=>")
                    string += moreString
                } while moreString != ""
                attr.value = string
                content.fastForward(1)
                node.setAttribute(name, attrValue: attr)
            } else {
                attr.doubleQuote = true
                attr.value = content.copyByToken(Content.Token.Attr, char: true)
                node.setAttribute(name, attrValue: attr)
            }
        } else {
            node.tag.setAttribute(name, attrValue: AttrValue(nil, doubleQuote: true))
            if content.char() != ">" {
                content.rewind(1)
            }
        }
    }
    
    content.skipByToken(Content.Token.Blank)
    if content.char() == "/" {
        node.tag.selfClosing = true
        content.fastForward(1)
    } else if selfClosing.contains(tag) {
        node.tag.selfClosing = true
    }
    
    content.fastForward(1)
    
    result.status = true
    result.node = node
    
    return result
}

このSwiftコードは、HTMLタグを解析するための関数parseTag()を定義しています。以下にその主要な部分を日本語で説明します。

  1. 初期化と基本チェック:
    • resultParseInfo型の変数で、解析結果を保持します。
    • content.char()<でない場合、resultをそのまま返します。
  2. 閉じタグの解析:
    • content.fastForward(1).char()/の場合、閉じタグであると判断します。
    • タグ名を取得し、selfClosingリストに含まれているかどうかをチェックします。
    • 含まれている場合はresult.statustrueに設定して返します。
    • 含まれていない場合は、resultにタグ名と閉じタグであることを示すフラグを設定して返します。
  3. 開始タグの解析:
    • タグ名を取得し、HtmlNodeオブジェクトを作成します。
    • >または/に到達するまで、属性を解析します。
    • 属性名と値を取得し、HtmlNodeに設定します。
  4. 自己閉じタグのチェック:
    • タグが自己閉じタグであるかどうかをチェックし、HtmlNodeに設定します。
  5. 結果の返却:
    • 解析が成功した場合、result.statustrueに設定し、result.nodeに解析されたHtmlNodeを設定して返します。

この関数は、HTMLタグを解析し、その結果をParseInfoオブジェクトとして返す役割を果たします。

結論

Creakの解析プロセスは、HTMLコンテンツの初期化、トークン化、そしてノードのツリー構造の構築を含みます。Domクラスは全体の解析を管理し、Contentクラスはトークン化されたHTML文字列のユーティリティ関数を提供します。HtmlNodeTextNodeクラスはHTMLドキュメント内の要素とテキストを表し、Tagクラスはタグの属性を管理します。この効率的で組織的なアプローチにより、CreakはSwiftにおけるHTML解析の強力なツールとなっています。


Back 2025.02.22 Donate