# `html.parser` — 간단한 HTML과 XHTML 구문 분석기

소스 코드: Lib/html/parser.py

이 모듈은 HTML(HyperText Mark-up Language)와 XHTML 형식의 텍스트 파일을 구문 분석하기 위한 기초로 사용되는 클래스 `HTMLParser`를 정의합니다.

class `html.parser.``HTMLParser`(*, convert_charrefs=True)

  잘못된 마크업을 구문 분석할 수 있는 구문 분석기 인스턴스를 만듭니다.

  convert_charrefs가 `True`(기본값)이면, (`script`/`style` 요소에 있는 것을 제외한) 모든 문자 참조(character references)가 자동으로 해당 유니코드 문자로 변환됩니다.

  `HTMLParser` 인스턴스는 HTML 데이터를 받아서 시작 태그, 종료 태그, 텍스트, 주석 및 기타 마크업 요소를 만날 때마다 처리기 메서드를 호출합니다. 사용자는 원하는 동작을 구현하기 위해 `HTMLParser`의 서브 클래스를 만들고 해당 메서드를 재정의해야 합니다.

  이 구문 분석기는 종료 태그가 시작 태그와 일치하는지 검사하거나, 바깥(outer) 요소를 닫음으로써 묵시적으로 닫힌 요소에 대해 종료 태그 처리기를 호출하지 않습니다.

  버전 3.4에서 변경: convert_charrefs 키워드 인자가 추가되었습니다.

  버전 3.5에서 변경: 인자 convert_charrefs의 기본값은 이제 `True`입니다.

## HTML 구문 분석기 응용 프로그램 예제

기본 예제로, 다음은 `HTMLParser` 클래스를 사용하여 시작 태그, 종료 태그 및 데이터를 만날 때마다 인쇄하는 간단한 HTML 구문 분석기입니다:

    from html.parser import HTMLParser

    class MyHTMLParser(HTMLParser):
        def handle_starttag(self, tag, attrs):
            print("Encountered a start tag:", tag)

        def handle_endtag(self, tag):
            print("Encountered an end tag :", tag)

        def handle_data(self, data):
            print("Encountered some data  :", data)

    parser = MyHTMLParser()
    parser.feed('<html><head><title>Test</title></head>'
                '<body><h1>Parse me!</h1></body></html>')

출력은 다음과 같습니다:

    Encountered a start tag: html
    Encountered a start tag: head
    Encountered a start tag: title
    Encountered some data  : Test
    Encountered an end tag : title
    Encountered an end tag : head
    Encountered a start tag: body
    Encountered a start tag: h1
    Encountered some data  : Parse me!
    Encountered an end tag : h1
    Encountered an end tag : body
    Encountered an end tag : html

## `HTMLParser` 메서드

`HTMLParser` 인스턴스에는 다음과 같은 메서드가 있습니다:

`HTMLParser.``feed`(data)

  구문 분석기에 텍스트를 입력합니다. 완전한 요소로 구성되어있는 부분까지 처리됩니다; 불완전한 데이터는 더 많은 데이터가 공급되거나 `close()`가 호출될 때까지 버퍼링 됩니다. data는 `str`이어야 합니다.

`HTMLParser.``close`()

  버퍼링 된 모든 데이터를 마치 파일 끝(end-of-file) 표시가 붙은 것처럼 처리합니다. 이 메서드는 파생 클래스에 의해 입력 끝에서의 추가 처리를 정의하기 위해 재정의될 수 있지만, 재정의된 버전에서는 항상 `HTMLParser` 베이스 클래스 메서드인 `close()`를 호출해야 합니다.

`HTMLParser.``reset`()

  인스턴스를 재설정합니다. 처리되지 않은 모든 데이터를 잃습니다. 이것은 인스턴스 생성 시에 묵시적으로 호출됩니다.

`HTMLParser.``getpos`()

  현재의 줄 번호와 오프셋(offset)을 반환합니다.

`HTMLParser.``get_starttag_text`()

  가장 최근에 열렸던 시작 태그의 텍스트를 반환합니다. 이것은 일반적으로 구조화된 처리에 필요하지 않지만, “배치된 대로(as deployed)” HTML을 다루거나 최소한의 변경(어트리뷰트 사이의 공백을 보존할 수 있음, 등등)으로 입력을 다시 생성하는 데 유용할 수 있습니다.

