orbeon: applying xslt transform in xbl on bound document - xslt

In Orbeon Forms I need to create a component (using XBL) that when bound to an instance like
<OCinstructionitem>
<OCp>paragraph 1</OCp>
<OCp>paragraph 2 <OCem>with italics part</OCem> rest of paragraph 2 </OCp>
</OCinstructionitem>
creates an editable div like this:
<div contentEditable="true">
<p>paragraph 1</p>
<p>paragraph 2 <i>with italics part</i> rest of paragraph 2 </p>
</div>
My thought was that I need to do this using XSLT. I get this working when the to-be-transformed XML is inside the xforms document:
<oc:instructionitem>
<OCinstructionitem>
<!-- here the xml of above -->
...
</OCinstructionitem>
</oc:instructionitem>
but I want to let the XSLT operate on the bound node as in:
<oc:instructionitem ref="OCinstructionitem"/>
However, I canot access the bound node from the XSLT.
My question: is that just not possible? Or do I have to modify my XBL?
My XBL code:
<xbl:binding id="oc-instructionitem" element="oc|instructionitem">
<xbl:template xxbl:transform="oxf:xslt">
<xsl:transform version="2.0">
<xsl:template match="#*|node()" priority="-100">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
<xsl:template match="text()" priority="-100" mode="in-paragraph">
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
<xsl:template match="OCem" mode="in-paragraph">
<x:i><xsl:value-of select="."/></x:i>
</xsl:template>
<xsl:template match="OCp">
<x:div>
<xsl:apply-templates mode="in-paragraph"/>
</x:div>
</xsl:template>
<xsl:template match="/*">
<x:div contentEditable="true">
<xsl:apply-templates/>
</x:div>
</xsl:template>
</xsl:transform>
</xbl:template>
</xbl:binding>
</xbl:xbl>
Any help would be greatly appreciated,
edit: after helpfull comment of tohuwawohu (below)
It seems that you need to define a variable which is bound to the instance data. Like this:
<xsl:template match="oc:instructionitem">
<xforms:group xbl:attr="model context ref bind" xxbl:scope="outer">
<xxforms:variable name="binding" as="node()?" xxbl:scope="inner" >
<xxforms:sequence select="." xxbl:scope="outer"/>
</xxforms:variable>
<xforms:group xxbl:scope="inner">
<!-- Variable pointing to external single-node binding -->
<xforms:group ref="$binding">
<xsl:call-template name="main"/>
</xforms:group>
</xforms:group>
</xforms:group>
</xsl:template>
<xsl:template name="main">
<x:div contentEditable="true">
<xforms:repeat nodeset="*">
<xsl:call-template name="paragraph"/>
</xforms:repeat>
</x:div>
</xsl:template>
However, the XSLT elements still cannot act on the data. It can only generate XFORMS elements, which can act on the data. This means that something like <xsl:template match="OCp"> will never be selected. This is why the above code uses named templates.
So the question still stands: can the bound data be made available to the xslt code?
Martijn

I didn't test it, but i think it should be possible by breaking the encapsulation. Without this, your XBL will only have access to the content of the bound node, as in your third code snippet. If you want to make the XBL access the XForms instance data, you will have to declare a variable inside the XBL that's pointing to the single-node binding of the bound node.
Here's the code snippet from the Wiki example:
<xforms:group xbl:attr="model context ref bind" xxbl:scope="outer">
<xforms:group xxbl:scope="inner">
<!-- Variable pointing to external single-node binding -->
<xxforms:variable name="binding" as="node()?">
<xxforms:sequence select="." xxbl:scope="outer"/>
</xxforms:variable>
...
</xforms:group>
</xforms:group>
Having defined the single-node binding like this, it should be possible to reference that variable and use it for xslt transformation. Just replace the XSLT template element matching the root node and point it to the content of the variable $binding:
<xsl:transform version="2.0">
<xsl:template match="#*|node()" priority="-100">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
<xsl:template match="text()" priority="-100" mode="in-paragraph">
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
<xsl:template match="OCem" mode="in-paragraph">
<x:i><xsl:value-of select="."/></x:i>
</xsl:template>
<xsl:template match="OCp">
<x:div>
<xsl:apply-templates mode="in-paragraph"/>
</x:div>
</xsl:template>
<xsl:template match="oc:instructionitem">
<xforms:group ref="$binding">
<x:div contentEditable="true">
<xsl:apply-templates/>
</x:div>
</xforms:group>
</xsl:template>
Hope this works for you. Maybe this requires making provisions if the bound node (in your example: oc:instructionitem) isn't empty, so the xslt may process both that content as well as the content of the $binding variable.

