Хабрахабр

Разрабатываем свой браузер с нуля. Часть первая: HTML

Всем привет!

Продолжаем цикл статей по разработке браузерного движка.

Мы рассмотрим HTML спецификацию и чем она плоха относительно производительности и потребления ресурсов при разборе HTML. В данной статье я расскажу как создать самый быстрый HTML-парсер c DOM.

Конференцию не каждый может посетить, плюс в статье больше деталей. С данной темой я докладывался на прошедшем HighLoad++.

Я предполагаю, что читатель обладает базовыми знаниями об HTML: теги, ноды, элементы, пространство имён.

Спецификация HTML

Прежде чем начать хоть как-то затрагивать реализацию HTML-парсера необходимо понять какой HTML спецификации верить.

Существует две HTML спецификации:

  1. WHATWG
    • Apple, Mozilla, Google, Microsoft
  2. W3C
    • Большой список компаний

Живой стандарт, большие компании у каждой из которых есть свой браузер/браузерный движок. Естественно, выбор пал на лидеров отрасли — WHATWG.

Процесс парсинга HTML

Процесс построения HTML дерева можно разделить на четыре части:

  1. Декодер
  2. Предварительная обработка
  3. Токенизатор
  4. Построение дерева

Рассмотрим каждую стадию по отдельности.

Декодер

Соответственно, нам необходимо конвертировать текущий байтовый поток в юникод символы. Токенизатор принимает на вход юникод символы (code points). Для этого необходимо воспользоваться спецификацией Encoding.

Для этого мы воспользуемся алгоритмом encoding sniffing algorithm. Если мы имеем HTML с неизвестной кодировкой (нет HTTP заголовка) то нам необходимо её определить до начала декодирования.

Если очень кратко то суть алгоритма сводится к тому, что мы ждем 500мс или первые 1024 байта из байтового потока и запускаем алгоритм prescan a byte stream to determine its encoding который пробует найти тег <meta> с атрибутами http-equiv, content или charset и пытается понять какую кодировку указал разработчик HTML.

