Seni parsing 2 atau transliterasi markup Anda sendiri

+ BONUS: saling memasukkan kelas satu sama lain dalam C ++


Halo, Habr! Artikel ini adalah kelanjutan langsung dari artikel The Art of Parsing atau DOM dengan tangan kita sendiri , di mana kita mengurai dokumen HTML dan membangun berdasarkan pada dasarnya pohon sintaksis abstrak (AST) dengan akses ke elemen apa pun melalui pengindeksan hanya menggunakan perpustakaan C ++ standar, dengan kata lain, kita belajar mengurai sendiri Hal-hal seperti XML. Izinkan saya mengingatkan Anda bahwa proses parsing, atau parsing / parsing, terdiri dari dua tahap: parsing leksikal (parsing teks menjadi token) dan membangun AST. Jika kami memeriksa yang pertama dengan sangat rinci, dengan contoh dan kode sumber, maka deskripsi yang kedua terlihat seperti boneka kupu-kupu kosong, yang hanya memiliki kulit, dan penulis mengekstraksi konten yang sangat baik sebelum dipublikasikan. Ada alasannya, untuk HTML itu sangat mudah untuk membangun pohon, Anda hanya perlu 4 kelas: tag kosong, blok, simpul teks, dan root dokumen yang diwarisi dari blok. Hari ini kita akan meninggalkan kesederhanaan di belakang dan membangun pohon di mana properti elemen, baik kosong dan blok, tidak akan terkandung dalam atribut tag, tetapi langsung di kelas, dan untuk ini Anda harus membuat banyak kelas. Sangat banyak. Kami akan membangun bukan dari bahasa markup sederhana yang terkenal, tetapi buat sendiri, dengan aturan yang ditunjukkan pada gambar di bawah potongan. Plus, pada akhirnya, kami akan menerjemahkan, atau, lebih tepatnya, menerjemahkan dokumen dengan artikel sebelumnya, ditandai dalam bahasa kami, dalam HTML, dan sebagai bonus, saya akan menjawab programmer pemula C ++ ke pertanyaan yang sepele tetapi sulit ditemukan: bagaimana cara menggabungkan kelas satu sama lain?



Catatan Tata Bahasa


Sebelum kita langsung membangun sebuah pohon, mari menyegarkan kembali ingatan kita dan mengklarifikasi beberapa detail pekerjaan pendahuluan. Apakah Anda masih ingat bahwa seluruh sintaks bahasa perlu ditulis dalam bentuk salah satu tata bahasa formal bebas konteks, misalnya, BNF? Tetapi sulit bagi programmer pemula untuk menguasainya segera, dan selain itu, tidak semua aturan yang mungkin dengan tata bahasa ini dapat dijelaskan. Dalam kasus seperti itu, jika Anda berada di jalan buntu dan tidak merumuskan aturan tertentu dalam bentuk yang benar, Anda dapat menuliskannya sebagai komentar dalam bahasa manusia alami, misalnya, seperti ini:

... <ordered_list_item> = <number_marker> <div> <number_marker> = <number> "." {<number> "."} " " <number> = <digit> {<digit>} !! link ending ">" and image/span ending "]" can't follow "\n" or document start 

Terjemahan: akhir tautan ">" dan elemen gambar / sebaris "]" tidak dapat langsung mengikuti awal baris atau dokumen .

Yaitu, jika pada awal baris lexer bertemu "]" atau ">", kita harus menginstruksikannya untuk mengabaikan makna khusus dari karakter ini dan bekerja dengannya seperti dengan teks biasa. Cara menambahkan komentar ke tata bahasa ini bukan satu-satunya, Anda dapat melakukannya dengan cara Anda sendiri. Pada akhirnya, file dengan deskripsi sintaks bukanlah sebuah makalah, tidak ada yang memaksa Anda untuk mengikuti semua aturan dan hanya penting bagi Anda untuk bekerja dengannya. Hal utama adalah jangan melupakan komentar yang dibuat dan mencerminkannya di bagian kanan kode.

Mari kita lihat deskripsi lengkap dari bahasa ini:

 <article> = {<article_item>} <article_item> = <underline> | <section> (* ARTICLE ITEMS *) <underline> = "___" {"_"} "\n" <section> = <div> {<div>} <div> = <paragraphs> | <title> | <quote> | <cite> | <unordered_list> | <ordered_list> (* SECTION ITEMS *) <paragraphs> = <paragraph> {"\n" <paragraph>} <paragraph> = <span> {<span>} ("\n" | <END>) <span> = <bold> | <italic> | <underlined> | <overlined> | <throwlined> | <subscript> | <superscript> | <marked> | <monospace> | <text> | <image> | <link> | <notification> <title> = <number signs> <left_angle_bracket> {<span>} <right_angle_bracket> ("\n" | <END>) <number signs> "######" | "#####" | "####" | "###" | "##" | "#" <quote> = "> " {<span>} ("\n" | <END>) <cite> = ">>" <number> ("\n" | <END>) <number> = <digit> {<digit>} (* PARAGRAPH ITEMS *) <bold> = "*[" {<span>} "]" <italic> = "%[" {<span>} "]" <underlined> = "_[" {<span>} "]" <overlined> = "^[" {<span>} "]" <throwlined> = "~[" {<span>} "]" <subscript> = "&[" {<span>} "]" <superscript> = "+[" {<span>} "]" <marked> = "=[" {<span>} "]" <monospace> = "`[" {<span>} "]" <text> = <textline> "\n" {<textline> "\n"} <textline> = <symbol> {<symbol>} <symbol> = /^[\n]/ <link> = "<<" <text> ">" {<span>} ">" <image> = "[<" <text> ">" [<text>] "]" <notification> = (" " | "\n") "@" <word> (" " | "\n" | <END>) <word> = (<letter> | <digit>) {<letter> | <digit>} <letter> = "a" | "b" | "c" | "d" | ... | "_" | "-" <digit> = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" (* LISTS *) <unordered_list> = <unordered_list_item> {<unordered_list_item>} <ordered_list> = <ordered_list_item> {<ordered_list_item>} <unordered_list_item> = <marker> <div> <marker> = ("*" {"*"}) | ("+" {"+"}) " " <ordered_list_item> = <number_marker> <div> <number_marker> = <number> "." {<number> "."} " " <number> = <digit> {<digit>} !! link ending ">" and image/span ending "]" can't follow "\n" or document start 

Terakhir kali, perlu untuk menuliskan terminal dan memeriksa setiap karakter yang masuk untuk kepatuhan dengan salah satu dari mereka. Tapi kemudian terminalnya adalah karakter tunggal! Sekarang, selain menyoroti terminal, perlu untuk membaginya sendiri menjadi kunci - yaitu, karakter. Mengapa "kunci" itu? Mereka adalah kunci ke lexer. Sebagai hasil dari semua tindakan, baris berikut akan muncul di file tata bahasa:

 (* TERMINALS *) "___...", "\n", "\n\n", "> ", ">>...", "###...[", "*[", "%[", "_[", "^[", "~[", "&[", "+[", "=[", "`[", "]", "<<", "[<", ">", " @... ", "\n@...\n", " @...\n", "\n@... ", "***... ", "+++... ", "123.56. " (* KEYS *) "_", "\n" ">", "#", "*", "%", "^", "~", "&", "+", "=", "`", "<", "[", "]", " ", "@", "1..9", ".", <END> 

Tumpukan jenis token yang diharapkan


