Kembangkan browser Anda dari awal. Bagian Satu: HTML


Halo semuanya!


Kami melanjutkan serangkaian artikel tentang pengembangan mesin peramban.


Pada artikel ini saya akan memberi tahu Anda cara membuat parser HTML tercepat dengan DOM. Kami akan melihat spesifikasi HTML dan mengapa itu buruk mengenai kinerja dan konsumsi sumber daya saat parsing HTML.


Dengan topik ini, saya melaporkan HighLoad ++ sebelumnya. Tidak semua orang dapat menghadiri konferensi, plus artikel tersebut memiliki lebih banyak detail.


Saya berasumsi bahwa pembaca memiliki pengetahuan dasar tentang HTML: tag, node, elemen, namespace.


Spesifikasi HTML


Sebelum Anda mulai menyentuh implementasi parser HTML, Anda perlu memahami spesifikasi HTML apa yang harus dipercaya.


Ada dua spesifikasi HTML:


  1. WHATWG
    • Apple, Mozilla, Google, Microsoft
  2. W3c
    • Daftar besar perusahaan

Secara alami, pilihan jatuh pada pemimpin industri - WHATWG . Standar hidup, perusahaan besar masing-masing dengan browser / mesin browser mereka sendiri.


UPDATE: Sayangnya, tautan yang diberikan ke spesifikasi tidak terbuka dari Rusia. Rupanya, "gema perang" dengan telegram.


Proses penguraian HTML


Proses membangun pohon HTML dapat dibagi menjadi empat bagian:


  1. Dekoder
  2. Pretreatment
  3. Tokenizer
  4. Membangun pohon

Kami mempertimbangkan setiap tahap secara terpisah.


Dekoder


Tokenizer menerima karakter Unicode (titik kode) sebagai input. Oleh karena itu, kita perlu mengkonversi aliran byte saat ini ke karakter Unicode. Untuk melakukan ini, gunakan spesifikasi Pengkodean .


Jika kita memiliki HTML dengan pengkodean yang tidak dikenal (tanpa header HTTP), maka kita perlu menentukannya sebelum memulai pengkodean. Untuk melakukan ini, kita akan menggunakan algoritma sniffing pengodean .


Jika sangat singkat, inti dari algoritma ini adalah kita menunggu 500 atau 1024 pertama dari aliran byte dan menjalankan prescan algoritma aliran byte untuk menentukan pengodeannya yang mencoba menemukan <meta> dengan atribut http-equiv , content atau charset dan percobaan memahami pengkodean yang ditunjukkan pengembang HTML.


Spesifikasi Encoding menetapkan set minimum penyandian yang didukung oleh mesin browser (total 21): UTF-8, ISO-8859-2, ISO-8859-7, ISO-8859-8, windows-874, windows-1250, windows-1251, windows -1252, windows-1254, windows-1255, windows-1256, windows-1257, windows-1258, gb18030, Big5, ISO-2022-JP, Shift_JIS, EUC-KR, UTF-16BE, UTF-16LE dan x-user -didefinisikan.


Pretreatment


Setelah kita mendekodekan byte menjadi karakter Unicode, kita perlu "membersihkan". Yaitu, ganti semua karakter pengembalian carriage ( \r ) diikuti oleh karakter umpan baris ( \n ) dengan karakter carriage return ( \r ). Lalu, ganti semua karakter carriage return dengan karakter baris baru ( \n ).


Demikian diuraikan dalam spesifikasinya. Yaitu, \r\n => \r , \r => \n .


Tetapi, pada kenyataannya, tidak ada yang melakukannya. Buat lebih mudah:


Jika Anda mendapatkan karakter pengembalian carriage ( \r ), lihat untuk melihat apakah ada karakter umpan baris ( \n ). Jika ada, maka kami mengubah kedua karakter ke karakter umpan baris ( \n ), jika tidak, maka kami hanya mengubah karakter pertama ( \r ) ke umpan baris ( \n ).


