فن تحليل أو افعل ذلك بنفسك DOM

مرحبا يا هبر! في الآونة الأخيرة ، توصلت إلى فكرة إنشاء لغة ترميزية بسيطة مثل تخفيض السعر ، والتي ستكون مثالية لمهامي ، وهي الكتابة السريعة للمحاضرات مع التنسيق والقدرة على إدراج صيغ رياضية "سريعًا" ، باستخدام لوحة مفاتيح واحدة فقط. لترجمة النص المكتوب بهذا التنسيق إلى نموذج أكثر قابلية للفهم ، على سبيل المثال ، مستند LibreOffice Writer ، فأنت بحاجة إلى محلل ، بمعنى آخر ، محلل . منذ أن اعتدت صناعة الدراجات ، ذهبت إلى محركات البحث باستخدام الاستعلامات "مثال المحلل اللغوي" ، "html to DOM" ، "كيفية تحليل html" ، وما إلى ذلك. لخيبة الأمل ، على كل الموارد التي تم العثور عليها ، إما أمثلة أولية مثل آلة حاسبة Straustrup مع العودية بواسطة النسب ، أو تم استخدام حلول جاهزة مثل المرن ، البيسون ، llvm و yacc. كان هناك المزيد من المكتبات المخصصة لتحليل اللغات المحددة بدقة (أدوات gumbo و jsoup و quickjson و Qt وما إلى ذلك). لم يكن أيٍّ أو آخر جزءًا من خططي لكتابة محلل الترميز الخاص بي بلغة C ++ باستخدام المكتبة القياسية فقط ، لذلك بدلاً من الموارد الإلكترونية ، أصبحت أدلة المعاهد الفنية مصدراً للمعرفة حول فن التحليل. حول كيفية أخذ النص وإنشاء AST (شجرة بناء الجملة المجردة) منه ، حول بعض العثرات التي صادفتها في هذه العملية ، سأخبرك بالأخطاء المحتملة اليوم.

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

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

  1. التحليل المعجمي للنص إلى الرموز هو أجزاء صغيرة من هذا النص لها معنى نحوي معين.
  2. التحليل هو بناء الرموز المميزة بناءً على قيمها لشجرة بناء الجملة المجردة (AST - شجرة بناء الجملة المجردة) ، أو نموذج كائن المستند ( نموذج كائن المستند DOM).

ولكن دعنا نحصل عليها بالترتيب. قبل فتح IDE المفضل لديك وكتابة التعليمات البرمجية ، تحتاج إلى تطوير قواعد اللغة للغة المستقبل. من بين القواعد النحوية الخالية من السياق ، والأكثر شهرة هي نموذج Backus-Naur (BNF) وشكل Backus-Naur الموسّع . لقد استخدمت تعايشهم مع الأخذ بأفضل شكل من الأشكال. يمكن تعريف أي تعبير من خلال التعبيرات الأخرى كما يلي:

<> = <_1> <_> <_2> 

هنا يتم تعريف تعبير واحد من خلال ثلاثة آخرين بعد واحد تلو الآخر. في المقابل ، يجب تمثيلهم أيضًا من خلال التعبيرات "الثالثة" ، إلخ.

متى تتوقف؟

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

 <_1> = <> (<_> | <_>) <> 

المحطات مكتفية ذاتيا ، ولا تحتاج إلى تعريف. يمكن كتابة الأمثلة المذكورة أعلاه على النحو التالي:

 <> = <_1> "+" <_2> <_1> = <> ("*" | "/") <> 

حيث "+" ، "*" ، "/" هي المحطات الطرفية.
تحتاج إلى اختيار أطراف من القواعد النحوية على الفور ، حتى يمكنك كتابتها في قائمة منفصلة في أسفل التعريفات الرئيسية - سوف تكون مفيدة في وقت لاحق.