В спецификации Encoding оговаривается минимальный набор поддерживаемых кодировок браузерным движком (всего 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 и x-user-defined.

Предварительная обработка

А именно, заменить все символы возврата каретки (\r) за которыми следует символ перевода строки (\n) на символ возврата каретки (\r). После того как мы декодировали байты в юникод символы нам необходимо провести "зачистку". Затем, заменить все символы возврата каретки на символ перевода строки (\n).

То есть, \r\n => \r, \r => \n. Так описано в спецификации.

Делают проще: Но, на самом деле так никто не делает.

Если есть то меняем оба символа на символ перевода строки (\n), если нет то меняем только первый символ (\r) на перевод строки (\n). Если попался символ возврата каретки (\r) то смотрим есть ли символ перевода строки (\n).

Да, всего-то надо избавиться от символов возврата каретки, чтобы они не попадали в токенизатор. На этом предварительная обработка данных завершена. Токенизатор не ожидает и не знает, что делать с символом возврата каретки.

Ошибки парсинга

Чтобы в дальнейшем не возникало вопросов стоит сразу рассказать, что такое ошибка парсинга (parse error).

Звучит грозно, но по факту это лишь предупреждение о том, что мы ожидали одно, а имеем другое. На самом деле ничего страшного.

Это сообщение которое сигнализирует, что у нас не валидный HTML. Ошибка парсинга не остановит процесс обработки данных или построение дерева.

Ошибку парсига можно получить за суррогатные пары, \0, неверное расположение тега, неверный <DOCTYPE> и ещё всякие.

К примеру, если указать "плохой" <DOCTYPE> то HTML дерево будет помечен как QUIRKS и изменится логика работы некоторых DOM функций. К слову, некоторые ошибки парсинга ведут к последствиям.

Токенизатор

Это конечный автомат (state machine) который имеет 80 состояний. Как уже было сказано ранее, токенизатор принимает на вход юникод символы. В зависимости от пришедшего символа токенизатор может: В каждом состоянии условия для юникод символов.

  1. Поменять своё состояние
  2. Сформировать токен и поменять состояние
  3. Ничего не делать, ждать следующий символ

Которые поступают в стадию построения дерева. Токенизатор создает токены шести видов: DOCTYPE, Start Tag, End Tag, Comment, Character, End-Of-File.

"Зачем же остальные?" — спросите вы. Примечательно, что токенизатор знает не о всех своих состояниях, а где о 40% (взял с потолка, для примера). Об остальных 60% знает стадия построения дерева.

То есть, обычно те теги в которых мы не ожидаем других тегов, а только закрытие себя. Это сделано для того, чтобы правильно парсить такие теги как <textarea>, <style>, <script>, <title> и так далее.

Любые теги в <title> будут восприниматься как текст пока он не встретит закрывающий тег для себя </title>. К примеру, тег <title> не может содержать в себе других тегов.

Ведь можно было просто указать токенизатору, что если встретим тег <title> то идем по "нужному нам пути". Зачем так сделано? Да, пространство имён влияет на поведение стадии построения дерева, которая в свою очередь меняет поведение токенизатора. И это было бы верно если не namespaces!

Для примера, рассмотрим поведение тега <title> в HTML и SVG пространстве имен:

HTML

<title><span>Текст</span></title>

Результат построения дерева:

<title> "<span>Текст</span>"

SVG

<svg><title><span>Текст</span></title></svg>

Результат построения дерева:

<svg> <title> <span> "Текст"

Во втором случаи (SVG namespace) на основе тега <span> был создан элемент. Мы видим, что в первом случаи (HTML namespace) тег <span> является текстом, не был создан элемент span. То есть, в зависимости от простарнства имени теги ведут себя по-разному.

Тортиком на этом "празднике жизни" служит тот факт, что сам токенизатор должен знать в каком пространстве имен находится стадия построения дерева. Но, это ещё не всё. И нужно это исключительно для того, чтобы правильно обработать CDATA.

Рассмотрим два примера с CDATA, два пространства имён:

HTML

<div><![CDATA[ Текст ]]></div>

Результат построения дерева:

<div> <!--[CDATA[ Текст ]]-->

SVG

<div><svg><![CDATA[ Текст ]]></svg></div>

Результат построения дерева:

<div> <svg> " Текст "

Во втором случаи токенизатор разобрал структуру CDATA и получил данные из неё. В первом случаи (HTML namespace) токенизатор воспринял CDATA за комментарий. В общем, правило такое: если встречаем CDATA не в HTML пространстве имён то парсим его, иначе считаем за комментарий.

Токенизатор должен знать в каком пространстве имен сейчас находится стадия построения дерева, а стадия построения дерева может менять состояния токенизатора. Вот такая получается жёсткая связь между токенизатором и построением дерева.

Токены

Тут стоит отметить, что все токены имеют подготовленные данные, то есть уже обработанные и "готовые к употреблению". Ниже будут рассмотрены все шесть видов токенов создаваемых токенизатором. Это значит, что все именнованные символьные ссылки (named character references), вроде &copy, будут преобразованы в юникод символы.

DOCTYPE Token

Токен содержит в себе: Токен DOCTYPE имеет свою структуру не похожую на остальные теги.

  1. Имя
  2. Public-идентификатор
  3. System-идентификатор

В современном HTML единственный правильный/валидный DOCTYPE должен иметь следующий вид:

<!DOCTYPE html>

DOCTYPE> будут считаться ошибкой парсинга. Все остальные <!

Start Tag Token

Открывающий тег может содержать в себе:

  1. Имя тега
  2. Атрибуты
  3. Флаги

К примеру,

<div key="value" />

Данный флаг никак не влияет на закрытие тега, но может вызвать ошибку парсинга для не void элементов. Открывающий тег может содержать флаг self-closing.

End Tag Token

Обладает всеми свойствами токена открывающего тега, но имеет перед именем тега косую /. Закрывающий тег.

</div key="value" />

Так же, ошибку парсинга вызовут атрибуты у закрывающего тега. Закрывающий тег может содержать флаг self-closing который вызовет ошибку парсинга. Они будут правильно распарсены, но выброшены на стадии построения дерева.

То есть, он полностью копируется из потока в токен. Токен комментария содержит в себе весь текст комментария.

Пример,

<!-- Комментарий -->

Character Token

Токен юникод символа. Пожалуй самый интересный токен. Может содержать в себе один (только один) символ.

Это очень накладно.
Давайте рассмотрим как это работает. На каждый символ в HTML будет создаваться токен и отправляться в стадию построения дерева.

Возьмём HTML данные:

<span>Слава императору! &reg;</span>

Ответ: 22. Как вы думаете сколько будет создано токенов для приведенного примера?

Рассмотрим список создаваемых токенов:

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

Но, конечно, многие создатели парсеров HTML по факту имеет только один токен в процессе обработки. Не утешительно, правда? Пуская его по кругу и затирая каждый раз новыми данными.

Почему не брать этот текст единым куском? Забежим вперед и ответим на вопрос: зачем так сделано? Ответ кроется в стадии построения дерева.

Именно на стадии построения дерева происходит склейка текста с разными условиями. Токенизатор бесполезен без стадии построения HTML дерева.

Условия, примерно, такие:

  1. Если пришел символьный токен с U+0000 (NULL) то вызываем ошибку парсинга и игнорируем токен.
  2. Если пришёл один из U+0009 (CHARACTER TABULATION), U+000A (LINE FEED (LF)), U+000C (FORM FEED (FF)) или U+0020 (SPACE) character токен тогда вызвать алгоритм восстановить активные элементы форматирования и вставить токен в дерево.

Символьный токен добавляется в дерево по алгоритму:

  1. Если текущая позиция вставки это не текстовая нода, тогда создать текстовую ноду, вставить её в дерево и добавить к ней данные из токена.
  2. Иначе добавить данные из токена к существующей текстовой ноде.

Необходимость на каждый символ создавать токен и отправлять на анализ в стадию построения дерева. Это поведение создает много проблем. Всё это крайне накладно по памяти, либо по времени. Мы не знаем размер текстовой ноды и нам приходится либо заранее выделить много памяти, либо делать реалоки.

End-Of-File Token

Данные закончились — сообщим об этом стадии построения дерева. Простой и понятный токен.

Построение дерева

Стадия построения дерева самая большая, занимает значимую часть спецификации, а так же способна вызывать летаргический сон и раздражение. Построение дерева — это конечный автомат имеющий 23 состояния с множеством условий для токенов (тегов, текста).

На вход принимаются токены и в зависимости от токена переключается состояние построения дерева. Всё устроено очень просто. На выходе мы имеем настоящий DOM.

Проблемы?

Давольно очевидными выглядят следующие проблемы:

Посимвольное копирование

На вход каждое состояние токенизатора принимает по одному символу которые он копирует/конвертирует в случаи необходимости: имена тегов, атрибуты, комментарии, символы.

Мы вынуждены заранее выделить неизвестное количество памяти под каждый атрибут, имя тега, комментарий и так далее. Это очень расточительно как по памяти так и по времени. А это, соответственно, ведет к реалокам, а реалоки ведут к потерянному времени.

А если представить, что HTML содержит 1000 тегов, а в каждом теге хотя бы по одному атрибуту, то мы получим адски медленную работу парсера.

Символьный токен

Получается, что мы создаем токен на каждый символ и отдаём его в построение дерева. Второй проблемой является символьный токен. Соответственно, тут все те же реалоки + постоянные проверки на присутствие текстовой ноды в текущем состоянии дерева. Построение дерева не знает сколько у нас будет этих токенов и не может сразу выделить память под нужное количество символов.

Монолитность системы

То есть, токенизатор зависит от состояния построения дерева, а построение дерева может управлять токенизатором. Большой проблемой является зависимость всего от всего. И всему виной пространство имен (namespaces).

Как будем решать проблемы?

Далее я опишу реализацию HTML парсера в своём проекте Lexbor, а так же решения всех озвученных проблем.

Предварительная обработка

Обучим токенизатор понимать возврат каретки (\r) как пробельный символ. Убираем предварительную обработку данных. Таким образом он будет прокинут в стадию построения дерева где мы с ним и разберёмся.

Токены

У нас будет один токен на всё. Легким движением руки мы унифицируем все токены. Вообще, во всём процессе парсинга будет только один токен.

Наш унифицированный токен будет содержать следующие поля:

  1. Tag ID
  2. Begin
  3. End
  4. Attributes
  5. Flags

Tag ID

Переводим всё в цифры. Мы не будем работать с текстовым представлением имени тега. Цифры легко сравнивать, с ними легче работать.

Создаём перечисление (enum) из всех известных тегов. Создаём статическую хеш-таблицу из всех известных тегов. Соответственно, в хеш-таблице ключом будет имя тега, а значением записать из перечисления. То есть, нам нужно жестко назначить каждому тегу идентификатор.

Для примера:

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, /* ... */
}

Всё это ради дальнейшего удобства. Из примера видно, что мы создали теги для END-OF-FILE токена, для текста, документа. Сделано это для того, чтобы не делать два сравнения: на тип ноды и на элемент. Приоткрывая занавес скажу, что в ноде (DOM Node Interface) у нас будет присутствовать Tag ID. То есть, если нам нужен DIV элемент то мы делаем одну проверку в ноде:

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

Но, можно конечно сделать и так:

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

Иначе говоря, пользователь может создать тег с именем text или end-of-file и если мы потом будем искать по имени тега то никаких ошибок не возникнет. Два нижних подчёркивания в LXB_TAG__ нужны для того, чтобы отделить общие теги от системных. Все системные теги начинаются с символа #.

Для 98. Но всё же, нода может хранить текстовое представление имени тега. В некоторых пространствах имён нам необходимо прописать префикс или имя тега с фиксированным регистром. 99999% нод этот параметр будет равен NULL. К примеру, baseProfile в SVG namespace.

Если мы имеем тег с чётко оговоренным регистром то: Логика работы простая.

  1. Добавляем его в общую базу тегов в нижнем регистре. Получаем идентификатор тега.
  2. К ноде добавляем идентификатор тега и оригинальное имя тега в текстовом представлении.

Пользовательские теги (custom elements)

Так-как мы имеем в статической хеш-таблице только те теги о которых знаем, а пользователь может создать любые то нам нужна динамическая хеш-таблица. Разработчик может создавать любые теги в HTML.

Когда к нам прийдет тег мы посмотрим есть ли он в статической хеш-таблицы. Выглядит всё очень просто. Если тега нет то посмотрим в динамической, если и там нет то увеличиваем счётчик идентификаторов на один и добавляем тег в динамическую таблицу.

Внутри токенизатора и после все сравнения идут по Tag ID (за редким исключением). Всё описанное происходит на стадии токенизатора.

Begin and End

Мы ничего не будет копировать и конвертировать. Теперь в токенизаторе у нас не будет обработки данных. Мы просто берём указатели на начало и конец данных.

Вся обработка данных, таких как символьные ссылки, будет происходить на стадии построения дерева.
Таким образом мы будем знать размер данных для последующего выделения памяти.

Attributes

Мы ничего не копируем, а просто сохраняем указатели на начало/конец имени и значения атрибутов. Тут всё так же просто. Все преобразования происходят в момент построения дерева.

Flags

Для этого используется битмап поле Flags. Так как мы унифицировали токены нам надо как-то сообщить построению дерева о типе токена.

Поле может содержать в себе следующие значения:

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
};

Только токенизатор знает как правильно конвертировать данные. Помимо типа токена, открывающий или закрывающий, есть значения для конвертера данных. Соответственно, токенизатор помечает в токене как данные должны быть обработаны.

Character Token

Да, теперь у нас есть новый тип токена: LXB_HTML_TOKEN_TYPE_TEXT. Из ранее описанного можно сделать вывод, что символьный токен у нас исчез. Теперь мы создаём токен на весь текст между тегами помечая, как его в дальнейшем надо обработать.

Нам нужно обучить его работать не с символьными токенами, а с текстовыми: конвертировать, удалять ненужные символы, пропускать пробелы и так далее. Из-за этого нам прийдется изменить условия в построении дерева.

В стадии построения дерева изменения будут минимальны. Но, здесь ничего сложного нет. Но, он нам и не нужен, это нормально. А вот токенизатор теперь у нас не соответствует спецификации от слова совсем. Наша задача получить HTML/DOM дерево полностью соответствующее спецификации.

Стадии токенизатора

По спецификации у нас каждая стадия принимает по одному символу и в зависимости от пришедшего символа принимает решения. Чтобы обеспечить высокую скорость обработки данных в токенизаторе мы в каждую стадию добавим свой итератор. Но, правда в том, что это очень затратно.

По спецификации мы должны скармливать по символу в стадию ATTRIBUTE_NAME пока не встретиться пробельный символ, и эта стадия не переключится на другую. К примеру, чтобы перейти от стадии ATTRIBUTE_NAME к стадии ATTRIBUTE_VALUE нам нужно найти пробельный символ в имени атрибута, что будет свидетельствовать об его окончании. Это очень затратно, обычно это реализуют через вызов функции на каждый символ или колбека вроде "tkz->next_code_point()".

В цикле ищем нужные нам символы для переключения и продолжаем работу на следующей стадии. Мы же добавляем в стадию ATTRIBUTE_NAME цикл и передадим входящий буфер целиком. Тут мы получим много выигрыша, даже оптимизации компилятора.