Ini melengkapi pemrosesan data awal. Ya, Anda hanya perlu menyingkirkan simbol carriage return agar tidak jatuh ke tokenizer. Tokenizer tidak mengharapkan dan tidak tahu apa yang harus dilakukan dengan simbol carriage return.


Kesalahan parsing


Sehingga di masa depan tidak ada pertanyaan, Anda harus segera memberi tahu apa itu ( parse error ).


Tidak ada yang salah. Kedengarannya mengancam, tetapi sebenarnya ini hanya peringatan bahwa kami mengharapkan satu, tetapi kami memiliki yang lain.


Kesalahan parsing tidak akan menghentikan pemrosesan data atau pembuatan pohon. Ini adalah pesan yang menandakan bahwa kita tidak memiliki HTML yang valid.


Kesalahan Parsig dapat diperoleh untuk pasangan pengganti, \0 , lokasi tag salah, salah <!DOCTYPE> dan segala macam hal lainnya.


Omong-omong, beberapa kesalahan penguraian menyebabkan konsekuensi. Misalnya, jika Anda menetapkan "buruk" <!DOCTYPE> maka pohon HTML akan ditandai sebagai QUIRKS dan logika beberapa fungsi DOM akan berubah.


Tokenizer


Seperti disebutkan sebelumnya, tokenizer menerima karakter Unicode sebagai input. Ini adalah mesin negara yang memiliki 80 negara. Di setiap negara bagian, ketentuan untuk karakter Unicode. Tergantung pada karakter yang diterima, tokenizer dapat:


  1. Ubah status Anda
  2. Buat token dan ubah status
  3. Jangan lakukan apa-apa, tunggu karakter selanjutnya

Tokenizer membuat enam jenis token: DOCTYPE, Tag Awal, Tag Akhir, Komentar, Karakter, End-Of-File. Yang memasuki tahap membangun pohon.


Perlu dicatat bahwa tokenizer tidak tahu tentang semua statusnya, tetapi di mana sekitar 40% (mengambil dari langit-langit, misalnya). "Kenapa sisanya?" - kamu bertanya. Sekitar 60% sisanya mengetahui tahap membangun pohon.


Ini dilakukan untuk mem-parsing tag dengan benar seperti <textarea> , <style> , <script> , <title> dan sebagainya. Artinya, biasanya tag tersebut di mana kita tidak mengharapkan tag lain, tetapi hanya menutup diri kita sendiri.


Misalnya, <title> tidak dapat berisi tag lain. Tag apa pun di <title> akan dianggap sebagai teks sampai bertemu dengan tag penutup untuk dirinya sendiri </title> .


Mengapa ini dilakukan? Lagipula, Anda bisa memberi tahu tokenizer bahwa jika kami memenuhi <title> maka kami menyusuri "jalur yang kami butuhkan." Dan itu akan benar jika bukan ruang nama! Ya, namespace mempengaruhi perilaku tahap pembuatan pohon, yang pada gilirannya mengubah perilaku tokenizer.


Sebagai contoh, pertimbangkan perilaku <title> dalam ruang nama HTML dan SVG:


HTML


 <title><span></span></title> 

Hasil membangun pohon:


 <title> "<span></span>" 

Svg


 <svg><title><span></span></title></svg> 

Hasil membangun pohon:


 <svg> <title> <span> "" 

Kita melihat bahwa dalam kasus pertama (HTML namespace) <span> adalah teks, elemen span tidak dibuat. Dalam kasus kedua (ruang nama SVG), sebuah elemen dibuat berdasarkan tag <span> . Artinya, tergantung pada namespace, tag berperilaku berbeda.


Tapi itu belum semuanya. Kue pada "perayaan kehidupan" ini adalah fakta bahwa tokenizer itu sendiri harus tahu di namespace apa tahap konstruksi pohon berada. Dan ini diperlukan hanya untuk menangani CDATA dengan benar.