Related

XSL copy without values is it possible?

I want to compare two xmls.
1. First compare XML strucutre/schema.
2. Compare values.
I am using beyond compare tool to compare. Since these two xmls are different values, there are lot many differences in comparison report, for which I am not interested. Since, my focus now is to only compare structure/schema.
I tried to copy the xmls by following template, and other as well. But every time it is with values.
I surfed on google, xsl-copy command itself copies everything for selected node/element..
Is there any ways with which I can filter out values and only schema is copied ?
My Data :
<root>
<Child1>xxxx</Child1>
<Child2>yyy</Child2>
<Child3>
<GrandChild1>dddd<GrandChild1>
<GrandChild2>erer<GrandChild2>
</Child3>
</root>
Template used :
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:copy/>
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
OutPut Required :
Something like..
<root>
<Child1></Child1>
<Child2></Child2>
<Child3>
<GrandChild1><GrandChild1>
<GrandChild2><GrandChild2>
</Child3>
</root>
At the moment, you have a template matching text() to copy it. What you need to do is remove this match from that template, and have a separate template match, that matches only non-whitespace text, and remove it.
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
For white-space only text (as used in indentation), these will be matched by XSLT'S built-in templates.
For attributes, use xsl:attribute to create a new attribute, without a value, rather than using xsl:copy which will copy the whole attribute.
<xsl:attribute name="{name()}" />
Note the use of Attribute Value Templates (the curly braces) to indicate the expression is to be evaluated to get the string to use.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:attribute name="{name()}" />
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
</xsl:stylesheet>
Also note that attributes are considered to be unordered in XML, so although you have code to sort the attributes, and they probably will appear in the right order, you can't guarantee it.

Is it possible to preprocess xml source within the same XSLT Stylesheet?

Within the same XSLT (2.0) Stylesheet and transformation I would like to:
1) first preprocess the whole XML Datasource (Add a attribute
with a specific calculation to certain elements)
and then
2: transform the changed XML Datasource with the sylesheet's templates.
How can I achieve this? A code example would be nice?
Yes it is possible. One possibility would be to to proceed as follows:
<xsl:template match="/">
<!-- store the modified content in a variable -->
<xsl:variable name="preprocessed.doc">
<xsl:apply-templates mode="preprocess" />
</xsl:variable>
<!-- process the modified contents -->
<xsl:apply-templates select="$preprocessed.doc/*" />
</xsl:template>
<!-- first pass: sample process to add an attribute named "q" -->
<xsl:template match="*" mode="preprocess">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="q"><xsl:number count="*" level="any" /></xsl:attribute>
<xsl:apply-templates mode="preprocess" />
</xsl:copy>
</xsl:template>
<!-- "normal" processing of the modified content. It is able to used the newly processed attribute. -->
<xsl:template match="*[#q <= 5]">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="*"/>
In the first pass, an attribute is added, counting the element in the input XML.
In the processing, we only retain the elements having the value of the q attribute set a number less or equals to 5.

XSLT identity transform with append and root element namespaces

I'm trying to create an XSLT library for the common task of passing through most of the content of XML data with small alterations.
The include file currently looks like this (pass-through.xslt):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- change default behaviour to copying all attributes and nodes -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- copy element but add a child node -->
<xsl:template name="append">
<xsl:param name="element"/>
<xsl:param name="appendage"/>
<xsl:element name="{name($element)}">
<xsl:copy-of select="$element/namespace::*"/>
<xsl:copy-of select="$element/#*"/>
<xsl:apply-templates select="$element/node()"/>
<xsl:copy-of select="$appendage"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
You could then create a stylesheet which included it and not have to worry repeating yourself over and over (calling stylesheet).
<xsl:stylesheet version="1.0"
xmlns:ns="http://example/namespace"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="pass-through.xslt"/>
<!-- only worry about the transform you want -->
<xsl:template match="element-to-be-modified">
...
</xsl:template>
</xsl:stylesheet>
If you only want to add an element to the document then call "append".
<xsl:stylesheet
This works except for namespaces on an element I'm appending to. If I have an XML file with a namespace on the root element it chokes saying there is no namespace binding for the prefix. If I add the namespace of the root element to the library then it's happy but that kind of defeats the purpose of the library if you've got to go modifying it for every use.
I'd be happy adding the xmlns:ns="uri" to the calling stylesheet but the scope of the namespace declaration seems to only extend to that stylesheet - not to the included one where the "append" template is.
I'd like to be able to transform
<ns:root xmlns:ns="http://example/namespace">
<ns:child>old child</ns:child>
</ns:root>
To
<ns:root xmlns:ns="http://example/namespace">
<ns:child>old child</ns:child>
<ns:child>new child!</ns:child>
</ns:root>
Without having to include the identity transform boilerplate every time. I've tried an assortment of things, including adding a namespace="{namespace-uri()}" to the element in the "append" template but nothing seems to preserve the namespace prefix on the element appended to.
Replace
<xsl:element name="{name($element)}">
by
<xsl:element name="{name($element)}" namespace="{namespace-uri($element)}">
If, when you call your named template "append" you are already positioned on the element you are copying/modifying, then there really is no need to pass the current element as a parameter. You can just use xsl:copy here
<xsl:template name="append">
<xsl:param name="appendage"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:copy-of select="$appendage"/>
</xsl:copy>
</xsl:template>
In your example, you would just call it like this
<xsl:template match="ns:root">
<xsl:call-template name="append">
<xsl:with-param name="appendage"><ns:child>new child!</ns:child></xsl:with-param>
</xsl:call-template>
</xsl:template>
Using xsl:copy should copy your element and retain the namespace.

