Convert XML to array of arrays in coldfusion - coldfusion

I am trying to convert a XML file to array of arrays.
The second element of array will contain node paths from root and rest element contains respective data.
XSL:
<cfsavecontent variable="local.xsl">
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<!--- Get all leaf nodes XPath: *[not(*)] --->
<xsl:key name="kNodeByPath" match="*[not(*)]"
use="concat(name(), '/', name(..), '/', name(../..), '/', name(../../..),
'/', name(../../../..), '/', name(../../../../..))"/>
<xsl:template match="*[not(*)][generate-id() = generate-id(key('kNodeByPath',
concat(name(), '/', name(..), '/', name(../..),
'/', name(../../..), '/', name(../../../..),
'/', name(../../../../..)))[1])]">
<xsl:apply-templates select="ancestor::*[parent::*]" mode="path"/>
<xsl:value-of select="name()"/>
<!--- Separate each path with comma --->
<xsl:text>,</xsl:text>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:value-of select="concat(name(), '.')"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
</cfsavecontent>
XML:
<?xml version="1.0" encoding="utf-8" ?>
<table>
<class>
<id>Test Data</id>
<title>Testing</title>
<description_url>Test Data</description_url>
<duration>2</duration>
<price>Test</price>
<instruction_language>Test Data</instruction_language>
<city>Online</city>
</class>
<class>
<id>Test Data</id>
<title>Testing</title>
<description_url>Test Data</description_url>
<duration>2</duration>
<price>Test</price>
<instruction_language>Test Data</instruction_language>
<city>Online</city>
</class>
<class>
<id>Test Data</id>
<title>Testing</title>
<description_url>Test Data</description_url>
<duration>2</duration>
<price>Test</price>
<instruction_language>Test Data</instruction_language>
<city>Online</city>
</class>
</table>
My Code:
<!--- Get all distinct leaf node paths --->
<cfset local.xPath = xmlTransform(local.xml,local.xsl)>
<cfif structKeyExists(local,"xPath") AND len(local.xPath) GT 0>
<cfset local.xPath = listToArray(local.xPath,",")>
<cfset local.arrayData[2] = local.xPath>
<cfloop from="1" to="#arrayLen(local.xPath)#" index="local.currentPath" step="1">
<cfset local.nodePath = replaceNoCase(local.xPath[local.currentPath], ".", "/", "ALL")>
<!--- Search node in XML --->
<cfset local.xmlData[local.currentPath] = xmlSearch(local.xml, "//#local.nodePath#")>
</cfloop>
<!--- Get class node --->
<cfset local.class = xmlSearch(local.xml, "//class")>
<cfloop from="1" to="#arrayLen(local.class)#" index="local.row" step="1">
<cfloop from="1" to="#arrayLen(local.arrayData[2])#" index="local.column" step="1">
<cfset local.arrayData[local.row + 2][local.column] = local.xmlData[local.column][local.row].xmlText>
</cfloop>
</cfloop>
</cfif>
<cfdump var="#local.arrayData#">
Actual and Expected Output:
Please help. Thanks in advance.

<cfsavecontent variable="local.xml">
<?xml version="1.0" encoding="UTF-8"?>
<table>
<class>
<id>Test Data</id>
<title>Testing</title>
<description_url>Test Data</description_url>
<duration>2</duration>
<price>Test</price>
<instruction_language>Test Data</instruction_language>
<city>Online</city>
</class>
<class>
<id>Test Data</id>
<title>Testing</title>
<description_url>Test Data</description_url>
<duration>2</duration>
<price>Test</price>
<instruction_language>Test Data</instruction_language>
<city>Online</city>
</class>
<class>
<id>Test Data</id>
<title>Testing</title>
<description_url>Test Data</description_url>
<duration>2</duration>
<price>Test</price>
<instruction_language>Test Data</instruction_language>
<city>Online</city>
</class>
</table>
</cfsavecontent>
<cfset local.XmlParseData = XmlParse(trim(local.xml))>
<cfdump var="#local.XmlParseData#"><cfabort>