Pertimbangkan dua contoh dengan CDATA , dua ruang nama:


HTML


 <div><![CDATA[  ]]></div> 

Hasil membangun pohon:


 <div> <!--[CDATA[  ]]--> 

Svg


 <div><svg><![CDATA[  ]]></svg></div> 

Hasil membangun pohon:


 <div> <svg> "  " 

Dalam kasus pertama (HTML namespace), tokenizer mengambil CDATA untuk dikomentari. Dalam kasus kedua, tokenizer membongkar struktur CDATA dan menerima data darinya. Secara umum, aturannya adalah ini: jika kita bertemu CDATA tidak dalam ruang nama HTML, maka kita menguraikannya, kalau tidak kita menganggapnya sebagai komentar.


Ini adalah hubungan yang erat antara tokenizer dan konstruksi pohon. Tokenizer harus tahu di mana namespace tahap pembangunan pohon saat ini berada, dan tahap konstruksi pohon dapat mengubah keadaan tokenizer.


Token


Di bawah ini kami akan mempertimbangkan keenam jenis token yang dibuat oleh tokenizer. Perlu dicatat bahwa semua token telah menyiapkan data, yaitu, sudah diproses dan "siap digunakan". Ini berarti bahwa semua referensi karakter bernama, seperti © , akan dikonversi ke karakter unicode.


Token DOCTYPE


Token DOCTYPE memiliki struktur sendiri yang tidak mirip dengan tag lain. Token berisi:


  1. Nama depan
  2. Pengidentifikasi publik
  3. Pengidentifikasi sistem

Dalam HTML modern, satu-satunya DOCTYPE yang valid / valid akan terlihat seperti ini:


 <!DOCTYPE html> 

Semua yang lain <!DOCTYPE> akan dianggap sebagai kesalahan penguraian.


Mulai token tag


Tag pembuka dapat berisi:


  1. Nama tag
  2. Atribut
  3. Bendera

Misalnya,


 <div key="value" /> 

Tag pembuka dapat berisi bendera yang self-closing . Bendera ini tidak memengaruhi penutupan tag, tetapi dapat menyebabkan kesalahan penguraian untuk elemen yang tidak batal .


Token tag akhir


Tag penutup. Ia memiliki semua properti token dari tag pembuka, tetapi memiliki garis miring / di depan nama tag.


 </div key="value" /> 

Tag penutup dapat berisi bendera yang self-closing yang akan menyebabkan kesalahan penguraian. Juga, kesalahan parsing akan disebabkan oleh atribut dari tag penutup. Mereka akan diurai dengan benar, tetapi dibuang pada tahap konstruksi pohon.


Token komentar


Token komentar berisi seluruh teks komentar. Artinya, sepenuhnya disalin dari aliran ke token.


Contoh


 <!--  --> 

Token karakter


Mungkin token paling menarik. Simbol Token Unicode. Dapat mengandung satu (hanya satu) karakter.


Token akan dibuat untuk setiap karakter dalam HTML dan dikirim ke tahap konstruksi pohon. Ini sangat mahal.
Mari kita lihat cara kerjanya.


Ambil data HTML:


 <span> ! &reg;</span> 

Menurut Anda, berapa banyak token yang akan dibuat untuk contoh ini? Jawab: 22.


Pertimbangkan daftar token yang dibuat:


 Start tag token: <span> Character token:  Character token:  Character token:  Character token:  Character token:  Character token: Character token:  Character token:  Character token:  Character token:  Character token:  Character token:  Character token:  Character token:  Character token:  Character token:  Character token: ! Character token: Character token: End tag token: </span> End-of-file token 

Tidak nyaman, bukan? Tetapi, tentu saja, banyak pembuat parser HTML sebenarnya hanya memiliki satu token selama pemrosesan. Jalankan dalam lingkaran dan timpa dengan data baru setiap kali.


