Creak: مكتبة تحليل HTML لـ Swift
Creak مصمم لتحليل مستندات HTML بكفاءة وبناء بنية شجرة تمثل عناصر المستند. تتضمن عملية التحليل عدة خطوات ومكونات رئيسية تعمل معًا لتحقيق هذا الهدف. فيما يلي شرح تفصيلي لكيفية تحليل Creak لـ HTML:
نظرة عامة على عملية التحليل
- التهيئة: تحميل وتنظيف سلسلة HTML.
- التجزئة: تقسيم سلسلة HTML إلى وحدات تمثل أجزاء مختلفة من HTML، مثل العلامات والنصوص.
- بناء هيكل الشجرة: استخدام الوحدات لبناء هيكل شجرة يمثل عناصر ونصوص مستند HTML.
المكونات الرئيسية
- فئة Dom: تدير عملية التحليل بأكملها وتخزن العقدة الجذرية لشجرة HTML التي تم تحليلها.
- فئة Content: توفر وظائف مساعدة لتحويل سلسلة HTML إلى وحدات (tokens).
- فئتا HtmlNode و TextNode: تمثلان العناصر وعقد النصوص في مستند HTML.
- فئة Tag: تمثل علامة 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). تتضمن هذه الفئة طرقًا لنسخ الأحرف من الموضع الحالي، وتخطي الأحرف، والتعامل مع العلامات والسمات كوحدات.
- copyUntil: نسخ الأحرف من الموضع الحالي حتى الوصول إلى الحرف المحدد.
- skipByToken: تخطي الأحرف بناءً على الرمز المحدد.
تُستخدم هذه الطرق لتحديد واستخراج أجزاء مختلفة من 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. الكود لا يحتاج إلى ترجمة حيث أن أسماء الدوال والمتغيرات تبقى كما هي في اللغة الإنجليزية.
- العقدة الجذرية: يبدأ التحليل من العقدة الجذرية (
HtmlNode
، ذات الوسم “root”). - العقدة النشطة: متغير
activeNode
يتتبع العقدة التي يتم معالجتها حاليًا. - محتوى النص: إذا تم اكتشاف محتوى نصي، يتم إنشاء
TextNode
وإضافتها إلى العقدة الحالية. - تحليل الوسم: إذا تم اكتشاف وسم، يتم استدعاء طريقة
parseTag
لمعالجته.
تحليل العلامات (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
}
ملاحظة: تم الاحتفاظ بالكود كما هو لأنه يحتوي على أسماء متغيرات ودوال بالإنجليزية، وهي أسماء تقنية لا يتم ترجمتها عادةً. إذا كنت بحاجة إلى شرح أو توضيح لأي جزء من الكود، فلا تتردد في طلب ذلك.
- تحديد العلامات: هذه الطريقة تميز ما إذا كانت العلامة علامة افتتاحية أم علامة إغلاق.
- الخصائص: تقوم بتحليل خصائص العلامة وإضافتها إلى
HtmlNode
. - العلامات ذاتية الإغلاق: تتعامل بشكل صحيح مع العلامات ذاتية الإغلاق.
الخلاصة
تتضمن عملية التحليل في Creak تهيئة محتوى HTML، وتقسيمه إلى وحدات، وبناء بنية شجرية للعقد. تدير فئة Dom
التحليل بشكل عام، بينما توفر فئة Content
وظائف مساعدة لتقسيم سلسلة HTML. تمثل فئتا HtmlNode
و TextNode
العناصر والنصوص في مستند HTML، بينما تدير فئة Tag
سمات العلامات. هذه الطريقة الفعالة والمنظمة تجعل Creak أداة قوية لتحليل HTML في Swift.