Красивая печать XML на Python


Ответы:


379
import xml.dom.minidom

dom = xml.dom.minidom.parse(xml_fname) # or xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = dom.toprettyxml()

35
Это даст вам довольно xml, но обратите внимание, что то, что выходит в текстовом узле, на самом деле отличается от того, что появилось - в текстовых узлах есть новые пробелы. Это может причинить вам неприятности, если вы ожидаете ТОЧНО того, что скармливать пище.
— Тодд Хопкинсон

49
@icnivad: хотя важно указать на этот факт, мне кажется странным, что кто-то захочет улучшить его XML, если пробелы будут иметь какое-то значение для них!
— Вааб

18
Ницца! Может свернуть это в один слой: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
— Антон И. Сипос

11
minidom широко известен как довольно плохая реализация XML. Если вы позволите себе добавлять внешние зависимости, lxml будет намного лучше.
— Букзор

26
Не фанат переопределения xml от модуля до выходного объекта, но метод работает иначе. Я хотел бы найти более хороший способ перейти от основного этри к красивой печати. Хотя lxml - это круто, бывают случаи, когда я предпочитаю придерживаться сути, если могу.
— Дэнни Стейпл

162

lxml недавно обновлен и включает в себя красивую функцию печати

import lxml.etree as etree

x = etree.parse("filename")
print etree.tostring(x, pretty_print=True)

Ознакомьтесь с руководством по lxml: http://lxml.de/tutorial.html


11
Единственным недостатком lxml является зависимость от внешних библиотек. Это, я думаю, не так плохо под Windows, библиотеки упакованы с модулем. Под Linux они aptitude installдалеко. Под OS / X я не уверен.
— интуитивно

4
В OS X вам просто нужны работающие gcc и easy_install / pip.
— Пкоч

