У меня есть стороннее приложение, которое анализирует магические строки в файле XML, хотя оно должно обрабатывать их как символьные литералы. В качестве примера предположим, что мой XML содержит следующий сегмент:
<element>Sentence containing magicString</element>
Чтобы стороннее приложение не анализировало magicString
как команду, я хочу преобразовать этот фрагмент xml в:
<element>Sentence containing magicString</element>
Как я могу добиться этого в Python, не выполняя глобальный поиск-замену (например, могут быть элементы с именем magicString
, которые нельзя переименовать, или XML недействителен)? Следующее иллюстрирует то, что я пытался:
from xml.etree import ElementTree
xml = ElementTree.parse(xmlPath)
element = xml.find('.//grandparent/parent/element'):
element.text = 'magicString'
xml.write(xmlPath)
Проблема в том, что при присвоении свойства Element.text
экранируется текст, поэтому результатом является XML-файл со следующим содержимым:
<element>&#109;agicString</element>
Знаете ли вы, как это сделать, чтобы XSLT не отменял экранирование символов? Я не могу этого понять, но в противном случае XSLT был бы правильным решением.
Мне понравилось решение Ицхака и Дэниела.
Возможно, вам тоже поможет хак с saxutils
ссылка:
import xml.etree.ElementTree as ET
from xml.sax.saxutils import escape
xml_ = """<element>Sentence containing magicString</element>"""
root = ET.fromstring(xml_)
print('Origin:', root.text)
a = escape(f"{root.text}", {"magicString": "magicString"})
print('Escaped:', a)
root.text = a
with open("w_xml", 'w') as f:
f.write(ET.tostring(root).decode().replace("&","&"))
# <element>Sentence containing magicString</element>
Выход:
Origin: Sentence containing magicString
Escaped: Sentence containing magicString
Вот решение на основе XSLT.
Он будет искать только текстовые узлы, содержащие «magicString». Элементы XML не затрагиваются.
Амперсанд всегда должен обозначаться как &
в XML. В противном случае XML был бы неправильно сформирован.
Вы можете скачать движок Saxon Python XSLT 3.0 здесь: Загрузки Python
Saxon Home Edition (HE) предоставляется бесплатно.
Входной XML
<root>
<magicString>Another sentence</magicString>
<element>Sentence containing magicString</element>
<city>Miami</city>
</root>
XSLT 3.0
<?xml version = "1.0"?>
<xsl:stylesheet version = "3.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method = "xml" omit-xml-declaration = "yes"
encoding = "UTF-8" indent = "yes"/>
<xsl:strip-space elements = "*"/>
<xsl:param name = "findMe" select = "'magicString'"/>
<!--Identity transform-->
<xsl:mode on-no-match = "shallow-copy"/>
<xsl:template match = "text()[contains(., $findMe)]">
<xsl:value-of select = "replace(., $findMe, concat('&#109;', substring($findMe, 2, 100)))"/>
</xsl:template>
</xsl:stylesheet>
Выходной XML
<root>
<magicString>Another sentence</magicString>
<element>Sentence containing &#109;agicString</element>
<city>Miami</city>
</root>
Питон
from saxonche import *
output_file = 'output.xml'
with PySaxonProcessor(license=False) as proc:
print(proc.version)
try:
xsltproc = proc.new_xslt30_processor()
document = proc.parse_xml(xml_file_name='input.xml')
executable = xsltproc.compile_stylesheet(stylesheet_file = "process.xslt")
output = executable.transform_to_string(xdm_node=document)
with open(output_file, 'wb') as f:
f.write(output.encode('utf-8'))
except PySaxonApiError as err:
print('Error during function call', err)
Вот модифицированная версия XSLT, в которой используется другой символ (в данном случае ²
(надстрочный индекс 2)) и карта символов для сопоставления его с нужной ссылкой на объект. Вам просто нужно использовать символ, которого еще нет в вашем вводе, чтобы его случайно не заменили.
<xsl:stylesheet version = "3.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" expand-text = "yes">
<xsl:output indent = "yes" use-character-maps = "magic"/>
<xsl:strip-space elements = "*"/>
<xsl:character-map name = "magic">
<xsl:output-character character = "²" string = "&#109;"/>
</xsl:character-map>
<xsl:param name = "findMe" select = "'magicString'"/>
<xsl:mode on-no-match = "shallow-copy"/>
<xsl:template match = "text()[contains(., $findMe)]">
<xsl:value-of select = "replace(., $findMe, '²'||substring($findMe, 2))"/>
</xsl:template>
</xsl:stylesheet>
Это производит:
<root>
<magicString>Another sentence</magicString>
<element>Sentence containing magicString</element>
<city>Miami</city>
</root>
Снимок экрана Python/XSLT в PyCharm, дающий желаемый результат:
Это решение все еще страдает от той же проблемы, и я тоже не могу понять, как ее обойти. Мне нужно, чтобы результат был <element>Sentence containing magicString</element>
. Представленный здесь XSLT экранирует амперсанд таким же образом, как я изначально пробовал.
Как я уже объяснил в ответе: «Амперсанд всегда должен быть обозначен как & в XML. В противном случае XML будет неправильно сформирован».
Литералы запрещенных символов XML: w3resource.com/xml/prohibited-character-literals.php
@JeffG - я добавил к ответу Ицхака модифицированный XSLT, который должен делать то, что вы просите. Я решил, что это лучше, чем создавать совершенно новый ответ. Ицхак, если хотите, я добавлю другой ответ и верну ваш в исходное состояние.
Судя по моему чтению стандарта XML, мой XML действителен. См. раздел 4.1 Ссылки на персонажей и сущности. Похоже, что требуется полная поддержка символьных объектов формы &#\d+;
и &#x[0-9A-Fa-f]+;
. Однако это может применяться только к ссылкам на сущности.
@DanielHaley, не беспокойся. мы хорошие.
У меня это все еще не работает, даже с учетом правок, предложенных @DanielHaley. Я могу либо создать что-то, содержащее &#109;agicString
, либо сущности расшириться до magicString
. Я не могу хоть убей определить, как исправить это с помощью XSLT. Может быть, это ограничение инструмента Saxon Python?
Обойти это невозможно: w3schools.com/xml/xml_syntax.asp Пожалуйста, проверьте раздел «Ссылки на сущности».
@JeffG — Используя Python Ицхака и входной XML вместе с моим XSLT, я получаю именно тот результат, который вы запросили с помощью magicString
. Я добавил скриншот того, как он работает в PyCharm.
Я не уверен, что заставило меня не работать, но я убедился, что предложенный код и таблица стилей действительно дают именно то, что я просил. Мне также удалось упростить вывод Python до executable.transform_to_file(xdm_node=document, output_file=output_file)
.
Почему бы не использовать для этой задачи XSLT и не вызывать его из Python?