다음 메서드는 데이터나 마크업 요소를 만날 때 호출되며 서브 클래스에서 재정의하려는 용도입니다. 베이스 클래스 구현은 아무 일도 하지 않습니다 (`handle_startendtag()`는 예외입니다).:

`HTMLParser.``handle_starttag`(tag, attrs)

  이 메서드는 태그의 시작(예를 들어, `<div id="main">`)을 처리하기 위해 호출됩니다.

  tag 인자는 소문자로 변환된 태그의 이름입니다. attrs 인자는 태그의 `<>` 화살괄호 안에 있는 어트리뷰트를 포함하는 `(name, value)` 쌍의 리스트입니다. name은 소문자로 변환되고, value의 따옴표는 제거되고, 문자와 엔티티 참조는 치환됩니다.

  예를 들어, 태그 `<A HREF="https://www.cwi.nl/">`의 경우, 이 메서드는 `handle_starttag('a', [('href', 'https://www.cwi.nl/')])`로 호출됩니다.

  `html.entities`의 모든 엔티티 참조가 어트리뷰트 값에서 치환됩니다.

`HTMLParser.``handle_endtag`(tag)

  이 메서드는 요소의 종료 태그(예를 들어, `</div>`)를 처리하기 위해 호출됩니다.

  tag 인자는 소문자로 변환된 태그의 이름입니다.

`HTMLParser.``handle_startendtag`(tag, attrs)

  `handle_starttag()`와 비슷하지만, 구문 분석기가 XHTML 스타일의 빈 태그(`<img ... />`)를 만날 때 호출됩니다. 이 메서드는 이 특정의 어휘 정보(lexical information)가 필요한 서브 클래스에 의해 재정의될 수 있습니다; 기본 구현은 단순히 `handle_starttag()`와 `handle_endtag()`를 호출합니다.

`HTMLParser.``handle_data`(data)

  이 메서드는 임의의 데이터(예를 들어, 텍스트 노드와 `<script>...</script>` 및 `<style>...</style>`의 내용)를 처리하기 위해 호출됩니다.

`HTMLParser.``handle_entityref`(name)

  이 메서드는 `&name;` 형식(예를 들어, `&gt;`)의 이름있는 문자 참조를 처리하기 위해 호출됩니다. 여기서 name은 일반 엔티티 참조(예를 들어, `'gt'`)입니다. convert_charrefs가 `True`이면, 이 메서드는 호출되지 않습니다.

`HTMLParser.``handle_charref`(name)

  이 메서드는 `&#NNN;`과 `&#xNNN;` 형식의 10진수 및 16진수 문자 참조를 처리하기 위해 호출됩니다. 예를 들어, `&gt;`에 해당하는 10진수는 `&#62;`이고, 반면에 16진수는 `&#x3E;`입니다; 이때 메서드는 `'62'`나 `'x3E'`를 받습니다. 이 메서드는 convert_charrefs가 `True`이면 호출되지 않습니다.

`HTMLParser.``handle_comment`(data)

  이 메서드는 주석을 만날 때 호출됩니다 (예를 들어, `<!--comment-->`).

  예를 들어, 주석 `<!-- comment -->`는 이 메서드가 인자 `' comment '`로 호출되도록 합니다.

  Internet Explorer 조건부 주석(condcoms)의 내용도 이 메서드로 보내지므로, `<!--[if IE 9]>IE9-specific content<![endif]-->`의 경우, 이 메서드는 `'[if IE 9]>IE9-specific content<![endif]'`를 받습니다.

`HTMLParser.``handle_decl`(decl)

  이 메서드는 HTML doctype 선언(예를 들어, `<!DOCTYPE html>`)을 처리하기 위해 호출됩니다.

  decl 매개 변수는 `<!...>` 마크업 내의 선언 전체 내용입니다 (예를 들어, `'DOCTYPE html'`).

`HTMLParser.``handle_pi`(data)

  처리 명령(processing instruction)을 만날 때 호출되는 메서드. data 매개 변수에는 전체 처리 명령이 포함됩니다. 예를 들어, 처리 명령 `<?proc color='red'>`의 경우, 이 메서드는 `handle_pi("proc color='red'")`로 호출됩니다. 파생 클래스에 의해 재정의되려는 목적입니다; 베이스 클래스 구현은 아무것도 수행하지 않습니다.

  참고

  `HTMLParser` 클래스는 처리 명령에 대해 SGML 구문 규칙을 사용합니다. 후행 `'?'`를 사용하는 XHTML 처리 명령은 `'?'`가 data에 포함되도록 합니다.

