Гарне друк 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.