Overriding match="*" template from DocBook XSL - xslt

DocBook XSL includes a template that matches all element
<xsl:template match="*">
<xsl:message> .... </xsl:message>
</xsl:template>
I need to override it with another template because my source XML tree contains more that just the DoocBook XML. If I specify such a template in the file it overrides all templates in DocBook XSL. It seems like that all imported templates, are prioritized on the order of import only, and NOT according to how specific the template is.
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:db="http://docbook.org/ns/docbook" version="1.0">
<xsl:import href="docbook-xsl-ns/xhtml/docbook.xsl" />
<xsl:import href="copy.xsl"/>
<xsl:template match="/">
<xsl:apply-templates select="//db:book"/>
</xsl:template>
</xsl:stylesheet>
copy.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<!-- go process attributes and children -->
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Sample XML source
<?xml version="1.0" encoding="UTF-8"?>
<root>
<http-host>localhost</http-host>
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:svg="http://www.w3.org/2000/svg" xmlns:m="http://www.w3.org/1998/Math/MathML" xml:id="course.528" xml:lang="en" version="5.0">
<info>
<title>Postoperative Complications</title>
</info>
<chapter xml:id="chapter.1">
<title>INTRODUCTION</title>
<para>Postoperative complications are a constant threat to the millions ....</para>
</chapter>
</book>
<errors></errors>
</root>
This is true for both Xalan and xsltproc processors. How do I override this template without having to change the DocBook XSL source. I tried messing with priorities but that did not work.

From what I understand, you want to apply the copy.xsl's template only for non-docbook elements. Try to be more specific in your copy.xsl - by being more specific in your copy.xsl, that template will get selected for all non-docbook elements.
copy.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform>
<xsl:template match="*[not(namespace-uri() = 'http://docbook.org/ns/docbook')]">
<xsl:element name="{local-name()}">
<!-- go process attributes and children -->
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Depending on the presence of DocBook elements within non-Docbook nodes, you might need to restrict the nodeset for which you apply at the apply-templates part as well(based on the namespace) and maybe mess around the apply-templates flow to make sure it handles it predictably. Hope this is of some use to you..

Related

How to remove the namespace name without removing the namespace declaration

I'm still pretty new to XSLT and I have the following XML which I need to remove the namespace. I also found the following XSLT which almost gets the job done with the exception that it will not retain the xmlns declaration.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<etd_ms:thesis xmlns:etd_ms="http://www.ndltd.org/standards/metadata/etdms/1.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.ndltd.org/standards/metadata/etdms/1.0/ http://www.ndltd.org/standards/metadata/etdms/1.0/etdms.xsd">
<etd_ms:title>Aspects of negritude in the works of two Harlem renaissance authors : Claude McKay and Langston Hughes</etd_ms:title>
<etd_ms:creator>Charles, Asselin</etd_ms:creator>
<etd_ms:subject/>
<etd_ms:publisher>Concordia University</etd_ms:publisher>
<etd_ms:contributor role="advisor">Butovsky, M</etd_ms:contributor>
<etd_ms:date>1980</etd_ms:date>
<etd_ms:type>Electronic Thesis or Dissertation</etd_ms:type>
<etd_ms:identifier>TC-QMG-1</etd_ms:identifier>
<etd_ms:format>text</etd_ms:format>
<etd_ms:identifier>https://spectrum.library.concordia.ca/1/1/MK49585.pdf</etd_ms:identifier>
<etd_ms:language>en</etd_ms:language>
<etd_ms:degree>
<etd_ms:name>M.A.</etd_ms:name>
<etd_ms:level>masters</etd_ms:level>
<etd_ms:discipline>Dept. of English</etd_ms:discipline>
<etd_ms:grantor>Concordia University</etd_ms:grantor>
</etd_ms:degree>
</etd_ms:thesis>
and here's the XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" encoding="utf-8" omit-xml-declaration="no"/>
<!-- Stylesheet to remove all namespaces from a document -->
<!-- NOTE: this will lead to attribute name clash, if an element contains
two attributes with same local name but different namespace prefix -->
<!-- Nodes that cannot have a namespace are copied as such -->
<!-- template to copy elements -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<!-- template to copy attributes -->
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- template to copy the rest of the nodes -->
<xsl:template match="comment() | text() | processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
The results is the following:
<?xml version="1.0" encoding="UTF-8"?>
<thesis xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.ndltd.org/standards/metadata/etdms/1.0/ http://www.ndltd.org/standards/metadata/etdms/1.0/etdms.xsd">
<title>Aspects of negritude in the works of two Harlem renaissance authors : Claude McKay and Langston Hughes</title>
<creator>Charles, Asselin</creator>
<subject/>
<publisher>Concordia University</publisher>
<contributor role="advisor">Butovsky, M</contributor>
<date>1980</date>
<type>Electronic Thesis or Dissertation</type>
<identifier>TC-QMG-1</identifier>
<format>text</format>
<identifier>https://spectrum.library.concordia.ca/1/1/MK49585.pdf</identifier>
<language>en</language>
<degree>
<name>M.A.</name>
<level>masters</level>
<discipline>Dept. of English</discipline>
<grantor>Concordia University</grantor>
</degree>
</thesis>
It's almost there, with the exception that I need to keep the xmlns declaration, so ultimatly the root element should be something like:
<thesis xmlns="http://www.ndltd.org/standards/metadata/etdms/1.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.ndltd.org/standards/metadata/etdms/1.0/ http://www.ndltd.org/standards/metadata/etdms/1.0/etdms.xsd">
Can someone help me resolving this issue? Thanks.
Change
<xsl:element name="{local-name()}">
to
<xsl:element name="{local-name()}" namespace="http://www.ndltd.org/standards/metadata/etdms/1.0/">

