पार्स करने या करने की कला-स्वयं डोम

नमस्कार, हेब्र! हाल ही में, मैं मार्कडाउन जैसी सरल मार्कअप भाषा बनाने के विचार के साथ आया था, जो कि मेरे कार्यों के लिए एकदम सही होगा, अर्थात्, स्वरूपण के साथ व्याख्यान का त्वरित लेखन और गणितीय फ़ार्मुलों को "मक्खी पर" सम्मिलित करने की क्षमता, केवल एक कीबोर्ड का उपयोग करके। इस प्रारूप में लिखे गए पाठ को अधिक समझने योग्य रूप में अनुवाद करने के लिए, उदाहरण के लिए, एक लिब्रे ऑफिस राइटर डॉक्यूमेंट, आपको दूसरे शब्दों में, एक पार्सर की आवश्यकता होती है । चूंकि मैं साइकिल का इस्तेमाल करता था, इसलिए मैं खोज इंजनों में "पार्सर उदाहरण", "एचटीएमएल से डोम", "एचटीएमएल पार्स कैसे करें" आदि के लिए खोज इंजन पर गया था, मेरी निराशा के लिए, सभी पाए गए संसाधनों पर, या तो प्राथमिक उदाहरण जैसे कि पुनरावर्ती के साथ स्ट्रैसपॉइंट कैलकुलेटर। फ्लेक्स, बाइसन, llvm और yacc जैसे डिसेंट या रेडी-मेड समाधानों का उपयोग किया गया। कड़ाई से परिभाषित भाषाओं (गंबू, jsoup, Rapidjson, Qt उपकरण, आदि) को पार्स करने के लिए और भी अधिक पुस्तकालय थे। न तो एक और न ही दूसरा मेरी मानक सी लाइब्रेरी के लिए सी ++ में अपना मार्कअप पार्सर लिखने की मेरी योजना का हिस्सा था, इसलिए मेरे इलेक्ट्रॉनिक संसाधनों के बजाय, तकनीकी संस्थानों के मैनुअल पार्सिंग की कला के बारे में ज्ञान का एक स्रोत बन गए। पाठ को कैसे लेना है और उससे निर्माण कैसे करें एएसटी (अमूर्त वाक्यविन्यास वृक्ष), इस प्रक्रिया में मेरे द्वारा किए गए कुछ नुकसानों के बारे में, मैं आज आपको संभावित त्रुटियों के बारे में बताऊंगा।

मैं तुरंत एक आरक्षण कर दूंगा - यदि आपका लक्ष्य आपकी स्वयं की स्क्रिप्टिंग भाषा या इससे भी अधिक जटिल है, तो यह लेख इसके कार्यान्वयन के लिए पर्याप्त नहीं होगा। आदर्श रूप से, आपको ऑटोमेटा और असतत संरचनाओं के सिद्धांत को अच्छी तरह से जानना होगा। लेकिन अभी के लिए, एक शुरुआती बिंदु के रूप में, मैं खुद को अपने अनुभव तक सीमित कर सकता हूं, जिसे मैंने उदारतापूर्वक कटौती के तहत साझा किया है। यह वही नहीं है जो मैंने मूल रूप से इरादा किया था, लेकिन यह एक उदाहरण के रूप में आदर्श है। हम एक सरल और परिचित भाषा के रूप में HTML को पार्स करेंगे।

सबसे पहले, पार्सिंग या पार्सिंग, टेक्स्ट को ऑब्जेक्ट मॉडल में बदलने की पूरी प्रक्रिया का पर्याय नहीं है। इस प्रक्रिया में दो चरण होते हैं:

  1. पाठ का टोकन में विश्लेषण इस पाठ के छोटे टुकड़े हैं जिनका एक निश्चित वाक्यगत अर्थ है।
  2. पार्सिंग एक सार सिंटैक्स ट्री (एएसटी - एब्स्ट्रैक्ट सिंटैक्स ट्री), या एक डॉक्यूमेंट ऑब्जेक्ट मॉडल (DOM - डॉक्यूमेंट ऑब्जेक्ट ऑब्जेक्ट मॉडल) के अपने मूल्यों के आधार पर टोकन का निर्माण है।

लेकिन इसे क्रम में लाएं। अपना पसंदीदा आईडीई और कोड लिखने से पहले, आपको भविष्य की भाषा का एक व्याकरण विकसित करने की आवश्यकता है। औपचारिक संदर्भ-मुक्त व्याकरण में, सबसे प्रसिद्ध बैकस-नौर (बीएनएफ) फॉर्म और विस्तारित बैकस-नौर फॉर्म हैं । मैंने उनके सिम्बायोसिस का उपयोग किया, दोनों रूपों का सबसे अच्छा लिया। किसी भी अभिव्यक्ति को अन्य भावों के माध्यम से इस प्रकार परिभाषित किया जा सकता है:

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

