Using XSLT 1.0
Is it possible to filter many to many attributes, i mean as below example:
"../../../../fieldmap/field[#name" i.e. more then 1 elements as fieldmap containing "field/#name" attribute are exists and it is comparing with definition/#title and there too more then one definition element exists containing #title.
EXAMPLE:
<xsl:for-each select="../../../../fieldmaps/field[#name=../destination/#title]">
Can you please suggest me how it could possible to achieve -- if field containing #name existed in any of defination/#title then only those records should be process within for-each loop?
(as now, it looks, it will just compare with first #title attribute and consider all fieldmaps/field/#name attributes)
Thanks
You can achieve that using a variable:
<xsl:variable name="titles" select="../destination/#title"/>
<!--now "titles" contains a nodeset with all the titles -->
<xsl:for-each select="../../../../fieldmaps/field[#name=$titles]">
<!-- you process each field with a name contained inside the titles nodeset -->
</xsl:for-each>
Here you have a simplified example:
INPUT:
<parent>
<fieldmaps>
<field name="One"/>
<field name="Two"/>
<field name="Three"/>
</fieldmaps>
<destinations>
<destination title="One"/>
<destination title="Two"/>
</destinations>
</parent>
TEMPLATE:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- ++++++++++++++++++++++++++++++++ -->
<xsl:template match="parent">
<Results>
<xsl:variable name="titles" select="destinations/destination/#title"/>
<xsl:for-each select="fieldmaps/field[#name=$titles]">
<Result title="{#name}"/>
</xsl:for-each>
</Results>
</xsl:template>
<!-- ++++++++++++++++++++++++++++++++ -->
</xsl:stylesheet>
OUTPUT:
<Results>
<Result title="One"/>
<Result title="Two"/>
</Results>
I hope this helps!
Related
XSL stylesheet is generating repeated output. Below is an example of it. The same thing is repeated thrice. In the first set of xml, i am only getting value of first attribute and in the secound from second attribute and so on.
<?xml version="1.0" encoding="UTF-8"?>
<obj>
<desc value="113662176"/>
<index value="" name="MATERIALNUMMER"/>
<index value="" name="DOKUMENTENART"/>
</obj>
<obj>
<desc value=""/>
<index value="66260383180" name="MATERIALNUMMER"/>
<index value="" name="DOKUMENTENART"/>
</obj>
<obj>
<desc value=""/>
<index value="" name="MATERIALNUMMER"/>
<index value="Fertigungsauftrag" name="DOKUMENTENART"/>
</obj>
I have also tired with xsl when and choose but the output was same. below is an example input xml with some attributes.
<?xml version = "1.0" encoding = "utf-8"?>
<root>
<document>
<field level = "document" name = "Fertigungsauftragsnummer" value = "113662176"/>
<field level = "document" name = "Materialnummer" value = "66260383180"/>
<field level = "document" name = "Dokumentenart" value = "Fertigungsauftrag"/>
</document>
</root>
below is the xsl style sheet which i am using for conversion. In the xsl template if i use match="/*" i dont get repeated output neither do i get values of xml attributes. It seems that for every xsl if we have one specific output. How can I make xsl style sheet to compile only once the input xml for all the xsl if statements?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root/document/*">
<xsl:text>
</xsl:text><xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>obj</xsl:text><xsl:text disable-output-escaping="yes">></xsl:text><xsl:text>
</xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>desc value="</xsl:text>
<xsl:if test="#name='Fertigungsauftragsnummer'">
<xsl:value-of select="#value" />
<xsl:if test="#name='Materialnummer'">
<xsl:value-of select="#value" />
<xsl:if test="#name='Dokumentenart'">
<xsl:value-of select="#value" />
</xsl:if>
</xsl:if>
</xsl:if>
<xsl:text disable-output-escaping="yes">"/></xsl:text><xsl:text>
</xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>/obj</xsl:text><xsl:text disable-output-escaping="yes">></xsl:text><xsl:text>
</xsl:text>
</xsl:template>
</xsl:transform>
Expected output is shown below
<?xml version="1.0" encoding="UTF-8"?>
<obj>
<desc value="113662176"/>
<index value="66260383180" name="MATERIALNUMMER"/>
<index value="Fertigungsauftrag" name="DOKUMENTENART"/>
</obj>
You current approach of using xsl:text with disable-output-escaping is not the right approach when building XML. You should be writing out the XML tags directly. It looks like you want to convert document elements into obj elements so you should have a template like this
<xsl:template match="/root/document">
<obj>
<xsl:apply-templates select="field" />
</obj>
</xsl:template>
(In your current XSLT, you were converting field into obj which is why you got three of them).
It also looks like you want fields with name "Fertigungsauftragsnummer" into a desc element, so just create a template for that.
<xsl:template match="field[#name='Fertigungsauftragsnummer']">
<desc value="{#value}"/>
</xsl:template>
Note the syntax for the attributes using curly braces. These are known as Attribute Value Templates.
For the other two fields, you can share a common template, as it looks like you just want to make the name upper-case in both cases
<xsl:template match="field">
<index value="{#value}" name="{translate(#name, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')}"/>
</xsl:template>
Try this XSLT
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/root/document">
<obj>
<xsl:apply-templates select="field" />
</obj>
</xsl:template>
<xsl:template match="field[#name='Fertigungsauftragsnummer']">
<desc value="{#value}"/>
</xsl:template>
<xsl:template match="field">
<index value="{#value}" name="{translate(#name, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')}"/>
</xsl:template>
</xsl:stylesheet>
I'm just starting out with XSLT, so bear with me. I am processing a fairly complex document with a structure similar to the one below. It is divided into two sections, data and meta. For each data/item I need to look up the "actual" class in the corresponding meta/item.
<root>
<data>
<item id="i1">
<h3 id="p2">akkakk</h3>
<p id="p3">osijaoid</p>
<p id="p4">jqidqjwd</p>
<item>
</data>
<meta>
<item ref="i1">
<p ref="p2" class="heading"/>
<p ref="p3" class="heading"/>
<p ref="p4" class="body"/>
</item>
</meta>
</root>
I need to group adjacent p elements in meta on their class attribute. I thought a helper function could make this a bit cleaner:
<xsl:function name="fn:node-groups" as="node()*">
<xsl:param name="parent"/>
<xsl:variable name="nodeRefs" select="root($parent)//meta/item[#ref = $parent/#id]/p"/>
<xsl:for-each-group select="$nodeRefs" group-adjacent="#class">
<group class="{#class}">
<xsl:for-each select="current-group()">
<node ref="{#ref}"/>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>
And then use it when processing the data nodes, like so. My problem is that I can not select further from the nodeset returned by the function.
<xsl:template match="//data/item">
<!-- works -->
<xsl:variable name="test1" select="fn:node-groups(.)"/>
<!-- works -->
<xsl:variable name="test2" select="fn:node-groups(.)/*"/>
<!-- does not work -->
<xsl:variable name="test3" select="fn:node-groups(.)/group[#class = 'heading']"/>
</xsl:template>
I can write a star as in test2, and that gives me all node nodes. But anything else just gives me an empty nodeset. Or at least it looks that way, but at this point I really don't know anymore.
I suppose your stylesheet has an xmlns="http://example.com/foo" namespace declaration scope for the function that puts the result elements in the function in that namespace and then obviously fn:node-groups(.)/group will not select them as it selects a group element in no namespace. So make sure your function overrides that namespace with <xsl:for-each-group select="$nodeRefs" group-adjacent="#class" xmlns="">.
Another reason could be that your stylesheet defines xpath-default-namespace.
I also think that your third variable needs to be <xsl:variable name="test3" select="mf:node-groups(.)/node"/> as obviously the node sequence returned by your function already is a sequence of group elements, if you want to filter that sequence then use <xsl:variable name="test3" select="fn:node-groups(.)[#class = 'heading']"/>
I was hoping I could get a little assistance with an XSLT transform. I can't seem to get it right.
Here is a sample of the source xml document:
<?xml version="1.0" encoding="UTF-8"?>
<Locations>
<header>
<location>Location Field</location>
<job_function>Job Function Field</job_function>
<count>Count</count>
</header>
<data>
<location>2177</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>OPS</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>SLS</job_function>
<count>5</count>
</data>
<data>
<location>2179</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2179</location>
<job_function>SEC</job_function>
<count>1</count>
</data>
</Locations>
I want to transform it into the following format:
<Locations>
<data>
<PeopleSoftID>2177</PeopleSoftID>
<ADM>1</ADM>
<OPS>1</OPS>
<SLS>5</SLS>
<TotalCount>7</TotalCount>
</data>
<data>
<PeopleSoftID>2179</PeopleSoftID>
<ADM>1</ADM>
<SEC>1</SEC>
<TotalCount>2</TotalCount>
</data>
</Locations>
So basically, as you can see in the sample source document there are multiple elements that have the same value. In the destination document, there should now only be one record (<PeopleSoftID> element) per <location> element value in the source document. Since there were 3 <location> elements with the value of 2177, the destination document now has just 1 <PeopleSoftID> element that contains that value. The value of the <job_function> element in the source document becomes an element in the destination document. The value of that new element ends up being the sibling value of the <count> element from the source document. The <TotalCount> element in the destination document is the SUM of the values of all the new elements that are generated from the source <job_function> element.
I hope that explanation did not confuse anybody =).
I am a little new to XSLTs still so I am having trouble getting the logic right on this.
I can only use XSLT 1.0 too.
If I did not provide enough information let me know, and I will try to provide more as soon as I am able.
Thanks guys!
Read up on xsl:key and grouping with the Muenchian Method
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<!--Group the data elements by their location values -->
<xsl:key name="data-by-location" match="data" use="location" />
<xsl:template match="Locations">
<xsl:copy>
<!--Get a distinct list of location values,
using the Muenchian Method -->
<xsl:for-each
select="data[generate-id() =
generate-id(key('data-by-location', location)[1])]">
<xsl:copy>
<PeopleSoftID>
<xsl:value-of select="location"/>
</PeopleSoftID>
<!--For every data element matching this location... -->
<xsl:for-each select="key('data-by-location',location)">
<!--Create an element using the job_function
as the element name -->
<xsl:element name="{job_function}">
<!--The value of the count element
as the value of the generated element-->
<xsl:value-of select="count"/>
</xsl:element>
</xsl:for-each>
<TotalCount>
<!--calculate the sum of all the count element values
for this location -->
<xsl:value-of select="sum(key('data-by-location',
location)/count)"/>
</TotalCount>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I've been looking at threads about sorting XML using <xsl:sort> and variables, and still can't get my sorting to work. Here's some XML structure for context:
<records>
<record>
<contributors>
<authors>
<author>Author 1</author>
<author>Author 2</author>
</authors>
</contributors>
<titles>
<title>I'm a Title!</title>
<secondary-title></secondary-title>
</titles>
<dates>
<year>1901</year>
</dates>
</record>
<record>...</record>
<record>...</record>
</records>
And here's the relevant XSL:
<xsl:variable name="sortby"
select="contributors/authors/author[1]" as="element()*"/>
<xsl:for-each select="//record">
<xsl:sort select="$sortby" order="ascending"/>
[a bunch of HTML to render the records as a bibliography]
</xsl:for-each>
If I copy the string in the variable's "select" attributes and paste it into sort, like this:
<xsl:sort select="contributors/authors/author[1]" order="ascending">
then it works. With the variable, it doesn't. I tried it both with and without as="element()*" -- Help?
It is not possible in general to perform dynamic evaluation of XPath expressions -- neither in XSLT/Xpath 1.0 or in XSLT/Xpath 2.0.
This said, one can always implement sorting guided by variables, if there are some limitations on their contents.
Here is an example that solves your specific problem and a class of similar problems:
<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:param name="pSortName" select="'authors'"/>
<xsl:param name="pSortPosition" select="1"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="records">
<records>
<xsl:apply-templates>
<xsl:sort select=
".//*[name()=$pSortName]/*
[position()=$pSortPosition]"/>
</xsl:apply-templates>
</records>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on this XML document:
<records>
<record>
<contributors>
<authors>
<author>X.Y.Z</author>
<author>A.B.C</author>
</authors>
</contributors>
<titles>
<title>Title B</title>
<secondary-title>Title AB</secondary-title>
</titles>
<dates>
<year>1901</year>
</dates>
</record>
<record>
<contributors>
<authors>
<author>T.U.V</author>
<author>D.E.F</author>
</authors>
</contributors>
<titles>
<title>Title A</title>
<secondary-title>Title BA</secondary-title>
</titles>
<dates>
<year>2001</year>
</dates>
</record>
</records>
the wanted, correct result (the records sorted by first author) is produced:
<records>
<record>
<contributors>
<authors>
<author>T.U.V</author>
<author>D.E.F</author>
</authors>
</contributors>
<titles>
<title>Title A</title>
<secondary-title>Title BA</secondary-title>
</titles>
<dates>
<year>2001</year>
</dates>
</record>
<record>
<contributors>
<authors>
<author>X.Y.Z</author>
<author>A.B.C</author>
</authors>
</contributors>
<titles>
<title>Title B</title>
<secondary-title>Title AB</secondary-title>
</titles>
<dates>
<year>1901</year>
</dates>
</record>
</records>
If we change the parameters to:
<xsl:param name="pSortName" select="'authors'"/>
<xsl:param name="pSortPosition" select="2"/>
then the transformation sorts using as sort-key the second author.
If we change the parameters to:
<xsl:param name="pSortName" select="'titles'"/>
<xsl:param name="pSortPosition" select="1"/>
then the transformation sorts using as sort-key the titles/title element.
If we change the parameters to:
<xsl:param name="pSortName" select="'titles'"/>
<xsl:param name="pSortPosition" select="2"/>
then the transformation sorts using as sort-key the titles/secondary-title element.
Do note: Here we assume that there will be a unique descendent of any element being sorted, whose name is equal to the value specified in pSortName. We also assume that this element has children elements and pSortPosition specifies the position of the child to be used as a sort key.
Two other solutions that haven't been mentioned:
(a) Many processors have an extension, called something like dyn:evaluate() that evaluates an XPath expression supplied in the form of a character string
(b) In some environments it's feasible to modify the stylesheet (using an XSLT transformation of course) before executing it. This allows you to insert whatever XPath expression you need. In XSLT 2.0 you can write the sort key as select="my:sort(.)", and then define my:sort() in a separate xsl:included stylesheet module.
Another related option which I've seen is to use an external entity: select="&sortkey;", where the entity reference can be redirected programmatically to a different XPath expression using an EntityResolver registered with the XML parser.
You can't use a variable in the select= portion of an xsl:sort element. The XSLT specification states:
xsl:sort has a select attribute whose value is an expression. For each node to be processed, the expression is evaluated with that node as the current node and with the complete list of nodes being processed in unsorted order as the current node list.
The expression is evaluated only once, not twice as you seem to expect. Your expression $sortby is evaluated once to result in something that is the same every time (the actual value depends on what was the current node at the time the xsl:variable assignment ran). Therefore, the sort does not change the order of the selected elements.
You must use a specific expression as the sort criteria, as you have discovered.
I am have trouble converting my xml using XSLT back to xml in the convert format, this is my XML below:
<?xml version="1.0" encoding="UTF-8" ?>
<DOCUMENTS>
<DOCUMENTS_INFO DOCUMENT_COUNT="8" />
<DOCUMENT_GROUP NAME="Invoices" DISPLAY_NAME="INVOICES">
<DOCUMENT>
<FIELD>
<name>USER</name>
<display_name>Deposited by</display_name>
<value>machine</value>
</FIELD>
<FIELD>
<name>DATE</name>
<display_name>Archive date</display_name>
<value>21/05/2009</value>
</FIELD>
</DOCUMENT>
<DOCUMENT>
<FIELD>
<name>USER</name>
<display_name>Deposited by</display_name>
<value>machine</value>
</FIELD>
<FIELD>
<name>DATE</name>
<display_name>Archive date</display_name>
<value>21/06/2009</value>
</FIELD>
</DOCUMENT>
</DOCUMENT_GROUP>
</DOCUMENTS>
I need to convert so I can read it into my datagrid in ASP.NET so the output would be something like this below:
Deposited by | Archive date
machine | 21/05/2009
machine | 21/06/2009
Many Thanks
You don't really need to do any conversion at all. If you want to pull the data into a dataset or something so that you can display it, all you need is a little bit of XPath.
The XPath to get all DOCUMENT elements:
//DOCUMENT
And the XPath to get a pair of value elements based on the name value, based in a DOCUMENT element:
FIELD[name = 'USER']/value
FIELD[name = 'DATE']/value
So depending on the technology you use to parse the XML, you'll basically need a loop over the first expression, and then run the following two expressions on the results of the first loop. In XSL it'll look something like this:
<xsl:template match="/">
<xsl:for-each select="//DOCUMENT">
<xsl:value-of select="FIELD[name = 'USER']/value"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="FIELD[name = 'DATE']/value"/>
</xsl:for-each>
</xsl:template>
Like anything, there's more than one way to do this. You can use a template as well:
<xsl:template match="/">
<xsl:apply-templates select="//DOCUMENT" />
</xsl:template>
<xsl:template match="DOCUMENT">
<xsl:value-of select="FIELD[name = 'USER']/value"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="FIELD[name = 'DATE']/value"/>
</xsl:template>
Both of these will give the output you gave as an example, but you're probably better off reading the XPath values directly into a dataset or writing your own adapter to pull these values out and load them directly into a datagrid.