adding xpath-default-namespace attribute to the final compiled/transformed schematron xslt file

I have to generate schematron files for various xml schema files using the available information under the appinfo elements (i do xsl transformation to generate schemantron files, which are compiled again later).
The xpath rules required for schematron assertions are written under this appinfo element. However, these xpath rules do not contain any namespace prefix. Thus i can't use the schematron 'ns' tag to add a namespace to the compiled final xslt file.
The solution would be adding the xpath-default-namespace attribute to the final compiled xslt. Unfortunately i couldn't find any tag for adding xpath-default-namespace attribute.
Is there any workaround for this case? thanks.
There does not appear to be an option currently available to set a xpath-default-namespace. In addition to transforming the generated XSLT, another option would be to modify/extend the schematron XSLT to generate the desired output so that you can get it generated in a single pass.
Create a stylesheet that imports the iso_schematron_skeleton_for_saxon.xsl
Override the template that generates the element to insert thexpath-default-namespace` attribute:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
xmlns:iso="http://purl.oclc.org/dsdl/schematron"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="2.0"
>
<xsl:import href="iso_schematron_skeleton_for_saxon.xsl"/>
<!-- Using XSLT 2 -->
<xsl:template
match="iso:schema[#queryBinding='xslt2' or #queryBinding ='xpath2']"
priority="10">
<axsl:stylesheet
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:saxon="http://saxon.sf.net/">
<!-- insert the default namespace attribute -->
<xsl:attribute name="xpath-default-namespace" select="'http://your/default/namespace/goes/here'"/>
<xsl:apply-templates
select="iso:ns" />
<!-- Handle the namespaces before the version attribute: reported to help SAXON -->
<xsl:attribute name="version">2.0</xsl:attribute>
<xsl:apply-templates select="." mode="stylesheetbody"/>
<!-- was xsl:call-template name="stylesheetbody"/ -->
</axsl:stylesheet>
</xsl:template>
</xsl:stylesheet>
Modify the iso_svrl_for_xslt2.xsl to import your overriding stylesheet:
Change the path to import your overriding XSLT:
<!-- Select the import statement and adjust the path as
necessary for your system.
-->
<xsl:import href="iso_schematron_skeleton_for_saxon_with_default_namespace.xsl"/>
Since XSLT is an XML file, you could transform the compiled/transformed schematron XSLT and insert #xpath-default-namespace yourself:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:attribute name="xpath-default-namespace" select="'http://your/default/namespace'"/>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to create template to match based upon an XSLT parameter

I'm trying to create a standard-use XSLT that will perform a given task based upon a user-provided XPATH expression as an XSLT parameter.
That is, I need something like this:
<xsl:template match="$paramContainingXPATH">
<!-- perform the task on the node(s) in the given xpath -->
</xsl:template>
For example, suppose I have some XML:
<xml>
<nodeA>whatever</nodeA>
<nodeB>whatever</nodeB>
<nodeC>whatever</nodeC>
<nodeD>whatever</nodeD>
<nodeE>whatever</nodeE>
</xml>
The XSLT needs to transform just a node or nodes matching a provided XPATH expression. So, if the xslt parameter is "/xml/nodeC", it processes nodeC. If the xslt parameter is "*[local-name() = 'nodeC' or local-name() = 'nodeE']", it processes nodeC and nodeE.
This should work for absolutely any XML message. That is, the XSLT cannot have any direct knowledge of the content of the XML. So, it could be a raw XML, or a SOAP Envelope.
I was guessing I might need to grab all the nodes matching the xpath, and then looping over them calling a named template, and using the standard identity template for all other nodes.
All advice is appreciated.
If you really need that feature with XSLT 1.0 or 2.0 then I think you should consider writing one stylesheet that takes that string parameter with the XPath expression and then simply generates the code of a second stylesheet where the XPath expression is used as a match pattern and the other needed templates like the identity template are included statically. Dynamic XPath evaluation is only available in XSLT 3.0 or in earlier versions as a proprietary extension mechanism.
You cannot match a template using a parameter - but you can traverse the tree and compare the path of each node with the given path. Here's a simple example:
XSLT 1.0
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="path" select="'/world/America/USA/California'"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="path-to-me">
<xsl:for-each select="ancestor-or-self::node()">
<xsl:value-of select="name()" />
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$path=$path-to-me">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Applied to a slightly more ambitious test input of:
<world>
<Europe>
<Germany>1</Germany>
<France>2</France>
<Italy>3</Italy>
</Europe>
<America>
<USA>
<NewYork>4</NewYork>
<California>5</California>
</USA>
<Canada>6</Canada>
</America>
</world>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<return>5</return>
</root>
This could be made more efficient by passing the accumulated path as a parameter of the recursive template, so that each node needs only to add its own name to the chain.
Note:
The given path must be absolute;
Predicates (including positional predicates) and attributes are not implemented in this. They probably could be, with a bit more effort;
Namespaces are ignored (I don't see how you could pass an XPath as a parameter and include namespaces anyway).
If your processor supports an evaluate() extension function, you could forgo the calculated text path and test for intersection instead.
Edit:
Here's an example using EXSLT dyn:evaluate() and set:intersection():
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dynamic"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="dyn set">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="path" select="'/world/America/USA/California'"/>
<xsl:variable name="path-set" select="dyn:evaluate($path)" />
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:if test="set:intersection(. , $path-set)">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Note that this will also work with with paths like:
/world/America/USA/*[2]
//California
and many others that the text comparison method could not accommodate.
I'm sending the element name as a param to the XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml"/>
<xsl:param name="user"/>
<xsl:template match="/">
<xsl:call-template name="generic" />
</xsl:template>
<xsl:template name="generic">
<count><xsl:value-of select="count(.//*[local-name()=$user])"/></count>
</xsl:template>
</xsl:stylesheet>
I hope this could help!

How to instruct XSLT to apply template only on children?

When applyng this XSLT:
<xsl:template match="e">
<xsl:value-of select="#name"/>
</xsl:template>
To this xml:
<root>
<e name="1"/>
<la>
<e name="bla"/>
</la>
</root>
I get both "1" and "bla".
Why is this so?
How can I make sure that the XSLT is applied only to the direct children of root?
Did you try match="root/e"? If you want to match nodes in a certain context, you need to provide the context in the rule, otherwise all nodes with the matching node name apply to the rule.
You may also use something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<xsl:apply-templates select="child::e"/>
</xsl:template>
<xsl:template match="e">
<xsl:value-of select="#name"/>
</xsl:template>
</xsl:stylesheet>

How to copy all child nodes of any type of a template context element

I am transforming XML into HTML using XSLT.
I have the following XML structure:
<root>
<element>
<subelement>
This is some html text which should be <span class="highlight">displayed highlighted</span>.
</subelement>
</element>
</root>
I use the following template for the transformation:
<xsl:template name="subelement">
<xsl:value-of select="." />
</xsl:template>
Unfortunately, I lose the <span>-tags.
Is there a way to keep them so the HTML is displayed correctly (highlighted)?
The correct way to get the all the contents of the current matching node (text nodes included) is:
<xsl:template match="subelement">
<xsl:copy-of select="node()"/>
</xsl:template>
This will copy everything descendent.
Try using <xsl:copy-of... instead of <xsl:value-of... for example:
<xsl:template name="subelement">
<xsl:copy-of select="*" />
</xsl:template>
Note the * which will stop the <subelement></subelement> bits being output to the results, rather than using . which will include the <subelement></subelement> bits .
For example, the xsl stylesheet:
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="root/element">
<output>
<xsl:apply-templates select="subelement"/>
</output>
</xsl:template>
<xsl:template match="subelement">
<xsl:copy-of select="*"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example xml file returns:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<span class="highlight">displayed highlighted</span>
</output>
The <xsl:value-of> declaration takes the concatenated contents of all text nodes within the element, in sequential order, and doesn't output any elements at all.
I'd recommend using <xsl:apply-templates> instead. Where it finds a text node, it will output the contents as-is, but you would need to define a template for handling span tags to convert them to html ones. If that span tag IS an html tag, then strictly speaking, you should have separate namespaces for your own document structure and html.