Гарне друк XML у Python


424

Який найкращий спосіб (або це різні способи) гарненько надрукувати 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, якби пробіли мали для них певне значення!
vaab

18
Приємно! Може згортати це до одного вкладиша: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
Антон І. Сіпос

11
minidom широко сприймається як досить погана реалізація XML. Якщо ви дозволите собі додавати зовнішні залежності, lxml набагато перевершує.
bukzor

26
Не любитель переосмислювати xml з модуля на вихідний об'єкт, але метод інакше працює. Я хотів би знайти приємніший спосіб перейти від основного ефіру до гарненького друку. Незважаючи на те, що lxml крутий, бувають випадки, коли я вважаю за краще тримати до основи, якщо зможу.
Danny Staple

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. В ОС / X я не впевнений.
інтуїтив

4
В OS X вам просто потрібний функціонуючий gcc та easy_install / pip.
pkoch

11
Симпатичний принтер lxml не є надійним і не дуже друкує ваш XML належним чином у багатьох випадках, пояснених у поширених питаннях lxml . Я кинув використовувати lxml для гарного друку після декількох кутових справ, які просто не працюють (тобто це не виправить: Bug # 910018 ). Усі ці проблеми пов'язані з використанням значень XML, що містять пробіли, які слід зберегти.
vaab

1
lxml також є частиною MacPorts, працює на мене безперебійно.
Єнс

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>

Відмова: Мабуть, є деякі обмеження.


Дякую! Це було моїм захопленням усіх гарних методів друку. Добре працює з кількома файлами, які я спробував.
iano

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

3
Це більше не потрібно в Python 2.7: toprettyxml () тепер xml.dom.minidom виробляє вихід на зразок '<id> 1 </id>' для вузлів, які мають рівно один дочірній текст тексту.
Маріус Гедмінас

Я змушений використовувати Python 2.6. Отже, цей трюк переформатування повторного вибору дуже корисний. Працювали так, як є, без проблем.
Майк Фінч

@Marius 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

Дякую, це найкраща відповідь поки що.
Джордж Chalhoub

20

BeautifulSoup має простий у використанні prettify()метод.

Відступає один пробіл на рівень відступу. Він працює набагато краще, ніж досить-відбиток lxml та є коротким та солодким.

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>гарненько друкує свій вхідний 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

Я намагався редагувати відповідь "Аде" вище, але переповнення стека не дозволило мені редагувати після того, як я спочатку надав відгуки анонімно. Це менш глючна версія функції для гарного друку 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 і використовувати текст / хвіст, щоб відступити його, як зазвичай очікує.

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

Ось рішення 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

Ви можете використовувати популярну зовнішню бібліотеку 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"?>вгорі.


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. Це спрацювало так, як і очікувалося
david-hoze

1
Помилки для імен тегів, які мають префікс простору імен і містять дефіс (наприклад, <ns: злучок-тег />; частина, що починається з дефісу, просто відкидається, даючи напр. <Ns: дефіс />.
Endre Обидва

@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 підтримує Моніку

1
Цей коментар видаляється. Знову я надсилаю офіційну скаргу (на додаток до 4-х прапорів) на порушення фальсифікацій на StackOverflow, і не зупиняться, поки це не буде досліджено спеціально командою безпеки (журнали доступу та історії версій). Вищезазначена мітка часу неправильна (за роками), і, ймовірно, вміст теж.
NYCeyes

1
Для мене це спрацювало чудово, не знаючи, що голос відмовився від документівlxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
Datanovice

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 difftooling .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оскільки вам не потрібно робити перевірку помилок
П'ятниця 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.