Create a structure in a flat xml file
I have an xml file made like this:
<car>Ferrari</car>
<color>red</color>
<speed>300</speed>
<car>Porsche</car>
<color>black</color>
<speed>310</speed>
I need to have it in this form:
<car name="Ferrari">
<color>red</color>
<speed>300</speed>
</car>
<car name="Porsche">
<color>black</color>
<speed>310</speed>
</car>
How can I do this? I'm struggling because I can't think of a way to create the structure I need from the flat lis of tags in t开发者_运维百科he original xml file.
My language of choice is Python, but any suggestion is welcome.
XSLT is the perfect tool for transforming one XML structure into another.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- copy the root element and handle its <car> children -->
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="car" />
<xsl:copy>
</xsl:template>
<!-- car elements become a container for their properties -->
<xsl:template match="car">
<car name="{normalize-space()}">
<!-- ** see 1) -->
<xsl:copy-of select="following-sibling::color[1]" />
<xsl:copy-of select="following-sibling::speed[1]" />
</car>
</xsl:template>
</xsl:stylesheet>
1) For this to work, your XML has to have a <color>
and a <speed>
for every <car>
. If that's not guaranteed, or number and kind of properties is generally variable, replace the two lines with the generic form of the copy statement:
<!-- any following-sibling element that "belongs" to the same <car> -->
<xsl:copy-of select="following-sibling::*[
generate-id(preceding-sibling::car[1]) = generate-id(current())
]" />
Applied to your XML (I implied a document element named <root>
), this would be the result
<root>
<car name="Ferrari">
<color>red</color>
<speed>300</speed>
</car>
<car name="Porsche">
<color>black</color>
<speed>310</speed>
</car>
</root>
Sample code that applies XSLT to XML in Python should be really easy to find, so I omit that here. It'll be hardly more than 4 or five lines of Python code.
I don't know about python, but presuming you had an XML parser that gave you hierarchial access to the nodes in an XML document, the semantics you'd want would be something like the following (warning, I tend to use PHP). Basically, store any non-"car" tags, and then when you encounter a new "car" tag treat it as a delimiting field and create the assembled XML node:
// Create an input and output handle
input_handle = parse_xml_document();
output_handle = new_xml_document();
// Assuming the <car>, <color> etc. nodes are
// the children of some, get them as an array
list_of_nodes = input_handle.get_list_child_nodes();
// These are empty variables for storing our data as we parse it
var car, color, speed = NULL
foreach(list_of_nodes as node)
{
if(node.tag_name() == "speed")
{
speed = node.value();
// etc for each type of non-delimiting field
}
if(node.tag_name() == "car")
{
// If there's already a car specified, take its data,
// insert it into the output xml structure and th
if(car != NULL)
{
// Add a new child node to the output document
node = output_handle.append_child_node("car");
// Set the attribute on this new output node
node.set_attribute("name", node.value());
// Add the stored child attributes
node.add_child("color", color);
node.add_child("speed", speed);
}
// Replace the value of car afterwards. This allows the
// first iteration to happen when there is no stored value
// for "car".
car = node.value();
}
}
IF your real life data is as simple as your example and there are no errors in it, you can use a regular expression substitution to do it in one hit:
import re
guff = """
<car>Ferrari</car>
<color>red</color>
<speed>300</speed>
<car>Porsche</car>
<color>black</color>
<speed>310</speed>
"""
pattern = r"""
<car>([^<]+)</car>\s*
<color>([^<]+)</color>\s*
<speed>([^<]+)</speed>\s*
"""
repl = r"""<car name="\1">
<color>\2</color>
<speed>\3</speed>
</car>
"""
regex = re.compile(pattern, re.VERBOSE)
output = regex.sub(repl, guff)
print output
Otherwise you had better read it 3 lines at a time, do some validations, and write it out one "car" element at a time, either using string processing or ElementTree.
Assuming the first element within the root is a car
element, and all non-car
elements "belong" to the last car
:
import xml.etree.cElementTree as etree
root = etree.XML('''<root>
<car>Ferrari</car>
<color>red</color>
<speed>300</speed>
<car>Porsche</car>
<color>black</color>
<speed>310</speed>
</root>''')
new_root = etree.Element('root')
for elem in root:
if elem.tag == 'car':
car = etree.SubElement(new_root, 'car', name=elem.text)
else:
car.append(elem)
new_root
would be:
<root><car name="Ferrari"><color>red</color>
<speed>300</speed>
</car><car name="Porsche"><color>black</color>
<speed>310</speed>
</car></root>
(I've assumed that the pretty whitespace was not important)
精彩评论