"html.parser" --- Analizador simple de HTML y XHTML
***************************************************

**Código fuente:** Lib/html/parser.py

======================================================================

Este módulo define una clase "HTMLParser" que sirve como base para
analizar archivos de texto formateados en HTML (*HyperText Mark-up
Language*) y XHTML.

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

   Cree una instancia de analizador capaz de analizar marcado no
   válido.

   Si *convert_charrefs* es "True" (el valor predeterminado), todas
   las referencias de caracteres (excepto las de los elementos
   "script"/"style") se convierten automáticamente en los caracteres
   Unicode correspondientes.

   Una instancia de "HTMLParser" se alimenta de datos HTML y llama a
   métodos de manejo cuando se encuentran etiquetas de inicio,
   etiquetas finales, texto, comentarios y otros elementos de marcado.
   El usuario debe subclasificar "HTMLParser" y anular sus métodos
   para implementar el comportamiento deseado.

   Este analizador no verifica que las etiquetas finales coincidan con
   las etiquetas iniciales ni llame al manejador de etiquetas finales
   para los elementos que se cierran implícitamente al cerrar un
   elemento externo.

   Distinto en la versión 3.4: argumento de palabra clave
   *convert_charrefs* agregado.

   Distinto en la versión 3.5: El valor predeterminado para el
   argumento *convert_charrefs* ahora es "True".


Aplicación ejemplo de un analizador sintáctico (*parser*) de HTML
=================================================================

Como ejemplo básico, a continuación hay un analizador HTML simple que
usa la clase "HTMLParser" para imprimir etiquetas de inicio, etiquetas
finales y datos a medida que se encuentran:

   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>')

La salida será entonces:

   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


Métodos "HTMLParser"
====================

instancias de "HTMLParser" tienen los siguientes métodos:

HTMLParser.feed(data)

   Alimente un poco de texto al analizador. Se procesa en la medida en
   que consta de elementos completos; los datos incompletos se
   almacenan en el búfer hasta que se introducen más datos o se llama
   a "close()". *data* debe ser "str".

HTMLParser.close()

   Fuerce el procesamiento de todos los datos almacenados como si
   fueran seguidos por una marca de fin de archivo. Este método puede
   ser redefinido por una clase derivada para definir un procesamiento
   adicional al final de la entrada, pero la versión redefinida
   siempre debe llamar a "HTMLParser" método de clase base "close()".

HTMLParser.reset()

   Restablecer la instancia. Pierde todos los datos no procesados.
   Esto se llama implícitamente en el momento de la instanciación.

HTMLParser.getpos()

   Retorna el número de línea actual y el desplazamiento.

HTMLParser.get_starttag_text()

   Retorna el texto de la etiqueta de inicio abierta más
   recientemente. Normalmente, esto no debería ser necesario para el
   procesamiento estructurado, pero puede ser útil para tratar con
   HTML "como implementado" o para volver a generar entradas con
   cambios mínimos (se puede preservar el espacio en blanco entre los
   atributos, etc.).

Los siguientes métodos se invocan cuando se encuentran datos o
elementos de marcado y deben anularse en una subclase. Las
implementaciones de la clase base no hacen nada (excepto
"handle_startendtag()"):

HTMLParser.handle_starttag(tag, attrs)

   This method is called to handle the start tag of an element (e.g.
   "<div id="main">").

   El argumento *tag* es el nombre de la etiqueta convertida a
   minúsculas. El argumento *attrs* es una lista de pares "(nombre,
   valor)" que contienen los atributos encontrados dentro de los
   corchetes "<>" de la etiqueta. El *name* se traducirá a minúsculas,
   se eliminarán las comillas en el *value* y se reemplazarán las
   referencias de caracteres y entidades.

   Por ejemplo, para la etiqueta "<A HREF="https://www.cwi.nl/">",
   este método se llamaría como "handle_starttag('a', [('href', 'https
   : //www.cwi.nl/ ')])".

   Todas las referencias de entidad de "html.entities" se reemplazan
   en los valores de los atributos.

