Creak: مكتبة تحليل HTML لـ Swift

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
}

2. الترميز (Tokenization)

توفر فئة Content وظائف مساعدة لتحليل سلاسل HTML إلى وحدات (tokens). تتضمن هذه الفئة طرقًا لنسخ الأحرف من الموضع الحالي، وتخطي الأحرف، والتعامل مع العلامات والسمات كوحدات.

تُستخدم هذه الطرق لتحديد واستخراج أجزاء مختلفة من HTML، مثل العلامات (tags)، الخصائص (attributes)، والمحتوى النصي.

3. بناء هيكل الشجرة

تتضمن طريقة parse في فئة Dom تمريرًا عبر سلسلة نصية من HTML، حيث تقوم بتحديد العلامات والنصوص، وبناء هيكل شجري يتكون من HtmlNode و TextNode.

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)
        }
    }
}

ترجمة:

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 ويبدو أنه جزء من عملية تحليل (parsing) لملف HTML. الكود لا يحتاج إلى ترجمة حيث أن أسماء الدوال والمتغيرات تبقى كما هي في اللغة الإنجليزية.

تحليل العلامات (Tags)

تقوم طريقة 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
}

ملاحظة: تم الاحتفاظ بالكود كما هو لأنه يحتوي على أسماء متغيرات ودوال بالإنجليزية، وهي أسماء تقنية لا يتم ترجمتها عادةً. إذا كنت بحاجة إلى شرح أو توضيح لأي جزء من الكود، فلا تتردد في طلب ذلك.

الخلاصة

تتضمن عملية التحليل في Creak تهيئة محتوى HTML، وتقسيمه إلى وحدات، وبناء بنية شجرية للعقد. تدير فئة Dom التحليل بشكل عام، بينما توفر فئة Content وظائف مساعدة لتقسيم سلسلة HTML. تمثل فئتا HtmlNode و TextNode العناصر والنصوص في مستند HTML، بينما تدير فئة Tag سمات العلامات. هذه الطريقة الفعالة والمنظمة تجعل Creak أداة قوية لتحليل HTML في Swift.


Back 2025.01.18 Donate