HTML 파서

keis·2019년 12월 2일
0

브라우저의 html 오류 유형

참고 사이트 : https://d2.naver.com/helloworld/59361

위 사이트의 파싱 알고리즘 부터 읽어봤다.

http://htmlcxx.sourceforge.net/ > c++ html 파서

파싱 알고리즘

HTML 은 일반적인 하향식 또는 상향식 파서로 파싱이 안되는데 이유는 다음과 같다.

  1. 언어의 너그러운 속성.
  2. 잘 알려져 있는 HTML 오류에 대한 브라우저의 관용
  3. 변경에 의한 재파싱. 일반적으로 소스는 파싱하는 동안 변하지 않지만 HTML 에서 document.write 을 포함하고 있는
    스크립트 태그는 토큰을 추가할 수 있기 때문에 실제로는 입력 과정에서 파싱이 수정된다.
    일반적인 파싱 기술을 사용할 수 없기 때문에 브라우저는 HTML 파싱을 위해 별도의 파서를 생성한다.

https://html.spec.whatwg.org/multipage/parsing.html > 파싱 알고리즘 설명 페이지

초기 상태는 "자료 상태" 이다. < 문자를 만나면 상태는 "태그 열림 상태"로 변한다. a 부터 z까지의 문자를 만나면 "시작

태그 토큰"을 생성하고 상태는 "태그 이름 상태"로 변하는데 이 상태는 > 문자를 만날 때까지 유지한다. 각 문자에는 새로운 토큰 이름이 붙는데 이 경우 생성된 토큰은 html 토큰이다.

문자에 도달하면 현재 토큰이 발행되고 상태는 다시 "자료 상태"로 바뀐다.

태그는 동일한 절차에 따라 처리된다. 지금까지 html 태그와 body 태그를 발행했고 다시 "자료 상태"로 돌아왔다.

Hello World의 H 문자를 만나면 문자 토큰이 생성되고 발행될 것이다. 이것은 종료 태그의 < 문자를 만날 때까지 진행된다. Hello World의 각 문자를 위한 문자 토큰을 발행할 것이다.

다시 "태그 열림 상태"가 되었다. / 문자는 종료 태그 토큰을 생성하고 "태그 이름 상태"로 변경 될 것이다. 이 상태는 > 문자를 만날 때까지 유지된다. 그리고 새로운 태그 토큰이 발행되고 다시 "자료 상태"가 된다. 또한 동일하게 처리될 것이다.

브라우저의 오류 처리

<html> 
  <mytag></mytag>
  <div>
    <p>
  </div>
  Really lousy HTML
  </p>
</html>

mytag 는 표준 태그가 아니고 "p" 태그와 "div" 태그는 중첩 오류가 있다. 그러나 브라우저는 투덜거리지 않고 올바르게 표시한다.

이는 파서가 HTML 제작자의 실수를 수정했기 때문이다.

파서는 적어도 다음과 같은 오류를 처리해야 한다.

  1. 어떤 태그의 안쪽에 추가하려는 태그가 금지된 것일 때 일단 허용된 태그를 먼저 닫고 금지된 태그는 외부에 추가한다.
  2. 파서가 직접 요소를 추가해서는 안된다. 문서 제작자에 의해 뒤늦게 요소가 추가될 수 있고 생략 가능한 경우도 있다.
    HTML, HEAD, BODY, TBODY, TR, TD, LI 태그가 이런 경우에 해당한다.
  3. 인라인 요소 안쪽에 블록 요소가 있는 경우 부모 블록 요소를 만날 때까지 모든 인라인 태그를 닫는다.
  4. 이런 방법이 도움이 되지 않으면 태그를 추가하거나 무시할 수 있는 상태가 될 때까지 요소를 닫는다.
    웹킷이 오류를 처리하는 예는 다음과 같다.


대신

어떤 사이트는
대신
을 사용한다. 인터넷 익스플로러, 파이어폭스와 호환성을 갖기 위해 웹킷은 이것을
으로 간주한다.

 if(t->isCloseTag(brTag) && m_document->inCompatMode()) { 
    reportError(MalformedBRError);
    t->beginTag = true;
}

오류는 내부적으로 처리하고 사용자에게는 표시하지 않는다.

어긋난 표

어긋난 표는 표 안에 또 다른 표가 th 또는 td 셀 내부에 있지 않은 것을 의미한다. 아래 예제와 같은 경우를 말한다.

<table>
 
    <table>
 
    <tr><td>inner table</td></tr>
 
    </table>
 
    <tr><td>outer table</td></tr>
 
</table>

이런 경우 웹킷은 표의 중첩을 분해하여 형제 요소가 되도록 처리한다.

<table>
 
    <tr><td>outer table</td></tr>
 
</table>
 
<table>
 
    <tr><td>inner table</td></tr>
 
</table>

코드는 다음과 같다.

if(m_inStrayTableContent && localName == tableTag) 
popBlock(tableTag);

웹킷은 이런 오류를 처리하는데 스택을 사용한다. 안쪽의 표는 바깥쪽 표의 외부로 옮겨져서 형제 요소가 된다.

중첩된 폼 요소

폼 안에 또 다른 폼을 넣은 경우 안쪽의 폼을 무시된다.

태그 중첩이 너무 깊을 때

www.liceo.edu.mx 사이트는 약 1,500 개 수준의 태그 중첩이 되어 있는 예제인데 모든 요소가 로 되어있다.

최대 20개의 중첩만 허용하고 나머지는 무시한다.

bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName) 
{
  
    unsigned i = 0;
    for (HTMLStackElem* curr = m_blockStack;
        i < cMaxRedundantTagDepth && curr && curr->tagName == tagName; 
        curr = curr->next, i++) { }
    return i != cMaxRedundantTagDepth;
}

잘 못 닫힌 html 또는 body 태그

깨진 html 을 지원한다. 일부 바보 같은 페이지는 문서가 끝나기 전에 body 를 닫아버리기 때문에 브라우저는 body 태그를 닫지 않는다. 대신 종료를 위해 end() 를 호출한다.

if (t->tagName == htmlTag || t->tagName == bodyTag ) 
   return;
  • 안 닫힌 태그, 안 닫힌 따옴표 등에 대해 블링크나 웹킷은 어떻게 처리하고 있는지 알아볼 것
unexpected-character-in-unquoted-attribute-value
This error occurs if the parser encounters a U+0022 ("), U+0027 ('), U+003C (<), U+003D (=), or U+0060 (`) code point in an unquoted attribute value.
The parser includes such code points in the attribute value.
Code points that trigger this error are usually a part of another syntactic construct and can be a sign of a typo around the attribute value.
U+0060 (`) is in the list of code points that trigger this error because certain legacy user agents treat it as a quote.
For example, consider the following markup:
<div foo=b'ar'>
Due to a misplaced U+0027 (') code point the parser sets the value of the "foo" attribute to "b'ar'".
  • encoding 을 어떻게 할 것인가...
  • 태그의 의미를 존중하는 html 필터 만들기 - streaming 방식...

  • 태그 사이에 나오는 텍스트 필터링 되지 않도록 수정
  • html 주석도 처리해야 한다
    * 확인하여 내용 누락 할 수 있도록 구성
  • tag 와 함께 나오는 attribute 도 처리해야 한다
    * chromiun 의 경우 자주 쓰이는 tag 들에 대해서 별도로 tokenizer 를 하고 있다.
// This is a direct transcription of step 4 from:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#fragment-case
 
static HTMLTokenizer::State tokenizerStateForContextElement(Element* contextElement, bool reportErrors, const HTMLParserOptions& options)
 
{
 
    if (!contextElement)
 
        return HTMLTokenizer::DataState;
 
 
    const QualifiedName& contextTag = contextElement->tagQName();
 
 
    if (contextTag.matches(titleTag) || contextTag.matches(textareaTag))
 
        return HTMLTokenizer::RCDATAState;
 
    if (contextTag.matches(styleTag)
 
        || contextTag.matches(xmpTag)
 
        || contextTag.matches(iframeTag)
 
        || (contextTag.matches(noembedTag) && options.pluginsEnabled)
 
        || (contextTag.matches(noscriptTag) && options.scriptEnabled)
 
        || contextTag.matches(noframesTag))
 
        return reportErrors ? HTMLTokenizer::RAWTEXTState : HTMLTokenizer::PLAINTEXTState;
 
    if (contextTag.matches(scriptTag))
 
        return reportErrors ? HTMLTokenizer::ScriptDataState : HTMLTokenizer::PLAINTEXTState;
 
    if (contextTag.matches(plaintextTag))
 
        return HTMLTokenizer::PLAINTEXTState;
 
    return HTMLTokenizer::DataState;
 
}
  <input class="download_button" onClick="javascript:urchinTracker('/downloads/314375');" type="submit" name="download" value="--> Stáhnout soubor <--" >
  • beautiful soup 의 경우 정규식을 쓰고있는데 우리는 정규식을 쓸 수가 없으므로 코드로 beautiful soup 이 하는 일을 흉내내본다.
  1. 여는 꺽쇠 '<' 부터 시작
  2. tag name
  3. tag name 다음에 white space 가 나오면 attribute 앞의 white space 로 간주
  4. white space 다음에 나오는 글자는 attribute 이름
  5. '=' 가 나온다
  6. '=' 다음은 '' 이거나 "" 이다.
  7. 또 white space 가 있는지 검사한다.
  8. 없다면 '/' 가 있는지 확인하고 tag 파싱을 종료한다
  • 짝이 안맞는 태그 처리
    짝이 안맞는 태그의 경우 tag 를 저장하고 있는 stack 을 뒤져 맞는 태그를 발견하는 경우 해당 위치 까지 있는 stack 의 태그를 모두 지운다.
    지워진 태그로인해 짝이 안맞게 되는 경우 닫는 태그 형태를 하고 있으면 해당 태그는 skip 한다


  • 태그 처리
    *
    의 경우
    or
    이렇게 태그가 입력 될 수 도 있다는 점을 고려한다.
  • html 개행
    * '\n' 을 만나는 경우 중복은 개행은 하나의 공백으로 처리한다.

0개의 댓글