Terakhir kali, sekali lagi, semuanya lebih sederhana, kami hanya memiliki 10 jenis token, tidak termasuk akhir, dan ada sedikit kesempatan untuk bingung di kebun binatang token ini. Sekarang jelas ada lebih banyak tipe. Biarkan saya mengingatkan Anda bahwa tugas lexer adalah membiarkan parser sesedikit mungkin bekerja, idealnya, hanya membangun pohon. Oleh karena itu, rangkaian jenis token harus mencerminkan esensi mereka seakurat mungkin. Pada artikel pertama saya memberikan contoh set yang baik, dalam hal ini saya akan memberikannya bersama dengan "anti-contoh". Lihat terminal memulai elemen teks sebaris (tebal - tebal, miring - miring, dll.)? Kita dapat menguraikannya menjadi sepasang token: master ("*", "%", dll.) Dan budak ("[") dan meneruskannya dalam bentuk ke parser. Mudah ditebak bahwa lebih baik membuat definisi yang tepat dari elemen teks pada level lexer, yaitu mendefinisikan "* [" sebagai "bold_start", "% [" sebagai "italic_start", dll. Semakin banyak jenis dan semakin akurat mereka mencerminkan diri mereka sendiri - semakin baik. Selain itu, yang kedua lebih penting daripada yang pertama. Misalnya, kita dapat mem-parsing pemberitahuan untuk simbol "@" dan nama pengguna, tetapi jelas, lebih baik membiarkannya digabungkan dalam satu token.
Kami memutuskan jenisnya. Di mana memulai prosedur untuk mem-parsing teks ke dalam token? Saat itu, mulailah dari awal. Apa yang bisa segera mengikuti awal dokumen yang diuraikan? Jangan terburu-buru menekuk jari-jari Anda. Tidak seperti HTML, semua 22 jenis di sini dapat dimulai. Tidak apa-apa, dipersenjatai dengan sedikit penyatuan, kami menulis seperti ini:

 curr_token_type = TEXT | UNDERLINE | TITLE_START | QUOTE_START | CITE | BOLD_START | ... 

dan dalam fungsi pemrosesan simbol:

 case TEXT | UNDERLINE | TITLE_START | QUOTE_START | CITE | ... 

Jika Anda tidak mengerti apa yang dipertaruhkan, baca artikel pertama .

Jangan takut dengan jenis umum dari token yang diharapkan. Karakter pertama dari string segera mengurangi panjangnya menjadi 2-4 jenis. Karena terminal kami multi-karakter, definisi didasarkan pada kunci.