how to set Click event on Xslt output

This is my XSLT file:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:for-each select="//child_4331">
<xsl:value-of select="*"/>
<xsl:value-of select="#value" />
<xsl:attribute name="onclick">
<xsl:call-template name="GetOnClickJavaScript" />
</xsl:attribute>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
How can I set the click event on child_4331 value?
You didn't say, but I'm assuming you want to copy the child_4331 element and add the onclick attribute.
I would get rid of the template matching '/' and create one to match 'child_4331'. Use xsl:copy to create a copy of the element and add the attribute inside it. If the child_4331 element has attributes or child elements you will want to use xsl:apply-templates to pick them up.
Here is a sample snippet. Your solution may vary depending on your desired output. I can't give you more without knowing what your source XML looks like and what you expect to see in the result.
<xsl:template match="child_4331">
<xsl:copy>
<xsl:attribute name="onclick">
<xsl:call-template name="GetOnClickJavaScript" />
</xsl:attribute>
</xsl:copy>
</xsl:template>

How to remove <b/> from a document

I'm trying to have an XSLT that copies most of the tags but removes empty "<b/>" tags. That is, it should copy as-is "<b> </b>" or "<b>toto</b>" but completely remove "<b/>".
I think the template would look like :
<xsl:template match="b">
<xsl:if test=".hasChildren()">
<xsl:element name="b">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:template>
But of course, the "hasChildren()" part doesn't exist ... Any idea ?
dsteinweg put me on the right track ... I ended up doing :
<xsl:template match="b">
<xsl:if test="./* or ./text()">
<xsl:element name="b">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:template>
This transformation ignores any <b> elements that do not have any node child. A node in this context means an element, text, comment or processing instruction node.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="b[not(node()]"/>
</xsl:stylesheet>
Notice that here we use one of the most fundamental XSLT design patterns -- using the identity transform and overriding it for specific nodes.
The overriding template will be selected only for nodes that are elements named "b" and do not have (any nodes as) children. This template is empty (does not have any contents), so the effect of its application is that the matching node is ignored/discarded and is not reproduced in the output.
This technique is very powerful and is widely used for such tasks and also for renaming, changing the contents or attributes, adding children or siblings to any specific node that can be matched (avery type of node with the exception of a namespace node can be used as a match pattern in the "match" attribute of <xsl:template/>
Hope this helped.
Cheers,
Dimitre Novatchev
I wonder if this will work?
<xsl:template match="b">
<xsl:if test="b/text()">
...
See if this will work.
<xsl:template match="b">
<xsl:if test=".!=''">
<xsl:element name="b">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:template>
An alternative would be to do the following:
<xsl:template match="b[not(text())]" />
<xsl:template match="b">
<b>
<xsl:apply-templates/>
</b>
</xsl:template>
You could put all the logic in the predicate, and set up a template to match only what you want and delete it:
<xsl:template match="b[not(node())] />
This assumes that you have an identity template later on in the transform, which it sounds like you do. That will automatically copy any "b" tags with content, which is what you want:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Edit: Now uses node() like Dimitri, below.
If you have access to update the original XML, you could try using use xml:space=preserve on the root element
<html xml:space="preserve">
...
</html>
This way, the space in the empty <b> </b> tag is preserved, and so can be distinguished from <b /> in the XSLT.
<xsl:template match="b">
<xsl:if test="text() != ''">
....
</xsl:if>
</xsl:template>