I have XML-file with complex hierarchical structure, which I copy with document fileName.xml. I want to insert a new element into other element. The target element calculating based on input file with concat('b_',$id).
For example fileName.xml:
<root>
<transform id="b_0">
<transform id="b_1">
<transform id="b_2">
<transform id="b_3"/>
<transform id="b_4"/>
</transform>
</transform>
</transform>
</root>
This is example of result document:
<root>
<transform id="b_0">
<transform id="obj_1"/>
<transform id="b_1">
<transform id="b_2">
<transform id="b_3">
<transform id="obj_2"/>
</transform>
<transform id="b_4"/>
</transform>
</transform>
</transform>
</root>
The pattern of my xslt code:
<xsl:variable name="transforms" select="document('fileName.xml')"/>
<xsl:variable name="table" select="."/>
<xsl:template match="tr">
<xsl:variable name="param" select="$table//tr/td[2]"/>
<xsl:variable name="id" select="concat('b_',$param)"/>
<xsl:copy-of select="$transforms"/>
<xsl:copy>
<Transform>
<xsl:attribute name="id"><xsl:value-of select="concat('obj_', position())"/></xsl:attribute>
<xsl:apply-templates select="$transforms/transform[#id = $id]"/>
</Transform>
</xsl:copy>
</xsl:template>
First of all, it is difficult to understand what you are trying to achieve, you don't show the input to you transform (although from the tag-names "table", "tr", "td", I'm guessing a html-formatted table).
You seem confused by some xslt-concepts:
the xsl:copy-of-tag copies a nodeset (in your case: the contents of the $transforms-variable) to the output
the xsl:copy-tag copies the current tag (in your case, a tr-tag, since that is what is matched by the template) to the output
Also, please note that:
position() will return the relative position of the current node (your tr) within its parent, maybe that is what you want, maybe not
$transforms/transform[#id=$id] will only match transform elements on the top level of $transforms. Use double slash // if you want to match elements on any level.
Now, for transforming the contents of the $transform-variable as you specify, inserting a new element inside an element you specify, you must create:
an identity-transform copying input to output
unless the tag is the one you are looking for, in which case you insert a new element and then copy
Since the tag you are looking for is dynamic, based on the $id-variable, this must be passed as a parameter. Based on your code, I guess you want to insert a new element inside the element having #id=$id as following-sibling.
You can create such a transform with this:
<xsl:template match="node()" mode="tx">
<!-- parameters controlling behaviour as input -->
<xsl:param name="id" />
<xsl:param name="pos" />
<!-- copies the current node -->
<xsl:copy>
<!-- copies attributes of the current node -->
<xsl:copy-of select="#*" />
<!-- if the next sibling with tag transform has id=$id, insert a new element -->
<xsl:if test="following-sibling::transform[position()=1 and #id=$id]">
<transform>
<xsl:attribute name="id">
<xsl:value-of select="concat('obj_', $pos)" />
</xsl:attribute>
</transform>
</xsl:if>
<!-- call recursively, make sure to include the parameters -->
<xsl:apply-templates select="node()" mode="tx">
<xsl:with-param name="id" select="$id"/>
<xsl:with-param name="pos" select="$pos" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
Note that I set a mode on this transform to be able to control when it is being called (I just want it called in specific circumstances, not on all input).
This can be called from your transform matching tr like this:
<xsl:template match="tr">
<xsl:variable name="param" select="$table//tr/td[2]" />
<xsl:variable name="id" select="concat('b_',$param)" />
<xsl:apply-templates select="$transforms" mode="tx">
<xsl:with-param name="id" select="$id"/>
<xsl:with-param name="pos" select="position()" />
</xsl:apply-templates>
</xsl:template>
A fully working example available here: http://xsltransform.net/bwdwsA/1
Related
I currently have something similar to the following XML:
<div class="newsFeed">
<div class="newsItem"><news position="3"/></div>
<categoryFilter dayFilter="4">
<div class="newsItem"><news position="2"/></div>
</categoryFilter>
</div>
I need to copy the XML, and output the nth news item on the node. Futhermore, I need to be able to filter that news. For this example, lets construct my news as follows:
<xsl:variable name="news">
<xsl:for-each select="1 to 30">
<item>
<day><xsl:value-of select=". mod 4" /></day>
<content>Content: <xsl:value-of select="." /></content>
</item>
</xsl:for-each>
</xsl:variable>
I actually use document() and use the for-each to sort it, but I'm trying to keep it succinct. This would mean that my output XML would be something like the following:
<div class="newsFeed">
<div class="newsItem">Content: 3</div>
<div class="newsItem">Content: 8</div>
</div>
The reason the second one is 8 is because the categoryFilter filters out every <item> where the day isn't 4 (which happens to be the 4th, 8th, 12th, and so on), and then we select the second one.
The XSLT to produce the above is as follows:
XSLT:
<xsl:template match="news">
<xsl:param name="items" select="$news" />
<xsl:variable name="position" select="#position" />
<xsl:copy-of select="$items/item[position()=$position]/content" />
</xsl:template>
<xsl:template match="categoryFilter">
<xsl:param name="items" select="$news" />
<xsl:variable name="day" select="#dayFilter" />
<xsl:variable name="filteredItems">
<xsl:for-each select="$items/item[day=$day]">
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates>
<xsl:with-param name="items" select="$filteredItems">
</xsl:apply-templates>
</xsl:template>
My problem lies with the <for-each>. It seems silly that I have to use a for-each to filter out the <item> nodes, but I can't find a better way. Simply doing a <xsl:variable select="$items/item[day=$day]"> changes the structure, and makes it so that the <xsl:template match="news"> doesn't work.
Is there a way to filter out child nodes without using a for-each? I am using <xsl:stylesheet version="3.0">
Instead of doing this...
<xsl:variable name="filteredItems">
<xsl:for-each select="$items/item[day=$day]">
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
... you could use a sequence. This will select the actual items, as opposed to create copies of them
<xsl:variable name="filteredItems">
<xsl:sequence select="$items/item[day=$day]" />
</xsl:variable>
This was, doing $filteredItems/item will still work.
Alternatively, you could take the opposite approach, and do away with the need to specify /item in all the expressions.
First, define your news variable like so:
<xsl:variable name="news" as="element()*">
This means you can write the expression that uses it like so:
<xsl:copy-of select="$news[position()=$position]/content" />
And similarly for filteredItems....
<xsl:variable name="filteredItems" select="$items[day=$day]" />
Below is my xml file.
<xml>
<top>
<main>
<firstname>John</firstname>
<lastname>John</lastname>
<table></table>
<chapter>
<firstname>Alex</firstname>
<lastname>Robert</lastname>
<p>Sample text chap</p>
<figure name="f1.svg"></figure>
<chapter>
<firstname>Rebec</firstname>
<lastname></lastname>
<p>Sample text</p>
<figure name="f2.svg"></figure>
</chapter>
</chapter>
</main>
</top>
</xml>
Desired output:
<bold>John
table
<bold>Robert
Sample text chap
f1.svg
<bold> Rebec
Sample text
f2.svg
Explaination: I have written an xslt to do this. I need to fetch the xml nodes dynamically. I cannot write: xsl:apply-templates select='main/lastname'. Because my xml format could change anytime.
I have tried a logic to first fetch all the xml nodes using '$root/*'. Then if 'table' element is encountered, i use xsl:apply-templates select='current()[name() = 'TABLE']' and perform table creation operations.
This works fine. I get the desired output but my figure elements only displays f1.svg at every place in the output. f2.svg is not shown.
And how do I match only 'lastname' and make it bold?
I want to make the code as generic/modular as possible so that it loops through all the elements of the xml tree and does some formatting on the specific nodes.
Below is a recursive xslt. With this my data is getting repeated. I am writing recursive template because xslt is not sequential.
XSLT:
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$NumberOfNodes" />
<xsl:with-param name="startIndex" select="1" />
<xsl:with-param name="context" select="$root/*" />
</xsl:call-template>
<xsl:template name="FetchNodes">
<xsl:param name="endIndex" />
<xsl:param name="startIndex" />
<xsl:param name="context" />
<xsl:if test="$startIndex <= $endIndex">
<xsl:if test="$context[$startIndex][name() = 'table']"">
<xsl:apply-templates select="$context[$startIndex][name() = 'table']"" mode="table" />
</xsl:if>
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$endIndex" />
<xsl:with-param name="startIndex" select="$startIndex + 1"/>
<xsl:with-param name="context" select="$context" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="node()" mode="table">
<xsl:value-of select="node()" />
</xsl:template>
With the above xslt, something is incorrect in the xpath of apply templates. Output is not proper.
I want XSL FO output.
Can anybody suggest something?
The problem it displaying "f1.svg" instead of "f2.svg" is because of this line
<xsl:variable name="ImageName">
<xsl:value-of select="$root/*/chapter/figure/#name" />
</xsl:variable>
You are already positioned on a figure at this point, so you only need to use a relative xpath expression here. The one you are currently using is an absolute path and so will always return the first #name attribute regardless of your context. It should look this this
<xsl:variable name="ImageName">
<xsl:value-of select="#name" />
</xsl:variable>
Or better still, like this
<xsl:variable name="ImageName" select="#name" />
Having said, the code is in a template that is trying to match an element a FIGURE element, which does not exist in the XML you have shown us. You can actually simplify the template match to this, for example
<xsl:template match="figure" mode="figure">
As for making things bold, you can just add the font-weight attribute to any block you want to make bold. Something like this:
<xsl:choose>
<xsl:when test="self::lastname">
<fo:inline font-weight="bold"><xsl:value-of select="text()" /></fo:inline>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="text()" />
</xsl:otherwise>
</xsl:choose>
EDIT: Having said all that, you may not be taking the correct approach to the problem. It may be better to use template matching, taking advantage of XSLT's built-in template to navigate over the document. Essentially, just write a template for each element you want to match, and generate the output, and then carry on matching its children.
For example, to turn a chapter into an fo:block do this
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
To output the firstname in bold, do this
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
To turn a figure into an image, do this (Note the use of Attribute Value Templates here, the curly braces indicate an expression to be evaluated, not output literally)
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
Try this XSLT as a starting point, and build on it
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="main">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
<xsl:template match="lastname"/>
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>
I have two tags in the input file, variable and type:
<variable baseType="int" name="X">
</variable>
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
And I need to generate the following output file:
<Item name="Y">
<Field name="X" type="Long" />
</Item>
So conceptually my approach here has been to convert the type tag into the Item tage, the variable instance to the Field. That's working fine:
<xsl:for-each select="type[#baseType='structure']">
<Item>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:for-each select="variableInstance">
<Field>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:attribute name="type">**THIS IS WHERE I'M STUCK**</xsl:attribute>
</Field>
</xsl:for-each>
</Item>
</xsl:for-each>
The problem I'm stuck on is:
I don't know how to get the variableInstance/Field tag to match on the variable tag by name, so I can access the baseType.
I need to map "int" to "Long" once I'm able to do 1.
Thanks in advance!
PROBLEM 1.
For the first problem that you have you can use a key:
<xsl:key name="variable-key" match="//variable" use="#name" />
That key is going to index all variable elements in the document, using their name. So now, we can access any of those elements by using the following XPath expression:
key('variable-key', 'X')
Using this approach is efficient when you have a lot of variable elements.
NOTE: this approach is not valid if each variable has its own scope (i.e. you have local variables which are not visible in different parts of the document). In that case this approach should be modified.
PROBLEM 2.
For mapping attributes you could use a template like the following:
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
The meaning of this transformation is: each time that we match a baseType attribute with int value, it has to be replaced by a Long value.
This transformation would be in place for each #baseType attribute in the document.
Using the described strategies a solution could be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- Index all variable elements in the document by name -->
<xsl:key name="variable-key"
match="//variable"
use="#name" />
<!-- Just for demo -->
<xsl:template match="text()" />
<!-- Identity template: copy attributes by default -->
<xsl:template match="#*">
<xsl:copy>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
<!-- Match the structure type -->
<xsl:template match="type[#baseType='structure']">
<Item>
<xsl:apply-templates select="*|#*" />
</Item>
</xsl:template>
<!-- Match the variable instance -->
<xsl:template match="variableInstance">
<Field>
<!-- Use the key to find the variable with the current name -->
<xsl:apply-templates select="#*|key('variable-key', #name)/#baseType" />
</Field>
</xsl:template>
<!-- Ignore attributes with baseType = 'structure' -->
<xsl:template match="#baseType[. = 'structure']" />
<!-- Change all baseType attributes with long values to an attribute
with the same name but with an int value -->
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
That code is going to transform the following XML document:
<!-- The code element is present just for demo -->
<code>
<variable baseType="int" name="X" />
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
</code>
into
<Item name="Y">
<Field baseType="Long" name="X"/>
</Item>
Oki I've got a solution for point 1 xsltcake slice or with use of templates. For point two I would probably use similar template to the one that Pablo Pozo used in his answer
I have xml like
<programs>
<program name="breaking laws" id="97;#ttt;#98;#tpl;#41;#fel" />
<program name="advanced technology" id="89;#hjk;#95;#uio;#81;#lpk" />
<program name="Emerging companies " id="88;#ple;#98;#tpl;#41;#fel" />
<program name="breakinglaws" id="97;#ttt" />
<program name="breakinglaws" id="97;#ttt;#98;#tpl;#81;#lpk" />
<program name="breakinglaws" id="99;#hklo;#95;#uio" />
</programs>
I would like to find all the unique ids text using xslt 1.0 i.e
ttt
tpl
fel
hjk
uio
lpk
ple
hklo
I was trying to do something using key and output tokens
<xsl:template name="output-tokens">
<xsl:param name="list" />
<xsl:variable name="newlist" select="concat(normalize-space($list), ' ')" />
<xsl:variable name="first" select="substring-before($newlist, ';#')" />
<xsl:variable name="remaining" select="substring-after($newlist, ';#')" />
<id>
<xsl:value-of select="$first" />
</id>
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$remaining" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Here is one solution
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/programs">
<!-- Variable containing concateneated list of all program elements**
<xsl:variable name="allprograms">
<xsl:apply-templates select="program"/>
</xsl:variable>
<!-- Call recursive template to split the list -->
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$allprograms" />
</xsl:call-template>
</xsl:template>
<!-- Template used to concatenate all program elements -->
<xsl:template match="program">
<xsl:value-of select="concat(#id, ';#') "/>
</xsl:template>
<!-- Recursive template to split out list into unique elements -->
<xsl:template name="output-tokens">
<!-- List to split -->
<xsl:param name="list"/>
<!-- List of all currently processed elements -->
<xsl:param name="newlist" select="';#'" />
<!-- Get first variable in list, and also the remaining part of the list -->
<xsl:variable name="first" select="substring-before($list, ';#')"/>
<xsl:variable name="remaining" select="substring-after($list, ';#')"/>
<!-- Check if first variable is not a number, and is not contained in currently processed list -->
<xsl:if test="number($first) != number($first) and not(contains($newlist, concat(';#', $first, ';#')))">
<id>
<xsl:value-of select="$first"/>
</id>
</xsl:if>
<!-- If there are still elements left in the list, call the template recursively -->
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$remaining"/>
<xsl:with-param name="newlist" select="concat($newlist, $first, ';#')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The idea is you first build up a single string containing all the program elements concatenated together. You then call a recursive template which gets the first element from the list, and then checks it has not already been processed (this is achieved by the template also containing a variable of already processed elements)
When the XSLT is applied to your sample XML, the following is output:
<id>ttt</id>
<id>tpl</id>
<id>fel</id>
<id>hjk</id>
<id>uio</id>
<id>lpk</id>
<id>ple</id>
<id>hklo</id>
The following works for me:
<xsl:variable name="core" select="document('CoreMain_v1.4.0.xsd')" />
<xsl:variable name="AcRec" select="document('AcademicRecord_v1.3.0.xsd')" />
<xsl:template match="xs:element">
<xsl:variable name="prefix" select="substring-before(#type, ':')" />
<xsl:variable name="name" select="substring-after(#type, ':')" />
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
But I use the same logic to handle the lookup of elements in the current or other documents based on the prefix, matching the node name in numerous places within the stylesheet. So, after changing the stylesheet version to 2.0, I tried:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
<xsl:function name="my:lookup">
<xsl:param name="attribute" />
<!-- parse the attribute for the prefix & name values -->
<xsl:variable name="prefix" select="substring-before($attribute, ':')" />
<xsl:variable name="name" select="substring-after($attribute, ':')" />
<!-- Switch statement based on the prefix value -->
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:function>
In my reading, I have only found examples of functions that return text - none call templates. I have the impression that an xsl:function should always return text/output...
After more investigation, it is entering the my:lookup function and the variables (prefix & name) are getting populated. So it does enter the xsl:choose statement, and the hits the appropriate when test. The issue appears to be with the apply-templates - value-of is displaying the child values; copy-of does as well, which I think is odd (shouldn't the output include the xml element declarations?). Why would there be a difference if code that works in a template declaration is moved to an xsl:function?
It's been a while since I did any serious XSLT, but IIRC your problem is not in the function, but in your template:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
The value-of statement won't inline the result tree returned by your function. Instead, it's going to try and reduce that result tree down into some kind of string, and inline that instead. This is why you're seeing the child values and not the elements themselves.
To inline the result tree returned by your function, you'll need to use some templates to copy the result tree into place.
So, your main template will need to change to this:
<xsl:template match="xs:element">
<xsl:apply-templates select="my:lookup(#type)" />
</xsl:template>
and you'll need some templates to do the recursive call. A quick google found a good discussion of the identity template that should do what you need.
(Please forgive any syntax errors, as I said, it's been a while ...)