I'm currently struggling with the XPath matching of XML input in my XSL-FO stylesheets. The root XML can have a varying namespace-prefix (in this example ns1, but could change anytime to something else and have no control of). The only information I have is the namespace (in my example http://www.foo.com/foo1).
XML input
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns1:letter xmlns:ns1="http://www.foo.com/foo1" xmlns:ns2="http://www.foo.com/foo2">
<surname>Doe</surname>
<givenname>John</givenname>
...
</ns1:letter>
XSL-FO stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format" xpath-default-namespace="http://www.foo.com/foo1">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<fo:root>
...
<!-- this does not match !!! -->
<xsl:value-of select="/letter/surname"/>
...
</fo:root>
</xsl:template>
</xsl:stylesheet>
In your XML, letter is in a namespace, but surname isn't. By using xpath-default-namespace in your XSLT you are assuming all unprefixed elements in your xpath are in that namespace.
What you could do, is explicitly declare the namespace with a prefix. The prefix does not have to match the XML, it is totally arbitrary. It is the namespace URL that has to match for it to work
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:foo="http://www.foo.com/foo1">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<fo:root>
<xsl:value-of select="/foo:letter/surname"/>
</fo:root>
</xsl:template>
</xsl:stylesheet>
Related
I have many XML files that I want to process with XSLT. I want the result to include custom CSS for the purpose of displaying the files a distinct way in Oxygen’s Author mode.
Input:
<?xml version="1.0" encoding="utf-8"?>
<alto xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v2# http://www.loc.gov/standards/alto/alto-v2.0.xsd" xmlns="http://www.loc.gov/standards/alto/ns-v2#">
<!—more XML-->
</alto>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.loc.gov/standards/alto/ns-v2#"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!—A series of templates that transform the XML-->
</xsl:stylesheet>
Desired Output:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="my-style.css"?>
<alto xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.loc.gov/standards/alto/ns-v2#"
xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v2# http://www.loc.gov/standards/alto/alto-v2.0.xsd">
<!—more XML-->
</alto>
What do I need to add to my stylesheet to get the declaration to display in each XML file?
Use the xsl:processing-instruction instruction.
So your stylesheet could look like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.loc.gov/standards/alto/ns-v2#"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:processing-instruction name="xml-stylesheet">href="my-style.css"</xsl:processing-instruction>
<xsl:apply-templates select="node()|#*" />
</xsl:template>
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output is:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="my-style.css"?>
<alto xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.loc.gov/standards/alto/ns-v2#"
xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v2# http://www.loc.gov/standards/alto/alto-v2.0.xsd"/>
Add
<xsl:template match="/">
<xsl:processing-instruction name="xml-stylesheet">href="my-style.css"</xsl:processing-instruction>
<xsl:next-match/>
</xsl:template>
at the end of the stylesheet or make sure you edit the template you have for match="/" and insert the <xsl:processing-instruction name="xml-stylesheet">href="my-style.css"</xsl:processing-instruction> there.
I am looking for XSL to transform provided input to expected output.I have just provided sample but actual input xml had more than 1000 nodes and as too many nodes not able to use CDATA section in XSL, could you please help.
Input:
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
Output:
<note>
<to><![CDATA[Tove]]></to>
<from><![CDATA[Jani]]></from>
<heading><![CDATA[Reminder]]></heading>
<body><![CDATA[Don't forget me this weekend!]]></body>
</note>
You can achieve this by using the cdata-section-elements attribute of the xsl:output element which specifies all the elements that should be output in CDATA sections.
So use the following XSLT-1.0:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" cdata-section-elements="to from heading body" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See that the cdata-section-elements denotes the elements to from heading body to enclose their content in a CDATA section. The identity template just copies all of the file with regard to this.
If your elements are in a namespace, you have to prefix the element's names in the cdata-section-elements with the appropriate namespace-prefix.
For example, if you have the following XML with a namespace on the root element, all children nodes are in that namespace, too.
<?xml version="1.0" encoding="utf-8"?>
<Bank xmlns="http://xxyy.x.com" Operation="Create">
<Customer type="random">
<CustomerId>Id10</CustomerId>
<CountryCode>CountryCode19</CountryCode>
<LanguageCode>LanguageCode20</LanguageCode>
<AddressArray>
<Address type="primary">
<StreetAddress>179 Alfred St</StreetAddress>
<City>Fortitude Valley</City>
<County>GR</County>
<Country>India</Country>
</Address>
</AddressArray>
</Customer>
</Bank>
Use this XSLT (pay attention to the namespace declaration on the xsl:stylesheet element):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:ns0="http://xxyy.x.com">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" cdata-section-elements="ns0:CustomerId ns0:CountryCode ns0:LanguageCode ns0:StreetAddress ns0:City ns0:County ns0:Country" omit-xml-declaration="yes" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Make sure that the namespace of the XSLT matches the namespace of the XML, here both are http://xxyy.x.com, but in your sample XML it is xxyy.x.com.
EDIT 2:
If you have a large amount of elements you can either add them all to cdata-section-elements (maybe by constructing it by another stylesheet) or use the solution I found here: Wrapping all elements in CDATA. It is kind of a hack.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:variable name="CDATABegin" select="'<![CDATA['" />
<xsl:variable name="CDATAEnd" select="']]>'" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="text()[normalize-space()]">
<xsl:value-of select="$CDATABegin" disable-output-escaping="yes"/>
<xsl:value-of select="." disable-output-escaping="yes"/>
<xsl:value-of select="$CDATAEnd" disable-output-escaping="yes"/>
</xsl:template>
</xsl:stylesheet>
This wraps all non-empty text() nodes in CDATA sections. But here, too, you'd have to mention all elements in template matching rules containing the CDATA wrapping code.
Which variant would be easier to apply depends on the greater scenario.
Is it possible to access Synapse XPATH extension from an XSLT. I want to use the get-property function in an XSLT, is it possible?
Ex:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:synapse="http://ws.apache.org/ns/synapse"
exclude-result-prefixes="synapse"
version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" />
<xsl:template match="/">
<xsl:copy-of select="synapse:get-property('paramName')" />
</xsl:template>
</xsl:stylesheet>
Using Visual Studio to perform the transform during development the resulting xml contains text from the source xml in the destination that was contained in tags that do not match my template criteria
I was expecting that my select Group in the first template to find any elements named Group that are immediate children of CrystalReport and pass them along in the apply template call. I understood that the match filter on my second template would only take in Group's that have an attribute of Level=1 and write them out. I'd expect everything else to be ignored.
Why does "not this" appear in my output?
source
<?xml version="1.0" encoding="utf-8" ?>
<!--
UPDATE: Note that adding the xmlns attribute causes all output
to disappear unless you use Chris's second solution.
-->
<CrystalReport xmlns="urn:crystal-reports:schemas:report-detail" >
<Group Level="1">
<GroupHeader>
<Section>
<Field FieldName="apple" />
</Section>
</GroupHeader>
<Group Level="2">
not this
</Group>
</Group>
</CrystalReport>
transform
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/CrystalReport">
<root>
<xsl:apply-templates select="Group"/>
</root>
</xsl:template>
<xsl:template match="Group[#Level='1']/GroupHeader">
<tag1><xsl:value-of select="Section/Field/#FieldName"/></tag1>
</xsl:template>
</xsl:stylesheet>
output
<?xml version="1.0" encoding="utf-8"?>
<root>
<tag1>apple</tag1>
not this
</root>
You are facing the problem where the built in templates of the XML parser are coming into play. You are apply templates to all of the Group elements, but only catching one of them with your own templates. The other is handled by the default templates which out put the values of all nodes. I suggest that you change
<xsl:template match="/CrystalReport">
into
<xsl:template match="/">
This will override the root default templates which are producing the extra output. You can find more on the built in template rules at http://www.w3.org/TR/xslt#built-in-rule
Then override the basic text() template so you final XSLT looks a bit like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="CrystalReport/Group"/>
</root>
</xsl:template>
<xsl:template match="Group[#Level='1']/GroupHeader">
<tag1>
<xsl:value-of select="Section/Field/#FieldName"/>
</tag1>
</xsl:template>
<xsl:template match="text()"/>
UPDATE
Or even simpler you could just match the desired elements and use something like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/CrystalReport">
<root>
<xsl:apply-templates select="Group[#Level='1']/GroupHeader"/>
</root>
</xsl:template>
<xsl:template match="GroupHeader">
<tag1>
<xsl:value-of select="Section/Field/#FieldName"/>
</tag1>
</xsl:template>
This will leave the default text() templates in place which can be very handy.
Try
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:crystal="urn:crystal-reports:schemas:report-detail">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/crystal:CrystalReport">
<root>
<xsl:apply-templates select="crystal:Group[#Level='1']/crystal:GroupHeader"/>
</root>
</xsl:template>
<xsl:template match="crystal:GroupHeader">
<tag1>
<xsl:value-of select="crystal:Section/crystal:Field/#FieldName"/>
</tag1>
</xsl:template>
I have an xml as below.
<?xml version="1.0" encoding="UTF-8"?>
<books xmlns="http://www.books.com/SRK">
<name>English</name>
</books
I required the following output after translation using xsl .
<?xml version="1.0" encoding="UTF-8"?>
<books>
<name>English</name>
</books>
I need an xsl to ignore the namespace.I have tried something but its not working with namespace.
I need your help.Your help would be appreciated.
This transformation:
<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="#*|node()[not(self::*)]">
<xsl:copy/>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
when applied to the provided XML document:
<books xmlns="http://www.books.com/SRK">
<name>English</name>
</books>
produces the wanted, correct result:
<books>
<name>English</name>
</books>
Its working only if i include the above templates if i add some other templates other than the above one then the translation is not at all working .None of the template getting executed.
Probably you are missing the declaration of the namespace for the book element. Example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="http://www.books.com/SRK"
exclude-result-prefixes="b">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- #dimitre's answer templates -->
<xsl:template match="b:name">
<!-- your template for name -->
</xsl:template>
</xsl:stylesheet>
Moreover, make sure to use local-name() function to get the name of an element without the related namespace.
Example
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="http://www.books.com/SRK"
exclude-result-prefixes="b">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="b:input">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="b:name"/>
</xsl:element>
</xsl:template>
<xsl:template match="b:name">
<xsl:element name="{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
<lhs>
<xsl:apply-templates select="following-sibling::b:lhs/b:evaluate"/>
</lhs>
</xsl:template>
<xsl:template match="b:evaluate">
Something to evaluate...
</xsl:template>
</xsl:stylesheet>
gets:
<?xml version="1.0" encoding="UTF-8"?>
<input>
<name>English</name>
<lhs>
Something to evaluate...
</lhs>
</input>
Second Example
You can create a separate transform called local-identity.xsl containing #Dimitre solution. Then you can import it in your transform. Because you have a namespace, to match elements you must change all your XPaths including the prefix you will declare in the transform, as in the following example:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:brl="http://www.xyz.com/BRL"
exclude-result-prefixes="brl"
version="1.0">
<xsl:import href="local-identity.xsl"/>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/brl:rule">
<!-- do your staff, select using brl prefix -->
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>