يتوفر وصف كامل لـ BNF على ويكيبيديا هنا وهنا . يعد تجميع القواعد اللغوية للغة مرحلة مهمة في إنشاء لغة لا تتسامح مع التافهة. خطأ واحد في ذلك يمكن أن يؤدي إلى رمز غير نشط بالكامل ، والتي يجب إعادة كتابتها من الصفر. لذلك ، قبل اتخاذ الخطوة التالية ، تأكد من عدم وجود مشكلات مثيرة للجدل في القواعد المترجمة. إذا كان لديك شاشتين ، فسيكون من المناسب لك أن تشغل شاشة واحدة بوثيقة نحوية لبقية عملك حتى تتمكن من تحريك عينيك إليها بسرعة عند الرمز. صدقوني ، عليك أن تفعل هذا في كل وقت. إليكم قواعد HTML5 BNF المترجمة:

 (* document *) <document> = {<document_part>} <END> <document_part> = <block> | <empty_tag> | <comment> | <macro_tag> | <text> <block> = <opening_tag> {<document_part>} <closing_tag> (* tags *) <opening_tag> = "<" {<ws>} <block_tag_name> [<attributes_list>] {<ws>} ">" <closing_tag> = "<" "/" {<ws>} <block_tag_name> {<ws>} ">" <empty_tag> = "<" "!" {<ws>} <empty_tag_name> [<attributes_list] {<ws>} ["/"] ">" <comment> = "<" "!" "--" <comment_text> "--" ">" <macro_tag> = "<" "?" <macro_text> "?" ">" <block_tag_name> = "a" | "abbr" | "address" | "article" | "aside" | "audio" | "b" | "bdo" | "blockquote" | "body" | "button" | "canvas" | "caption" | "cite" | "code" | "colgroup" | "data" | "datalist" | "dd" | "del" | "details" | "dfn" | "dialog" | "div" | "dl" | "dt" | "em" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "html" | "i" | "iframe" | "ins" | "kbd" | "label" | "legend" | "li" | "main" | "map" | "mark" | "meter" | "nav" | "noscript" | "object" | "ol" | "optgroup" | "option" | "output" | "p" | "picture" | "pre" | "progress" | "q" | "ruby" | "rb" | "rt" | "rtc" | "rp" | "s" | "samp" | "script" | "section" | "select" | "small" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "table" | "tbody" | "td" | "template" | "textarea" | "tfoot" | "th" | "thead" | "time" | "title" | "tr" | "track" | "u" | "ul" | "var" | "video" <empty_tag_name> = "area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link" | "menuitem" | "meta" | "param" | "source" | "track" | "wbr" (* attributes *) <attributes_list> = <ws> {<ws>} <attribute> {<ws> {<ws>} <attribute>} <attribute> = <empty_attribute> | <unquoted_attribute> | <single_quoted_attribute> | <double_quoted_attribute> <empty_attribute> = <attribute_name> <unquoted_attribute> = <attribute_name> {<ws>} "=" {<ws>} <unquoted_attribute_value> <single_quoted_attribute> = <attribute_name> {<ws>} "=" {<ws>} "'" <single_quoted_attribute_value> "'" <double_quoted_attribute> = <attribute_name> {<ws>} "=" {<ws>} "\"" <double_quoted_attribute_value> "\"" <attribute_name> = (<letter> | <digit>) {<letter> | <digit>} {* attribute values *) <unquoted_attribute_value> = /^[\s"'=<>/]/ {/^[\s"'=<>/]/} <single_quoted_attribute_value> = /^[']/ {/^[']/} <double_quoted_attribute_value> = /^["]/ {/^["]/} (* nonterminals *) <text> = {/^[<>]/} <comment_text> = ... <macro_text> = ... <letter> = /[a-zA-Z]/ <digit> = /[0-9]/ <ws> = " " | "\t" | "\n" (* terminals *) "<", ">", "/", "!", "?", " ", "\t", "\n" 

عندما تكون القواعد النحوية جاهزة ، يمكنك المتابعة إلى محلل المعجم (اسم آخر للمحلل اللغوي ، لأنه بالإضافة إلى التحليل ، فإنه يحدد الأخطاء المعجمية في المستند). للوهلة الأولى ، كل شيء بسيط: استيعاب الحروف ، الكتابة إلى المخزن المؤقت ، وعندما يتم الكشف عن محطة رئيسية ، حدد الرمز المميز المستلم كرمز مع نوع معين ، أليس كذلك؟ نعم ، فقط نوع الرمز المميز هنا أكثر أهمية من الرمز. ساوضح الان بالطبع ، يجب أن يحتوي إجراء تفكيك (ifsteam & file) على حلقة تقرأ حرفًا واحدًا من دفق الإدخال وترسله إلى إجراء العملية (const char & c) ، حيث تتم معالجة هذا الحرف. يبدو أن إجراء العملية يجب أن يحتوي على رمز التبديل © ، حيث يكون لكل رمز مفتاح وظائفه الخاصة ، اعتمادًا على نوع الرمز المميز الحالي. في الواقع ، فإن العكس هو الصحيح: من الأفضل استخدام رمز التبديل للتحقق من نوع الرمز المميز ، وتحديد وظائف الأحرف. علاوة على ذلك ، فإن الرمز المميز الحالي غالبًا ما يكون له نوع غير محدد ، واحد من العديد. على سبيل المثال ، بعد فتح قوس الزاوية ، يمكنك أن ترى: فتح ، إغلاق ، علامات فارغة ، وكذلك تعليق على نمط HTML أو علامة ماكرو (نص PHP مرفق في "<؟ ...؟>". ولكل هذه النقابات تحتاج إلى حالتك الخاصة. كيف يكون هذا تطبيق؟ باستخدام إشارات البت. دع عددًا محدودًا من أنواع الرموز المميزة (كلما كان ذلك أفضل ، لأن مهمة المحلل اللغوي هي ترك أقل قدر ممكن من العمل في بناء الجملة). لكل نوع ، يتم إعطاء عدد فريد من الدرجة الثانية (1 ، 2 ، 4 ، 8) وما إلى ذلك). ثم في التنسيق الثنائي سيبدو كما يلي: 0001 ، 0010 ، 0 100 ، وما إلى ذلك ، ومع إضافة bitwise لأي عدد من أي نوع ، سيتم الحصول على رقم فريد.

 enum Token_type { END = 1, TEXT = 2, OPENING_BLOCK_TAG_NAME = 4, CLOSING_BLOCK_TAG_NAME = 8, EMPTY_TAG_NAME = 16, COMMENT = 32, MACRO_TAG = 64, ATTRIBUTE_NAME = 128, UNQUOTED_ATTRIBUTE_VALUE = 256, SINGLE_QUOTED_ATTRIBUTE_VALUE = 512, DOUBLE_QUOTED_ATTRIBUTE_VALUE = 1024 }; 

عملية إجراء مبتورة:

 void Lexer::process (const char &c) { switch (curr_token_type) { case END: { throw string("unexpected ending!"); break; } case TEXT: { if (c == '>') throw string("unexpected symbol: \">\"!"); else if (c == '<') { if (!buffer.empty()) { add(buffer, TEXT); buffer.clear(); } curr_token_type = OPENING_BLOCK_TAG_NAME | CLOSING_BLOCK_TAG_NAME | EMPTY_TAG_NAME | COMMENT | MACRO_TAG; } else buffer.push_back(c); break; } case OPENING_BLOCK_TAG_NAME: { throw string("error!"); break; } case CLOSING_BLOCK_TAG_NAME: { if (c == '<') throw string("unexpected symbol: \"<\"!"); else if (c == '/') throw string("unexpected symbol: \"<\"!"); else if (c == '!') throw string("unexpected symbol: \"!\"!"); else if (c == '?') throw string("unexpected symbol: \"?\"!"); else if (c == ' ') throw string("unexpected symbol: \" \"!"); else if (c == '\t') throw string("unexpected symbol: \"\\t\"!"); else if (c == '\n') throw string("unexpected symbol: \"\\n\"!"); else if (c == '>') { for (unsigned int i(0); i < BLOCK_TAGS_COUNT; i++) if (buffer == block_tags[i]) { add(buffer, CLOSING_BLOCK_TAG_NAME); buffer.clear(); curr_token_type = TEXT; break; } } else buffer.push_back(c); break; } case EMPTY_TAG_NAME: { throw string("error!"); break; } case COMMENT: { ... break; } case MACRO_TAG: { ... break; } case OPENING_BLOCK_TAG_NAME | CLOSING_BLOCK_TAG_NAME | EMPTY_TAG_NAME | COMMENT | MACRO_TAG: { ... break; } case EMPTY_TAG_NAME | COMMENT: { ... break; } case ATTRIBUTE_NAME: { ... break; } case ATTRIBUTE_NAME | UNQUOTED_ATTRIBUTE_VALUE | SINGLE_QUOTED_ATTRIBUTE_VALUE | DOUBLE_QUOTED_ATTRIBUTE_VALUE: { ... break; } case UNQUOTED_ATTRIBUTE_VALUE | SINGLE_QUOTED_ATTRIBUTE_VALUE | DOUBLE_QUOTED_ATTRIBUTE_VALUE: { ... break; } case UNQUOTED_ATTRIBUTE_VALUE: { ... break; } case SINGLE_QUOTED_ATTRIBUTE_VALUE: { ... break; } case DOUBLE_QUOTED_ATTRIBUTE_VALUE: { ... break; } } } 

نتحقق من مفتاح التبديل من نوع الرمز المميز (أو الرموز) ، وفي كل حالة نحدد الإجراءات الخاصة بكل من المحطات الطرفية الرئيسية. لا توجد وظائف كثيرة ، فكل شخص يقوم بتنفيذ إجراءات بسيطة: إما إضافة حرف إلى المخزن المؤقت ، أو تفريغ المخزن المؤقت في الرمز المميز التالي ، أو تغيير نوع الرمز المميز (الرموز) المتوقع ، أو رمي استثناء. من السهل تحديد الإجراء المطلوب باستخدام القواعد النحوية المكتوبة أعلاه باستخدام محرر نصوص قابلة للبحث. ابحث فقط عن كل تضمينات الرمز المميز (الرموز) في تعريفات التعبيرات الأخرى ، ثم إدراج هذه التعبيرات في "الثالث" ، إلخ. فيما يلي مثال لعلامة فتح في محرر نص gedit:

التوجه في قواعد اللغة الرسمية

في البداية ، يعد التنقل بالقواعد أمرًا صعبًا ، ولكن بمرور الوقت والخبرة ، لن يصبح الأمر أكثر تعقيدًا من تقسيم العمود. وهنا هو تفكيك الإجراء:

 void Lexer::disassemble (ifstream &file) { tokens_count = 0; curr_token_type = 0; unsigned long line(1), pos(1); try { char c; curr_token_type = TEXT; while ((c = file.get()) != EOF) { if (c == '\n') { pos = 1; line++; } else pos++; process(c); } if (buffer.size() != 0) { if (!(curr_token_type | TEXT)) throw string("text was expected!"); add(buffer, TEXT); buffer.clear(); } add("", END); } catch (const string &error) { throw string("lexer: " + to_string(line) + "," + to_string(pos) + ": " + error); } } 

من الواضح أن الرمز المميز المتوقع الأول ضروري لتعيين الكتابة على TEXT ، وفي النهاية قم بإضافة الرمز المميز للنوع END مع أي نص (أو فارغ ، هنا).

على سبيل المثال ، أخذت أحد قوالب مستندات HTML الخاصة بي مع تعليق ، وأضفت سكريبت PHP زائفًا إليه ، وقمت بمعالجته باستخدام lexer ، وعرضت قائمة الرموز المميزة بالتنسيق "[" <token_text> ": <token_type>]". إليك ما حدث:

وثيقة نفسها
 <!DOCTYPE html> <html lang="ru"> <head> <meta http-equiv="content-type" content="text/html" charset="utf-8" /> <meta name="author" content="Interquadro" /> <meta name="description" content="" /> <meta name="keywords" content=""> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="format-detection" content="telephone=no" /> <meta http-equiv="x-rim-auto-match" content="telephone=none" /> <meta name="referrer" content="no-referrer" /> <meta name="_suburl" content="" /> <title></title> <link rel="shortcut icon" href=".ico" /> <link rel="stylesheet" type="text/css" href=".css" title="" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5-els.js"></script> <![endif]--> </head> <body> <header> <div id="intro"> </div> </header> <nav> <ul id="nav"> <li class="nav"><a href="#">  </a></li> <li class="nav"><a href="#">  </a></li> <li class="nav"><a href="">  </a></li> </ul> </nav> <main id="content"> <?php ?> </main> <footer> <hr /> <small id="copyright">Copyright © 2019.   .</small> </footer> </body> </html> 


قائمة الرموز
 ["! DOCTYPE": EMPTY_TAG_NAME]
 ["html": ATTRIBUTE_NAME]
 ["
 ": النص]
 ["html": OPENING_BLOCK_TAG_NAME]
 ["lang": ATTRIBUTE_NAME]
 ["en": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
 ": النص]
 ["head": OPENING_BLOCK_TAG_NAME]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["http-equiv": ATTRIBUTE_NAME]
 ["نوع المحتوى": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["text / html": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["charset": ATTRIBUTE_NAME]
 ["utf-8": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["الاسم": ATTRIBUTE_NAME]
 ["المؤلف": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["Interquadro": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["الاسم": ATTRIBUTE_NAME]
 ["الوصف": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["الاسم": ATTRIBUTE_NAME]
 ["الكلمات الرئيسية": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["الاسم": ATTRIBUTE_NAME]
 ["منفذ العرض": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["العرض = عرض الجهاز ، المقياس الأولي = 1": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["الاسم": ATTRIBUTE_NAME]
 ["اكتشاف التنسيق": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["رقم الهاتف = لا": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["http-equiv": ATTRIBUTE_NAME]
 ["x-rim-auto-match": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["هاتف = لا شيء": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["الاسم": ATTRIBUTE_NAME]
 ["الإحالة": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["بدون مرجع": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["meta": EMPTY_TAG_NAME]
 ["الاسم": ATTRIBUTE_NAME]
 ["_suburl": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["المحتوى": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	
	 ": النص]
 ["title": OPENING_BLOCK_TAG_NAME]
 ["title": CLOSING_BLOCK_TAG_NAME]
 ["
	 ": النص]
 ["link": EMPTY_TAG_NAME]
 ["rel": ATTRIBUTE_NAME]
 ["رمز الاختصار": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["href": ATTRIBUTE_NAME]
 [".ico": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
	 ": النص]
 ["link": EMPTY_TAG_NAME]
 ["rel": ATTRIBUTE_NAME]
 ["ورقة أنماط": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["type": ATTRIBUTE_NAME]
 ["text / css": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["href": ATTRIBUTE_NAME]
 [".css": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["title": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["

	 ": النص]
 ["[if lt IE 9]>
	 <script src = "http://html5shiv.googlecode.com/svn/trunk/html5-els.js"> </script>
	 <! [endif] ": COMMENT]
 ["
 ": النص]
 ["head": CLOSING_BLOCK_TAG_NAME]
 ["

 ": النص]
 ["body": OPENING_BLOCK_TAG_NAME]
 ["
	 ": النص]
 ["header": OPENING_BLOCK_TAG_NAME]
 ["
		 ": النص]
 ["div": OPENING_BLOCK_TAG_NAME]
 ["id": ATTRIBUTE_NAME]
 ["intro": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["

		 ": النص]
 ["div": CLOSING_BLOCK_TAG_NAME]
 ["
	 ": النص]
 ["header": CLOSING_BLOCK_TAG_NAME]
 ["
	 ": النص]
 ["التنقل": OPENING_BLOCK_TAG_NAME]
 ["
		 ": النص]
 ["ul": OPENING_BLOCK_TAG_NAME]
 ["id": ATTRIBUTE_NAME]
 ["التنقل": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
			 ": النص]
 ["li": OPENING_BLOCK_TAG_NAME]
 ["class": ATTRIBUTE_NAME]
 ["التنقل": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["a": OPENING_BLOCK_TAG_NAME]
 ["href": ATTRIBUTE_NAME]
 ["#": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["الصفحة الرئيسية": النص]
 ["a": CLOSING_BLOCK_TAG_NAME]
 ["li": CLOSING_BLOCK_TAG_NAME]
 ["
			 ": النص]
 ["li": OPENING_BLOCK_TAG_NAME]
 ["class": ATTRIBUTE_NAME]
 ["التنقل": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["a": OPENING_BLOCK_TAG_NAME]
 ["href": ATTRIBUTE_NAME]
 ["#": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["مراجعة": النص]
 ["a": CLOSING_BLOCK_TAG_NAME]
 ["li": CLOSING_BLOCK_TAG_NAME]
 ["
			 ": النص]
 ["li": OPENING_BLOCK_TAG_NAME]
 ["class": ATTRIBUTE_NAME]
 ["التنقل": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["a": OPENING_BLOCK_TAG_NAME]
 ["href": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["مساعدة": النص]
 ["a": CLOSING_BLOCK_TAG_NAME]
 ["li": CLOSING_BLOCK_TAG_NAME]
 ["
		 ": النص]
 ["ul": CLOSING_BLOCK_TAG_NAME]
 ["
	 ": النص]
 ["التنقل": CLOSING_BLOCK_TAG_NAME]
 ["

	 ": النص]
 ["main": OPENING_BLOCK_TAG_NAME]
 ["id": ATTRIBUTE_NAME]
 ["المحتوى": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["
		 ": النص]
 ["php": MACRO_TAG]
 ["
	 ": النص]
 ["main": CLOSING_BLOCK_TAG_NAME]
 ["

	 ": النص]
 ["تذييل": OPENING_BLOCK_TAG_NAME]
 ["
		 ": النص]
 ["hr": EMPTY_TAG_NAME]
 ["
		 ": النص]
 ["صغير": OPENING_BLOCK_TAG_NAME]
 ["id": ATTRIBUTE_NAME]
 ["حقوق الطبع والنشر": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["حقوق الطبع والنشر © 2019. جميع الحقوق محفوظة."  : النص]
 ["صغير": CLOSING_BLOCK_TAG_NAME]
 ["
	 ": النص]
 ["تذييل": CLOSING_BLOCK_TAG_NAME]
 ["
 ": النص]
 ["body": CLOSING_BLOCK_TAG_NAME]
 ["

 ": النص]
 ["html": CLOSING_BLOCK_TAG_NAME]
 ["
 ": النص]
 ["": END]



الآن نحن مستعدون لبدء الجزء الثاني - بناء شجرة بناء الجملة. نظرًا لأن العلامات الخاصة بنا لها سمات ، فإن العقد الشجرية ، بالإضافة إلى التواصل مع العقد الأخرى ، سوف تحتوي على صفيفات من أزواج القيمة الرئيسية. يمكن أن يسمى البناء الناتج بحق نموذج كائن مستند DOM المذكورة في عنوان المقالة.

كم عدد الفئات التي تحتاجها لتنفيذ جميع خصائص عناصر HTML؟

من الناحية المثالية ، هناك فئة واحدة لكل عنصر بحيث يمكن تعريف أوراق الأنماط المتتالية لها ، لكننا سنقصر أنفسنا على ثلاثة - علامة "Node" فارغة ، وكتلة "Block" موروثة (محتوى محصور بين علامتين مزدوجتين) وموروثة من له مع جذر شجرة الجذر. نحدد أيضًا في المحلل اللغوي مجموعة من العلامات التي قد تحتوي على نص ، مثل <p> ، <li> ، <strong> ، وما إلى ذلك ، للتخلص من الرموز المميزة بنص غير مستبدل. الآن صغير. إذا كنت قد عملت جيدًا في محلل المعجم ، فإن مهمة النحوي هي ببساطة امتصاص الرموز وإجراء واحدة من ثلاث عمليات في العقدة المفتوحة: أضف عقدة فارغة إليه ، أو افتح عقدة جديدة أو أغلقها بنفسك عن طريق إعادة المؤشر إلى الأصل. بالنسبة إلى الأخير ، يجب أن تحتوي جميع الفئات ، بدءًا من العقدة الأساسية ، على مؤشر تم الحصول عليه عند إنشاء العنصر. وتسمى هذه العملية تحليل من أعلى إلى أسفل .

تحليل الإجراء:

 void Parser::parse (const Lexer &lexer) { Block * open_block = (Block*) tree; Node * last_node = (Node*) tree; try { unsigned long long size = lexer.count(); for (unsigned long long i(0); i < size-2; i++) { switch (lexer[i].type) { case Lexer::TEXT: { for (unsigned int j(0); j < TEXT_TAGS_COUNT; j++) if (open_block->get_name() == text_tags[j]) last_node = open_block->add("TEXT", lexer[i].lexeme); break; } case Lexer::OPENING_BLOCK_TAG_NAME: { last_node = open_block = open_block->open(lexer[i].lexeme); break; } case Lexer::CLOSING_BLOCK_TAG_NAME: { if (lexer[i].lexeme != open_block->get_name()) throw string("unexpected closing tag: </" + lexer[i].lexeme + ">"); open_block = open_block->close(); break; } case Lexer::EMPTY_TAG_NAME: { last_node = open_block->add(lexer[i].lexeme); break; } case Lexer::COMMENT: { last_node = open_block->add("COMMENT", lexer[i].lexeme); break; } case Lexer::MACRO_TAG: { last_node = open_block->add("MACRO", lexer[i].lexeme); break; } case Lexer::ATTRIBUTE_NAME: { last_node->add_attr(lexer[i].lexeme, lexer[i].lexeme); break; } case Lexer::UNQUOTED_ATTRIBUTE_VALUE: { last_node->set_last_attr(lexer[i].lexeme); break; } case Lexer::SINGLE_QUOTED_ATTRIBUTE_VALUE: { last_node->set_last_attr(lexer[i].lexeme); break; } case Lexer::DOUBLE_QUOTED_ATTRIBUTE_VALUE: { last_node->set_last_attr(lexer[i].lexeme); break; } case Lexer::END: { if (open_block->get_type() != Node::ROOT) throw string("unexpected ending!"); open_block->close(); } } } } catch (const string &error) { throw string("parser: " + error); } } 

هذا كل شئ! إذا قمت بكل شيء بشكل صحيح ، يمكن عرض الشجرة الناتجة:

 | 
 + - <الجذر>
   | 
   + - <! DOCTYPE>
   | 
   + - <html>
     | 
     + - <head>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <meta>
     |  | 
     |  + - <العنوان>
     |  | 
     |  + - <link>
     |  | 
     |  + - <link>
     |  | 
     |  + - <COMMENT>
     | 
     + - <body>
       | 
       + - <header>
       |  | 
       |  + - <div>
       | 
       + - <nav>
       |  | 
       |  + - <ul>
       |  | 
       |  + - <li>
       |  |  | 
       |  |  + - <a>
       |  | 
       |  + - <li>
       |  |  | 
       |  |  + - <a>
       |  | 
       |  + - <li>
       |  | 
       |  + - <a>
       | 
       + - <رئيسي>
       |  | 
       |  + - <MACRO>
       | 
       + - <footer>
         | 
         + - <hr>
         | 
         + - <small>


ومع ذلك ، على الرغم من أنه يمكن تسمية الشجرة الناتجة بالفعل بـ DOM ، إلا أن المحلل اللغوي الخاص بنا بعيد عن امتلاء jQuery أو Jsoup أو beautifulsoup أو Gumbo ، خاصة لأنه لا يمكن معالجة النص الموجود بين علامات <style> و <script> المقترنة بشكل صحيح ، وبالتالي المصدر حتى أحضره. لكنني سأضيف بالتأكيد إذا كان سكان هابروفسك يعبرون عن هذه الرغبة. النجاحات

سكرتير خاص شغل أكواد المصدر في الوصول العام. IMHO ، الخام ، لذلك سوف أخطط لمكتبة كاملة.

الجزء الثاني.

Source: https://habr.com/ru/post/ar442964/


All Articles