Transform a xml to a string using xslt? - html

How can I transform the xml in format1 to a string as mentioned in format2 using XSLT?
format 1
<children>
<child data="test1">
<content><name>test1</name>
<child>
<content><name>test1child</name>
</child>
</child>
</children>
format 2
"<root>"
+"<item id='test1'>"
+"<content><name>test1</name></content>"
+"<item parent_id='test1'>"
+"<content><name>test1child</name>"
+"</content>"
+"</item>"
+</item>
+"<root>"
so children should replace with root, child should replaced with item and child of child should be replaced with *item parent_id of parent id*. Is it possible to do with xslt?

Assuming XSLT 2.0, here is some suggestion on how to approach that, assuming you really want some string output with quotes and plus symbols (to get Javascript or similar code to construct XML as a string?):
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:template match="*">
<xsl:param name="name" select="name()"/>
<xsl:text><</xsl:text>
<xsl:value-of select="$name"/>
<xsl:apply-templates select="#*"/>
<xsl:text>></xsl:text>
<xsl:apply-templates/>
<xsl:text></</xsl:text>
<xsl:value-of select="$name"/>
<xsl:text>></xsl:text>
</xsl:template>
<xsl:template match="#*">
<xsl:param name="name" select="name()"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$name"/>
<xsl:text>='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
</xsl:template>
<xsl:template match="text()[matches(., '^\s+$')]">
<xsl:text>"
+"</xsl:text>
</xsl:template>
<xsl:template match="/children">
<xsl:text>"</xsl:text>
<xsl:next-match>
<xsl:with-param name="name" select="'root'"/>
</xsl:next-match>
<xsl:text>"</xsl:text>
</xsl:template>
<xsl:template match="child">
<xsl:next-match>
<xsl:with-param name="name" select="'item'"/>
</xsl:next-match>
</xsl:template>
<xsl:template match="child//child">
<xsl:variable name="copy" as="element()">
<xsl:copy>
<xsl:attribute name="parent_id" select="ancestor::child[1]/#data"/>
<xsl:copy-of select="#* , node()"/>
</xsl:copy>
</xsl:variable>
<xsl:apply-templates select="$copy"/>
</xsl:template>
</xsl:stylesheet>
That transforms the input
<children>
<child data="test1">
<content><name>test1</name></content>
<child>
<content><name>test1child</name></content>
</child>
</child>
</children>
into the result
"<root>"
+"<item data='test1'>"
+"<content><name>test1</name></content>"
+"<item parent_id='test1'>"
+"<content><name>test1child</name></content>"
+"</item>"
+"</item>"
+"</root>"
The code so far has many shortcomings however as it would not construct well-formed XML with properly escaped attribute values and also does not care to output namespace declarations. You could however adapt more sophisticated solutions like http://lenzconsulting.com/xml-to-string/.

Related

Handle excluded element in a foreach