Mari kita maju dan menjawab pertanyaan: mengapa ini dilakukan? Mengapa tidak mengambil teks ini dalam satu bagian? Jawabannya terletak pada fase konstruksi pohon.


Tokenizer tidak berguna tanpa tahap membangun pohon HTML. Pada tahap membangun pohon, teks direkatkan dengan kondisi yang berbeda.


Syaratnya kira-kira sebagai berikut:


  1. Jika token karakter dengan U+0000 ( NULL ) tiba, maka kami menyebabkan kesalahan penguraian dan mengabaikan token.
  2. Jika salah satu dari U+0009 ( CHARACTER TABULATION ), U+000A ( LINE FEED (LF) ), U+000C ( FORM FEED (FF) ) atau token karakter U+0020 ( SPACE ) datang maka panggil algoritma untuk mengembalikan elemen format aktif dan masukkan token ke pohon.

Token simbol ditambahkan ke pohon sesuai dengan algoritma:


  1. Jika posisi penyisipan saat ini bukan simpul teks, maka buat simpul teks, masukkan ke dalam pohon dan tambahkan data dari token ke sana.
  2. Jika tidak, tambahkan data dari token ke simpul teks yang ada.

Perilaku ini menciptakan banyak masalah. Perlunya setiap simbol untuk membuat token dan mengirim untuk analisis ke tahap membangun pohon. Kami tidak tahu ukuran simpul teks dan kami harus mengalokasikan banyak memori di muka atau membuat realoks. Semua ini sangat mahal dari ingatan atau waktu.


Token akhir file


Token sederhana dan jelas. Data sudah selesai - biarkan kami memberi tahu Anda tentang tahap konstruksi pohon ini.


Membangun pohon


Bangunan pohon adalah mesin negara dengan 23 negara dengan banyak syarat untuk token (tag, teks). Tahap membangun pohon adalah yang terbesar, menempati bagian penting dari spesifikasi, dan juga mampu menyebabkan tidur lesu dan iritasi.


Semuanya diatur dengan sangat sederhana. Token diterima pada input dan, tergantung pada token, keadaan konstruksi pohon diaktifkan. Pada output, kami memiliki DOM nyata.


Masalah?


Masalah-masalah berikut tampaknya cukup jelas:


Penyalinan karakter-per-karakter


Setiap keadaan tokenizer menerima satu karakter pada input, yang disalin / dikonversi bila perlu: nama tag, atribut, komentar, simbol.


Ini sangat boros baik dalam memori maupun waktu. Kami dipaksa untuk melakukan pra-alokasi jumlah memori yang tidak diketahui untuk setiap atribut, nama tag, komentar, dan sebagainya. Dan ini, karenanya, mengarah ke realoks, dan realoks menyebabkan waktu yang hilang.


Dan jika Anda bayangkan HTML berisi 1000 tag, dan setiap tag memiliki setidaknya satu atribut, maka kami mendapatkan pengurai yang sangat lambat.


Token karakter


Masalah kedua adalah token karakter. Ternyata kami membuat token untuk setiap simbol dan memberikannya untuk membangun pohon. Membangun pohon tidak tahu berapa banyak token yang akan kita miliki dan tidak dapat segera mengalokasikan memori untuk jumlah karakter yang diperlukan. Dengan demikian, di sini semua realoks yang sama + cek konstan untuk keberadaan simpul teks dalam keadaan pohon saat ini.


Sistem monolitik


Masalah besar adalah ketergantungan segalanya pada segalanya. Artinya, tokenizer tergantung pada keadaan membangun pohon, dan konstruksi pohon dapat mengendalikan tokenizer. Dan semuanya harus disalahkan untuk namespace (ruang nama).


Bagaimana kita memecahkan masalah?


Selanjutnya, saya akan menjelaskan implementasi parser HTML di proyek Lexbor saya, serta solusi untuk semua masalah yang disuarakan.


Pretreatment