11
Красивый принтер lxml не надежен и не будет правильно печатать ваш XML во многих случаях, описанных в FAQ по lxml . Я перестал использовать lxml для красивой печати после нескольких угловых случаев, которые просто не работают (т.е. это не исправит: ошибка # 910018 ). Все эти проблемы связаны с использованием значений XML, содержащих пробелы, которые должны быть сохранены.
— Вааб

1
lxml также является частью MacPorts, у меня работает без сбоев.
— Jens

14
Так как в Python 3 обычно требуется , чтобы работать с ул (= юникод строки в Python 2), лучше использовать это: print(etree.tostring(x, pretty_print=True, encoding="unicode")). Запись в выходной файл возможна всего за одну строку, промежуточная переменная не требуется:etree.parse("filename").write("outputfile", encoding="utf-8")
— Thor

109

Другое решение - заимствовать эту indentфункцию для использования с библиотекой ElementTree, которая встроена в Python начиная с 2.5. Вот как это будет выглядеть:

from xml.etree import ElementTree

def indent(elem, level=0):
    i = "\n" + level*"  "
    j = "\n" + (level-1)*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
        for subelem in elem:
            indent(subelem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = j
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = j
    return elem        

root = ElementTree.parse('/tmp/xmlfile').getroot()
indent(root)
ElementTree.dump(root)

... а затем просто используйте lxml tostring!
— Стефано

2
Обратите внимание, что вы все еще можете сделать tree.write([filename])для записи в файл ( treeбудучи экземпляром ElementTree).
— Бук

16
Эта ссылка effbot.org/zone/element-lib.htm#prettyprint имеет правильный код. Код здесь что-то не так. Нужно отредактировать.
— Озеро Эйлвин

Нет, вы не можете, так как elementtree.getroot () не имеет этого метода, его имеет только объект elementtree. @bouke
— shinzou

1
Вот как вы можете записать в файл:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
— e-malito

47

Вот мое (хакерское?) Решение, чтобы обойти уродливую проблему текстового узла.

uglyXml = doc.toprettyxml(indent='  ')

text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)    
prettyXml = text_re.sub('>\g<1></', uglyXml)

print prettyXml

Приведенный выше код будет производить:

<?xml version="1.0" ?>
<issues>
  <issue>
    <id>1</id>
    <title>Add Visual Studio 2005 and 2008 solution files</title>
    <details>We need Visual Studio 2005/2008 project files for Windows.</details>
  </issue>
</issues>

Вместо этого:

<?xml version="1.0" ?>
<issues>
  <issue>
    <id>
      1
    </id>
    <title>
      Add Visual Studio 2005 and 2008 solution files
    </title>
    <details>
      We need Visual Studio 2005/2008 project files for Windows.
    </details>
  </issue>
</issues>

Отказ от ответственности: вероятно, есть некоторые ограничения.


Спасибо! Это была моя единственная претензия ко всем красивым методам печати. Хорошо работает с несколькими файлами, которые я пробовал.
— 2010 г.

Я нашел довольно «почти идентичное» решение, но вы более прямолинейно, с использованием re.compileдо subоперации (я использовал re.findall()дважды, zipи forцикл с str.replace()...)
— heltonbiker

3
Это больше не требуется в Python 2.7: функция toprettyxml () в xml.dom.minidom теперь выдает вывод, подобный «<id> 1 </ id>» по умолчанию, для узлов, которые имеют ровно один текстовый дочерний узел.
— Мариус Гедминас

Я вынужден использовать Python 2.6. Таким образом, этот трюк переформатирования регулярных выражений очень полезен. Работал как есть без проблем.
— Майк Финч

@Marus Gedminas У меня работает 2.7.2 и «по умолчанию» определенно не так, как вы говорите.
— posfan12

23

Как отмечали другие, в lxml встроен симпатичный принтер.

Имейте в виду, что по умолчанию он изменяет разделы CDATA на обычный текст, что может привести к неприятным результатам.

Вот функция Python, которая сохраняет входной файл и изменяет только отступ (обратите внимание на strip_cdata=False). Кроме того, он гарантирует, что вывод использует UTF-8 в качестве кодировки вместо ASCII по умолчанию (обратите внимание на encoding='utf-8'):

from lxml import etree

def prettyPrintXml(xmlFilePathToPrettyPrint):
    assert xmlFilePathToPrettyPrint is not None
    parser = etree.XMLParser(resolve_entities=False, strip_cdata=False)
    document = etree.parse(xmlFilePathToPrettyPrint, parser)
    document.write(xmlFilePathToPrettyPrint, pretty_print=True, encoding='utf-8')

Пример использования:

prettyPrintXml('some_folder/some_file.xml')

1
Сейчас немного поздно. Но я думаю, что lxml исправил CDATA? CDATA - это CDATA на моей стороне.
— Elwc

Спасибо, это лучший ответ на данный момент.
— Джордж Чалхуб

20

BeautifulSoup имеет простой в использовании prettify()метод.

Он отступает один пробел на уровень отступа. Он работает намного лучше, чем lxml's pretty_print, и он короткий и приятный.

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()

1
Получение этого сообщения об ошибке:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
— hadoop

12

Если у вас есть, xmllintвы можете создать подпроцесс и использовать его. xmllint --format <file>Pretty-печатает свой входной XML в стандартный вывод.

Обратите внимание, что этот метод использует внешнюю по отношению к python программу, что делает его своего рода хаком.

def pretty_print_xml(xml):
    proc = subprocess.Popen(
        ['xmllint', '--format', '/dev/stdin'],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
    )
    (output, error_output) = proc.communicate(xml);
    return output

print(pretty_print_xml(data))

11

Я попытался отредактировать ответ "ade" выше, но переполнение стека не позволило мне редактировать после того, как я первоначально предоставил отзыв анонимно. Это менее глючная версия функции для красивой печати ElementTree.

def indent(elem, level=0, more_sibs=False):
    i = "\n"
    if level:
        i += (level-1) * '  '
    num_kids = len(elem)
    if num_kids:
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
            if level:
                elem.text += '  '
        count = 0
        for kid in elem:
            indent(kid, level+1, count < num_kids - 1)
            count += 1
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
            if more_sibs:
                elem.tail += '  '
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i
            if more_sibs:
                elem.tail += '  '

8

Если вы используете реализацию DOM, у каждого есть своя собственная форма встроенной симпатичной печати:

# minidom
#
document.toprettyxml()

# 4DOM
#
xml.dom.ext.PrettyPrint(document, stream)

# pxdom (or other DOM Level 3 LS-compliant imp)
#
serializer.domConfig.setParameter('format-pretty-print', True)
serializer.writeToString(document)

Если вы используете что-то другое без собственного симпатичного принтера - или эти симпатичные принтеры делают это не совсем так, как вы хотите - вам, вероятно, придется написать или создать подкласс своего собственного сериализатора.


6

У меня были некоторые проблемы с красивой печатью минидома. Я получал UnicodeError всякий раз, когда пытался печатать документ с символами за пределами заданной кодировки, например, если в документе была буква β, и я пытался doc.toprettyxml(encoding='latin-1'). Вот мой обходной путь для этого:

def toprettyxml(doc, encoding):
    """Return a pretty-printed XML document in a given encoding."""
    unistr = doc.toprettyxml().replace(u'<?xml version="1.0" ?>',
                          u'<?xml version="1.0" encoding="%s"?>' % encoding)
    return unistr.encode(encoding, 'xmlcharrefreplace')

5
from yattag import indent

pretty_string = indent(ugly_string)

Он не будет добавлять пробелы или переводы строк внутри текстовых узлов, если вы не попросите об этом с помощью:

indent(mystring, indent_text = True)

Вы можете указать, какой должна быть единица отступа и как должен выглядеть перевод строки.

pretty_xml_string = indent(
    ugly_xml_string,
    indentation = '    ',
    newline = '\r\n'
)

Документ находится на домашней странице http://www.yattag.org .


4

Я написал решение, чтобы пройтись по существующему ElementTree и использовать text / tail для отступа, как обычно ожидают.

def prettify(element, indent='  '):
    queue = [(0, element)]  # (level, element)
    while queue:
        level, element = queue.pop(0)
        children = [(level + 1, child) for child in list(element)]
        if children:
            element.text = '\n' + indent * (level+1)  # for child open
        if queue:
            element.tail = '\n' + indent * queue[0][0]  # for sibling open
        else:
            element.tail = '\n' + indent * (level-1)  # for parent close
        queue[0:0] = children  # prepend so children come before siblings

3

Довольно неплохо выглядит XML для python для этой задачи. (Также назван соответствующим образом.)

Альтернативой является использование pyXML , который имеет функцию PrettyPrint .


HTTPError: 404 Client Error: Not Found for url: https://pypi.org/simple/xmlpp/Думаю, этот проект сейчас на чердаке, позор.
— 8bitjunkie

3

Вы можете использовать популярную внешнюю библиотеку xmltodict , unparseи pretty=Trueвы получите лучший результат:

xmltodict.unparse(
    xmltodict.parse(my_xml), full_document=False, pretty=True)

full_document=Falseпротив <?xml version="1.0" encoding="UTF-8"?>наверху.


3

Вот решение Python3, которое избавляет от уродливой проблемы новой строки (тонны пробелов) и использует только стандартные библиотеки в отличие от большинства других реализаций.

import xml.etree.ElementTree as ET
import xml.dom.minidom
import os

def pretty_print_xml_given_root(root, output_xml):
    """
    Useful for when you are editing xml data on the fly
    """
    xml_string = xml.dom.minidom.parseString(ET.tostring(root)).toprettyxml()
    xml_string = os.linesep.join([s for s in xml_string.splitlines() if s.strip()]) # remove the weird newline issue
    with open(output_xml, "w") as file_out:
        file_out.write(xml_string)

def pretty_print_xml_given_file(input_xml, output_xml):
    """
    Useful for when you want to reformat an already existing xml file
    """
    tree = ET.parse(input_xml)
    root = tree.getroot()
    pretty_print_xml_given_root(root, output_xml)

Я нашел, как исправить общую проблему с новой строкой здесь .


2

Взгляните на модуль vkbeautify .

Это Python-версия моего очень популярного плагина javascript / nodejs с тем же именем. Он может красиво печатать / минимизировать текст XML, JSON и CSS. Ввод и вывод могут быть строки / файла в любых комбинациях. Это очень компактно и не имеет никакой зависимости.

Примеры :

import vkbeautify as vkb

vkb.xml(text)                       
vkb.xml(text, 'path/to/dest/file')  
vkb.xml('path/to/src/file')        
vkb.xml('path/to/src/file', 'path/to/dest/file') 

Эта конкретная библиотека обрабатывает проблему Ugly Text Node.
— Кэмерон Лоуэлл Палмер

1

В качестве альтернативы, если вы не хотите повторной обработки, есть библиотека xmlpp.py с get_pprint()функцией. Это хорошо и без проблем работало для моих сценариев использования, без необходимости повторной обработки объекта lxml ElementTree.


1
Пробовал minidom и lxml и не получал правильно отформатированный и с отступом xml. Это сработало, как и ожидалось
— Дэвид Хоз

1
Сбой для имен тегов, которые начинаются с префикса пространства имен и содержат дефис (например, <ns: дефис-тег />; часть, начинающаяся с дефиса, просто отбрасывается, давая, например, <ns: дефис / /.
— Endre Both

@EndreBoth Хороший улов, я не тестировал, но, возможно, было бы легко исправить это в коде xmlpp.py?
— Габорист

1

Вы можете попробовать этот вариант ...

Устанавливаем BeautifulSoupи бэкэнд lxml(парсер) библиотеки:

user$ pip3 install lxml bs4

Обработайте ваш XML-документ:

from bs4 import BeautifulSoup

with open('/path/to/file.xml', 'r') as doc: 
    for line in doc: 
        print(BeautifulSoup(line, 'lxml-xml').prettify())  

1
'lxml'использует анализатор HTML lxml - см. документацию BS4 . Вам нужен 'xml'или 'lxml-xml'для парсера XML.
— user2357112 поддерживает Monica

1
Этот комментарий постоянно удаляется. Опять же, я ввел официальную жалобу (в дополнение к 4-флагам) на пост-вмешательство в StackOverflow и не остановлюсь, пока это не будет расследовано командой безопасности (журналы доступа и истории версий). Вышеприведенная временная метка неверна (по годам) и, скорее всего, тоже содержание.
— NYCeyes

1
Это работало хорошо для меня, не уверенный в lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
— отрицательном

1
@Datanovice Я рад, что это помогло тебе. Что касается подозрительного понижающего голоса, кто-то подделал мой первоначальный ответ (который правильно был указан изначально lxml-xml), а затем они продолжили понижать его в тот же день. Я подал официальную жалобу в S / O, но они отказались расследовать. Во всяком случае, я с тех пор «деформировал» свой ответ, который теперь снова верен (и указывает, lxml-xmlкак это было изначально). Спасибо.
— NYCeyes

0

У меня была эта проблема, и я решил ее так:

def write_xml_file (self, file, xml_root_element, xml_declaration=False, pretty_print=False, encoding='unicode', indent='\t'):
    pretty_printed_xml = etree.tostring(xml_root_element, xml_declaration=xml_declaration, pretty_print=pretty_print, encoding=encoding)
    if pretty_print: pretty_printed_xml = pretty_printed_xml.replace('  ', indent)
    file.write(pretty_printed_xml)

В моем коде этот метод называется так:

try:
    with open(file_path, 'w') as file:
        file.write('<?xml version="1.0" encoding="utf-8" ?>')

        # create some xml content using etree ...

        xml_parser = XMLParser()
        xml_parser.write_xml_file(file, xml_root, xml_declaration=False, pretty_print=True, encoding='unicode', indent='\t')

except IOError:
    print("Error while writing in log file!")

Это работает только потому, что etree по умолчанию использует two spacesотступ, что, по-моему, не особо подчеркивает отступ и поэтому не очень красиво. Я не мог указать какую-либо настройку для etree или параметр для любой функции, чтобы изменить стандартный отступ etree. Мне нравится, как легко использовать etree, но это меня очень раздражало.


0

Для преобразования всего XML-документа в красивый XML-документ
(например, если вы распаковали [unzipped] файл LibreOffice Writer .odt или .ods и хотите преобразовать уродливый файл "content.xml" в симпатичный файл для автоматизированный контроль версий GIT и git difftoolИНГИ из .odt / файлы .ods , такие , как я реализация здесь )

import xml.dom.minidom

file = open("./content.xml", 'r')
xml_string = file.read()
file.close()

parsed_xml = xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = parsed_xml.toprettyxml()

file = open("./content_new.xml", 'w')
file.write(pretty_xml_as_string)
file.close()

Рекомендации:
- Спасибо ответу Бена Ноланда на этой странице, который помог мне пройти большую часть пути.


0
from lxml import etree
import xml.dom.minidom as mmd

xml_root = etree.parse(xml_fiel_path, etree.XMLParser())

def print_xml(xml_root):
    plain_xml = etree.tostring(xml_root).decode('utf-8')
    urgly_xml = ''.join(plain_xml .split())
    good_xml = mmd.parseString(urgly_xml)
    print(good_xml.toprettyxml(indent='    ',))

Это хорошо работает для XML с китайским!


0

Если по какой-то причине вы не можете заполучить какой-либо из модулей Python, упомянутых другими пользователями, я предлагаю следующее решение для Python 2.7:

import subprocess

def makePretty(filepath):
  cmd = "xmllint --format " + filepath
  prettyXML = subprocess.check_output(cmd, shell = True)
  with open(filepath, "w") as outfile:
    outfile.write(prettyXML)

Насколько я знаю, это решение будет работать на Unix-системах, в которых xmllintустановлен пакет.


xmllint уже был предложен в другом ответе: stackoverflow.com/a/10133365/407651
— mzjn

@mzjn Я видел ответ, но упростил свой до того, check_outputчто вам не нужно проверять ошибки
— Friday Sky

-1

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

    f = open(file_name,'r')
    xml = f.read()
    f.close()

    #Removing old indendations
    raw_xml = ''        
    for line in xml:
        raw_xml += line

    xml = raw_xml

    new_xml = ''
    indent = '    '
    deepness = 0

    for i in range((len(xml))):

        new_xml += xml[i]   
        if(i<len(xml)-3):

            simpleSplit = xml[i:(i+2)] == '><'
            advancSplit = xml[i:(i+3)] == '></'        
            end = xml[i:(i+2)] == '/>'    
            start = xml[i] == '<'

            if(advancSplit):
                deepness += -1
                new_xml += '\n' + indent*deepness
                simpleSplit = False
                deepness += -1
            if(simpleSplit):
                new_xml += '\n' + indent*deepness
            if(start):
                deepness += 1
            if(end):
                deepness += -1

    f = open(file_name,'w')
    f.write(new_xml)
    f.close()

Это работает для меня, возможно, кто-то будет использовать его :)


Покажите скриншот фрагмента до и после, и, возможно, вы избежите будущих отрицательных голосов. Я не пробовал ваш код, и, очевидно, другие ответы здесь лучше (и более общие / полностью сформированные, так как они основаны на хороших библиотеках), но я не уверен, почему вы получили здесь отрицательный ответ. Люди должны оставлять комментарии, когда они понижают голос.
— Габриэль Стейплс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.