In first template, I am intentionally excluding an element ('milk') because the parsed data map is relatively flat and I would like to use XSLT to categorize and structure the data. The aim is to process the excluded element ('milk') in the second template. The both templates works running them one at a time. Running the templates together will not show the result of the excluded element ('milk') which should set another attribute name and attribute value.
JSON:
<data>
{
"storage": {
"pencils": 12,
"milk": 8,
"rulers": 4
}
}
</data>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform
version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:storage="http://www.exammple.com/1"
xmlns:office="http://www.exammple.com/2"
xmlns:item="http://www.exammple.com/3"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<storage:one>
<xsl:apply-templates select="json-to-xml(.)"/>
</storage:one>
</xsl:template>
<!-- Print map -->
<!-- <xsl:template match="*[#key = 'storage']"> <xsl:copy-of select=".."/> </xsl:template> -->
<xsl:template match="*[#key='storage']">
<xsl:for-each select="*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="*[#key='milk']">
<xsl:for-each select=".">
<xsl:element name="item:{#key}">
<xsl:attribute name="beverage">plant-2</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:transform>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
xmlns:office="http://www.exammple.com/2"
xmlns:storage="http://www.exammple.com/1">
<item:pencils office="plant-1">12</item:pencils>
<item:rulers office="plant-1">4</item:rulers>
</storage:one>
Wanted result:
<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
xmlns:office="http://www.exammple.com/2"
xmlns:storage="http://www.exammple.com/1">
<item:pencils office="plant-1">12</item:pencils>
<item:rulers office="plant-1">4</item:rulers>
<item:milk beverage="plant-2">8</item:milk>
</storage:one>
Your second template is never matched, because it is never reached. All elements are processed by <xsl:template match="*[#key='storage']"> - which doesn't have an <xsl:apply-templates ...> to reach further templates.
Your first template does not recurse into its children. So add an <xsl:apply-templates select="*" /> to the end of the first template:
<xsl:template match="*[#key='storage']">
<xsl:for-each select="*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
<xsl:apply-templates select="*" />
</xsl:template>
This will try to apply further templates at the level of "storage" and therefore match the second template.
I would write templates for each different output type and if the order of the output is different from the order of the input throw in an xsl:sort or an XPath 3.1 sort call to change the order:
<xsl:template match="data">
<storage:one>
<xsl:apply-templates select="json-to-xml(.)"/>
</storage:one>
</xsl:template>
<xsl:template match="*[#key = 'storage']">
<xsl:apply-templates select="sort(*, (), function($el) { $el/#key = 'milk' })"/>
</xsl:template>
<xsl:template match="*[#key='storage']/*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[#key='storage']/*[#key='milk']">
<xsl:element name="item:{#key}">
<xsl:attribute name="beverage">plant-2</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:template>

XSLT template to replace string with string Including HTML Tags

I have this template to replace once string with another:
<xsl:template name="X">
<xsl:param name="field"/>
<xsl:param name="target"/>
<xsl:param name="change"/>
<xsl:value-of select='replace($field, $target, $change )' />
</xsl:template>
This is how I am calling it:
<xsl:call-template name="X">
<xsl:with-param name="field" select="artist"/>
<xsl:with-param name="target" select="'\\n'"/>
<xsl:with-param name="change" select="'
'"/>
</xsl:call-template>
This works in most cases except for the case demonstrated here.
I am trying to replace the string "\n" with a HTML line break <br> or <br />.
I want an actual new line not the tag visible in the output.
I know that it is matching on the \n but I can not make the line break happen.
I either get a literal <br> in the output or nothing happens.
You will need to change your approach. Your template is using replace() which works with strings. You want to replace with markup. I'd use xsl:analyze-string instead:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="break"><br/></xsl:variable>
<xsl:call-template name="X">
<xsl:with-param name="field" select="artist"/>
<xsl:with-param name="target" select="'\n'"/>
<xsl:with-param name="change" select="$break"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="X">
<xsl:param name="field"/>
<xsl:param name="target"/>
<xsl:param name="change"/>
<!-- <xsl:value-of select='replace($field, $target, $change )' />-->
<xsl:analyze-string select="$field" regex="{$target}">
<xsl:matching-substring>
<xsl:sequence select="$change"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>

How to remove some content from the elements using XSLT

For the image tag, I need to keep only the image file with "file/" content using XSLT in the JSON output:
My input XML file is:
<image>binary/alias/my.jpg</image>
XSL which is used as:
<xsl:template match="image">
image: <xsl:apply-templates/>,
</xsl:template>
JSON output which I get is:
image: binary/alias/my.jpg
I need the output as:
image: files/my.jpg
Please help me on this. Thanks in advance.
In XSLT 2.0 you can do:
<xsl:template match="image">
<xsl:text>image: files/</xsl:text>
<xsl:value-of select="tokenize(., '/')[last()]"/>
</xsl:template>
To get the string after the last occurrence of a char (in this case '/') you need (in XSLT-1.0) a recursive template. Applying this template, the solution is straightforward: Output the desired prefix text 'image: files/' and append the result of the recursive template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="image">
image: files/<xsl:call-template name="LastOccurrence">
<xsl:with-param name="value" select="text()" />
<xsl:with-param name="separator" select="'/'" />
</xsl:call-template>
</xsl:template>
<xsl:template name="LastOccurrence">
<xsl:param name="value" />
<xsl:param name="separator" select="'/'" />
<xsl:choose>
<xsl:when test="contains($value, $separator)">
<xsl:call-template name="LastOccurrence">
<xsl:with-param name="value" select="substring-after($value, $separator)" />
<xsl:with-param name="separator" select="$separator" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The LastOccurrence template was inspired by this SO post.

sum of previous nodes as value of current node in XSLT

I am trying to obtain the target xml such that value of a node is comma seperated values of sum of previous similar node values. For e.g:
Input:
<Items>
<Item>
<name>Drakshi</name>
<price>50</price>
</Item>
<Item>
<name>Godambi</name>
<price>30</price>
</Item>
<Item>
<name>Badami</name>
<price>70</price>
</Item>
</Items>
Output:
<result>
50,80,150
</result>
As you can see above it is 50, (50+30), (50+30+70)
I tried with for-each Item and was able to find the sum of only current node and previous selected node. Can you please guide me here
Try something like this:
<xsl:template match="Items">
<result>
<xsl:for-each select="Item">
<xsl:value-of select="sum(preceding-sibling::Item/price | price) " />
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
</result>
</xsl:template>
While you could use sum(preceding-sibling::...) a more efficient solution would use a recursive template to accumulate the sum one value at a time:
<xsl:template match="/Items">
<result>
<xsl:call-template name="run-total">
<xsl:with-param name="values" select="Item/price"/>
</xsl:call-template>
</result>
</xsl:template>
<xsl:template name="run-total">
<xsl:param name="values"/>
<xsl:param name="i" select="1"/>
<xsl:param name="total" select="0"/>
<xsl:variable name="balance" select="$total + $values[$i]" />
<xsl:value-of select="$balance" />
<xsl:if test="$i < count($values)">
<xsl:text>,</xsl:text>
<xsl:call-template name="run-total">
<xsl:with-param name="values" select="$values"/>
<xsl:with-param name="i" select="$i + 1"/>
<xsl:with-param name="total" select="$balance"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
If you want to solve it with XSLT 2.0 then I don't think you need any user defined function, you can write a single XPath 2.0 expression:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="Items">
<result>
<xsl:value-of
select="for $pos in 1 to count(Item)
return sum(subsequence(Item, 1, $pos)/price)"
separator=","/>
</result>
</xsl:template>
</xsl:stylesheet>
Thanks for the replies. I followed this approach which worked for me:
<xsl:function name="kk:getSumTillIndex">
<xsl:param name="index"/>
<xsl:variable name="Var_PriceArray" select="$Items/item[position()<=$index]//price/text()"/>
<xsl:for-each select="$Var_PriceArray">
<Item>
<xsl:value-of select="current()"/>
</Item>
</xsl:for-each>
</xsl:function>
<xsl:function name="kk:calulatePriceSequence">
<xsl:variable name="set" select="$Items/Item" />
<xsl:variable name="count" select="count($set)" />
<xsl:for-each select="$set">
<xsl:if test="position() <= $count">
<xsl:value-of select="sum(kk:getSumTillIndex(position()))"/>
<xsl:if test="position() < number($count)-1">
<xsl:value-of select="','" />
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:function>

Saxon XSLT 2.0 casting error the doesnt make sense for me

I am trying to perform simple transformation.
The error reported by the proccessor is: An attribute node (name) cannot be created after the children of the containing element. The error is pointing to this line <xsl:apply-templates select="#*|node()"/> into the last template.
EDIT#1:
This is the input:
<root>
<processor>../../library/saxon-he-9-6-0-7j/saxon9he.jar</processor>
<test-case name="test-case-1">
<object-under-test category="template" name="text-align"/>
<parameters>
<input name="text">text</input>
<input name="min-lenght">8</input>
<input name="align">left</input>
<output name="result"/>
</parameters>
<criteria>
<criterion class="equal" to="'text '"/>
</criteria>
</test-case>
</root>
This is the XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslAlt="dummy" version="1.0">
<xsl:namespace-alias stylesheet-prefix="xslAlt" result-prefix="xsl"/>
<!--~~~-->
<xsl:template match="root">
<xslAlt:stylesheet version="1.0">
<xslAlt:output method="xml"/>
<xslAlt:include href="../../../product/templates.xsl"/>
<xslAlt:template name="root" match="/">
<xsl:apply-templates select="test-case"/>
</xslAlt:template>
</xslAlt:stylesheet>
</xsl:template>
<!--~~-->
<xsl:template match="test-case">
<test-case name="{concat('test-case-', string(position()))}">
<xsl:variable name="test-case-return" select="concat('test-case-',string(position()),'-return')"/>
<xslAlt:variable name="{$test-case-return}">
<xslAlt:call-template name="{object-under-test/#name}">
<xsl:for-each select="parameters/input">
<xsl:choose>
<xsl:when test="string(number()) = 'NaN'">
<xslAlt:with-param name="{#name}" select="'{.}'"/>
</xsl:when>
<xsl:otherwise>
<xslAlt:with-param name="{#name}" select="{.}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xslAlt:call-template>
</xslAlt:variable>
<!--~~-->
<xslAlt:variable name="result" select="translate(${$test-case-return},{string('$space')},{string('$nbsp')})"/>
<xsl:apply-templates select="#*|node()"/>
</test-case>
</xsl:template>
<!--~~-->
<xsl:template match="criteria">
<criteria>
<xslAlt:variable name="test-result">
<xslAlt:choose>
<xslAlt:when test="{translate(criterion/#to,' ',' ')} = $result">TEST-PASSED</xslAlt:when>
<xslAlt:otherwise>TEST-FAILED</xslAlt:otherwise>
</xslAlt:choose>
</xslAlt:variable>
<xsl:apply-templates select="#*|node()"/>
</criteria>
</xsl:template>
<!--~~-->
<xsl:template match="#to">
<xsl:attribute name="to">
<xsl:value-of select="translate(.,' ',' ')"/>
<!--translate replace-->
</xsl:attribute>
<xsl:attribute name="result">{<xsl:value-of select="string('$test-result')"/></xsl:attribute>
</xsl:template>
<!--~~-->
<xsl:template match="parameters/output">
<output name="{#name}">
<xslAlt:value-of select="$result"/>
</output>
</xsl:template>
<!--~~-->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/><!--THE ERROR IS POINTING HERE!!!-->
</xsl:copy>
</xsl:template>
<!--~~-->
</xsl:stylesheet>
The error reported by the proccessor is: An attribute node (name)
cannot be created after the children of the containing element.
This is not a "casting error". What it says is that you are trying to create an attribute after some children have already been created.
AFAICT, it happens here:
<xsl:template match="test-case">
<test-case name="{concat('test-case-', string(position()))}">
<!--here child nodes are being created!!! ~-->
<xsl:apply-templates select="#*|node()"/>
</test-case>
</xsl:template>
If you change (line #35):
<xsl:apply-templates select="#*|node()"/>
to:
<xsl:apply-templates select="node()"/>
the error will go away, because now it will stop trying to copy the existing attributes. You would not want this to happen anyway, since it would overwrite the name attribute you have created yourself.
Caveat:
I have not examined your stylesheet in-depth.