Related

XSLT, how to recognize different pattern during transformation?

I have a source xml, that looks like below:
<root>
<CompoundPredicate booleanOperator="surrogate">
<CompoundPredicate booleanOperator="and">
<True />
<SimplePredicate field="MODELYEAR" operator="lessOrEqual" value="1999" />
</CompoundPredicate>
<False />
</CompoundPredicate>
<CompoundPredicate booleanOperator="surrogate">
<CompoundPredicate booleanOperator="and">
<True />
<SimplePredicate field="MODELYEAR" operator="lessOrEqual" value="1999" />
</CompoundPredicate>
<SimplePredicate field="AGE" operator="lessOrEqual" value="40" />
<False />
</CompoundPredicate>
</root>
I want to perform a transformation in such way, that
1). if there is only 'False' element after the inner 'CompoundPredicate' element, then delete the outer 'CompoundPredicate' element and the element which appears after the inner 'CompoundPredicate' element. For example,
<CompoundPredicate booleanOperator="surrogate">
<CompoundPredicate booleanOperator="and">
<True />
<SimplePredicate field="MODELYEAR" operator="lessOrEqual" value="1999" />
</CompoundPredicate>
<False />
</CompoundPredicate>
becomes
<CompoundPredicate booleanOperator="and">
<True />
<SimplePredicate field="MODELYEAR" operator="lessOrEqual" value="1999" />
</CompoundPredicate>
2) if there are other elements after the inner 'CompoundPredicate' element other than 'False', then only delete 'False' element which appears after the inner 'CompoundPredicate' element. For example,
<CompoundPredicate booleanOperator="surrogate">
<CompoundPredicate booleanOperator="and">
<True />
<SimplePredicate field="MODELYEAR" operator="lessOrEqual" value="1999" />
</CompoundPredicate>
<SimplePredicate field="AGE" operator="lessOrEqual" value="40" />
<False />
</CompoundPredicate>
becomes
<CompoundPredicate booleanOperator="surrogate">
<CompoundPredicate booleanOperator="and">
<True />
<SimplePredicate field="MODELYEAR" operator="lessOrEqual" value="1999" />
</CompoundPredicate>
<SimplePredicate field="AGE" operator="lessOrEqual" value="40" />
</CompoundPredicate>
For this problem, I don`t even know how to start. I would really appreciate your help. Thanks a lot.
See if this points you in the right direction. It's based on a restatement of your rules, which may or may not be correct:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- for outer 'CompoundPredicate' that contains only inner 'CompoundPredicate' and/or 'False' -->
<xsl:template match="root/CompoundPredicate[not(*[not(self::CompoundPredicate or self::False)])]">
<xsl:apply-templates select="CompoundPredicate"/>
</xsl:template>
<!-- remove 'False' elements, children of outer 'CompoundPredicate' -->
<xsl:template match="root/CompoundPredicate/False"/>
</xsl:stylesheet>

XSLT 1.0 - extract node-set and pass as param

I am given this XML and have to render quite a bit from it and most is working fine, but I am stomped trying to extract the node-set of color whos key matches the key of the bar element and the attribute is a hardcoded string ('data' in this case). The node-set is to be passed as parameter to a template and each color line must only appear once:
<report>
<settings>
<colors>
<color key="1-1" name="frame" value="..." ... />
<color key="1-1" name="data" value="..." ... />
<color key="2-1" name="frame" value="..." ... />
<color key="2-1" name="data" value="..." ... />
<color key="3-1" name="frame" value="..." ... />
<color key="3-1" name="data" value="..." ... />
</colors>
<comp>
<cont>
<bar key="1-1" .../>
<bar key="1-1" .../>
<bar key="2-1" .../>
</cont>
<comp>
<!-- possibly more <comp/cont/bar> below that may not be mixed with the above -->
</settings>
</report>
In my XSLT file I have this (extract):
<xsl:key name="barnode" match="bar" use="#key"/>
<xsl:key name="colorlookup" match="/report/settings/colors/color" use="#key"/>
<!-- this runs at the `cont` element level, i.e. `bar` can be accessed without prefix -->
<!-- set $x to the node-list of bars with unique #key attribute -->
<xsl:call-template name="renderit">
<xsl:with-param name="colors">
<!-- 'bars' contains node-set of 'bar' elements with #key being unique -->
<xsl:variable name="bars" select="bar[generate-id() = generate-id(key('barnode', #key)[1])]"/>
<xsl:for-each select="$bars">
<xsl:value-of select="key('colorlookup', #key)[#name='data']"/>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
The problem is, that this does not pass a node-set, but a tree-fragment. Is it possible to make a select that does the same as above, but returns a node-set?
Edit:
Expected node-set:
<color key="1-1" name="data" value="..." ... />
<color key="2-1" name="data" value="..." ... />
I am not sure if the presented XSLT will even generate this result tree fragment as I don't know how to print it (for debug purposes).
Try
<xsl:with-param name="colors" select="key('colorlookup', bar[generate-id() = generate-id(key('barnode', #key)[1])]/#key)[#name = 'data']"/>

Removing duplicates from a bag returned by xsl:key based on attribute value

I have the following xml file.
<Bank>
<Person personId="1" type="1071" deleted="0">
</Person>
<Person personId="2" type="1071" deleted="0">
</Person>
<Person personId="3" type="1071" deleted="0">
</Person>
<Account>
<Role personId="1" type="1025" />
</Account>
<Account>
<Role personId="1" type="1025" />
</Account>
<Account>
<Role personId="1" type="1018" />
</Account>
<Account>
<Role personId="3" type="1025" />
<Role personId="1" type="1018" />
</Account>
<Account>
<Role personId="2" type="1025" />
</Account>
</Bank>
and the following xsl transformation.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="ISO-8859-1" />
<xsl:strip-space elements="*" />
<xsl:key name="roleKey"
match="Role[(#type = '1025' or #type = '1018' or #type = '1022' or #type = '1023') and not(#validTo)]"
use="#personId" />
<xsl:template match="Person">
<xsl:value-of select="#personId" />
<xsl:variable name="roles" select="key('roleKey', #personId)" />
<xsl:for-each select="$roles">
<xsl:text>;</xsl:text><xsl:value-of select="#type" />
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
The actual result is following and I would like to remove the duplicated type values.
1;1025;1025;1018;1018
2;1025
3;1025
The expected results should be like follows...
1;1025;1018
2;1025
3;1025
I have tried the tips involving keyword following from this website and also the trick with the Muenchian method. They all do not work as they seem to be browsing through the whole document and matching the duplicates for each and every Person element, whereas I want to remove duplicates only in a Person context defined by personId attribute.
How do I remove those duplicates from the bag returned by key function? Or maybe there is a method that I can use in xsl:for-each to print only what I want to?
I can only use what there is available in XSLT 1.0. I do not have a possibility to use any XSLT 2.0 processor.
Ok, do not know if this is the best solution available but I achieved what I wanted by introducing such a key and using Muenchian method.
<xsl:key name="typeKey" match="Role" use="concat(#type, '|', #personId)" />
The whole transformation looks like that after the change...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="ISO-8859-1" />
<xsl:strip-space elements="*" />
<xsl:key name="roleKey"
match="Role[(#type = '1025' or #type = '1018' or #type = '1022' or #type = '1023') and not(#validTo)]"
use="#personId" />
<xsl:key name="typeKey" match="Role" use="concat(#type, '|', #personId)" />
<xsl:template match="Person">
<xsl:value-of select="#personId" />
<xsl:variable name="roles" select="key('roleKey', #personId)" />
<xsl:for-each select="$roles[generate-id() = generate-id(key('typeKey', concat(#type, '|', #personId)))]">
<xsl:text>;</xsl:text><xsl:value-of select="#type" />
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
And the actual result is now...
1;1025;1018
2;1025
3;1025

XSLT getting grandchild node attribute values in template

Given XML like this:
<?xml version="1.0" encoding="UTF-8"?>
<Products>
<Product someId="1EFAD9659EC">
<Identifiers>
<Identifier Id="234532423" Name="globalTradeItemNumber (GTIN)" Value="00671657621322" />
<Identifier Id="99845898" Name="Internal Supplier Part #" Value="DEL 20 10B015000" />
<Identifier Id="49348598" Name="MFG Model # (Series)" Value="DEL 20 10B015000" />
<Identifier Id="439854985" Name="MFG Part # (OEM)" Value="DEL 20 10B015000" />
<Identifier Id="2349832489" Name="UPC" Value="671657621322" />
</Identifiers>
</Product>
<Product someId="1EFAD9659EC">
<Identifiers>
<Identifier Id="234532423" Name="globalTradeItemNumber (GTIN)" Value="51651518" />
<Identifier Id="99845898" Name="Internal Supplier Part #" Value="TIM 20 10B015000" />
<Identifier Id="49348598" Name="MFG Model # (Series)" Value="TOM 20 10B015000" />
<Identifier Id="439854985" Name="MFG Part # (OEM)" Value="TAK 20 10B015000" />
<Identifier Id="2349832489" Name="UPC" Value="87468387468" />
</Identifiers>
</Product>
. . .
I want to end up with something like
...
<Product upc="671657621322"/>
<Product upc="87468387468"/>
...
But what I'm getting is
...
<Product upc="true"/>
<Product upc="true"/>
...
I keep getting the boolean answer to my select rather than the value of the attribute. What silly thing am I doing wrong here? This is the XSLT I'm trying:
...
<xsl:template match="/">
<Output>
<xsl:apply-templates />
</Output>
</xsl:template>
<xsl:template match="Product">
<xsl:variable name="productCode" select="./Identifiers/Identifier/#Name='UPC'"/>
<Product upc="{$productCode}">
</Product>
</xsl:template>
...
Thanks.
You are using the wrong xpath selection. Use:
select="Identifiers/Identifier[#Name='UPC']/#Value"
If you are interested only with those two node values match them with the template:
<xsl:template match="Product/Identifiers/Identifier[#Name='UPC']">
<xsl:variable name="productCode" select="#Value"/>
<Product upc="{$productCode}">
</Product>
</xsl:template>

XSLT variable print as an attribute

Is there's a way to print the variable in the element as an attribute?
sample xml:
<list>
<name>John Doe</name>
<name>Paul Niel</name>
<name>Luke Dee</name>
</list>
Here's my sample xslt;
<xsl:variable name="isDisabled">
<xsl:if test="name='John Doe'">
<xsl:attribute name="disabled">disabled</xsl:attribute>
</xsl:if>
</xsl:variable>
and I want to print the isDisabled varible like this;
<input id="textName" name="name" type="text" {$isDisabled} />
output;
<input id="textName" name="name" type="text" disabled="disabled" />
You don't need any variable to accomplish this task.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="name[. = 'John Doe']">
<input id="textName" name="name" type="text" disabled="disabled" />
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<list>
<name>John Doe</name>
<name>Paul Niel</name>
<name>Luke Dee</name>
</list>
the wanted, correct result is produced:
<input id="textName" name="name" type="text" disabled="disabled"/>
Explanation:
Proper use of templates and template pattern matching.
Note: If you have a case (not this one) where it is really necessary to use a variable to create an attribute, this can be done in the following way:
<input id="textName" name="name" type="text" disabled="{$isDisabled}"/>
Explanation:
Proper use of AVT (Attribute Value Templates)