Красивая печать 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.