Python заменяет XML-текст Escape-последовательностью

У меня есть стороннее приложение, которое анализирует магические строки в файле XML, хотя оно должно обрабатывать их как символьные литералы. В качестве примера предположим, что мой XML содержит следующий сегмент:

<element>Sentence containing magicString</element>

Чтобы стороннее приложение не анализировало magicString как команду, я хочу преобразовать этот фрагмент xml в:

<element>Sentence containing &#109;agicString</element>

Как я могу добиться этого в Python, не выполняя глобальный поиск-замену (например, могут быть элементы с именем magicString, которые нельзя переименовать, или XML недействителен)? Следующее иллюстрирует то, что я пытался:

from xml.etree import ElementTree
xml = ElementTree.parse(xmlPath)
element = xml.find('.//grandparent/parent/element'):
element.text = '&#109;agicString'
xml.write(xmlPath)

Проблема в том, что при присвоении свойства Element.text экранируется текст, поэтому результатом является XML-файл со следующим содержимым:

<element>&amp;#109;agicString</element>

Почему бы не использовать для этой задачи XSLT и не вызывать его из Python?

Yitzhak Khabinsky 23.08.2024 01:12

Знаете ли вы, как это сделать, чтобы XSLT не отменял экранирование символов? Я не могу этого понять, но в противном случае XSLT был бы правильным решением.

Jeff G 23.08.2024 18:41
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
79
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Мне понравилось решение Ицхака и Дэниела. Возможно, вам тоже поможет хак с 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": "&#109;agicString"})
print('Escaped:', a)
root.text = a

with open("w_xml", 'w') as f:
    f.write(ET.tostring(root).decode().replace("&amp;","&")) 
    # <element>Sentence containing &#109;agicString</element>

Выход:

Origin: Sentence containing magicString
Escaped: Sentence containing &#109;agicString
Ответ принят как подходящий

Вот решение на основе XSLT.

Он будет искать только текстовые узлы, содержащие «magicString». Элементы XML не затрагиваются.

Амперсанд всегда должен обозначаться как &amp; в 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('&amp;#109;', substring($findMe, 2, 100)))"/>
    </xsl:template>
</xsl:stylesheet>

Выходной XML

<root>
  <magicString>Another sentence</magicString>
  <element>Sentence containing &amp;#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, в которой используется другой символ (в данном случае &#178; (надстрочный индекс 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 = "&#178;" string = "&amp;#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, '&#178;'||substring($findMe, 2))"/>
    </xsl:template>
    
</xsl:stylesheet>

Это производит:

<root>
   <magicString>Another sentence</magicString>
   <element>Sentence containing &#109;agicString</element>
   <city>Miami</city>
</root>

Снимок экрана Python/XSLT в PyCharm, дающий желаемый результат:

Это решение все еще страдает от той же проблемы, и я тоже не могу понять, как ее обойти. Мне нужно, чтобы результат был <element>Sentence containing &#109;agicString</element>. Представленный здесь XSLT экранирует амперсанд таким же образом, как я изначально пробовал.

Jeff G 23.08.2024 18:38

Как я уже объяснил в ответе: «Амперсанд всегда должен быть обозначен как &amp; в XML. В противном случае XML будет неправильно сформирован».

Yitzhak Khabinsky 23.08.2024 18:44

Литералы запрещенных символов XML: w3resource.com/xml/prohibited-character-literals.php

Yitzhak Khabinsky 23.08.2024 18:52

@JeffG - я добавил к ответу Ицхака модифицированный XSLT, который должен делать то, что вы просите. Я решил, что это лучше, чем создавать совершенно новый ответ. Ицхак, если хотите, я добавлю другой ответ и верну ваш в исходное состояние.

Daniel Haley 23.08.2024 19:14

Судя по моему чтению стандарта XML, мой XML действителен. См. раздел 4.1 Ссылки на персонажей и сущности. Похоже, что требуется полная поддержка символьных объектов формы &#\d+; и &#x[0-9A-Fa-f]+;. Однако это может применяться только к ссылкам на сущности.

Jeff G 23.08.2024 20:10

@DanielHaley, не беспокойся. мы хорошие.

Yitzhak Khabinsky 23.08.2024 20:17

У меня это все еще не работает, даже с учетом правок, предложенных @DanielHaley. Я могу либо создать что-то, содержащее &amp;#109;agicString, либо сущности расшириться до magicString. Я не могу хоть убей определить, как исправить это с помощью XSLT. Может быть, это ограничение инструмента Saxon Python?

Jeff G 23.08.2024 20:36

Обойти это невозможно: w3schools.com/xml/xml_syntax.asp Пожалуйста, проверьте раздел «Ссылки на сущности».

Yitzhak Khabinsky 23.08.2024 20:54

@JeffG — Используя Python Ицхака и входной XML вместе с моим XSLT, я получаю именно тот результат, который вы запросили с помощью &#109;agicString. Я добавил скриншот того, как он работает в PyCharm.

Daniel Haley 23.08.2024 21:14

Я не уверен, что заставило меня не работать, но я убедился, что предложенный код и таблица стилей действительно дают именно то, что я просил. Мне также удалось упростить вывод Python до executable.transform_to_file(xdm_node=document, output_file=output_file).

Jeff G 26.08.2024 23:33

Другие вопросы по теме

Похожие вопросы