PHP: How to add the missing tags in each group of elements in XML?
This is the XML that we have right now:
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age></age>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age></age>
<sex>Female</sex>
</person>
</persons>
As you could see the first group of elements only has three tags namely firstname, surname, and age while the second group has an additional tag name sex.
What we need is to make all the element groups in the XML contains all the tags that each group has, in thi开发者_开发问答s case the first group should also contain the sex tag but in a blank state as well like this:
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age></age>
<sex></sex>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age></age>
<sex>Female</sex>
</person>
</persons>
Also what if there's a third, fourth or on the 50th group has another new tag named nickname? In this case all the group should have the tag nickname as well but in a blank state.
How could I do this efficiently in PHP?
Using SimpleXML, the script makes two passes: one to find all the possible tags, the other to create empty elements:
$str = <<<STR
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age></age>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age></age>
<sex>Female</sex>
</person>
</persons>
STR;
$xml = simplexml_load_string($str);
// Create an array of all the possible tags
$tags = array();
foreach($xml->person as $person)
{
$current_tags = array_keys(get_object_vars($person));
$tags = array_unique(array_merge($tags, $current_tags));
}
// Add empty tags to elements who don't have them
foreach($xml->person as $person)
{
foreach($tags as $tag)
{
if(!property_exists($person, $tag))
{
$person->$tag = '';
}
}
}
// Output the new XML
echo $xml->asXML();
The easiest to make this maintainable over a long time (e.g. anticipating more new fields and stuff like that) would be to process the XML with an XSLT that contains all the required fields:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/persons">
<persons>
<xsl:apply-templates select="person"/>
</persons>
</xsl:template>
<xsl:template match="person">
<person>
<firstname><xsl:value-of select="firstname"/></firstname>
<surname><xsl:value-of select="surname"/></surname>
<age><xsl:value-of select="age"/></age>
<sex><xsl:value-of select="sex"/></sex>
</person>
</xsl:template>
</xsl:stylesheet>
Then, whenever you get a new copy from the generator service, do (manual)
$dom = new DOMDocument;
$dom->load('people.xml');
$xsl = new DOMDocument;
$xsl->load('people.xsl');
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
echo $proc->transformToXML($dom);
This will produce (demo)
<?xml version="1.0"?>
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age/>
<sex/>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age/>
<sex>Female</sex>
</person>
</persons>
I agree with @Gordon that the best solution here is XSLT. However, I suggest using a slightly different XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="person">
<person>
<xsl:apply-templates select="@* | *[not(self::sex)]"/>
<sex><xsl:value-of select="sex"/></sex>
</person>
</xsl:template>
</xsl:stylesheet>
I tried it out with W3Schools' online XSLT evaluator and it works as requested.
精彩评论