Самое страшное, что мы тем самым сломали поддержку чанков (chunks) из коробки. Но! Благодаря посимвольной обработки в каждой стадии токенизатора у нас была поддержка чанков, а теперь мы это сломали.

Как реализовать поддержку чанков?! Как исправить? Всё просто, вводим понятия входящих буферов (Incoming Buffer).

Incoming Buffer

Например, если данные мы получаем по сети. Часто HTML парсят кусками (chunks). Естественно, данные могут быть "порваны" в любом месте. Чтобы не простаивать в ожидании оставшихся данных мы можем отправить на обработку/парсинг уже полученные данные. К примеру, имеем два буфера:

Первый

<div clas

Второй

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

Указатели на разные пользовательские буфера. Так как мы ничего не копируем на стадии токенизации, а только берем указатели на начало и конец данных то у нас появляется проблема. А учитывая тот факт, что разработчики часто используют один и тот же буфер для данных то мы имеем дело с указателем на начало несуществующих данных.

Чтобы решить озвученные проблемы нам необходимо понимать когда данные из пользовательского буфера больше не нужны.
Решение очень простое:

  1. Если мы парсим кусками то каждый входящий кусок мы копируем во входящий буфер (Incoming Buffer).
  2. После парсинга текущего куска (ранее скопированного) мы смотрим, а не остался ли какой нибудь незавершенный токен? То есть, присутствуют ли указатели на текущий пользовательский буфер в последнем токене. Если указатели отсутствуют то освобождаем входящий буфер, если нет то оставляем его до тех пор пока он нужен. В 99% случаев входящий буфер уничтожится при поступлении следующего куска.

Для цельных данных копирование не происходит. Флагом "копировать входящий буфер" можно управлять.

Можно не копировать предварительно весь буфер, а после обработки скопировать только нужные (оставшийся хвост) данные и поменять указатель в токене на наш новый буфер. Конечно, данную логику можно оптимизировать и усложнить. Но, данная оптимизация, на текущий момент, является преждевременной. Получится всё лучше и быстрее. Текущая реализация работает быстро и не затратно по памяти.

Проблема: данные в токене

Но, при нашем подходе хранения указателей возникает другая проблема: в определенный момент возникает необходимость посмотреть на данные в токене. С проблемой парсинга кусков мы разобрались, можно выдохнуть. Если мы будем заглядывать в данные токена то нам придётся склеивать их (если они из разных кусков), а это дико затратно по времени. Мы этого сделать не можем. В спецификации есть пару моментов когда надо посмотреть чего мы там накопили в токене.

То есть, мы заранее знаем когда будут проверки и можем пойти по разному пути обработки данных в токенизаторе. Решается данная проблема просто: добавлением дополнительных стадий обработки в токенизатор.

Стадия построения дерева

Тут изменения минимальны.

Соответственно, нам необходимо обучить стадию построения дерева понимать новый токен. У нас больше нет символьных токенов, но зато появились текстовые.

Вот как это выглядит:

По спецификации

tree_build_in_body_character(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. Убирали предварительную обработку (перенесли в токенизатор)
  2. Токенизатор
    • Добавили Incoming Buffer
    • Унифицировали токены
    • Вместо имён Tag ID
    • Символьный токен: не один, а N+ символов
    • Свой итератор в каждом состоянии
    • Обработка токена в построении дерева
    • Один токен на всё
  3. Построение дерева
    • Модифицируем условия для символьных токенов

На i7 2012 года, на одном ядре, мы имеем среднюю скорость парсинга 235MB в секунду (Amazon-страницы).

5/2 раза, то есть запас ещё есть. Конечно же, я знаю как увеличить этот показатель в 1. Собственно, а дальше парсинг CSS и создание собственного грамара (Grammar, то есть, генерация эффективного кода для парсинга по Grammar). Но, мне необходимо двигаться дальше. В отличии от парсинга HTML, парсинг CSS намного сложнее, там есть где "развернуться".

Исходники

Описанный подход парсинга и построения HTML дерева реализован в Lexbor HTML.

P.S.

Как обычно, статья будет на готовый код. Следующая статья будет о парсинге CSS и Grammar. Ждать где-то 6-8 месяцев.

Для тех, у кого есть время

К примеру, если вы любите в свободное время писать документацию.
Не стесняйтесь поддерживать проект рублём (на другие валюты я тоже не обижусь). Не стесняйтесь помогать проекту. Об этом в личку.

Спасибо за внимание!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть