2011-02-11 11 views
6

¿Existe alguna herramienta de Python puro para tomar algo de HTML y truncarlo lo más cercano posible, pero asegúrese de que el fragmento resultante esté bien formado? Por ejemplo, teniendo en cuenta este código HTML:Truncamiento de HTML en Python

<h1>This is a header</h1> 
<p>This is a paragraph</p> 

no produciría:

<h1>This is a hea 

pero:

<h1>This is a header</h1> 

o al menos:

<h1>This is a hea</h1> 

no puedo encuentra uno que funcione, aunque encontré uno que depende de pullparser, que es obsoleto y está muerto.

+0

"El resultado sería:" .. Teniendo en cuenta lo parámetros? número de caracteres en una fila? número de dom-elementos, jerarquía? – akira

+0

Probablemente sea una cantidad de caracteres de contenido o una cantidad de caracteres HTML. Yo no soy exigente. – JasonFruit

Respuesta

6

No creo que se necesita un analizador de pleno derecho - sólo tiene que tokenize la cadena de la entrada en uno de:

  • texto
  • etiqueta abierta
  • etiqueta de cierre
  • autocierre
  • etiqueta
  • entidad de caracteres

una vez que tenga una corriente de fichas por el estilo, es fácil de usar una pila para realizar un seguimiento de lo que las etiquetas tienen que cerrar. De hecho, me encontré con este problema hace un tiempo y escribí una pequeña biblioteca para hacer esto:

https://github.com/eentzel/htmltruncate.py

Ha funcionado bien para mí, y maneja la mayor parte de los casos de esquina, así, como el marcado de forma arbitraria anidada, contando las entidades de caracteres como un solo carácter, devolviendo un error en marcado incorrecto, etc.

Producirá:

<h1>This is a hea</h1> 

en su ejemplo. Esto quizás podría cambiarse, pero es difícil en el caso general: ¿qué sucede si estás tratando de truncar a 10 caracteres, pero la etiqueta <h1> no está cerrada para otra, digamos, 300 caracteres?

+0

Esto es exactamente lo que trabajé, y me escribí a mí mismo. La única diferencia práctica entre la tuya y la mía era que permitía truncar solo en ubicaciones entre palabras. – JasonFruit

+0

Necesitaba exactamente eso y realicé saltos entre palabras implementados. Es muy simple, la diferencia con respecto al original es como 5 líneas: https://github.com/enkore/typeflow/blob/master/htmltruncate.py alrededor de la línea cincuenta – dom0

0

Mi pensamiento inicial sería utilizar un analizador XML (tal vez python's sax parser), entonces probablemente cuente los caracteres de texto en cada elemento xml. Ignoraría el recuento de caracteres de las etiquetas para hacerlo más uniforme y más simple, pero cualquiera debería ser posible.

+0

Como comenté en la respuesta de funktku, ¿alguien * ya no lo ha hecho? – JasonFruit

+0

@JasonFruit Oh, ya veo lo que quieres decir ahora. No sé si realmente es tan común y tan simple de hacer. – Petriborg

0

Recomiendo primero analizar por completo el HTML y truncar. Un gran analizador de HTML para Python es lxml. Después de analizar y truncar, puede imprimirlo nuevamente en formato HTML.

+0

¿Pero todavía no ha hecho * alguien * eso? Entiendo el problema, pero parece tan común que alguien debe tener una solución. – JasonFruit

0

Mire HTML Tidy para limpiar/reformatear/reindentar HTML.

+0

No es la mejor opción, y realmente no es una cosa de Python. – JasonFruit

+0

Hay un par de bibliotecas de Python vinculantes para Tidy, compruébalo. Lo uso para limpiar el HTML de MS-Word que algunos usuarios pegan en CMS. –

+0

Tampoco especifiqué que estoy usando Google App Engine, donde solo puedo introducir bibliotecas Python puras. – JasonFruit

5

Si está utilizando DJANGO lib, puede simplemente:

from django.utils import text, html 

    class class_name(): 


     def trim_string(self, stringf, limit, offset = 0): 
      return stringf[offset:limit] 

     def trim_html_words(self, html, limit, offset = 0): 
      return text.truncate_html_words(html, limit) 


     def remove_html(self, htmls, tag, limit = 'all', offset = 0): 
      return html.strip_tags(htmls) 

De todas formas, aquí está el código de truncate_html_words de Django:

import re 

def truncate_html_words(s, num): 
    """ 
    Truncates html to a certain number of words (not counting tags and comments). 
    Closes opened tags if they were correctly closed in the given html. 
    """ 
    length = int(num) 
    if length <= 0: 
     return '' 
    html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input') 
    # Set up regular expressions 
    re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)') 
    re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>') 
    # Count non-HTML words and keep note of open tags 
    pos = 0 
    ellipsis_pos = 0 
    words = 0 
    open_tags = [] 
    while words <= length: 
     m = re_words.search(s, pos) 
     if not m: 
      # Checked through whole string 
      break 
     pos = m.end(0) 
     if m.group(1): 
      # It's an actual non-HTML word 
      words += 1 
      if words == length: 
       ellipsis_pos = pos 
      continue 
     # Check for tag 
     tag = re_tag.match(m.group(0)) 
     if not tag or ellipsis_pos: 
      # Don't worry about non tags or tags after our truncate point 
      continue 
     closing_tag, tagname, self_closing = tag.groups() 
     tagname = tagname.lower() # Element names are always case-insensitive 
     if self_closing or tagname in html4_singlets: 
      pass 
     elif closing_tag: 
      # Check for match in open tags list 
      try: 
       i = open_tags.index(tagname) 
      except ValueError: 
       pass 
      else: 
       # SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags 
       open_tags = open_tags[i+1:] 
     else: 
      # Add it to the start of the open tags list 
      open_tags.insert(0, tagname) 
    if words <= length: 
     # Don't try to close tags if we don't need to truncate 
     return s 
    out = s[:ellipsis_pos] + ' ...' 
    # Close any tags still open 
    for tag in open_tags: 
     out += '</%s>' % tag 
    # Return string 
    return out 
+0

Estoy usando CherryPy, pero podría valer la pena importar 'django.utils.text' si no se agrega demasiado el costo de inicio. Lo intentaré. – JasonFruit

+1

La función 'truncate_html_words' está en http://code.djangoproject.com/browser/django/trunk/django/utils/text.py. –

+0

Analizar HTML usando expresiones regulares (como Django hace arriba) es una muy mala idea. – slacy

2

Esto servirá su requirement.An fácil de usar analizador de HTML y corrector de mal marcado

http://www.crummy.com/software/BeautifulSoup/

+0

Miré aquí primero antes de hacer la pregunta. No está mal, pero me corresponde a mí contar los caracteres de contenido y truncar en el punto correcto, aunque hace un buen trabajo al corregir el marcado una vez que lo hace. – JasonFruit

3

Usted puede hacer esto en una línea con BeautifulSoup (suponiendo que desea truncar en un cierto número de caracteres fuente, no en un número de caracteres de contenido):

from BeautifulSoup import BeautifulSoup 

def truncate_html(html, length): 
    return unicode(BeautifulSoup(html[:length])) 
3

he encontrado la respuesta por slacy muy útil y lo votaría mejor si tuviera la reputación, sin embargo, había algo más que destacar. En mi entorno tenía instalado html5lib y BeautifulSoup4. BeautifulSoup usó el analizador html5lib y esto dio como resultado que mi fragmento html se envolviera en etiquetas html y body, que no es lo que yo quería.

>>> truncate_html("<p>sdfsdaf</p>", 4) 
u'<html><head></head><body><p>s</p></body></html>' 

Para resolver estos problemas me dijeron BeautifulSoup utilizar el analizador de Python:

from bs4 import BeautifulSoup 
def truncate_html(html, length): 
    return unicode(BeautifulSoup(html[:length], "html.parser")) 

>>> truncate_html("<p>sdfsdaf</p>", 4) 
u'<p>s</p>'