Kami menghapus pemrosesan data awal. Kami akan melatih tokenizer untuk memahami carriage return ( \r ) sebagai karakter spasi. Dengan demikian, dia akan dilemparkan ke panggung membangun pohon di mana kita akan mengetahuinya.


Token


Dengan gerakan pergelangan tangan, kami menyatukan semua token. Kami akan memiliki satu token untuk semuanya. Secara umum, hanya akan ada satu token di seluruh proses parsing.


Token terpadu kami akan berisi bidang-bidang berikut:


  1. Tag id
  2. Mulai
  3. Akhir
  4. Atribut
  5. Bendera

Tag id


Kami tidak akan bekerja dengan representasi tekstual dari nama tag. Kami menerjemahkan semuanya menjadi angka. Jumlahnya mudah dibandingkan, lebih mudah untuk dikerjakan.


Kami membuat tabel hash statis dari semua tag yang dikenal. Kami membuat enum dari semua tag yang dikenal. Artinya, kita perlu menetapkan pengidentifikasi untuk setiap tag secara kaku. Dengan demikian, dalam tabel hash, kuncinya adalah nama tag, dan nilainya ditulis dari enumerasi.


Sebagai contoh:


 typedef enum { LXB_TAG__UNDEF = 0x0000, LXB_TAG__END_OF_FILE = 0x0001, LXB_TAG__TEXT = 0x0002, LXB_TAG__DOCUMENT = 0x0003, LXB_TAG__EM_COMMENT = 0x0004, LXB_TAG__EM_DOCTYPE = 0x0005, LXB_TAG_A = 0x0006, LXB_TAG_ABBR = 0x0007, LXB_TAG_ACRONYM = 0x0008, LXB_TAG_ADDRESS = 0x0009, LXB_TAG_ALTGLYPH = 0x000a, /* ... */ } 

Seperti yang Anda lihat dari contoh, kami membuat tag untuk token END-OF-FILE , untuk teks, untuk dokumen. Semua ini demi kenyamanan lebih lanjut. Membuka tirai, saya akan mengatakan bahwa di simpul ( DOM Node Interface ) kita akan memiliki Tag ID . Ini dilakukan agar tidak membuat dua perbandingan: pada jenis node dan pada elemen. Artinya, jika kita membutuhkan elemen DIV , maka kita melakukan satu pemeriksaan di simpul:


 if (node->tag_id == LXB_TAG_DIV) { /* Best code */ } 

Tetapi, tentu saja, Anda dapat melakukan ini:


 if (node->type == LXB_DOM_NODE_TYPE_ELEMENT && node->tag_id == LXB_TAG_DIV) { /* Oh, code */ } 

LXB_TAG__ dua garis bawah dalam LXB_TAG__ untuk memisahkan tag umum dari tag sistem. Dengan kata lain, pengguna dapat membuat tag dengan text nama atau end-of-file dan jika kita mencari berdasarkan nama tag, maka tidak akan terjadi kesalahan. Semua tag sistem dimulai dengan # .


Tapi tetap saja, sebuah node dapat menyimpan representasi tekstual dari nama tag. Untuk 98,99999% node, parameter ini akan menjadi NULL . Di beberapa ruang nama, kita perlu menentukan awalan atau nama tag dengan register tetap. Misalnya, baseProfile di ruang nama SVG.


Logikanya sederhana. Jika kami memiliki tag dengan register yang jelas maka:


  1. Tambahkan ke basis umum tag dalam huruf kecil. Dapatkan id tag.
  2. Tambahkan pengenal tag dan nama tag asli dalam representasi teks ke node.

Tag khusus


Pengembang dapat membuat tag apa pun dalam HTML. Karena kita hanya memiliki tag yang kita ketahui di tabel hash statis, dan pengguna dapat membuat tag, kita membutuhkan tabel hash dinamis.


