Довольно печатать XML на Python

380

Каков наилучший способ (или даже различные способы) для красивой печати xml в Python?

Теги:
pretty-print

19 ответов

345
Лучший ответ
import xml.dom.minidom

dom = xml.dom.minidom.parse(xml_fname) # or xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = dom.toprettyxml()
  • 29
    Это даст вам довольно xml, но обратите внимание, что то, что выходит в текстовом узле, на самом деле отличается от того, что появилось - в текстовых узлах есть новые пробелы. Это может вызвать у вас проблемы, если вы ожидаете ТОЧНО того, что подали, чтобы питаться.
  • 36
    @icnivad: хотя важно указать на этот факт, мне кажется странным, что кто-то захочет улучшить его XML, если пробелы будут иметь какое-то значение для них!
Показать ещё 13 комментариев
145

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

import lxml.etree as etree

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

Проверьте учебник lxml: http://lxml.de/tutorial.html

  • 7
    Единственным недостатком lxml является зависимость от внешних библиотек. Это, я думаю, не так уж плохо под Windows, библиотеки упакованы с модулем. Под Linux они aptitude install . Под OS / X я не уверен.
  • 4
    В OS X вам просто нужны работающие gcc и easy_install / pip.
Показать ещё 6 комментариев
92

Другим решением является заимствовать эту функцию 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)
  • 0
    ... а затем просто используйте lxml tostring!
  • 2
    Обратите внимание, что вы все еще можете сделать tree.write([filename]) для записи в файл ( tree является экземпляром ElementTree).
Показать ещё 3 комментария
44

Здесь мое (хакерское?) решение, чтобы обойти проблему уродливого текста node.

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>

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

  • 0
    Спасибо! Это была моя единственная претензия ко всем красивым методам печати. Хорошо работает с несколькими файлами, которые я пробовал.
  • 0
    Я нашел довольно «почти идентичное» решение, но у вас более прямое re.compile использовать re.compile до операции sub (я дважды использовал re.findall() , zip и цикл for с str.replace() ...)
Показать ещё 2 комментария
19

Как отмечали другие, 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 на моей стороне.
  • 0
    Спасибо, это лучший ответ на данный момент.
12

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

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

from bs4 import BeautifulSoup

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

Если у вас есть 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))
9

Я попытался отредактировать ответ "ade" выше, но Qaru не разрешил мне редактировать после того, как я первоначально предоставил анонимную обратную связь. Это менее сложная версия функции для правильной печати 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.

3

Хорошая печать XML для python выглядит довольно хорошо для этой задачи. (Соответственно названо тоже.)

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

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') 
  • 0
    Эта конкретная библиотека обрабатывает проблему Ugly Text Node.
2

Я написал решение, чтобы пройти через существующий 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
1

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

  • 1
    Пробовал minidom и lxml и не получал правильно отформатированный и с отступом xml. Это сработало, как и ожидалось
  • 1
    Сбой для имен тегов, которые начинаются с префикса пространства имен и содержат дефис (например, <ns: дефис-тег />; часть, начинающаяся с дефиса, просто отбрасывается, давая, например, <ns: дефис / /.
Показать ещё 1 комментарий
1

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

0

Для преобразования всего XML-документа в документ xml
(например: если вы извлекли [распаковали] файл LibtOffice.odt или.ods, и вы хотите преобразовать уродливый файл "content.xml" в красивую для автоматического управления версиями git difftool и git difftool ing.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

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

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 или параметра для любой функции, чтобы изменить стандартный отступ epree. Мне нравится, как легко использовать etree, но это меня действительно раздражало.

0

Я решил это с некоторыми строками кода, открыв файл, перейдя через него и добавив отступ, а затем сохранил его снова. Я работал с небольшими файлами 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()

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

  • 0
    Покажите скриншот фрагмента до и после, и, возможно, вы избежите будущих понижений. Я не пробовал ваш код, и, очевидно, другие ответы здесь лучше, я думаю (и более общие / полностью сформированные, так как они основаны на хороших библиотеках), но я не уверен, почему вы получили здесь отрицательную оценку. Люди должны оставлять комментарии, когда они понижают голос.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню