Create XPath for each node in XML using a custom attribute (name in this case)

I have a huge XML file that looks like this (but much larger):

<?xml version="1.0" encoding="UTF-8"?> <suite id="1" name="SuiteName"> <displayNameKey>something</displayNameKey> <displayName>something</displayName> <application id="2" name="Manager"> <displayNameKey>appName</displayNameKey> <displayName>appName</displayName> <category id="12" name="navigation"> <displayNameKey>managerNavigation</displayNameKey> <displayName>managerNavigation</displayName> <description>mgr_navigation</description> <property id="13" name="httpPort" type="integer_property" width="40"> <displayNameKey>managerHttpPort</displayNameKey> <displayName>managerHttpPort</displayName> <value>80</value> </property> <property id="14" name="httpsPort" type="integer_property" width="40"> <displayNameKey>managerHttpsPort</displayNameKey> <displayName>managerHttpsPort</displayName> <value>443</value> </property> <property id="15" name="welcomePageURI" type="url_property" width="40" hidden="true"> <displayNameKey>welcomePageURI</displayNameKey> <displayName>welcomePageURI</displayName> <value>jsp/index.jsp</value> </property> <property id="16" name="serverURL" type="url_property" width="40"> <displayNameKey>serverURL</displayNameKey> <displayName>serverURL</displayName> <value>somevalue</value> </property> </category> <category id="17" name="datafiltering"> <displayNameKey>managerDataFiltering</displayNameKey> <displayName>managerDataFiltering</displayName> <description>mgr_data_filtering</description> <property id="18" name="defaultTableName" type="string_property" width="40"> <displayNameKey>defaultTableName</displayNameKey> <displayName>defaultTableName</displayName> </property> <property id="19" name="defaultAudienceName" type="string_property" width="40"> <displayNameKey>defaultAudienceName</displayNameKey> <displayName>defaultAudienceName</displayName> </property> </category> </application> </suite> 

I need to create an XPath expression for each property, but not use positions or identifiers, but the name attribute. That is, for a file above the desired result, it is similar:

 /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="httpPort"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="httpsPort"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="welcomePageURI"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="serverURL"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="datafiltering"]/property[@name="defaultTableName"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="datafiltering"]/property[@name="defaultAudienceName"] 

All XPath generators I found to only generate XPath using an attribute or name position, for example / suite [0] / application [0] / category [1] / ...

Can you please recommend me a way to generate XPath for all properties in my file? And one more thing: the structure is a variable - that is, there can be from 0 to N nested categories, such as

 /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="cat1"]/category[@name="cat2"]/category[@name="cat3"]/property[@name="property1"] /suite[@name="SuiteName"]/application[@name="Manager"]/property[@name="property2"] 
+4
source share
3 answers

You can do it in php as follows:

 <?php $xml = <<<XML <?xml version="1.0" encoding="UTF-8"?> <suite id="1" name="SuiteName"> <displayNameKey>something</displayNameKey> <displayName>something</displayName> <application id="2" name="Manager"> <displayNameKey>appName</displayNameKey> <displayName>appName</displayName> <category id="12" name="navigation"> <displayNameKey>managerNavigation</displayNameKey> <displayName>managerNavigation</displayName> <description>mgr_navigation</description> <property id="13" name="httpPort" type="integer_property" width="40"> <displayNameKey>managerHttpPort</displayNameKey> <displayName>managerHttpPort</displayName> <value>80</value> </property> <property id="14" name="httpsPort" type="integer_property" width="40"> <displayNameKey>managerHttpsPort</displayNameKey> <displayName>managerHttpsPort</displayName> <value>443</value> </property> <property id="15" name="welcomePageURI" type="url_property" width="40" hidden="true"> <displayNameKey>welcomePageURI</displayNameKey> <displayName>welcomePageURI</displayName> <value>jsp/index.jsp</value> </property> <property id="16" name="serverURL" type="url_property" width="40"> <displayNameKey>serverURL</displayNameKey> <displayName>serverURL</displayName> <value>somevalue</value> </property> </category> <category id="17" name="datafiltering"> <displayNameKey>managerDataFiltering</displayNameKey> <displayName>managerDataFiltering</displayName> <description>mgr_data_filtering</description> <property id="18" name="defaultTableName" type="string_property" width="40"> <displayNameKey>defaultTableName</displayNameKey> <displayName>defaultTableName</displayName> </property> <property id="19" name="defaultAudienceName" type="string_property" width="40"> <displayNameKey>defaultAudienceName</displayNameKey> <displayName>defaultAudienceName</displayName> </property> </category> </application> </suite> XML; function genXpath($xml, $att, $current = null) { if($current == null) $current = '/*'; $new = $current.'[@'.$att.']'; $result = $xml->xpath($new); if($current[strlen($current) - 1] == '*') { $current = substr($current, 0, strlen($current) - 1); } if(count($result)) { foreach($result as $node) { $prev = $current; $current .= $node->getName().'[@'.$att.'="'.$node->attributes()->$att.'"]/*'; genXpath($xml, $att, $current); $current = $prev; } } else { $current = substr($current, 0, strlen($current) - 1); echo $current.'<br />'; } } // how to use $xml = new SimpleXMLElement($xml); genXpath($xml, "name"); ?> 