Semuanya terlihat sangat sederhana. Ketika tag datang kepada kita, kita akan melihat apakah tag itu ada di tabel hash statis. Jika tidak ada tag, maka mari kita lihat yang dinamis, jika tidak ada di sana, maka kami menambah penghitung pengenal dengan satu dan menambahkan tag ke tabel dinamis.


Semua yang dijelaskan terjadi pada tahap tokenizer. Di dalam tokenizer dan setelah semua perbandingan pergi dengan Tag ID (dengan pengecualian langka).


Mulai dan akhiri


Sekarang di tokenizer kita tidak akan memiliki pemrosesan data. Kami tidak akan menyalin dan mengonversi apa pun. Kami hanya membawa petunjuk ke awal dan akhir data.


Semua pemrosesan data, seperti tautan simbolik, akan berlangsung pada tahap pembangunan pohon.
Dengan demikian, kita akan mengetahui ukuran data untuk alokasi memori selanjutnya.


Atribut


Semuanya sesederhana di sini. Kami tidak menyalin apa pun, tetapi hanya menyimpan pointer ke awal / akhir nama dan nilai atribut. Semua transformasi terjadi pada saat pohon dibangun.


Bendera


Karena kami memiliki token yang disatukan, kami perlu memberi tahu bangunan pohon tentang jenis token. Untuk melakukan ini, gunakan bidang bitmap Bendera.


Bidang dapat berisi nilai-nilai berikut:


 enum { LXB_HTML_TOKEN_TYPE_OPEN = 0x0000, LXB_HTML_TOKEN_TYPE_CLOSE = 0x0001, LXB_HTML_TOKEN_TYPE_CLOSE_SELF = 0x0002, LXB_HTML_TOKEN_TYPE_TEXT = 0x0004, LXB_HTML_TOKEN_TYPE_DATA = 0x0008, LXB_HTML_TOKEN_TYPE_RCDATA = 0x0010, LXB_HTML_TOKEN_TYPE_CDATA = 0x0020, LXB_HTML_TOKEN_TYPE_NULL = 0x0040, LXB_HTML_TOKEN_TYPE_FORCE_QUIRKS = 0x0080, LXB_HTML_TOKEN_TYPE_DONE = 0x0100 }; 

Selain jenis token yang membuka atau menutup, ada nilai untuk konverter data. Hanya tokenizer yang tahu cara mengonversi data dengan benar. Oleh karena itu, tokenizer menandai di token bagaimana data harus diproses.


Token karakter


Dari yang dijelaskan sebelumnya, kita dapat menyimpulkan bahwa simbol token telah menghilang dari kita. Ya, sekarang kami memiliki jenis token baru: LXB_HTML_TOKEN_TYPE_TEXT . Sekarang kita membuat token untuk seluruh teks di antara tag, menandai bagaimana itu harus diproses di masa depan.


Karena itu, kita harus mengubah kondisi dalam pembangunan pohon. Kita perlu melatihnya untuk bekerja bukan dengan token simbolik, tetapi dengan token teks: konversi, hapus karakter yang tidak perlu, lewati spasi dan sebagainya.


Tapi, tidak ada yang rumit. Pada tahap membangun pohon, perubahannya akan minimal. Tetapi tokenizer sekarang tidak cocok dengan spesifikasi dari kata sama sekali. Tapi kami tidak membutuhkannya, itu normal. Tugas kami adalah untuk mendapatkan pohon HTML / DOM yang sepenuhnya sesuai dengan spesifikasi.


Tahapan Tokenizer


Untuk memastikan pemrosesan data kecepatan tinggi dalam tokenizer, kami akan menambahkan iterator kami ke setiap tahap. Menurut spesifikasi, setiap tahap menerima satu simbol untuk kami dan, tergantung pada simbol yang telah tiba, membuat keputusan. Tapi, kenyataannya sangat mahal.


Misalnya, untuk berpindah dari tahap ATTRIBUTE_NAME ke tahap ATTRIBUTE_VALUE kita perlu menemukan spasi putih di nama atribut, yang akan menunjukkan akhirnya. Menurut spesifikasinya, kita harus memberi makan berdasarkan karakter ke panggung ATTRIBUTE_NAME hingga karakter spasi putih ATTRIBUTE_NAME , dan tahap ini tidak beralih ke yang lain. Ini sangat mahal, biasanya ini diterapkan melalui pemanggilan fungsi untuk setiap karakter atau panggilan balik seperti "tkz-> next_code_point ()".


Kami menambahkan loop ke tahap ATTRIBUTE_NAME dan meneruskan seluruh buffer yang masuk. Dalam loop, kita mencari simbol yang kita butuhkan untuk beralih dan terus bekerja pada tahap selanjutnya. Di sini kita mendapatkan banyak kemenangan, bahkan optimasi kompiler.


Tapi! Yang terburuk adalah kami dengan demikian mematahkan dukungan bongkahan (bongkahan) di luar kotak. Berkat pemrosesan karakter-oleh-simbol di setiap tahap tokenizer, kami memiliki dukungan untuk bongkahan, dan sekarang kami telah memecahkannya.


Bagaimana cara memperbaikinya? Bagaimana cara mengimplementasikan dukungan untuk bongkahan?! Sederhana saja, kami memperkenalkan konsep buffer yang masuk (Buffer Masuk).


Buffer masuk


Seringkali HTML mem-parsing dalam potongan. Misalnya, jika kita menerima data melalui jaringan. Agar tidak diam saat menunggu data yang tersisa, kami dapat mengirim data yang sudah diterima untuk diproses / diurai. Secara alami, data dapat dirobek di mana saja. Sebagai contoh, kami memiliki dua buffer:


Pertama


 <div clas 

Kedua


 s="oh-no-oh-no"> 

Karena kami tidak menyalin apa pun pada tahap tokenization, tetapi hanya mengambil petunjuk ke awal dan akhir data, kami memiliki masalah. Pointer ke buffer pengguna yang berbeda. , .


.
:


  1. (Incoming Buffer).
  2. ( ) , ? , . , . 99% .

" " . .


, . , ( ) . . , , , . .


:


, . , : . . ( ), . .


: . , .



.


, . , .


:



 tree_build_in_body_character(token) { if (token.code_point == '\0') { /* Parse error, ignore token */ } else if (token.code_point == whitespaces) { /* Insert element */' } /* ... */ } 

Lexbor HTML


 tree_build_in_body_character(token) { lexbor_str_t str = {0}; lxb_html_parser_char_t pc = {0}; pc.drop_null = true; tree->status = lxb_html_token_parse_data(token, &pc, &str, tree->document->mem->text); if (token->type & LXB_HTML_TOKEN_TYPE_NULL) { /* Parse error */ } /* Insert element if not empty */ } 

, . :


 pc.replace_null /*   '\0'    (REPLACEMENT CHARACTER (U+FFFD)) */ pc.drop_null /*   '\0' */ pc.is_attribute /*          " " */ pc.state /*  .        . */ 

. - \0 , - REPLACEMENT CHARACTER . - , - . .


, . . , <head> . , , : " ". , .


<sarcasm>

HTML ( ) sarcasm . .


 An end tag whose tag name is "sarcasm" Take a deep breath, then act as described in the "any other end tag" entry below. 

.



HTML DOM/HTML Interfaces HTML/DOM HTML .


, :


  1. ( )

    • Incoming Buffer
    • Tag ID
    • ̆ : , N+
    • ̆
    • ̈


i7 2012 , , 235MB (Amazon-).


, 1.5/2 , . , . , CSS (Grammar, , Grammar). HTML, CSS , "".


Kode sumber


HTML Lexbor HTML .


PS


CSS Grammar. , . - 6-8 .


,

. , .
( ). .


Terima kasih atas perhatian anda!

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


All Articles