17.01.2014, 18:22
|
|
Новичок
|
|
Регистрация: 05.09.2010
Сообщений: 2,298
|
|
Парсинг HTML -> DOM в нормальных браузерах (таки проблема)
Господа, пишу библиотеку по работе с DOM (для новых браузеров) и столкнулся с проблемой парсинга html. Конечная цель - сделать максимально компактно (< 1K). Это очередной мой опен соурц проект, о котором будет рассказано на Хабре и здесь.
К сожалению, я не могу найти простого решения парсинга HTML и представления его в виде DOM. Что первое приходит в голову? Создание элемента (дива) и вставка в него html.
div = document.createElement( 'div' );
div.innerHTML = '<p>a<p>b';
alert( div.innerHTML );
myDomNodes = div.children;
Проблема возникает, когда нам нужно распарсить элемент таблицы.
div = document.createElement( 'div' );
div.innerHTML = '<tr>a</tr><tr>b</tr>';
alert( div.innerHTML );
Понятно, почему так происходит: tr, td, tbody, thead, tfoot... не могут находиться внутри элементов, не являющихся их родителями по спецификации.
Другой вариант: использовать DOMParser
var doc = new DOMParser().parseFromString( '<tr>a</tr><tr>b</tr>', "text/html");
alert( doc.body.innerHTML );
Та же проблема.
Пробуем поменять контент-тайп на xml (потмо можно "усыновить" ноды методом document.adoptNode):
var doc = new DOMParser().parseFromString( '<tr>a</tr><tr>b</tr>', "application/xml");
console.log( doc );
Не ок. Метод будет парсить любой код выше (<tr>a</tr><tr>b</tr>, <p>a<p>b) с ошибками (не закрытый тег, который по стандарту html и не требуется закрывать, отсутствия рут элемента....)
Значит и этот метод нам не подходит.
А что по поводу DocumentFragment? Ничего. У него нет свойства innerHTML. Печаль.
Как это делается в библиотеках?
http://habrahabr.ru/post/164533/
Но предварительно jQuery пытается найти, не нужно ли обрамить наш код как-то дополнительно. Берется самый первый найденный в коде тег и ищется в служебном объекте wrapMap.
В Библиотеках это делается через, простите, жопу.
На дворе 2014 год и нет ни одного способа распарсить HTML в последнем хроме и файерфоксе? Не верю.
|
|
17.01.2014, 18:45
|
|
Профессор
|
|
Регистрация: 11.09.2010
Сообщений: 8,804
|
|
Сообщение от FINoM
|
На дворе 2014 год и нет ни одного способа распарсить HTML в последнем хроме и файерфоксе?
|
Если ты хочешь парсить фрагмент, то ты обязан указать контекст фрагмента. Соответственно создавать надо не div, а tbody
Сообщение от FINoM
|
А что по поводу DocumentFragment? Ничего. У него нет свойства innerHTML. Печаль.
|
Это по той же самой причине - парсеру нужно знать контекст для корректного разбора HTML. А у фрагмента нет контекста.
Сообщение от FINoM
|
В Библиотеках это делается через, простите, жопу.
|
Зато от юзера не требуется указание контекста. То есть это элемент "умного поведения" - такие вещи обычно и делаются "через жопу"
А вот то что DOMParser не умеет парсить фрагменты - это недочет, как мне кажется.
__________________
В личку только с интересными предложениями
|
|
18.01.2014, 02:21
|
Профессор
|
|
Регистрация: 23.10.2010
Сообщений: 2,718
|
|
Все-таки хотел узнать за каким парсить хтмл вообще? Ну я понимаю читеркод, когда исходник не твой и надо ковырять чужой моск. Но если он твой, то какой может быть задача пихать еду через задницу?
|
|
18.01.2014, 02:35
|
|
Новичок
|
|
Регистрация: 05.09.2010
Сообщений: 2,298
|
|
Сообщение от danik.js
|
парсеру нужно знать контекст для корректного разбора HTML
|
В этом и косяк. Когда я юзаю createElement, методу совершенно пофиг на контекст создаваемого элемента. Но, блин, обычной, очевидно нужной, функции обычного парсинга в ДОМе нет.
|
|
18.01.2014, 07:26
|
|
Профессор
|
|
Регистрация: 11.09.2010
Сообщений: 8,804
|
|
Сообщение от FINoM
|
Но, блин, обычной, очевидно нужной, функции обычного парсинга в ДОМе нет.
|
Специфика HTML синтаксиса такова, что парсеру нужно знать контекст. От контекста зависит поведение парсера. Понимаешь?
Это XML можно разобрать в отрыве от контекста. С HTML это не прокатит.
Например если HTML-парсер встретит тег <td> разбирая содержимое div'а, то он проигнорирует этот тег и вставит только его содержимое. А если это произойдет в контексте <table>, <tbody> или <tr> то он его не проигнорирует, да к тому же создаст недостающие части таблицы (tbody, tr). То есть HTML парсер имеет кучу состояний, зависящих от контекста.
Теперь понимаешь почему у DocumentFragment нет innerHTML?
Ах да, в разных контекстах не только разное поведение при встрече тегов, но даже в некоторых контекстах теги могут вобще распознаваться как текст. Это касается контекстов <title>, <noscript>, <plaintext>, <textarea> и тд. Внутри <script> вообще особое поведение - в нем распознается только закрывающий тег </script>
Матчасть: http://www.whatwg.org/specs/web-apps...sing-algorithm
__________________
В личку только с интересными предложениями
|
|
18.01.2014, 07:28
|
|
Профессор
|
|
Регистрация: 11.09.2010
Сообщений: 8,804
|
|
Сообщение от FINoM
|
Когда я юзаю createElement, методу совершенно пофиг на контекст создаваемого элемента
|
Для создания элемента парсер не требуется. Контекст нужен только для парсера. А парсер нужен только для разбора HTML-кода.
__________________
В личку только с интересными предложениями
|
|
18.01.2014, 13:38
|
|
Новичок
|
|
Регистрация: 05.09.2010
Сообщений: 2,298
|
|
Сообщение от danik.js
|
Теперь понимаешь почему у DocumentFragment нет innerHTML?
|
Нет, не понимаю. Логично предположить, что для DocumentFragment можно реализовать парсинг HTML с "любым" контекстом. Понятное дело, что если мы парсим <div><td></div>, то этот гипотетический парсер вернул бы только <div>, так как в диве не может быть <td> но если <td> на верхнем уровне (<td><div></div>), зачем ему вообще контекст? Почему персер не может, условно, сделать document.createElement( 'элемент верхнего уровня' );, а затем уже, для элементов внутри, исправлять ошибки.
Ваша аргументация мне понятна. Описание поведения в таких случаях описано в спецификации. И моё недовольство касается не реализации чего-либо в браузерах, а документального описания реализации в спецификации.
Сообщение от danik.js
|
Для создания элемента парсер не требуется.
|
Почему? Они бы могли .createElement сделать тоже зависимым от контекста: document.createElement( 'tbody', document.querySelector( '#tbody' ) ). Это тоже было бы верно. Ответ на этот вопрос у меня один: они посчитали это неудобным и решили реализовать создание элементов в отрыве от контекста. Спасибо им. Почему они не посчитали удобным реализовать парсер, мне не понятно.
Ну да ладно, ответа на мой вопрос нет и, судя по всему не будет. А спорить мы можем долго.
|
|
18.01.2014, 14:19
|
|
Профессор
|
|
Регистрация: 11.09.2010
Сообщений: 8,804
|
|
Сообщение от FINoM
|
Почему?
|
Что за вопрос. Ты считаешь что при вызове метода createElelement() происходит вызов парсера? А че парсить то?
Сообщение от FINoM
|
Они бы могли .createElement сделать тоже зависимым от контекста
|
А какой в этом смысл?
Сообщение от danik.js
|
Почему персер не может, условно, сделать document.createElement( 'элемент верхнего уровня' );, а затем уже, для элементов внутри, исправлять ошибки
|
Потому что это мало требуемая сомнительная фишка (что это за ситуация, когда ты не знаешь в какой контекст будут вставлены распарсенные DOM-элементы? ), которая бы усложнила и без того запутанную логику парсера.
И наверное если подумать, то всплывут ситуации, где нельзя "догадаться" о контексте.
__________________
В личку только с интересными предложениями
|
|
18.01.2014, 14:43
|
|
Новичок
|
|
Регистрация: 05.09.2010
Сообщений: 2,298
|
|
danik.js, вы, как и в предыдущих сообщениях защищаете спецификацию. Моя точка зрения: спецификаторы должны были до 2014 (!) года предусмотреть безконтекстный парсер.
Повторюсь.
Сообщение от FINoM
|
Почему персер не может, условно, сделать document.createElement( 'элемент верхнего уровня' );, а затем уже, для элементов внутри, исправлять ошибки.
|
Косяк спецификации, на мой взгляд.
Сообщение от danik.js
|
Потому что это мало требуемая сомнительная фишка (что это за ситуация, когда ты не знаешь в какой контекст будут вставлены распарсенные DOM-элементы? ),
|
Сообщение от danik.js
|
И наверное если подумать, то всплывут ситуации, где нельзя "догадаться" о контексте.
|
Угу. Например, задача из первого поста.
|
|
19.01.2014, 17:38
|
|
Новичок
|
|
Регистрация: 05.09.2010
Сообщений: 2,298
|
|
Написал простейшее решение. Проверяется только первый тег в строке, для остальных контекст остается тем же, что и для первого.
window.parseHTML = function( html ) {
var node = document.createElement( 'div' ),
// wrapMap is taken from jQuery
wrapMap = {
option: [ 1, "<select multiple='multiple'>", "</select>" ],
legend: [ 1, "<fieldset>", "</fieldset>" ],
thead: [ 1, "<table>", "</table>" ],
tr: [ 2, "<table><tbody>", "</tbody></table>" ],
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
area: [ 1, "<map>", "</map>" ],
_default: [ 0, "", "" ]
},
wrapper,
i;
wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;
wrapper = wrapMap[ /<([\w:]+)/.exec( html )[ 1 ] ] || wrapMap._default;
node.innerHTML = wrapper[ 1 ] + html + wrapper[ 2 ];
i = wrapper[ 0 ];
while( i-- ) {
node = node.firstElementChild;
}
return node.children;
};
|
|
|
|