Calling a named template from an imported .xsl file explicitely - xslt

I have the following situation:
I am using a 3rd party xsl template system (kosit XRechnung), which allows to configure custom adaptions via specification of your own .xsl files as processing entry points.
Here's the process:
+--------+
| |
| |
| |
| in.xml |
| |
+--------+
|
|
|
V
+--------+
| |
| | import +--------+
| my.xsl | <-------- | |
| | | adapt | import +---------+
| | | .xsl | <-------| |
+--------+ | | | default |
| | | | .xsl |
-| +--------+ | |
| | |
V +---------+
+--------+
| |
| |
| out |
| .xml |
| |
+--------+
my.xsl is the processing entry point fed with some .xml data, which is provided by me
adapt.xslis an adaption which provides a named template called at some point by
default.xsl which does a lot of stuff to create the final output XML document.
default.xsl:
<!-- ... -->
<xsl:when test="s:scenario">
<rep:scenarioMatched>
<xsl:apply-templates select="s:scenario" mode="copy" />
<xsl:call-template name="documentData" />
<xsl:sequence select="$validationStepResults" />
</rep:scenarioMatched>
</xsl:when>
<!-- ... -->
adapt.xsl:
<!-- Overwritten (default.xsl) -->
<xsl:template name="documentData">
<rep:documentData>
<xsl:for-each select="
$input-document/*/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName,
$input-document/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:Name">
<seller>
<xsl:value-of select="."/>
</seller>
</xsl:for-each>
<xsl:for-each select="
$input-document/*/cbc:ID,
$input-document/rsm:CrossIndustryInvoice/rsm:ExchangedDocument/ram:ID">
<id>
<xsl:value-of select="."/>
</id>
</xsl:for-each>
<xsl:for-each select="$input-document/*/cbc:IssueDate,
$input-document/rsm:CrossIndustryInvoice/rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString">
<issueDate>
<xsl:value-of select="."/>
</issueDate>
</xsl:for-each>
</rep:documentData>
</xsl:template>
Now in my top level .xsl I can again override the named template which is called in the default.xsl:
my.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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rep="http://www.xoev.de/de/validator/varl/1"
xmlns:s="http://www.xoev.de/de/validator/framework/1/scenarios"
xmlns:in="http://www.xoev.de/de/validator/framework/1/createreportinput"
xmlns:svrl="http://purl.oclc.org/dsdl/svrl"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
xmlns:myns="urn:mycompany:xrechnung:data:enhancements"
exclude-result-prefixes="xs"
version="2.0">
<xsl:import href="./adapt.xsl"/>
<xsl:output method="xml" indent="yes"/>
<!-- Overwritten (default.xsl) -->
<xsl:template name="documentData">
<rep:documentData>
<myns:MyContainer>
<myns:MyElement>
<!-- Enhanced stuff -->
</myns:MyElement>
</myns:MyContainer>
<!-- xsl:call-template name="documentData"/-->
</rep:documentData>
</xsl:template>
</xsl:stylesheet>
The question is:
How to apply the stuff defined in adapt.xsl without copying that xsl code for the documentData named template (without changing default.xsl or adapt.xsl)?

In the context of XSLT 3 packages this is possible, see https://www.w3.org/TR/xslt-30/#refer-to-overridden:
Within a named template appearing as a child of xsl:override, the name
xsl:original may appear as the value of the name attribute of
xsl:call-template: for example, <xsl:call-template name="xsl:original"/>.
Saxon 9.8 and later support XSLT 3 with packages.
For the concrete example I think you would need to use a fourth file (e.g. adapt-package-wrapper.xsl) that is a named package importing adapt.xsl e.g.
<xsl:package xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
name="http://example.com/adapt1" package-version="1.0">
<xsl:expose component="template" names="documentData" visibility="public"/>
<xsl:import href="adapt.xsl"/>
</xsl:package>
Then in my.xsl you would need to declare the override:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:use-package name="http://example.com/adapt1" version="1.0">
<xsl:override>
<xsl:template name="documentData">
<rep:documentData>
<myns:MyContainer>
<myns:MyElement>
<!-- Enhanced stuff -->
</myns:MyElement>
</myns:MyContainer>
<!-- xsl:call-template name="documentData"/-->
<xsl:call-template name="xsl:original"/>
</rep:documentData>
</xsl:template>
</xsl:override>
</xsl:use-package>
...
</xsl:stylesheet>
The main issue then is letting Saxon know how to map the package name http://example.com/adapt1 to a package file, on the command line you would use e.g. -lib:adapt-package-wrapper.xsl. Or you can use a configuration file. Not sure whether you can do that in your environment.

Related

XSLT disable-output-escaping but allow some entities

I have the following xslt to output comments but it is vulnerable to XSS if I disable all output escaping.
<xsl:value-of disable-output-escaping="yes" select="//datafor:field[datafor:name='comments']/datafor:value" />
How can I allow only the following html attributes so that I can retain some formating in the rendered output?
'b,strong,i,ul,ol,li,p,br,p[style]',div,div[class]'
Or, using SaxonJS, in the browser, you could call into JavaScript to parse the HTML fragment and process it e.g.
function parseHTML(html) {
return new DOMParser().parseFromString(html, 'text/html');
}
const xml = `<orders>
<order>
<id>o1</id>
<date>2022-06-12</date>
<comments><![CDATA[I want the following extras: <ol>
<li>32 GB RAM</li>
<li>1000 GB SSD</li>
]]></comments>
</order>
</orders>`;
const xslt = `<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:js="http://saxonica.com/ns/globalJS"
expand-text="yes">
<xsl:mode name="html"/>
<xsl:template mode="html" match="b | strong | i | ul | ol | li | p | br | p | div | p/#style | div/#class" xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:copy>
<xsl:apply-templates select="#* | node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<html>
<head>
<title>Test</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="orders">
<h1>Orders</h1>
<xsl:where-populated>
<ol>
<xsl:apply-templates/>
</ol>
</xsl:where-populated>
</xsl:template>
<xsl:template match="order">
<li>Order {id} from {format-date(date, '[D] [M] [Y0000]')}
<div>
<h2>Comments</h2>
<div>
<xsl:apply-templates select="js:parseHTML(string(comments))" mode="html"/>
</div>
</div></li>
</xsl:template>
</xsl:stylesheet>`;
const result = SaxonJS.XPath.evaluate(`transform(map {
'stylesheet-text' : $xslt,
'source-node' : parse-xml($xml)
}
)?output/*/*/node()`,
[],
{ params : {
xslt: xslt,
xml: xml
}
}
);
document.body.append(...result);
<script src="https://martin-honnen.github.io/xslt3fiddle/js/SaxonJS2.js"></script>
With the XSLT implementation of an HTML tag soup parser by David Carlisle it would be
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:d="data:,dpc"
xmlns:js="http://saxonica.com/ns/globalJS"
expand-text="yes">
<xsl:import href="https://github.com/davidcarlisle/web-xslt/raw/main/htmlparse/htmlparse.xsl"/>
<xsl:mode name="html"/>
<xsl:template mode="html" match="b | strong | i | ul | ol | li | p | br | p | div | p/#style | div/#class" xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:copy>
<xsl:apply-templates select="#* | node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<html>
<head>
<title>Test</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="orders">
<h1>Orders</h1>
<xsl:where-populated>
<ol>
<xsl:apply-templates/>
</ol>
</xsl:where-populated>
</xsl:template>
<xsl:template match="order">
<li>Order {id} from {format-date(date, '[D] [M] [Y0000]')}
<div>
<h2>Comments</h2>
<div>
<xsl:apply-templates select="d:htmlparse(comments)" mode="html"/>
</div>
</div></li>
</xsl:template>
</xsl:stylesheet>
Online sample.
The current samples copy only the listed elements but copy any text node through, if elements like e.g. script need to be completely stripped add an empty template <xsl:template mode="html" match="script" xpath-default-namespace="http://www.w3.org/1999/xhtml"/>.
Of course, consider to download the HTML parser module and xsl:import a local file for your own application instead of pulling from github.

I cannot add a specific attribut to an element using Saxon in XMLSpy

I am transforming a document trying to provide templates to all used elements, and catching the ones I might miss with a "catch all others filter". This works mostly as expected, and I can create a lot of elements with the correct attributes, but I get into trouble with one special attribute - "valign". Everything else works in the supplied code.
I originally included all matches that were supposed to be copied in one template, but here I have tried splitting my xslt match up into different templates, but I still get the same result (which was expected but hey, I had to try...).
When I use the XMLSpy debugger, the transform also works.
Source xml snippet:
<?xml version="1.0" encoding="UTF-8"?>
<content>
<table tocentry="1">
<tgroup align="left" char="" charoff="50" cols="2">
<colspec colname="colspec0" colwidth="1*"/>
<colspec colname="colspec1" colwidth="1.5*"/>
<tbody valign="top">
<row>
<entry morerows="0" rotate="0" valign="top">
<para>Volume washing fluid</para>
</entry>
<entry morerows="0" rotate="0" valign="top">
<para>3 dmĀ³</para>
</entry>
</row>
</tbody>
</tgroup>
</table>
</content>
</dmodule>
XSLT 2.0 snippet:
<?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" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" exclude-result-prefixes="fo xs fn">
<xsl:output encoding="utf-8" indent="yes" method="xml"/>
<xsl:template match="#*|*">
<xsl:comment>warning, node not handled by defined templates: "<xsl:copy-of select="local-name()"/>"</xsl:comment>
</xsl:template>
<xsl:template match="/content">
<xsl:apply-templates/>
</xsl:template>
<!--**************************************************-->
<!-- ************** Common ********-->
<!--**************************************************-->
<xsl:template match="table |
tgroup |
tbody |
colspec |
row |
entry |
figure |
para[not(parent::note or parent::warning or parent::caution or following-sibling::seqlist)] |
legend |
note |
title |
warning |
caution |
note
">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#align |
#char |
#charoff |
#colname |
#cols |
#colwidth |
#id |
#morerows |
#tocentry |
#valign">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I want this to work as an identity transform.
The only way I can get around this is to use a "copy-of" when I find a table element.
Error message in XMLSpy (set up with Saxon) is the following:
Error evaluating ((attr{xsi:noNamespaceSchemaLocation=...}, ...)) on line 146 column 26 of basics_001.xsl:
XTDE0410: An attribute node (valign) cannot be created after a child of the containing
element. Most recent element start tag was output at line 131 of module basics_001.xsl
In template rule with match="#valign" on line 144 of basics_001.xsl
invoked by xsl:apply-templates at file:/C:/basics_001.xsl#131
In template rule with match="entry" on line 129 of basics_001.xsl
invoked by xsl:apply-templates at ....
I think your approach to output a comment as a child node for attributes not being matched elsewhere is casusing the problem; you will need to use xsl:message to output information about not-matched nodes. Clearly if the rotate attribute results in a child comment output you can't expect to be able to output any attribute node (like valign) afterwards.

Is there a way to store the value of a XML-Tag inside a Tag attribute

im trying to make a xslt-stylesheet that trasnforms a xml Document into another xml Document and applies some filters. Im having specially the issue that im trying to acess a value that is stored inside a Tag (Name_BLANK_of_BLANK_programmer) and i want it to store into a tag attribute(Testplan name=" HERE COMES THE VALUE ").
I want to transform this=
<Testplan>
<Name_BLANK_of_BLANK_programmer>136 - MEL 1 S - 24 DC</Name_BLANK_of_BLANK_programmer>
<BlackboxType>0</BlackboxType>
<ShowTestStepGrafics>0</ShowTestStepGrafics>
into this=
<Testplan number="136 - MEL 1 S - 24 DC">
<Instruction>3500</Instruction>
<Steps>
You havn't upload proper input and desire output so as i assumed and suggest that you can do like this, see link:
<?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" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Testplan">
<Testplan number="{Name_BLANK_of_BLANK_programmer}">
<xsl:apply-templates select="node() except Name_BLANK_of_BLANK_programmer"/>
</Testplan>
</xsl:template>
</xsl:stylesheet>

