1

How do you format an XML document to make it easy to read element attributes?

I have a xml based webservice that returns one or two elements, but with hundreds of attributes. As I'm doing development, I sometimes need to debug this service, but it can be hard since the output is just one blob.

Consider this:

$ echo '<root><foo z="26" y="25" x="24" a="1" b="2" c="3" d="something more"/></root>' | xmllint --format -
<?xml version="1.0"?>
<root>
  <foo z="26" y="25" x="24" a="1" b="2" c="3" d="something more"/>
</root>

I have found that tr works pretty good, but not ideal:

$ echo '<root><foo z="26" y="25" x="24" a="1" b="2" c="3" d="something more"/></root>' | xmllint --format - | tr ' ' \\\n
<?xml
version="1.0"?>
<root>


<foo
z="26"
y="25"
x="24"
a="1"
b="2"
c="3"
d="something
more"/>
</root>

Ideally the output would be something in between the xmllint and a funky hack

<?xml version="1.0"?>
<root>
  <foo
    z="26"
    y="25"
    x="24"
    a="1"
    b="2"
    c="3"
    d="something more"/>
</root>

That way i can grep for things, or sort or whatever.

2
  • You are already using an XML parser, use that to query instead of trying to abuse sed/grep/awk. Alternatively, use xmlstarlet instead of xmllint. Commented Apr 23, 2014 at 9:31
  • @AdrianFrühwirth, my goal is to make a script that I could call like curl, but would format the response cleanly. That way I can do formattedCurl.sh http://foo/service | grep prop | sort. You're right I can use xmllint's xpath functionality, but the trick is the XML data from the service is not consistent. I tried xmlstartlet format, but it appears to work exactly like xmllint -format. Perhaps I should look into rendering the document with xslt, or perhaps a carefully crafted sed or awk statement.
    – mlathe
    Commented Apr 23, 2014 at 16:51

2 Answers 2

1

I would strongly urge away from grep/sed - they don't work for XML.

But fortunately, perl and XML::Twig have all sorts of magic for reformatting and extracting values as you wish. get_xpath works nicely for extracting a value, or you have twig_handlers to handle elements depending on use case. (Or just iterate using children or similar).

But anyway - to format your XML:

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig->new(
    pretty_print  => 'nsgmls',
);
$twig->parse (\*DATA);
$twig->print;


__DATA__
<?xml version="1.0"?>
<root>
  <foo
    z="26"
    y="25"
    x="24"
    a="1"
    b="2"
    c="3"
    d="something more"/>
</root>

in nsgmls printing, this gives:

<?xml version="1.0"?>
<root
><foo
a="1"
b="2"
c="3"
d="something more"
x="24"
y="25"
z="26"
/></root>

indented_a gives you:

<?xml version="1.0"?>
<root>
  <foo
      a="1"
      b="2"
      c="3"
      d="something more"
      x="24"
      y="25"
      z="26"
  />
</root>

Which seems to be pretty close to what you're seeking?

2
  • Is there a way to keep XML::Twig from alphabetizing the attributes? Commented Apr 20, 2017 at 15:55
  • Yes. When calling new set keep_atts_order => 1. But you should be wary of anything that breaks if the ordering of attributes changes, because it's not parsing XML correctly.
    – Sobrique
    Commented Apr 25, 2017 at 10:17
0

You could use the fantastic BeautifulSoup Python library. This code sample retrieves an XML file from a URL given as input argument to the script, parses it using LXML and then pretty prints it.

#!/usr/bin/env python
import sys
import urllib2
import bs4

soup = bs4.BeautifulSoup(urllib2.urlopen(sys.argv[1]), ["lxml", "xml"])
print(soup.prettify())

If you don't have access to LXML, you could try without ["lxml", "xml"], though this will parse the data als HTML rather than XML and is not the proper way.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .