insertar etiquetas en el texto de ElementTree
-
21-09-2019 - |
Pregunta
Estoy usando Python ElementoÁrbol Módulo para manipular HTML.Quiero enfatizar ciertas palabras y mi solución actual es:
for e in tree.getiterator():
for attr in 'text', 'tail':
words = (getattr(e, attr) or '').split()
change = False
for i, word in enumerate(words):
word = clean_word.sub('', word)
if word.lower() in glossary:
change = True
words[i] = word.replace(word, '<b>' + word + '</b>')
if change:
setattr(e, attr, ' '.join(words))
Lo anterior examina el texto de cada elemento y enfatiza las palabras importantes que encuentra.Sin embargo, lo hace incrustando etiquetas HTML en los atributos de texto, que se escapan al renderizar, por lo que necesito contrarrestar con:
html = etree.tostring(tree).replace('>', '>').replace('<', '<')
Esto me incomoda así que quiero hacerlo correctamente.Sin embargo, para incrustar un nuevo elemento necesitaría cambiar los atributos 'texto' y 'cola' para que el texto enfatizado apareciera en la misma posición.Y esto sería realmente complicado al iterar como se indicó anteriormente.
Se agradecería cualquier consejo sobre cómo hacer esto correctamente.¡Estoy seguro de que hay algo que me he perdido en la API!
Solución
También puedes usar xslt y una función xpath personalizada para hacer esto.
A continuación se muestra un ejemplo.Todavía necesita algo de trabajo, por ejemplo, limpiar espacios en blanco adicionales al final de los elementos y manejar texto mixto, pero es otra idea.
dada esta entrada:
<html>
<head>
</head>
<body>
<p>here is some text to bold</p>
<p>and some more</p>
</body>
</html>
y el glosario contiene las dos palabras: algunos, negrita
entonces el resultado del ejemplo es:
<?xml version="1.0"?>
<html>
<head/>
<body>
<p>here is <b>some</b> text to <b>bold</b> </p>
<p>and <b>some</b> more </p>
</body>
</html>
Aquí está el código, también lo publiqué en http://bkc.pastebin.com/f545a8e1d
from lxml import etree
stylesheet = etree.XML("""
<xsl:stylesheet version="1.0"
xmlns:btest="uri:bolder"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*">
<xsl:copy />
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name(.)}">
<xsl:copy-of select="@*" />
<xsl:apply-templates select="text()" />
<xsl:apply-templates select="./*" />
</xsl:element>
</xsl:template>
<xsl:template match="text()">
<xsl:copy-of select="btest:bolder(.)/node()" />
</xsl:template>
</xsl:stylesheet>
""")
glossary = ['some', 'bold']
def bolder(context, s):
results = []
r = None
for word in s[0].split():
if word in glossary:
if r is not None:
results.append(r)
r = etree.Element('r')
b = etree.SubElement(r, 'b')
b.text = word
b.tail = ' '
results.append(r)
r = None
else:
if r is None:
r = etree.Element('r')
r.text = '%s%s ' % (r.text or '', word)
if r is not None:
results.append(r)
return results
def test():
ns = etree.FunctionNamespace('uri:bolder') # register global namespace
ns['bolder'] = bolder # define function in new global namespace
transform = etree.XSLT(stylesheet)
print str(transform(etree.XML("""<html><head></head><body><p>here is some text to bold</p><p>and some more</p></body></html>""")))
if __name__ == "__main__":
test()
Otros consejos
A pesar de elementtree es muy fácil de usar para la mayoría de las tareas de procesamiento XML, también es un inconveniente para contenido mixto. Sugiero usar analizador DOM:
from xml.dom import minidom
import re
ws_split = re.compile(r'\s+', re.U).split
def processNode(parent):
doc = parent.ownerDocument
for node in parent.childNodes[:]:
if node.nodeType==node.TEXT_NODE:
words = ws_split(node.nodeValue)
new_words = []
changed = False
for word in words:
if word in glossary:
text = ' '.join(new_words+[''])
parent.insertBefore(doc.createTextNode(text), node)
b = doc.createElement('b')
b.appendChild(doc.createTextNode(word))
parent.insertBefore(b, node)
new_words = ['']
changed = True
else:
new_words.append(word)
if changed:
text = ' '.join(new_words)
print text
parent.replaceChild(doc.createTextNode(text), node)
else:
processNode(node)
También he utilizado expresiones regulares para separar palabras para evitar que se peguen entre sí:
>>> ' '.join(ws_split('a b '))
'a b '
>>> ' '.join('a b '.split())
'a b'