Pass param to xslt as node in apache camel

Is it possible to pass param as node not as string?
camel context:
<setHeader headerName="document_as_node">
<simple>${body}</simple>
</setHeader>
xslt:
<xsl:param name="document_as_node" />
body is a xml document, but I'm passsing it as string (I got error when I'm trying to use this param in xpath). How can I pass this as node or how can I transform it?
Hope this example will help:
from("timer:foo?period=30s")
.setBody(constant("<oldWrapTag><someTag>123</someTag></oldWrapTag>"))
.convertBodyTo(org.w3c.dom.Document.class)
.setBody(xpath("//someTag"))
.setHeader("insert", simple("body"))
.to("xslt:/xslt/test.xsl")
.to("log:body?showBody=true")
;
xslt :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="insert"/>
<xsl:template match="/">
<xsl:element name="wrapTag">
<xsl:copy-of select="$insert"/>
</xsl:element>
</xsl:template>
Output:
2018-10-24 14:03:07,952 | INFO | 10 - timer://foo | body | 247 - org.apache.camel.camel-core - 2.16.3 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: <?xml version="1.0" encoding="UTF-8"?><wrapTag><someTag>123</someTag></wrapTag>]

How to limit the scope of ancestor search in XSLT?

Example input:
<body>
<ul>
<li>
<p>Some text with <b><i><span style="color:red">formatting</span></i></b></p>
</li>
</ul>
</body>
The 'inline' elements are b, i, or span
Suppose the context node is the nested span element in the example.
How do I get all ancestor inline elements, without the search going past the containing p element?
The expression ancestor-or-self::*[self::b | self::i | self::span] should actually work, as there can't be ancestor inline elements containing 'block' level elements such as p, ul, etc.
But, I wonder if there is some performance cost that could be avoided, as I think the search will continue past the containing p element. Is there a way to avoid that?
Edit
This is what I've come up with so far:
<xsl:function name="my:getInlineSeq" as="element() *">
<xsl:param name="pElem" as="element()" />
<xsl:if test="$pElem[self::b | self::i | self::span]">
<xsl:sequence select="my:getInlineSeq($pElem/parent::*)" />
<xsl:sequence select="$pElem" />
</xsl:if>
</xsl:function>
But, I'm unsure if there is an better or 'standard' way of solving this problem.
Note: the $pElem/parent::* in the code will always return an element as inline items must have a block element container such as p, h1, etc. Also, the assumption is the $pElem parameter provided initially is always an inline element.
Using
<xsl:function name="mf:block" as="element()">
<xsl:param name="inline" as="element()"/>
<xsl:sequence select="$inline/ancestor::*[self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6 | self::li | self::p][1]"/>
</xsl:function>
and your assumption about block and inline elements you could rewrite ancestor-or-self::*[self::b | self::i | self::span] in the context of match="span | b | i" to
for $block in mf:block(.) return ancestor-or-self::*[. >> $block]
I think (or assuming you use <xsl:variable name="block" select="mf:block(.)"/> to ancestor-or-self::*[. >> $block]. I have no idea whether that performs any better than your attempt.
Tried to test the expression at http://xsltransform.net/3MvmrzK where
<xsl:template match="span | b | i">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="inline-ancestor" select="ancestor-or-self::*[self::b | self::i | self::span]/name()"/>
<xsl:attribute name="test-block" select="for $block in mf:block(.) return ancestor-or-self::*[. >> $block]/name()"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
gives the same result for the inline elements with
<p>Some text with <b inline-ancestor="b" test-block="b"><i inline-ancestor="b i" test-block="b i"><span style="color:red" inline-ancestor="b i span" test-block="b i span">formatting</span></i></b></p>