HTMLParser.handle_endtag(tag)

   Este método se llama para manejar la etiqueta final de un elemento
   (por ejemplo, "</div>")

   El argumento *tag* es el nombre de la etiqueta convertida a
   minúsculas.

HTMLParser.handle_startendtag(tag, attrs)

   Similar a "handle_starttag()", pero llamado cuando el analizador
   encuentra una etiqueta vacía de estilo XHTML ("<img .../>"). Este
   método puede ser anulado por subclases que requieren esta
   información léxica particular; la implementación predeterminada
   simplemente llama "handle_starttag()" y "handle_endtag()".

HTMLParser.handle_data(data)

   Este método se llama para procesar datos arbitrarios (por ejemplo,
   nodos de texto y el contenido de "<script>...</script>" y
   "<style>...</style>").

HTMLParser.handle_entityref(name)

   Este método se llama para procesar una referencia de caracteres con
   nombre del formulario "&name;" (por ejemplo, "&gt;"), donde *name*
   es una referencia de entidad general (por ejemplo, "'gt'"). Este
   método nunca se llama si *convert_charrefs* es "True".

HTMLParser.handle_charref(name)

   This method is called to process decimal and hexadecimal numeric
   character references of the form "&#*NNN*;" and "&#x*NNN*;".  For
   example, the decimal equivalent for "&gt;" is "&#62;", whereas the
   hexadecimal is "&#x3E;"; in this case the method will receive
   "'62'" or "'x3E'".  This method is never called if
   *convert_charrefs* is "True".

HTMLParser.handle_comment(data)

   Este método se llama cuando se encuentra un comentario (por
   ejemplo, "<!--comment-->").

   Por ejemplo, el comentario "<! - comment ->" hará que se llame a
   este método con el argumento "'comment'".

   El contenido de los comentarios condicionales de Internet Explorer
   (*condcoms*) también se enviará a este método, por lo tanto, para
   "<!--[if IE 9]>IE9-specific content<![endif]-->", este método
   recibirá "'[if IE 9]>IE9-specific content<![endif]'".

HTMLParser.handle_decl(decl)

   Este método se llama para manejar una declaración de tipo de
   documento HTML (por ejemplo, "<!DOCTYPE html>").

   El parámetro *decl* será todo el contenido de la declaración dentro
   del "<!...>" *markup* (por ejemplo, "'DOCTYPE html'").

HTMLParser.handle_pi(data)

   Método llamado cuando se encuentra una instrucción de
   procesamiento. El parámetro *data* contendrá toda la instrucción de
   procesamiento. Por ejemplo, para la instrucción de procesamiento
   "<?proc color='red'>", este método se llamaría como
   "handle_pi("proc color='red'")". Está destinado a ser anulado por
   una clase derivada; La implementación de la clase base no hace
   nada.

   Nota:

     La clase "HTMLParser" utiliza las reglas sintácticas SGML para
     procesar instrucciones. Una instrucción de procesamiento XHTML
     que use el "'?'" final hará que se incluya el "'?'" en *data*.

HTMLParser.unknown_decl(data)

   Se llama a este método cuando el analizador lee una declaración no
   reconocida.

   El parámetro *data* será el contenido completo de la declaración
   dentro del marcado "<! [...]>". A veces es útil ser reemplazado por
   una clase derivada. La implementación de la clase base no hace
   nada.


Ejemplos
========

La siguiente clase implementa un analizador que se utilizará para
ilustrar más ejemplos:

   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()

Analizando un *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"

Analizando un elemento con algunos atributos y un título:

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

El contenido de los elementos "script" y "style" se retorna tal cual,
sin más análisis

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

Analizando comentarios:

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

Analizar referencias de caracteres con nombre y numéricos y
convertirlos al carácter correcto (nota: estas 3 referencias son todas
equivalentes a "'>'"):

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

La alimentación de fragmentos incompletos a "feed()" funciona, pero
"handle_data()" podría llamarse más de una vez (a menos que
*convert_charrefs* esté configurado como "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

Analizar HTML no válido (por ejemplo, atributos sin comillas) también
funciona:

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