I have an XML file here:
<?xml version="1.0" encoding="utf-16"?>
<class>
<student>
<name>Ben</name>
<age>1</age>
<ages>4</ages>
<entryprofile></entryprofile>
<node>1</node>
</student>
<student>
<name>Steve</name>
<age>2</age>
<ages>3</ages>
<entryprofile></entryprofile>
<node>1</node>
</student>
</class>
I am trying to promote the entryprofile element so that it becomes a child element of student, rather than an attribute of student--and I want it to contain node. I have tried to apply the following XSL in order to do this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="student">
<xsl:copy-of select="name"/>
<new-level>
<xsl:copy-of select="entryprofile"/>
</new-level>
</xsl:template>
</xsl:stylesheet>
This doesn't seem to be doing much apart from this:
<?xml version="1.0" encoding="utf-16"?>
<name>Ben</name><new-level><entryprofile>g</entryprofile></new-level>
<name>Steve</name><new-level><entryprofile>g</entryprofile></new-level>
But what I am looking for is this:
<?xml version="1.0" encoding="utf-16"?>
<class>
<student>
<name>Ben</name>
<age>1</age>
<ages>4</ages>
<entryprofile>
<node>1</node>
</entryprofile>
</student>
<student>
<name>Steve</name>
<age>2</age>
<ages>3</ages>
<entryprofile>
<node>1</node>
</entryprofile>
</student>
</class>
You can see there that entryprofile for both stdudents has become a child on account of node becoming a child of entryprofile.
Would anyone know where I am going wrong, and what I can do to achieve my desired result? Many thanks.
This XSLT will produce the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="entryprofile">
<xsl:copy>
<xsl:copy-of select="../node"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node"/>
</xsl:stylesheet>
Here are two alternatives that accomplish the same thing in slightly different ways:
This one is the correct version of what you tried to do:
<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>
<xsl:template match="student">
<xsl:copy>
<xsl:copy-of select="name | age | ages | profile"/>
<entryprofile>
<xsl:copy-of select="node"/>
</entryprofile>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This one is a shorter version of the above:
<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:template match="/class">
<xsl:copy>
<xsl:for-each select="student">
<xsl:copy>
<xsl:copy-of select="name | age | ages | profile"/>
<entryprofile>
<xsl:copy-of select="node"/>
</entryprofile>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Related
I'm trying to convert HTML content to uppercase using XSLT, but the requirement is to keep the tag hierarchy unaltered (i.e., just change the text).
For example: <p>some text <b>other text</b></p>
should result in
<p>SOME TEXT <b>OTHER TEXT</b></p>.
With the following XSLT I managed to convert the text to uppercase, but the result loses the tag hierarchy.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" indent="no" method="xhtml" standalone="0" version="1.0"/>
<xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template match="/">
<xsl:value-of select="translate(/, $smallcase, $uppercase)"/>
</xsl:template>
</xsl:transform>
Is there any way to keep the tags unaltered?
Thanks in advance.
Try:
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:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="translate(., $smallcase, $uppercase)"/>
</xsl:template>
</xsl:stylesheet>
Note that xhtml is not a valid output method in XSLT 1.0.
I would like to select Parent node without Child node.
Example:
<Shop>
<Product>
<ProductId>1</ProductId>
<Description>ProductList</Description>
<Milk>
<MilkId></MilkId>
</Milk>
</Product>
</Shop>
Desired Output:
<Shop>
<Product>
<ProductId>1</ProductId>
<Description>ProductList</Description>
</Product>
</Shop>
I tried below XSLT but it failed to return correct result:
<xsl:copy-of select="//Product/not[Milk]"/>
Thank you for any help.
Update:
XSLT:
<xsl:copy-of select="Product/*[not(self::Milk)]" />
Returns:
<ProductId>1</ProductId>
I need below structure to be returned:
<Shop>
<Product>
<ProductId>1</ProductId>
<Description>ProductList</Description>
</Product>
</Shop>
You can use a variant of the Identity template:
<!-- Identity template variant -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[local-name()!='Milk']|#*" />
</xsl:copy>
</xsl:template>
Or, as a more XSLT-2.0 way suggested in the comments
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node() except Milk|#*" />
</xsl:copy>
</xsl:template>
It copies all nodes except for the ones named Milk and its children.
If you want to apply this only to Product nodes, you also have to use the Identity template and change the matching rule to
<xsl:template match="Product">...
A solution using only xsl:copy-of could be copying the Product element and then copy all of its children (except for the Milk ones) with
<xsl:copy-of select="Product/*[not(self::Milk)]" />
Or, in a whole XSLT-2.0 template
<xsl:template match="//Product">
<xsl:copy>
<xsl:copy-of select="* except Milk" />
</xsl:copy>
</xsl:template>
A whole XSLT-1.0 stylesheet could look like
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Product">
<xsl:copy>
<xsl:copy-of select="*[not(self::Milk)]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Its output is:
<?xml version="1.0"?>
<Shop>
<Product>
<ProductId>1</ProductId>
<Description>ProductList</Description>
</Product>
</Shop>
Updated with Minimal, Complete and Verifiable information thanks #Kenneth for drawing my attention.
So, I'm trying to rename an xml attribute using xslt, but I keep getting this error message:
Reference to undeclared namespace prefix: 'json'.
This is my source xml:
<?xml version="1.0" encoding="utf-8"?>
<data>
<code>SO000009</code>
<businessPartner>70833A356B9A428CBDDCD2A76A49681F</businessPartner>
<startDateTime>2018-01-25T15:24:27Z</startDateTime>
<subject>Test</subject>
<equipments jsonArray="true">80202</equipments>
</data>
This is how I want the xml to be:
<?xml version="1.0" encoding="utf-16"?>
<data xmlns:json="http://james.newtonking.com/projects/json">
<code>SO000009</code>
<businessPartner>70833A356B9A428CBDDCD2A76A49681F</businessPartner>
<startDateTime>2018-01-25T15:24:27Z</startDateTime>
<subject>Test</subject>
<equipments json:Array="true">80202</equipments>
</data>
And this is my xslt:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/data">
<data xmlns:json="http://james.newtonking.com/projects/json">
<xsl:apply-templates select="node()|#*" />
</data>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="#jsonArray">
<xsl:attribute name="json:Array">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When I'm trying to convert my xml, I keep getting this message:
Error occurred while compiling stylesheet 'CS_jsonArray.xslt'.
Code: 0x80004005
Reference to undeclared namespace prefix: 'json'.
After looking in the 'Related' sidebar, I did found the answer to my own question.
XSL transformation - Namespace prefix undeclared
So for those who have a same issue, be sure the namespace you want to change the attribute names for, is also in the xslt tag itself...
This is how my xslt started:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>...
This is how I changed it:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:json="http://james.newtonking.com/projects/json">
<xsl:output indent="yes"/>...
When transforming an XML document with XSLT, is it possible to convert embedded JSON (i.e. JSON formatted content) in the process?
For example the following: -
<form>
<data>[{"id":1,"name":"Hello"},{"id":2,"name":"World"}]</data>
</form>
Would be converted to: -
<form>
<data>
<id name="Hello">1</id>
<id name="World">2</id>
</data>
</form>
Parsing JSON is supported in XSLT 3.0 so using the commercial versions of Saxon 9.7 you could use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="data">
<xsl:copy>
<xsl:apply-templates select="parse-json(.)?*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=".[. instance of map(xs:string, item())]">
<id name="{.?name}">
<xsl:value-of select=".?id"/>
</id>
</xsl:template>
</xsl:stylesheet>
Using the open source version of Saxon 9.7 (i.e. Saxon 9.7 HE) the following takes up the suggestion made by wero to use json-to-xml and shows how to implement the requirement:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math fn"
version="3.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="data">
<xsl:copy>
<xsl:apply-templates select="json-to-xml(.)//fn:map"/>
</xsl:copy>
</xsl:template>
<xsl:template match="fn:map">
<id name="{fn:string[#key = 'name']}">
<xsl:value-of select="fn:number[#key = 'id']"/>
</id>
</xsl:template>
</xsl:stylesheet>
Saxon 9.7 HE is available on Maven and from http://saxon.sourceforge.net/
It should be possible in XSLT 3.0, given that it has a json-to-xml function:
Parses a string supplied in the form of a JSON text, returning the
results in the form of an XML document node.
You could try to get this to run with the current implementation in Saxon.
Using XSLT, how can one remove all the text nodes from HTML, but keep the element tags, attribute names, and attribute values?
<table id="preserve-this-value">
<caption>Lose this text node</caption>
Transformation:
<table id="preserve-this-value">
<caption></caption>
Thanks :)
Use this template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
It copies all nodes (elements, attributes) except text nodes.