Sederhana, lihat sendiri:

 if (c == '_') { buffer.push_back('_'); curr_token_type = TEXT | UNDERLINE | UNDERLINED_START; 

Garis bawah segera menentukan token yang sedang dibangun ke salah satu dari tiga jenis: teks biasa, garis horizontal, atau awal teks yang digarisbawahi ("_ [").

Kembali ke masalah, bagaimana cara melacak semua jenis generik dan ingat untuk memproses semuanya? Dapatkan tumpukan ... di buku catatan! Itu benar, tulis semua tipe generik yang muncul setelah "curr_token_type = ..." di daftar, dan setelah memproses satu, ambil yang lain dari daftar dari akhir. Anda dapat mengatur pekerjaan dengan daftar dan seperti dengan antrian, itu tidak masalah. Yang utama adalah Anda tidak akan lupa jenis apa yang sudah diproses dan yang masih harus diproses.

Pohon kelas


Akhirnya, kami sampai di penguraian. Di sini Anda perlu menentukan kelas node (node) dari pohon masa depan dengan cara yang sama seperti yang kita tentukan dengan jenis token. Untuk melakukan ini, buka kembali buku catatan dan tulis yang berikut ini:

 Node { Node * parent, Node_type type } #- Root { Root_item[] children, ulong children_count } 

Jadi kami mendefinisikan kelas dasar masa depan dari semua node dan turunannya - akar pohon, yaitu dokumen itu sendiri. Dokumen (lihat BPF di atas) terdiri dari dua jenis node: bagian dan garis horizontal (garis bawah). Kami mendefinisikan kelas dasar Root_item untuk mereka dan menggambarkan masing-masing dengan cara yang sama seperti kami menggambarkan root. Selain itu, di sini, di notepad, kami segera menunjukkan semua bidang kelas lainnya, jika ada. Untuk akar, ini adalah jumlah "anak-anak" —yaitu, bagian internal dan garis horizontal. Bagian ini terdiri dari elemen-elemen yang kita akan mendefinisikan kelas dasar Div dan seterusnya, bergerak secara rekursif melalui tata bahasa, kita akan menentukan semua kelas yang dibutuhkan. Sebelum menulis kode, kami mendefinisikan semua inklusi header di sini. Ini sederhana: semua keturunan langsung dari kelas umum dasar harus dimasukkan dalam kelas yang mengandungnya.

Kami menunjukkan dependensi ini dalam bentuk daftar setelah kisi, dan kami mendapatkan dokumen berikut:

 Node { Node * parent, Node_type type } #- Root { Root_item[] children, ulong children_count } #Underline, #Section Root_item {} #- Underline {} Section { Div[] children, ulong children_count } #Paragraph, #Title, #Quote, #Cite, #Unordered_list, #Ordered_list Div {} #- Paragraph { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Title { char level, Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Quote { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Cite { ulong number } #- Unordered_list { Div } #Paragraph, #Title, #Quote, #Cite, #Ordered_list Ordered_list { Div } #Paragraph, #Title, #Quote, #Cite, Unordered list Span {} #- Bold { Span[] children, ulong children_count } #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Italic { Span[] children, ulong children_count } #Bold, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Underlined { Span[] children, ulong children_count } #Bold, #Italic, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Overlined { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Throwlined { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Subscript { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Superscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Superscript { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Marked, #Monospace, #Text, #Image, #Link, #Notification Marked { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Monospace, #Text, #Image, #Link, #Notification Monospace { Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Text, #Image, #Link, #Notification Text { string text } #- Image { string src, string alt } #- Link { string URL, Span[] children, ulong children_count } #Bold, #Italic, #Underlined, #Overlined, #Throwlined, #Subscript, #Superscript, #Marked, #Monospace, #Text, #Image, #Notification Notification { string user } #- 

Di sini saya menandai "# -" tidak adanya dependensi dan menghapus masuknya kelas dalam diri mereka sendiri.
Kami perhatikan bahwa semua kelas pemformatan bawaan (Tebal, Miring, ...) saling bergantung satu sama lain dan, di samping itu, pada kelas Tautan, yang juga bergantung pada mereka! Dalam posisi yang sama adalah Unordered_list dan Ordered_list. Memasukkan header satu sama lain tidak hanya akan menyebabkan mengabaikan salah satu dari mereka, seperti yang diharapkan, tetapi juga tidak akan melewati validasi oleh preprosesor, dan inklusi satu sisi tidak akan memungkinkan kita untuk mendeklarasikan di dalam kelas yang disertakan fungsi membuka elemen kelas dan mengembalikan tautan ke sana. Bagaimana menjadi Ada dua cara.

Inklusi kelas satu sama lain


Pertama, lihat kelas Bold, Italic dan seterusnya ke Monospace. Mereka sama. Sedemikian rupa sehingga mereka dapat digabungkan menjadi satu kelas "Inline". Mungkin keputusan ini akan menimbulkan keraguan. Itu juga menyebabkan saya, tetapi dalam praktiknya, perbedaan di antara mereka hanya memengaruhi bentuk presentasi dalam bentuk pohon di terminal dan tag dalam HTML. Jika Anda melihat bahwa beberapa kelas berisi bidang yang sama, memiliki dependensi yang sama dan umumnya memiliki deskripsi serupa dalam tata bahasa formal, jangan ragu untuk menggabungkannya. Jadi Anda membuatnya lebih mudah untuk diri sendiri dan prosesor.

Tetapi trik semacam itu tidak akan berfungsi dengan kelas Link, karena mengandung bidang tambahan - string URL. Kami akan menggunakan metode kedua.

Apakah semua orang tahu bahwa memisahkan kelas menjadi deklarasi dan definisi adalah bentuk yang baik dalam pemrograman C ++? Di header dengan ekstensi .h atau .hpp - deklarasi, di sumber dengan ekstensi .cpp - definisi, kan? Dan sekarang saya beralih ke pendatang baru untuk pemrograman: duduk dan kencangkan sabuk pengaman Anda, karena itu akan menjadi tidak menyenangkan. Lagi pula, apa yang kami meresepkan dalam file dengan ekstensi .h tidak lebih dari definisi kelas. Dan dalam file .cpp, sudah ada implementasi metode kelas ini. Tidak mengerti Kami ditipu di sekolah. Kelas dinyatakan sebagai fungsi, dalam satu baris, jika tidak mengandung templat.

Ini bahkan lebih sederhana daripada fungsi, karena tidak memiliki argumen. Berikut ini adalah deklarasi kelas tipikal:

 class MyClass; 

Dan itu dia! Dan deklarasi bidang dan metode sudah definisi .

Kami akan mengambil keuntungan dari ini. Kami menyertakan judul kelas Inline dalam judul kelas Link, dan di dalamnya mendeklarasikan kelas Link itu sendiri sebelum mendefinisikan kelas Inline. File inline.h akan terlihat seperti ini:

 #ifndef INLINE_H #define INLINE_H #include "AST/span.h" #include "AST/text.h" #include "AST/image.h" #include "AST/notification.h" class Link; class Inline : public Span { public: static const unsigned long MAX_CHILDREN_COUNT = 0xFFFFFF; private: Span ** children; unsigned long children_count; unsigned long extended; void extend(); public: Inline(const Node * parent, const Node_type &type); Inline(const Span * span); ~Inline(); Inline * add_text(const string &text); Inline * add_image(const string &src, const string &alt); Inline * add_notification(const string &user); Link * open_link(const string &URL); ... 

Kelas Inline masih tidak tahu apa-apa tentang kelas Link, bidang dan metodenya, tetapi ia tahu pasti tentang keberadaannya. Oleh karena itu, kita dapat mendeklarasikan metode yang mengembalikan pointer ke objek kelas Link, atau menerimanya sebagai argumen. Pointer kata tidak dipilih secara acak, kelas Inline belum tahu bagaimana membangun objek bertipe Link, karena tidak memiliki akses ke konstruktornya, tetapi dapat bekerja dengan semua pointer, karena mereka semua memiliki antarmuka yang sama. Tapi kita tidak butuh benda di sini. Tetapi dalam implementasi metode open_link, sebuah objek bertipe Link dibuat dan sebuah pointer ke sana dikembalikan, yang berarti bahwa pada saat preprocessor memasuki metode ini, konstruktor dan metode tautan lainnya yang mungkin perlu metode open_link harus dideklarasikan. Di sini kita mengambil keuntungan dari membagi kode sumber menjadi file terpisah dengan tajuk dan implementasi. File inline.h termasuk dalam file inline.cpp (“undercloud”), tetapi file link.h tidak termasuk dalam inline.h. Jadi, memasukkannya ke inline.cpp akan menjadi penyertaan pertama untuk preprosesor. Maka file inline.cpp akan mulai seperti ini:

 #include "inline.h" #include "link.h" ... 

Saya ulangi semua hal di atas. Judul kelas Ah termasuk dalam judul kelas Bh seperti biasa, dan kelas B dideklarasikan sebelum kelas A dan kami memasukkan judulnya dalam sumber A.cpp. Metode ini bukan satu-satunya, tetapi paling sederhana, menurut saya.

Saya perhatikan bahwa penyertaan bersama kelas semacam itu tidak mencegah kelas B dari mewarisi dari kelas A, jika kita menuliskan deklarasi sebelum definisi kelas A. Itulah yang saya lakukan, mewarisi Ordered_list dari Unordered_list.

Membangun pohon


Jadi, kita sampai pada konstruksi pohon sintaksis abstrak. Dalam artikel terakhir, fungsinya muat dalam 50 baris. Spoiler: kali ini telah berkembang menjadi hampir 1400. Prinsip operasi adalah sama: kami memeriksa jenis setiap token dan, tergantung pada itu, jalankan bagian kode tertentu, menyimpan simpul pohon terbuka dalam memori. Tetapi jika untuk parsing HTML hampir semua bagian berisi satu dan hanya satu dari tiga perintah: tambahkan simpul kosong di dalam yang terbuka, buka simpul baru di yang terbuka dan tutup simpul yang terbuka, kembalikan induknya, maka tindakan yang diinginkan di sini juga tergantung pada jenis simpul terbuka. Misalnya, jika token "garis horizontal" mulai diproses, dan simpul terbuka adalah root dokumen, maka semua yang diperlukan adalah menambahkan garis ke simpul terbuka ini menggunakan casting dan fungsi dengan nama tambahan add_line (), sesuatu seperti ini:

 if (type == Node::ROOT) static_case<Root*>(open_node)->add_line(); 

Tetapi jika simpul terbuka adalah paragraf (Paragraph), maka pertama-tama Anda harus menutupnya dan semua leluhur yang mungkin (daftar berpoin dan bernomor) sampai simpul terbuka menjadi tipe "bagian", dan kemudian tutup juga:

 else if (type == Node::PARAGRAPH) { open_node = static_cast<Paragraph*>(open_node)->close(); while (open_node->get_type() != Node::SECTION) { if (open_node->get_type() == Node::UNORDERED_LIST) open_node = static_cast<Unordered_list*>(open_node)->close(); else if (open_node->get_type() == Node::UNORDERED_LIST) open_node = static_cast<Unordered_list*>(open_node)->close(); else if (open_node->get_type() == Node::PARAGRAPH) open_node = static_cast<Paragraph*>(open_node)->close(); } open_node = static_cast<Section*>(open_node)->close(); open_node = tree->add_line(); } 

Jika simpul terbuka adalah keterangan gambar, maka garis horizontal memecah tata bahasa sama sekali, dan pengecualian harus dibuang, dan jika simpul terbuka bukan tautan, dan token yang masuk ">" memiliki tipe "LINK_FINISH", itu harus diproses bukan sebagai akhir tautan, tetapi bagaimana teks, dll.

Dengan demikian, switch / case tree, yang memeriksa tipe token yang masuk, harus berisi switch / case tree yang lain, yang memeriksa tipe node terbuka. Pada awalnya sulit untuk menangani konstruksi seperti itu, tetapi tidak perlu untuk memulai dari awal, dari kondisi pertama. Anda dapat membuat dokumen standar berlabel dengan bahasa Anda / mengandung skrip dalam bahasa Anda dan menerapkan ketentuan saat dokumen berjalan dengan memeriksa hasilnya dengan mengeluarkan pohon pseudografis ke terminal. Saya mengambil artikel sebelumnya sebagai dokumen, token pertama yang diterima adalah awal dari judul. Jadi, kami memproses token dengan tipe TITLE_START. Berikut ini adalah teks header dan braket kotak penutup, kami memproses token tipe TEXT dan SPAN_OR_IMAGE_FINISH.

Setelah itu, kita akan memiliki mini-tree:

 <article> | +-<section> | +-<h1> | +-"   DOM  " 

Sepanjang jalan, Anda akan melihat bahwa beberapa kelas menyertakan metode yang sama dengan algoritma yang sama. Misalnya, paragraf paragraf paragraf dan kutipan harga membuka tautan dengan cara yang sama dan menambahkan teks ke dalamnya. Dalam kasus seperti itu, solusi terbaik ketika refactoring adalah membuat satu kelas dengan metode ini dan mewarisi node yang diperlukan dari itu. Saya mencoba menerapkan ini, tetapi keterampilan saya tidak cukup, dan saya bingung dalam ambiguitas saat casting, jadi saya hanya memberikan hasil lexer dan parser:

Artikel itu sendiri
 @2che >>442964 #[   DOM  ] , !          markdown,       ,   —           « »,     .   ,    ,    , ,  LibreOffice Writer,  %[ ],   — %[].     ,        «parser example», «html to DOM», «how to parse html»  .   ,              ,    ,   flex, bison, llvm  yacc. ,      ,    (gumbo, jsoup, rapidjson,  Qt  .)  ,              C++     ,              .  ,        AST (  ),    ,      ,       .  , —    —       ,       .           .           ,      .    ,    ,     .    HTML,      .  , ,  %[ ] —         .      : 1. *[ ]    —    ,    . 2. *[ ] —        %[  ] (AST — abstract syntax tree),  %[  ] (DOM — document object model).    .  ,     IDE   ,     .   -    — %[ - ()]  %[  -].    ,     .        : > `[<> = <_1> <_> <_2>]       ,    . ,   ,     «»   ..   ?            : %[]  %[]. *[] — ,  : > `[<_1> = <> (<_> | <_>) <>] *[] ,    .      : > `[<> = <_1> "+" <_2> <_1> = <> ("*" | "/") <>]  "+", "*", "/" — .      ,          , —   .       <<https://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D0%BC%D0%B0_%D0%91%D1%8D%D0%BA%D1%83%D1%81%D0%B0_%E2%80%94_%D0%9D%D0%B0%D1%83%D1%80%D0%B0>>  <<https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_%D1%84%D0%BE%D1%80%D0%BC%D0%B0_%D0%91%D1%8D%D0%BA%D1%83%D1%81%D0%B0_%E2%80%94_%D0%9D%D0%B0%D1%83%D1%80%D0%B0>>.    —    ,   .          ,     . ,     , ,        .     ,             ,        ,   . ,    .     HTML5   : > `[stub]   ,      (   , ..  ,      ).     :  ,                , ? ,       ,  .  .  ,  disassemble(ifsteam &file)   ,             process(const char &c),    . ,   process   switch,              .     :    switch    ,     .  ,       ,   . ,      : , ,  ,      HTML    ( PHP,   "<?… ?>".        case.   ?    .       (  —  ,      —      ).         (1, 2, 4, 8  .).        : 0001, 0010, 0100  ..,           .      ,  .   : > `[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 };]   process: > `[stub]    switch    ( ),    case       .    ,    :     ,      ,      (),   .               .       ()    ,      «»  ..         gedit: [<https://hsto.org/webt/72/fw/tw/72fwtwt_waeie4ulzftkxua356w.png>]     ,           .     disassemble: > `[stub]        TEXT,       END    ( ,  ).         HTML-  ,    - PHP,         "[ "<_>": <_> ]".   : > =[ `[stub]] =[ `[stub]]        —   .     ,             -.         DOM,    .        HTML-?   —      ,         ,     —   «Node»,     «Block» (,     )       «Root».      ,    , ,  <p>, <li>, <strong>  ,      .    .      ,    —            :     ,     ,    .   ,   ,    Node,   ,    .    %[  ].  : > `[stub]   !     ,      : `[| +--<ROOT> | +--<!DOCTYPE> | +--<html> | +--<head> | | | +--<meta> | | | +--<meta> | | | +--<meta> | | | +--<meta> | | | +--<meta> | | | +--<meta> | | | +--<meta> | | | +--<meta> | | | +--<meta> | | | +--<title> | | | +--<link> | | | +--<link> | | | +--<COMMENT> | +--<body> | +--<header> | | | +--<div> | +--<nav> | | | +--<ul> | | | +--<li> | | | | | +--<a> | | | +--<li> | | | | | +--<a> | | | +--<li> | | | +--<a> | +--<main> | | | +--<MACRO> | +--<footer> | +--<hr> | +--<small>] ,       DOM,   jQuery, Jsoup, beautifulsoup  Gumbo   ,   ,       ,     <style>  <script>,      .   ,     . . PS     <<https://gitlab.com/2che/nyHTML>>. , ,      . 

Token dari lexer
 0: ["2che": PEMBERITAHUAN]
1: ["
": NEWLINE]
2: ["442964": CITE]
3: ["# [": TITLE_START]
4: ["The Art of Parsing atau Do-It-Yourself DOM": TEXT]
5: ["]": SPAN_OR_IMAGE_FINISH]
6: ["

": DOUBLE_NEWLINE]
7: [ ", !          markdown,       ,   —           « »,     .   ,    ,    , ,  LibreOffice Writer,  " : TEXT ]
8: [ "%[" : ITALIC_START ]
9: [ " " : TEXT ]
10: [ "]" : SPAN_OR_IMAGE_FINISH ]
11: [ ",   — " : TEXT ]
12: [ "%[" : ITALIC_START ]
13: [ "" : TEXT ]
14: [ "]" : SPAN_OR_IMAGE_FINISH ]
15: [ ".     ,        «parser example», «html to DOM», «how to parse html»  .   ,              ,    ,   flex, bison, llvm  yacc. ,      ,    (gumbo, jsoup, rapidjson,  Qt  .)  ,              C++     ,              .  ,        AST (  ),    ,      ,       ." : TEXT ]
16: [ "

" : DOUBLE_NEWLINE ]
17: [ " , —    —       ,       .           .           ,      .    ,    ,     .    HTML,      ." : TEXT ]
18: [ "

" : DOUBLE_NEWLINE ]
19: [ " , ,  " : TEXT ]
20: [ "%[" : ITALIC_START ]
21: [ " " : TEXT ]
22: [ "]" : SPAN_OR_IMAGE_FINISH ]
23: [ " —         .      :" : TEXT ]
24: [ "
" : NEWLINE ]
25: [ "1. " : ORDERED_LIST_ITEM_MARKER ]
26: [ "*[" : BOLD_START ]
27: [ " " : TEXT ]
28: [ "]" : SPAN_OR_IMAGE_FINISH ]
29: [ "    —    ,    ." : TEXT ]
30: [ "
" : NEWLINE ]
31: [ "2. " : ORDERED_LIST_ITEM_MARKER ]
32: [ "*[" : BOLD_START ]
33: [ " " : TEXT ]
34: [ "]" : SPAN_OR_IMAGE_FINISH ]
35: [ " —        " : TEXT ]
36: [ "%[" : ITALIC_START ]
37: [ "  " : TEXT ]
38: [ "]" : SPAN_OR_IMAGE_FINISH ]
39: [ " (AST — abstract syntax tree),  " : TEXT ]
40: [ "%[" : ITALIC_START ]
41: [ "  " : TEXT ]
42: [ "]" : SPAN_OR_IMAGE_FINISH ]
43: [ " (DOM — document object model)." : TEXT ]
44: [ "

" : DOUBLE_NEWLINE ]
45: [ "   .  ,     IDE   ,     .   -    — " : TEXT ]
46: [ "%[" : ITALIC_START ]
47: [ " - ()" : TEXT ]
48: [ "]" : SPAN_OR_IMAGE_FINISH ]
49: [ "  " : TEXT ]
50: [ "%[" : ITALIC_START ]
51: [ "  -" : TEXT ]
52: [ "]" : SPAN_OR_IMAGE_FINISH ]
53: [ ".    ,     .        :" : TEXT ]
54: [ "
" : NEWLINE ]
55: [ "> " : QUOTE_START ]
56: [ "`[" : MONOSPACE_START ]
57: [ "<" : TEXT ]
58: [ ">" : LINK_FINISH ]
59: [ " = <_1" : TEXT ]
60: [ ">" : LINK_FINISH ]
61: [ " <_" : TEXT ]
62: [ ">" : LINK_FINISH ]
63: [ " <_2" : TEXT ]
64: [ ">" : LINK_FINISH ]
65: [ "]" : SPAN_OR_IMAGE_FINISH ]
66: [ "

" : DOUBLE_NEWLINE ]
67: [ "      ,    . ,   ,     «»   .." : TEXT ]
68: [ "

" : DOUBLE_NEWLINE ]
69: [ "  ?" : TEXT ]
70: [ "

" : DOUBLE_NEWLINE ]
71: [ "           : " : TEXT ]
72: [ "%[" : ITALIC_START ]
73: [ "" : TEXT ]
74: [ "]" : SPAN_OR_IMAGE_FINISH ]
75: [ "  " : TEXT ]
76: [ "%[" : ITALIC_START ]
77: [ "" : TEXT ]
78: [ "]" : SPAN_OR_IMAGE_FINISH ]
79: [ ". " : TEXT ]
80: [ "*[" : BOLD_START ]
81: [ "" : TEXT ]
82: [ "]" : SPAN_OR_IMAGE_FINISH ]
83: [ " — ,  :" : TEXT ]
84: [ "
" : NEWLINE ]
85: [ "> " : QUOTE_START ]
86: [ "`[" : MONOSPACE_START ]
87: [ "<_1" : TEXT ]
88: [ ">" : LINK_FINISH ]
89: [ " = <" : TEXT ]
90: [ ">" : LINK_FINISH ]
91: [ " (<_" : TEXT ]
92: [ ">" : LINK_FINISH ]
93: [ " | <_" : TEXT ]
94: [ ">" : LINK_FINISH ]
95: [ ") <" : TEXT ]
96: [ ">" : LINK_FINISH ]
97: [ "]" : SPAN_OR_IMAGE_FINISH ]
98: [ "

" : DOUBLE_NEWLINE ]
99: [ "*[" : BOLD_START ]
100: [ "" : TEXT ]
101: [ "]" : SPAN_OR_IMAGE_FINISH ]
102: [ " ,    .      :" : TEXT ]
103: [ "
" : NEWLINE ]
104: [ "> " : QUOTE_START ]
105: [ "`[" : MONOSPACE_START ]
106: [ "<" : TEXT ]
107: [ ">" : LINK_FINISH ]
108: [ " = <_1" : TEXT ]
109: [ ">" : LINK_FINISH ]
110: [ " "+" <_2" : TEXT ]
111: [ ">" : LINK_FINISH ]
112: [ "
" : NEWLINE ]
113: [ "<_1" : TEXT ]
114: [ ">" : LINK_FINISH ]
115: [ " = <" : TEXT ]
116: [ ">" : LINK_FINISH ]
117: [ " ("*" | "/") <" : TEXT ]
118: [ ">" : LINK_FINISH ]
119: [ "]" : SPAN_OR_IMAGE_FINISH ]
120: [ "

" : DOUBLE_NEWLINE ]
121: [ " "+", "*", "/" — ." : TEXT ]
122: [ "
" : NEWLINE ]
123: [ "     ,          , —   ." : TEXT ]
124: [ "

" : DOUBLE_NEWLINE ]
125: [ "      " : TEXT ]
126: [ "<<" : LINK_START ]
127: [ "https://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D0%BC%D0%B0_%D0%91%D1%8D%D0%BA%D1%83%D1%81%D0%B0_%E2%80%94_%D0%9D%D0%B0%D1%83%D1%80%D0%B0" : TEXT ]
128: [ ">" : LINK_FINISH ]
129: [ "" : TEXT ]
130: [ ">" : LINK_FINISH ]
131: [ "  " : TEXT ]
132: [ "<<" : LINK_START ]
133: [ "https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_%D1%84%D0%BE%D1%80%D0%BC%D0%B0_%D0%91%D1%8D%D0%BA%D1%83%D1%81%D0%B0_%E2%80%94_%D0%9D%D0%B0%D1%83%D1%80%D0%B0" : TEXT ]
134: [ ">" : LINK_FINISH ]
135: [ "" : TEXT ]
136: [ ">" : LINK_FINISH ]
137: [ ".    —    ,   .          ,     . ,     , ,        .     ,             ,        ,   . ,    .     HTML5   :" : TEXT ]
138: [ "
" : NEWLINE ]
139: [ "> " : QUOTE_START ]
140: [ "`[" : MONOSPACE_START ]
141: [ "stub" : TEXT ]
142: [ "]" : SPAN_OR_IMAGE_FINISH ]
143: [ "

" : DOUBLE_NEWLINE ]
144: [ "  ,      (   , ..  ,      ).     :  ,                , ? ,       ,  .  .  ,  disassemble(ifsteam &file)   ,             process(const char &c),    . ,   process   switch,              .     :    switch    ,     .  ,       ,   . ,      : , ,  ,      HTML    ( PHP,   "<?… ?" : TEXT ]
145: [ ">" : LINK_FINISH ]
146: [ "".        case.   ?    .       (  —  ,      —      ).         (1, 2, 4, 8  .).        : 0001, 0010, 0100  ..,           .      ,  .   :" : TEXT ]
147: [ "
" : NEWLINE ]
148: [ "> " : QUOTE_START ]
149: [ "`[" : MONOSPACE_START ]
150: [ "enum Token_type {" : TEXT ]
151: [ "
" : NEWLINE ]
152: [ " END = 1, TEXT = 2," : TEXT ]
153: [ "
" : NEWLINE ]
154: [ " OPENING_BLOCK_TAG_NAME = 4, CLOSING_BLOCK_TAG_NAME = 8, EMPTY_TAG_NAME = 16, COMMENT = 32, MACRO_TAG = 64," : TEXT ]
155: [ "
" : NEWLINE ]
156: [ " ATTRIBUTE_NAME = 128, UNQUOTED_ATTRIBUTE_VALUE = 256, SINGLE_QUOTED_ATTRIBUTE_VALUE = 512, DOUBLE_QUOTED_ATTRIBUTE_VALUE = 1024" : TEXT ]
157: [ "
" : NEWLINE ]
158: [ "};" : TEXT ]
159: [ "]" : SPAN_OR_IMAGE_FINISH ]
160: [ "

" : DOUBLE_NEWLINE ]
161: [ "  process:" : TEXT ]
162: [ "
" : NEWLINE ]
163: [ "> " : QUOTE_START ]
164: [ "`[" : MONOSPACE_START ]
165: [ "stub" : TEXT ]
166: [ "]" : SPAN_OR_IMAGE_FINISH ]
167: [ "

" : DOUBLE_NEWLINE ]
168: [ "   switch    ( ),    case       .    ,    :     ,      ,      (),   .               .       ()    ,      «»  ..         gedit:" : TEXT ]
169: [ "
" : NEWLINE ]
170: [ "[<" : IMAGE_START ]
171: [ "https://hsto.org/webt/72/fw/tw/72fwtwt_waeie4ulzftkxua356w.png" : TEXT ]
172: [ ">" : LINK_FINISH ]
173: [ "]" : SPAN_OR_IMAGE_FINISH ]
174: [ "

" : DOUBLE_NEWLINE ]
175: [ "    ,           .     disassemble:" : TEXT ]
176: [ "
" : NEWLINE ]
177: [ "> " : QUOTE_START ]
178: [ "`[" : MONOSPACE_START ]
179: [ "stub" : TEXT ]
180: [ "]" : SPAN_OR_IMAGE_FINISH ]
181: [ "

" : DOUBLE_NEWLINE ]
182: [ "       TEXT,       END    ( ,  )." : TEXT ]
183: [ "

" : DOUBLE_NEWLINE ]
184: [ "        HTML-  ,    - PHP,         "[ "<_" : TEXT ]
185: [ ">" : LINK_FINISH ]
186: [ "": <_" : TEXT ]
187: [ ">" : LINK_FINISH ]
188: [ " " : TEXT ]
189: [ "]" : SPAN_OR_IMAGE_FINISH ]
190: [ "".   :" : TEXT ]
191: [ "
" : NEWLINE ]
192: [ "> " : QUOTE_START ]
193: [ "=[" : MARKED_START ]
194: [ " " : TEXT ]
195: [ "`[" : MONOSPACE_START ]
196: [ "stub" : TEXT ]
197: [ "]" : SPAN_OR_IMAGE_FINISH ]
198: [ "]" : SPAN_OR_IMAGE_FINISH ]
199: [ "
" : NEWLINE ]
200: [ "=[" : MARKED_START ]
201: [ " " : TEXT ]
202: [ "`[" : MONOSPACE_START ]
203: [ "stub" : TEXT ]
204: [ "]" : SPAN_OR_IMAGE_FINISH ]
205: [ "]" : SPAN_OR_IMAGE_FINISH ]
206: [ "

" : DOUBLE_NEWLINE ]
207: [ "       —   .     ,             -.         DOM,    ." : TEXT ]
208: [ "

" : DOUBLE_NEWLINE ]
209: [ "       HTML-?" : TEXT ]
210: [ "

" : DOUBLE_NEWLINE ]
211: [ "  —      ,         ,     —   «Node»,     «Block» (,     )       «Root».      ,    , ,  <p" : TEXT ]
212: [ ">" : LINK_FINISH ]
213: [ ", <li" : TEXT ]
214: [ ">" : LINK_FINISH ]
215: [ ", <strong" : TEXT ]
216: [ ">" : LINK_FINISH ]
217: [ "  ,      .    .      ,    —            :     ,     ,    .   ,   ,    Node,   ,    .    " : TEXT ]
218: [ "%[" : ITALIC_START ]
219: [ "  " : TEXT ]
220: [ "]" : SPAN_OR_IMAGE_FINISH ]
221: [ "." : TEXT ]
222: [ "

" : DOUBLE_NEWLINE ]
223: [ " :" : TEXT ]https://gitlab.com/2che/markedit
224: [ "
" : NEWLINE ]
225: [ "> " : QUOTE_START ]
226: [ "`[" : MONOSPACE_START ]
227: [ "stub" : TEXT ]
228: [ "]" : SPAN_OR_IMAGE_FINISH ]
229: [ "

" : DOUBLE_NEWLINE ]
230: [ "  !     ,      :" : TEXT ]
231: [ "
" : NEWLINE ]
232: [ "`[" : MONOSPACE_START ]
233: [ "| " : TEXT ]
234: [ "
" : NEWLINE ]
235: [ "+--<ROOT" : TEXT ]
236: [ ">" : LINK_FINISH ]
237: [ "
" : NEWLINE ]
238: [ " | " : TEXT ]
239: [ "
" : NEWLINE ]
240: [ " +--<!DOCTYPE" : TEXT ]
241: [ ">" : LINK_FINISH ]
242: [ "
" : NEWLINE ]
243: [ " | " : TEXT ]
244: [ "
" : NEWLINE ]
245: [ " +--<html" : TEXT ]
246: [ ">" : LINK_FINISH ]
247: [ "
" : NEWLINE ]
248: [ " | " : TEXT ]
249: [ "
" : NEWLINE ]
250: [ " +--<head" : TEXT ]
251: [ ">" : LINK_FINISH ]
252: [ "
" : NEWLINE ]
253: [ " | | " : TEXT ]
254: [ "
" : NEWLINE ]
255: [ " | +--<meta" : TEXT ]
256: [ ">" : LINK_FINISH ]
257: [ "
" : NEWLINE ]
258: [ " | | " : TEXT ]
259: [ "
" : NEWLINE ]
260: [ " | +--<meta" : TEXT ]
261: [ ">" : LINK_FINISH ]
262: [ "
" : NEWLINE ]
263: [ " | | " : TEXT ]
264: [ "
" : NEWLINE ]
265: [ " | +--<meta" : TEXT ]
266: [ ">" : LINK_FINISH ]
267: [ "
" : NEWLINE ]
268: [ " | | " : TEXT ]
269: [ "
" : NEWLINE ]
270: [ " | +--<meta" : TEXT ]
271: [ ">" : LINK_FINISH ]
272: [ "
" : NEWLINE ]
273: [ " | | " : TEXT ]
274: [ "
" : NEWLINE ]
275: [ " | +--<meta" : TEXT ]
276: [ ">" : LINK_FINISH ]
277: [ "
" : NEWLINE ]
278: [ " | | " : TEXT ]
279: [ "
" : NEWLINE ]
280: [ " | +--<meta" : TEXT ]
281: [ ">" : LINK_FINISH ]
282: [ "
" : NEWLINE ]
283: [ " | | " : TEXT ]
284: [ "
" : NEWLINE ]
285: [ " | +--<meta" : TEXT ]
286: [ ">" : LINK_FINISH ]
287: [ "
" : NEWLINE ]
288: [ " | | " : TEXT ]
289: [ "
" : NEWLINE ]
290: [ " | +--<meta" : TEXT ]
291: [ ">" : LINK_FINISH ]
292: [ "
" : NEWLINE ]
293: [ " | | " : TEXT ]
294: [ "
" : NEWLINE ]
295: [ " | +--<meta" : TEXT ]
296: [ ">" : LINK_FINISH ]
297: [ "
" : NEWLINE ]
298: [ " | | " : TEXT ]
299: [ "
" : NEWLINE ]
300: [ " | +--<title" : TEXT ]
301: [ ">" : LINK_FINISH ]
302: [ "
" : NEWLINE ]
303: [ " | | " : TEXT ]
304: [ "
" : NEWLINE ]
305: [ " | +--<link" : TEXT ]
306: [ ">" : LINK_FINISH ]
307: [ "
" : NEWLINE ]
308: [ " | | " : TEXT ]
309: [ "
" : NEWLINE ]
310: [ " | +--<link" : TEXT ]
311: [ ">" : LINK_FINISH ]
312: [ "
" : NEWLINE ]
313: [ " | | " : TEXT ]
314: [ "
" : NEWLINE ]
315: [ " | +--<COMMENT" : TEXT ]
316: [ ">" : LINK_FINISH ]
317: [ "
" : NEWLINE ]
318: [ " | " : TEXT ]
319: [ "
" : NEWLINE ]
320: [ " +--<body" : TEXT ]
321: [ ">" : LINK_FINISH ]
322: [ "
" : NEWLINE ]
323: [ " | " : TEXT ]
324: [ "
" : NEWLINE ]
325: [ " +--<header" : TEXT ]
326: [ ">" : LINK_FINISH ]
327: [ "
" : NEWLINE ]
328: [ " | | " : TEXT ]
329: [ "
" : NEWLINE ]
330: [ " | +--<div" : TEXT ]
331: [ ">" : LINK_FINISH ]
332: [ "
" : NEWLINE ]
333: [ " | " : TEXT ]
334: [ "
" : NEWLINE ]
335: [ " +--<nav" : TEXT ]
336: [ ">" : LINK_FINISH ]
337: [ "
" : NEWLINE ]
338: [ " | | " : TEXT ]
339: [ "
" : NEWLINE ]
340: [ " | +--<ul" : TEXT ]
341: [ ">" : LINK_FINISH ]
342: [ "
" : NEWLINE ]
343: [ " | | " : TEXT ]
344: [ "
" : NEWLINE ]
345: [ " | +--<li" : TEXT ]
346: [ ">" : LINK_FINISH ]
347: [ "
" : NEWLINE ]
348: [ " | | | " : TEXT ]
349: [ "
" : NEWLINE ]
350: [ " | | +--<a" : TEXT ]
351: [ ">" : LINK_FINISH ]
352: [ "
" : NEWLINE ]
353: [ " | | " : TEXT ]
354: [ "
" : NEWLINE ]
355: [ " | +--<li" : TEXT ]
356: [ ">" : LINK_FINISH ]
357: [ "
" : NEWLINE ]
358: [ " | | | " : TEXT ]
359: [ "
" : NEWLINE ]
360: [ " | | +--<a" : TEXT ]
361: [ ">" : LINK_FINISH ]
362: [ "
" : NEWLINE ]
363: [ " | | " : TEXT ]
364: [ "
" : NEWLINE ]
365: [ " | +--<li" : TEXT ]
366: [ ">" : LINK_FINISH ]
367: [ "
" : NEWLINE ]
368: [ " | | " : TEXT ]
369: [ "
" : NEWLINE ]
370: [ " | +--<a" : TEXT ]
371: [ ">" : LINK_FINISH ]
372: [ "
" : NEWLINE ]
373: [ " | " : TEXT ]
374: [ "
" : NEWLINE ]
375: [ " +--<main" : TEXT ]
376: [ ">" : LINK_FINISH ]
377: [ "
" : NEWLINE ]
378: [ " | | " : TEXT ]
379: [ "
" : NEWLINE ]
380: [ " | +--<MACRO" : TEXT ]
381: [ ">" : LINK_FINISH ]
382: [ "
" : NEWLINE ]
383: [ " | " : TEXT ]
384: [ "
" : NEWLINE ]
385: [ " +--<footer" : TEXT ]
386: [ ">" : LINK_FINISH ]
387: [ "
" : NEWLINE ]
388: [ " | " : TEXT ]
389: [ "
" : NEWLINE ]
390: [ " +--<hr" : TEXT ]
391: [ ">" : LINK_FINISH ]
392: [ "
" : NEWLINE ]
393: [ " | " : TEXT ]
394: [ "
" : NEWLINE ]
395: [ " +--<small" : TEXT ]
396: [ ">" : LINK_FINISH ]
397: [ "]" : SPAN_OR_IMAGE_FINISH ]
398: [ "
" : NEWLINE ]
399: [ " " : TEXT ]
400: [ "
" : NEWLINE ]
401: [ ",       DOM,   jQuery, Jsoup, beautifulsoup  Gumbo   ,   ,       ,     <style" : TEXT ]
402: [ ">" : LINK_FINISH ]
403: [ "  <script" : TEXT ]
404: [ ">" : LINK_FINISH ]
405: [ ",      .   ,     . ." : TEXT ]
406: [ "

" : DOUBLE_NEWLINE ]
407: [ "PS     " : TEXT ]
408: [ "<<" : LINK_START ]
409: [ "https://gitlab.com/2che/nyHTML" : TEXT ]
410: [ ">" : LINK_FINISH ]
411: [ "" : TEXT ]
412: [ ">" : LINK_FINISH ]
413: [ ". , ,      ." : TEXT ]
414: [ "
" : NEWLINE ]
415: [ "" : END ]


Pohon sintaksis
 <pre><article> | +-<section> | +-<p> | | | +-@2che | | | +-"\n" | +->>442964 | +-<h1> | | | +-"   DOM  " | +-<p> | | | +-", !       ..." | | | +-<i> | | | | | +-" " | | | +-",   — " | | | +-<i> | | | | | +-"" | | | +-".     ,   ..." | +-<p> | | | +-" , —    —   ..." | +-<p> | | | +-" , ,  " | | | +-<i> | | | | | +-" " | | | +-" —        ..." | | | +-"\n" | | | +-<b> | | | | | +-" " | | | +-"    —    ,  ..." | | | +-"\n" | | | +-<b> | | | | | +-" " | | | +-" —        " | | | +-<i> | | | | | +-"  " | | | +-" (AST — abstract syntax tree),  " | | | +-<i> | | | | | +-"  " | | | +-" (DOM — document object model)." | +-<p> | | | +-"   .  ,     ..." | | | +-<i> | | | | | +-" - ()" | | | +-"  " | | | +-<i> | | | | | +-"  -" | | | +-".    ,     .  ..." | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"<" | | | +-">" | | | +-" = <_1" | | | +-">" | | | +-" <_" | | | +-">" | | | +-" <_2" | | | +-">" | +-<p> | | | +-"      , ..." | +-<p> | | | +-"  ?" | +-<p> | | | +-"      ..." | | | +-<i> | | | | | +-"" | | | +-"  " | | | +-<i> | | | | | +-"" | | | +-". " | | | +-<b> | | | | | +-"" | | | +-" — ,  :" | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"<_1" | | | +-">" | | | +-" = <" | | | +-">" | | | +-" (<_" | | | +-">" | | | +-" | <_" | | | +-">" | | | +-") <" | | | +-">" | +-<p> | | | +-<b> | | | | | +-"" | | | +-" ,    .   ..." | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"<" | | | +-">" | | | +-" = <_1" | | | +-">" | | | +-" "+" <_2" | | | +-">" | | | +-"\n" | | | +-"<_1" | | | +-">" | | | +-" = <" | | | +-">" | | | +-" ("*" | "/") <" | | | +-">" | +-<p> | | | +-" "+", "*", "/" — ." | | | +-"\n" | | | +-"     ,  ..." | +-<p> | | | +-"      " | | | +-<a> | | | | | +-"" | | | +-"  " | | | +-<a> | | | | | +-"" | | | +-".    —    ..." | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"  ,     ..." | | | +-">" | | | +-"".        case.   ..." | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"enum Token_type {" | | | +-"\n" | | | +-" END = 1, TEXT = 2," | | | +-"\n" | | | +-" OPENING_BLOCK_TAG_NAME = 4, CLOSING_BLOCK_TAG_NAME = 8, EMPTY_TAG_NAME = 16, COMMENT = 32, MACRO..." | | | +-"\n" | | | +-" ATTRIBUTE_NAME = 128, UNQUOTED_ATTRIBUTE_VALUE = 256, SINGLE_QUOTED_ATTRIBUTE_VALUE = 512, DOUBL..." | | | +-"\n" | | | +-"};" | +-<p> | | | +-"  process:" | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"   switch    (  ..." | | | +-"\n" | +-<p> | | | +-<img /> | +-<p> | | | +-"    ,   ..." | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"      ..." | +-<p> | | | +-"        HTML- ..." | | | +-">" | | | +-"": <_" | | | +-">" | | | +-" " | | | +-"]" | | | +-"".   :" | | | +-"\n" | +-<blockquote> | | | +-<mark> | | | | | +-" " | | | | | +-<pre> | | | | | +-"stub" | | | +-"\n" | | | +-<mark> | | | +-" " | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"       —  ..." | +-<p> | | | +-"       HTML-..." | +-<p> | | | +-"  —      ,  ..." | | | +-">" | | | +-", <li" | | | +-">" | | | +-", <strong" | | | +-">" | | | +-"  ,      .  ..." | | | +-<i> | | | | | +-"  " | | | +-"." | +-<p> | | | +-" :" | | | +-"\n" | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"  !     ,   ..." | | | +-"\n" | | | +-<pre> | | | | | +-"| " | | | | | +-"\n" | | | | | +-"+--<ROOT" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<!DOCTYPE" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<html" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<head" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<meta" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<title" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<link" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<link" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<COMMENT" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<body" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<header" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<div" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<nav" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<ul" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<li" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | | " | | | | | +-"\n" | | | | | +-" | | +--<a" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<li" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | | " | | | | | +-"\n" | | | | | +-" | | +--<a" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<li" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<a" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<main" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | | " | | | | | +-"\n" | | | | | +-" | +--<MACRO" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<footer" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<hr" | | | | | +-">" | | | | | +-"\n" | | | | | +-" | " | | | | | +-"\n" | | | | | +-" +--<small" | | | | | +-">" | | | +-"\n" | | | +-" " | | | +-"\n" | | | +-",       ..." | | | +-">" | | | +-"  <script" | | | +-">" | | | +-",      .    ..." | +-<p> | +-"PS     " | +-<a> | | | +-"" | +-". , ,     ..." | +-"\n" </pre> 

Semuanya bagus, tetapi ada terlalu banyak simpul teks yang saling berurutan. Selain itu, saya ingin tanda kutip berturut-turut digabungkan menjadi satu. Untuk melakukan ini, kita harus melalui pohon lagi dan melakukan penggabungan , yaitu, adhesi elemen homogen satu sama lain. Saya tidak akan menjelaskan detail proses ini, saya akan melampirkan sumbernya, tetapi untuk saat ini lihat caranya

Pohon setelah penggabungan:
 <pre><article> | +-<section> | +-<p> | | | +-@2che | | | +-"\n" | +->>442964 | +-<h1> | | | +-"   DOM  " | +-<p> | | | +-", !       ..." | | | +-<i> | | | | | +-" " | | | +-",   — " | | | +-<i> | | | | | +-"" | | | +-".     ,   ..." | +-<p> | | | +-" , —    —   ..." | +-<p> | | | +-" , ,  " | | | +-<i> | | | | | +-" " | | | +-" —        ..." | | | +-<b> | | | | | +-" " | | | +-"    —    ,  ..." | | | +-<b> | | | | | +-" " | | | +-" —        " | | | +-<i> | | | | | +-"  " | | | +-" (AST — abstract syntax tree),  " | | | +-<i> | | | | | +-"  " | | | +-" (DOM — document object model)." | +-<p> | | | +-"   .  ,     ..." | | | +-<i> | | | | | +-" - ()" | | | +-"  " | | | +-<i> | | | | | +-"  -" | | | +-".    ,     .  ..." | +-<blockquote> | | | +-<pre> | | | +-"<> = <_1> <_> <_2>" | +-<p> | | | +-"      , ..." | +-<p> | | | +-"  ?" | +-<p> | | | +-"      ..." | | | +-<i> | | | | | +-"" | | | +-"  " | | | +-<i> | | | | | +-"" | | | +-". " | | | +-<b> | | | | | +-"" | | | +-" — ,  :\n" | +-<blockquote> | | | +-<pre> | | | +-"<_1> = <> (<_> | <_>) < ..." | +-<p> | | | +-<b> | | | | | +-"" | | | +-" ,    .   ..." | +-<blockquote> | | | +-<pre> | | | +-"<> = <_1> "+" <_2>\n<_1> = <..." | +-<p> | | | +-" "+", "*", "/" — .\n    ..." | +-<p> | | | +-"      " | | | +-<a> | | | | | +-"" | | | +-"  " | | | +-<a> | | | | | +-"" | | | +-".    —    ..." | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"  ,     ..." | +-<blockquote> | | | +-<pre> | | | +-"enum Token_type {\n END = 1, TEXT = 2,\n OPENING_BLOCK_TAG_NAME = 4, CLOSING_BLOCK_TAG_NAME = ..." | +-<p> | | | +-"  process:\n" | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"   switch    (  ..." | +-<p> | | | +-<img /> | +-<p> | | | +-"    ,   ..." | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"      ..." | +-<p> | | | +-"        HTML- ..." | +-<blockquote> | | | +-<mark> | | | | | +-" " | | | | | +-<pre> | | | | | +-"stub" | | | +-"\n" | | | +-<mark> | | | +-" " | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"       —  ..." | +-<p> | | | +-"       HTML-..." | +-<p> | | | +-"  —      ,  ..." | | | +-<i> | | | | | +-"  " | | | +-"." | +-<p> | | | +-" :\n" | +-<blockquote> | | | +-<pre> | | | +-"stub" | +-<p> | | | +-"  !     ,   ..." | | | +-<pre> | | | | | +-"| \n+--<ROOT>\n | \n +--<!DOCTYPE>\n | \n +--<html>\n | \n +--<head>\n | | \n | +--<..." | | | +-"\n \n,     ..." | +-<p> | +-"PS     " | +-<a> | | | +-"" | +-". , ,     ..." </pre> 

Langkah terakhir yang tersisa adalah mewakili pohon ini dalam bentuk HTML. Semuanya sederhana di sini: Kami membuat garis dalam metode root dengan awal markup utama (membuka elemen html dan tubuh, blok kepala) dan mulai melampirkan garis yang dikembalikan dari elemen anak-anak yang serupa. Di sini kita berhadapan dengan keturunan rekursif lagi: setiap kelas, ketika to_HTML () metode virtual dipanggil, membuat garis, menempatkan markup utamanya di dalamnya, lalu memanggil metode yang sama pada setiap keturunannya, menggabungkan garis, melengkapi markup primer dan mengembalikannya ke induk panggilan. Di sini, misalnya, tampak seperti metode ini untuk kelas Inline (menggabungkan elemen inline berformat):
 string Inline::to_HTML (const unsigned int &level) { string HTML; // ******    -   ****** switch (type) { case BOLD: { HTML.append("<b>"); break; } case ITALIC: { HTML.append("<i>"); break; } case UNDERLINED: { HTML.append("<ins>"); break; } case OVERLINED: { HTML.append("<span class=\"overlined\">"); break; } case THROWLINED: { HTML.append("<del>"); break; } case SUBSCRIPT: { HTML.append("<sub>"); break; } case SUPERSCRIPT: { HTML.append("<sup>"); break; } case MARKED: { HTML.append("<mark>"); break; } case MONOSPACE: { HTML.append("<pre>"); break; } default: throw string("invalid inline type: " + type_str() + "!"); } // *** *** *** *** // ******   -    ****** for (unsigned long i(0); i < children_count; i++) HTML.append(children[i]->to_HTML(level+1)); // *** *** *** *** // ******    -   ****** switch (type) { case BOLD: { HTML.append("</b>"); break; } case ITALIC: { HTML.append("</i>"); break; } case UNDERLINED: { HTML.append("</ins>"); break; } case OVERLINED: { HTML.append("</span>"); break; } case THROWLINED: { HTML.append("</del>"); break; } case SUBSCRIPT: { HTML.append("</sub>"); break; } case SUPERSCRIPT: { HTML.append("</sup>"); break; } case MARKED: { HTML.append("</mark>"); break; } case MONOSPACE: { HTML.append("</pre>"); break; } default: throw string("invalid inline type: " + type_str() + "!"); } // *** *** *** *** return HTML; } 

Itu saja.Saya harap sekarang, setelah membaca kedua artikel, Anda dapat dengan mudah mengimplementasikan penerjemah untuk bahasa pemrograman Anda. Jika Anda memiliki pertanyaan, tanyakan di komentar. Dan inilah sumbernya . Berhasil.

PS Saya lupa menyebutkan perisai . Ini diimplementasikan secara sederhana: jika karakter berikutnya dalam prosedur parsing leksikal adalah backslash ("\"), itu diabaikan dan karakter berikutnya diproses, tetapi di samping itu, nilai Boolean true dikirim ke fungsi pemrosesan karakter, memberikan perintah untuk melarikan diri. Kemudian, jika simbol ini, misalnya, adalah "[", makna khususnya diabaikan, dan ia hanya bergabung dengan token yang sedang dibangun sebagai teks. Jika tidak, fungsi mengembalikan false dan karakter diproses seperti biasa.

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


All Articles