यहां एक के बाद एक तीन अभिव्यक्ति को दूसरे के माध्यम से परिभाषित किया गया है। बदले में, उन्हें "तीसरे" भाव आदि के माध्यम से भी दर्शाया जाना चाहिए।

कब रुकना है?

औपचारिक व्याकरण में किसी भी भाषा के वाक्य-विन्यास का वर्णन दो प्रकार के टोकन के होते हैं: टर्मिनल और गैर-टर्मिनलअपरिमेयल्स ऐसे भाव हैं जिन्हें परिभाषित करने की आवश्यकता है:

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

टर्मिनल आत्मनिर्भर हैं, उन्हें परिभाषित करने की आवश्यकता नहीं है। उपरोक्त उदाहरण इस तरह लिखे जा सकते हैं:

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

जहां "+", "*", "/" टर्मिनल हैं।
आपको व्याकरण से तुरंत टर्मिनलों का चयन करने की आवश्यकता है, आप उन्हें मुख्य परिभाषाओं के तल पर एक अलग सूची में भी लिख सकते हैं - वे बाद में काम में आएंगे।

बीएनएफ का पूरा विवरण विकिपीडिया पर और यहाँ उपलब्ध है । किसी भाषा के व्याकरण का संकलन एक ऐसी भाषा बनाने में एक महत्वपूर्ण चरण है जो तुच्छता को सहन नहीं करता है। इसमें एक गलती पूरी तरह से निष्क्रिय कोड को जन्म दे सकती है, जिसे खरोंच से फिर से लिखना होगा। इसलिए, अगला कदम उठाने से पहले, सुनिश्चित करें कि संकलित व्याकरण में कोई विवादास्पद मुद्दे नहीं हैं। यदि आपके पास दो मॉनिटर हैं, तो आपके लिए अपने बाकी के काम के लिए एक व्याकरण दस्तावेज़ के साथ एक मॉनिटर पर कब्जा करना सुविधाजनक होगा ताकि आप कोड करते समय अपनी आंखों को जल्दी से उसमें स्थानांतरित कर सकें। मेरा विश्वास करो, आपको हर समय ऐसा करना होगा। यहाँ मेरा संकलित 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 और फ़ाइल) प्रक्रिया में एक लूप होना चाहिए जो इनपुट स्ट्रीम से एक वर्ण को पढ़ता है और इसे प्रक्रिया (const char & c) प्रक्रिया में भेजता है, जहां यह चरित्र संसाधित होता है। ऐसा लगता है कि प्रक्रिया प्रक्रिया में स्विच शामिल करने की आवश्यकता है © जहां प्रत्येक कुंजी प्रतीक के अपने कार्य हैं, जो वर्तमान प्रकार के टोकन पर निर्भर करता है। वास्तव में, विपरीत सच है: टोकन के प्रकार की जांच करने के लिए स्विच का उपयोग करना बेहतर है, और वर्णों के लिए फ़ंक्शन को परिभाषित करना है। इसके अलावा, वर्तमान टोकन में अक्सर एक अनिश्चित प्रकार होता है, कई में से एक। उदाहरण के लिए, कोण कोष्ठक खोलने के बाद, आप देख सकते हैं: खोलने, बंद करने, खाली टैग, साथ ही साथ HTML शैली की टिप्पणी या मैक्रो टैग (PHP स्क्रिप्ट "<? ...?>" में संलग्न है। और ऐसे सभी यूनियनों के लिए आपको अपना मामला चाहिए। यह कैसे है? लागू करना। बिट झंडे का उपयोग करना। टोकन प्रकारों की एक सीमित संख्या दें। (अधिक बेहतर है, क्योंकि लेक्सिकल एनालाइज़र का कार्य सिंटैक्स के लिए जितना संभव हो उतना कम काम छोड़ना है)। प्रत्येक प्रकार के लिए, डिग्री दो की एक अद्वितीय संख्या दी गई है (1, 2, 4, 8। आदि)। फिर बाइनरी प्रारूप में वे इस तरह दिखाई देंगे: 0001, 0010, 0 100, आदि, और किसी भी प्रकार की किसी भी संख्या के बिटवाइज़ जोड़ के साथ, एक अद्वितीय संख्या प्राप्त की जाती है। यदि पाठ विवरण को समझना मुश्किल है, तो मैं कोड दूंगा। यहां प्रकारों की परिभाषा दी गई है:

 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 स्क्रिप्ट जोड़ा, इसे एक लेसर के साथ संसाधित किया, और प्रारूप में टोकन की एक सूची प्रदर्शित की "" [<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]
 ["लैंग": ATTRIBUTE_NAME]
 ["एन": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
 ": पाठ]
 ["हेड": OPENING_BLOCK_TAG_NAME]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["http-equiv": ATTRIBUTE_NAME]
 ["सामग्री-प्रकार": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["टेक्स्ट / html": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["चारसेट": ATTRIBUTE_NAME]
 ["utf-8": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["नाम": ATTRIBUTE_NAME]
 ["लेखक": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["इंटरक्वाड्रो": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["नाम": ATTRIBUTE_NAME]
 ["विवरण": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["नाम": ATTRIBUTE_NAME]
 ["कीवर्ड": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["नाम": ATTRIBUTE_NAME]
 ["व्यूपोर्ट": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["चौड़ाई = उपकरण-चौड़ाई, प्रारंभिक-स्केल = 1": DOUBLE_QUOTED_ATTRIBUTE_ATALAL]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["नाम": ATTRIBUTE_NAME]
 ["प्रारूप-पता लगाना": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["टेलीफोन = नहीं": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["http-equiv": ATTRIBUTE_NAME]
 ["एक्स-रिम-ऑटो-मैच": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["टेलीफोन = कोई नहीं": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["नाम": ATTRIBUTE_NAME]
 ["रेफ़रर": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["नो-रेफ़रर": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["मेटा": EMPTY_TAG_NAME]
 ["नाम": ATTRIBUTE_NAME]
 ["_suburl": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सामग्री": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	
	 ": पाठ]
 ["शीर्षक": OPENING_BLOCK_TAG_NAME]
 ["शीर्षक": CLOSING_BLOCK_TAG_NAME]
 [ "
	 ": पाठ]
 ["लिंक": EMPTY_TAG_NAME]
 ["rel": ATTRIBUTE_NAME]
 ["शॉर्टकट आइकन": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["href": ATTRIBUTE_NAME]
 [".ico": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
	 ": पाठ]
 ["लिंक": EMPTY_TAG_NAME]
 ["rel": ATTRIBUTE_NAME]
 ["स्टाइलशीट": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["प्रकार": ATTRIBUTE_NAME]
 ["पाठ / सीएसएस": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["href": ATTRIBUTE_NAME]
 [".css": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["शीर्षक": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "

	 ": पाठ]
 ["[अगर लेट आईई 9]>
	 <script src = "http://html5shiv.googlecode.com/svn/trunk/html5-els.js"> </ script>
	 <! [endif] ": COMMENT]
 [ "
 ": पाठ]
 ["सिर": CLOSING_BLOCK_TAG_NAME]
 [ "

 ": पाठ]
 ["बॉडी": OPENING_BLOCK_TAG_NAME]
 [ "
	 ": पाठ]
 ["हेडर": OPENING_BLOCK_TAG_NAME]
 [ "
		 ": पाठ]
 ["div": OPENING_BLOCK_TAG_NAME]
 ["आईडी": ATTRIBUTE_NAME]
 ["परिचय": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "

		 ": पाठ]
 ["div": CLOSING_BLOCK_TAG_NAME]
 [ "
	 ": पाठ]
 ["हेडर": CLOSING_BLOCK_TAG_NAME]
 [ "
	 ": पाठ]
 ["नौसेना": OPENING_BLOCK_TAG_NAME]
 [ "
		 ": पाठ]
 ["उल": OPENING_BLOCK_TAG_NAME]
 ["आईडी": ATTRIBUTE_NAME]
 ["नौसेना": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
			 ": पाठ]
 ["ली": OPENING_BLOCK_TAG_NAME]
 ["वर्ग": ATTRIBUTE_NAME]
 ["नौसेना": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["a": OPENING_BLOCK_TAG_NAME]
 ["href": ATTRIBUTE_NAME]
 ["#": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["होम": पाठ]
 ["a": CLOSING_BLOCK_TAG_NAME]
 ["ली": CLOSING_BLOCK_TAG_NAME]
 [ "
			 ": पाठ]
 ["ली": OPENING_BLOCK_TAG_NAME]
 ["वर्ग": ATTRIBUTE_NAME]
 ["नौसेना": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["a": OPENING_BLOCK_TAG_NAME]
 ["href": ATTRIBUTE_NAME]
 ["#": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["समीक्षा": पाठ]
 ["a": CLOSING_BLOCK_TAG_NAME]
 ["ली": CLOSING_BLOCK_TAG_NAME]
 [ "
			 ": पाठ]
 ["ली": OPENING_BLOCK_TAG_NAME]
 ["वर्ग": ATTRIBUTE_NAME]
 ["नौसेना": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["a": OPENING_BLOCK_TAG_NAME]
 ["href": ATTRIBUTE_NAME]
 ["": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["सहायता": पाठ]
 ["a": CLOSING_BLOCK_TAG_NAME]
 ["ली": CLOSING_BLOCK_TAG_NAME]
 [ "
		 ": पाठ]
 ["उल": CLOSING_BLOCK_TAG_NAME]
 [ "
	 ": पाठ]
 ["नौसेना": CLOSING_BLOCK_TAG_NAME]
 [ "

	 ": पाठ]
 ["मुख्य": OPENING_BLOCK_TAG_NAME]
 ["आईडी": ATTRIBUTE_NAME]
 ["सामग्री": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 [ "
		 ": पाठ]
 ["php": MACRO_TAG]
 [ "
	 ": पाठ]
 ["मुख्य": CLOSING_BLOCK_TAG_NAME]
 [ "

	 ": पाठ]
 ["पाद": OPENING_BLOCK_TAG_NAME]
 [ "
		 ": पाठ]
 ["घंटा": EMPTY_TAG_NAME]
 [ "
		 ": पाठ]
 ["छोटा": OPENING_BLOCK_TAG_NAME]
 ["आईडी": ATTRIBUTE_NAME]
 ["कॉपीराइट": DOUBLE_QUOTED_ATTRIBUTE_VALUE]
 ["कॉपीराइट © 2019। सभी अधिकार सुरक्षित।"  : पाठ]
 ["छोटा": CLOSING_BLOCK_TAG_NAME]
 [ "
	 ": पाठ]
 ["पाद": CLOSING_BLOCK_TAG_NAME]
 [ "
 ": पाठ]
 ["बॉडी": CLOSING_BLOCK_TAG_NAME]
 [ "

 ": पाठ]
 ["html": CLOSING_BLOCK_TAG_NAME]
 [ "
 ": पाठ]
 ["": END]



अब हम दूसरा भाग शुरू करने के लिए तैयार हैं - एक वाक्यविन्यास पेड़ का निर्माण। चूंकि हमारे टैग में विशेषताएं हैं, ट्री नोड्स, अन्य नोड्स के साथ संचार के अलावा, कुंजी-मूल्य जोड़े के सरणियों में शामिल होंगे। परिणामी निर्माण को लेख के शीर्षक में उल्लिखित DOM दस्तावेज़ का ऑब्जेक्ट मॉडल कहा जा सकता है।

HTML तत्वों के सभी गुणों को लागू करने के लिए आपको कितने वर्गों की आवश्यकता है?

आदर्श रूप से, प्रत्येक तत्व के लिए एक वर्ग होता है ताकि कैस्केडिंग स्टाइल शीट को उनके लिए परिभाषित किया जा सके, लेकिन हम खुद को तीन तक सीमित रखेंगे - एक खाली "नोड" टैग, एक विरासत में मिला "ब्लॉक" ब्लॉक (दो युग्मित टैग के बीच संलग्न सामग्री) और विरासत में मिला। जड़ वृक्ष की जड़ के साथ। हम पार्सर में उन टैगों की एक सरणी को भी परिभाषित करते हैं जिनमें पाठ हो सकता है, जैसे कि अनपेक्षित पाठ के साथ टोकन निकालने के लिए पाठ <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>
     | 
     + - <सिर>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <मेटा>
     |  | 
     |  + - <शीर्षक>
     |  | 
     |  + - <लिंक>
     |  | 
     |  + - <लिंक>
     |  | 
     |  + - <COMMENT>
     | 
     + - <शरीर>
       | 
       + - <हैडर>
       |  | 
       |  + - <div>
       | 
       + - <नौसेना>
       |  | 
       |  + - <ul>
       |  | 
       |  + - <li>
       |  |  | 
       |  |  + - <a>
       |  | 
       |  + - <li>
       |  |  | 
       |  |  + - <a>
       |  | 
       |  + - <li>
       |  | 
       |  + - <a>
       | 
       + - <मुख्य>
       |  | 
       |  + - <MACRO>
       | 
       + - <पाद>
         | 
         + - <hr>
         | 
         + - <छोटा>


हालाँकि, परिणामी वृक्ष को वास्तव में DOM कहा जा सकता है, हमारा पार्सर पूर्ण jQuery, Jsoup, beautifulsoup या Gumbo से दूर है, विशेष रूप से क्योंकि यह सही ढंग से युग्मित <style> और <script> टैग्स के बीच स्थित पाठ को संसाधित नहीं कर सकता है, और इसलिए स्रोत जब तक मैं इसे नहीं लाता। लेकिन मैं निश्चित रूप से जोड़ूंगा अगर हैबरोवस्क निवासियों ने ऐसी इच्छा व्यक्त की। गुड लक।

सार्वजनिक पहुंच में PS भरा हुआ स्रोत कोड । IMHO, कच्चा, इसलिए मैं एक पूर्ण पुस्तकालय की योजना बनाऊंगा।

PSS दूसरा भाग।

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


All Articles