`HTMLParser.``unknown_decl`(data)

  이 메서드는 구문 분석기가 인식할 수 없는 선언을 읽었을 때 호출됩니다.

  data 매개 변수는 `<![...]>` 마크업 안에 있는 선언의 전체 내용입니다. 파생 클래스가 재정의하는 것이 때때로 유용합니다. 베이스 클래스 구현은 아무것도 수행하지 않습니다.

## 예제

다음 클래스는 더 많은 예를 설명하는 데 사용할 구문 분석기를 구현합니다:

    from html.parser import HTMLParser
    from html.entities import name2codepoint

    class MyHTMLParser(HTMLParser):
        def handle_starttag(self, tag, attrs):
            print("Start tag:", tag)
            for attr in attrs:
                print("     attr:", attr)

        def handle_endtag(self, tag):
            print("End tag  :", tag)

        def handle_data(self, data):
            print("Data     :", data)

        def handle_comment(self, data):
            print("Comment  :", data)

        def handle_entityref(self, name):
            c = chr(name2codepoint[name])
            print("Named ent:", c)

        def handle_charref(self, name):
            if name.startswith('x'):
                c = chr(int(name[1:], 16))
            else:
                c = chr(int(name))
            print("Num ent  :", c)

        def handle_decl(self, data):
            print("Decl     :", data)

    parser = MyHTMLParser()

doctype 구문 분석하기:

    >>> parser.feed('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
    ...             '"http://www.w3.org/TR/html4/strict.dtd">')
    Decl     : DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"

몇 가지 어트리뷰트를 가진 요소와 제목을 구문 분석하기:

    >>> parser.feed('<img src="python-logo.png" alt="The Python logo">')
    Start tag: img
         attr: ('src', 'python-logo.png')
         attr: ('alt', 'The Python logo')
    >>>
    >>> parser.feed('<h1>Python</h1>')
    Start tag: h1
    Data     : Python
    End tag  : h1

`script`와 `style` 요소의 내용은 더 구문 분석하지 않고 있는 그대로 반환됩니다:

    >>> parser.feed('<style type="text/css">#python { color: green }</style>')
    Start tag: style
         attr: ('type', 'text/css')
    Data     : #python { color: green }
    End tag  : style

    >>> parser.feed('<script type="text/javascript">'
    ...             'alert("<strong>hello!</strong>");</script>')
    Start tag: script
         attr: ('type', 'text/javascript')
    Data     : alert("<strong>hello!</strong>");
    End tag  : script

주석 구문 분석하기:

    >>> parser.feed('<!-- a comment -->'
    ...             '<!--[if IE 9]>IE-specific content<![endif]-->')
    Comment  :  a comment
    Comment  : [if IE 9]>IE-specific content<![endif]

이름있는 문자 참조와 숫자 문자 참조를 구문 분석하고 올바른 문자로 변환합니다 (참고: 이 3개의 참조는 모두 `'>'`와 동등합니다):

    >>> parser.feed('&gt;&#62;&#x3E;')
    Named ent: >
    Num ent  : >
    Num ent  : >

불완전한 청크를 `feed()`로 보내는 것이 작동합니다만, `handle_data()`가 두 번 이상 호출될 수 있습니다 (convert_charrefs가 `True`로 설정되지 않은 한):

    >>> for chunk in ['<sp', 'an>buff', 'ered ', 'text</s', 'pan>']:
    ...     parser.feed(chunk)
    ...
    Start tag: span
    Data     : buff
    Data     : ered
    Data     : text
    End tag  : span

잘못된 HTML(예를 들어, 따옴표 처리되지 않은 어트리뷰트)을 구문 분석하는 것도 동작합니다:

    >>> parser.feed('<p><a class=link href=#main>tag soup</p ></a>')
    Start tag: p
    Start tag: a
         attr: ('class', 'link')
         attr: ('href', '#main')
    Data     : tag soup
    End tag  : p
    End tag  : a