It outputs something like this:

 /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="httpPort"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="httpsPort"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="welcomePageURI"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="serverURL"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="datafiltering"]/property[@name="defaultTableName"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="datafiltering"]/property[@name="defaultAudienceName"] 

Hope this helps. And also you can specify the desired attribute name.

The function itself and its use:

 <?php function genXpath($xml, $att, $current = null) { if($current == null) $current = '/*'; $new = $current.'[@'.$att.']'; $result = $xml->xpath($new); if($current[strlen($current) - 1] == '*') { $current = substr($current, 0, strlen($current) - 1); } if(count($result)) { foreach($result as $node) { $prev = $current; $current .= $node->getName().'[@'.$att.'="'.$node->attributes()->$att.'"]/*'; genXpath($xml, $att, $current); $current = $prev; } } else { $current = substr($current, 0, strlen($current) - 1); echo $current.'<br />'; } } // how to use $xml = "your xml string"; // you can read it from a file $xml = new SimpleXMLElement($xml); genXpath($xml, "name"); 

The algorithm is what is important here, you can easily transfer it to any other programming language. All that is needed is xpath support and a change in the way information is obtained from the result specified by the xpath request.

Yours faithfully,
blind

+1
source

This is probably the shortest and simplest XSLT transform that implements the required processing (without name templates, without explicit conditional instructions in general, without xsl:for-each and without using // ) :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="property"> <xsl:apply-templates select="ancestor::*" mode="build"/> <xsl:value-of select= "concat('/property[@name=&quot;', @name, '&quot;]')"/> <xsl:text>&#xA;</xsl:text> </xsl:template> <xsl:template match="*" mode="build"> <xsl:value-of select= "concat('/',name(),'[@name=&quot;', @name, '&quot;]')"/> </xsl:template> <xsl:template match="text()"/> </xsl:stylesheet> 

when this conversion is applied to the provided XML document :

 <?xml version="1.0" encoding="UTF-8"?> <suite id="1" name="SuiteName"> <displayNameKey>something</displayNameKey> <displayName>something</displayName> <application id="2" name="Manager"> <displayNameKey>appName</displayNameKey> <displayName>appName</displayName> <category id="12" name="navigation"> <displayNameKey>managerNavigation</displayNameKey> <displayName>managerNavigation</displayName> <description>mgr_navigation</description> <property id="13" name="httpPort" type="integer_property" width="40"> <displayNameKey>managerHttpPort</displayNameKey> <displayName>managerHttpPort</displayName> <value>80</value> </property> <property id="14" name="httpsPort" type="integer_property" width="40"> <displayNameKey>managerHttpsPort</displayNameKey> <displayName>managerHttpsPort</displayName> <value>443</value> </property> <property id="15" name="welcomePageURI" type="url_property" width="40" hidden="true"> <displayNameKey>welcomePageURI</displayNameKey> <displayName>welcomePageURI</displayName> <value>jsp/index.jsp</value> </property> <property id="16" name="serverURL" type="url_property" width="40"> <displayNameKey>serverURL</displayNameKey> <displayName>serverURL</displayName> <value>somevalue</value> </property> </category> <category id="17" name="datafiltering"> <displayNameKey>managerDataFiltering</displayNameKey> <displayName>managerDataFiltering</displayName> <description>mgr_data_filtering</description> <property id="18" name="defaultTableName" type="string_property" width="40"> <displayNameKey>defaultTableName</displayNameKey> <displayName>defaultTableName</displayName> </property> <property id="19" name="defaultAudienceName" type="string_property" width="40"> <displayNameKey>defaultAudienceName</displayNameKey> <displayName>defaultAudienceName</displayName> </property> </category> </application> </suite> 

required, the correct result is obtained :

 /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="httpPort"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="httpsPort"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="welcomePageURI"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="navigation"]/property[@name="serverURL"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="datafiltering"]/property[@name="defaultTableName"] /suite[@name="SuiteName"]/application[@name="Manager"]/category[@name="datafiltering"]/property[@name="defaultAudienceName"] 
+1
source

I would do something like this:

 <xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text" /> <xsl:template match="/"> <xsl:for-each select="//property"> <xsl:call-template name="add-parent-xpath"/> <xsl:text> </xsl:text> </xsl:for-each> </xsl:template> <xsl:template name="add-parent-xpath"> <xsl:if test="name(.) != 'suite'"> <xsl:for-each select=".."> <xsl:call-template name="add-parent-xpath" /> </xsl:for-each> </xsl:if> <xsl:value-of select="concat('/', name(.), '[@name=&quot;', @name, '&quot;]')"/> </xsl:template> </xsl:transform> 

It starts by selecting each node property, and then recursively rises to all the remaining nodes before the package. As the recursion splits, it emits an xpath to select this node, so you get the xpath for the set, then the next level, etc., down to the property.

0
source

All Articles