We're using XSLT2. Wondering if this is possible.
We have a tag filter, where a customer can choose to see all the themes which match ALL of their selections.
Here's an idea of the XML structure:
<themes>
<theme>
<name>Apple</name>
<tags>
<tag id="1">
<tag id="2">
</tags>
</theme>
<theme>
<name>Banana</name>
<tags>
<tag id="2">
<tag id="3">
</tags>
</theme>
<theme>
<name>Kiwifruit</name>
<tags>
<tag id="2">
<tag id="3">
</tags>
</theme>
</themes>
The customer chooses tags 2 and 3. The result we want is to only show is Banana and Kiwifruit, as they have all the tags the user selected.
We can't use the AND operator as the list of tags is long and unknown. We currently have this list passed into the XSLT and then tokenised:
<xsl:param name="tag_ids"/>
<xsl:variable name="tag_id_list" select="tokenize($tag_ids,',')"/>
This statement selects any theme that has any of the tag_id_list:
<xsl:for-each select="themes/theme/tags/tag[#id=$tag_id_list]">
But we're trying to find a XPath statement that makes sure the has ALL the s in $tag_id_list
Any ideas?! Thanks in advance.
You want this if the tags have to be in the right order:
themes/theme/tags[deep-equal($tag_id_list, tag/#id)]
or this if they can be in any order:
themes/theme/tags[
(every $tag in $tag_id_list satisfies $tag = tag/#id)
and
(every $tag in tag/#id satisfies $tag = $tag_id_list)]
You could count the number of tags that match, and see if it equals to the number of tags in tag_id_list. For example
<xsl:variable name="tagcount" select="count($tag_id_list)" />
<xsl:for-each select="themes/theme[count(tags/tag[#id=$tag_id_list]) = $tagcount]">
If the customer could enter duplicate tags (like '2,2,3') then you might have to change tagcount to this
<xsl:variable name="tagcount" select="count(distinct-values($tag_id_list))" />
Related
Consider this XML
I want to sum all the values in Quantity and for those lines missing a value, I want to use default value of 1,00
I previously asked a question skipping the lines without Quantity
(Need to summarize nodes where data can be empty).
Got a great answer to use
sum(/Top/Lines/Line[string(#Quantity)]/number(translate(#Quantity, ',', '.')))
How can I adjust the code above to fit my new requirements?
Thanks
/M
<Top>
<Lines>
<Line ID="1" Quantity="1,00" />
<Line ID="2" Quantity="11,00" />
<Line ID="3" Quantity="" />
<Line ID="4" Quantity="" />
<Line ID="5" Quantity="10,00" />
</Lines>
</Top>
Assuming you're still using XPath/XSLT 2.0, you could so:
sum(/Top/Lines/Line/(if (string(#Quantity)) then number(translate(#Quantity, ',', '.')) else 1))
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']"/>
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!
XSLT available is 1.0.
I'm working on a dual-language site in an XML-based CMS (Symphony CMS), and need to replace the English version of a category name with the French version.
This is my source XML.
<data>
<our-news-categories-for-list-fr>
<entry id="118">
<title-fr handle="technology">Technologie</title-fr>
</entry>
<entry id="117">
<title-fr handle="healthcare">Santé</title-fr>
</entry>
</our-news-categories-for-list-fr>
<our-news-article-fr>
<entry id="100">
<categories>
<item id="117" handle="healthcare" section-handle="our-news-categories" section-name="Our News categories">Healthcare</item>
<item id="118" handle="technology" section-handle="our-news-categories" section-name="Our News categories">Technology</item>
</categories>
<main-text-fr mode="formatted"><p>Blah blah</p></main-text-fr>
</entry>
</our-news-article-fr>
</data>
This is part of the XSLT that I currently have for the French version.
<xsl:template match="data">
<xsl:apply-templates select="our-news-article-fr/entry"/>
</xsl:template>
<xsl:template match="our-news-article-fr/entry">
<xsl:if test="categories/item">
<p class="category">In:</p>
<ul class="category">
<xsl:for-each select="categories/item">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template match>
The problem: the visible text of the anchor (<xsl:value-of select="."/>) gives the English version of the category title.
The handles of the following nodes match (all handles are in English), and so I'm thinking I should be able to match one from the other.
/data/our-news-categories-for-list-fr/entry/title-fr/#handle (value of title-fr node is French translation of category title)
/data/our-news-article-fr/entry/categories/item/#handle
I'm new to XSLT and am struggling to find how to do this.
Many thanks.
Add <xsl:key name="k1" match="our-news-categories-for-list-fr/entry" use="#id"/> as a child of your XSLT stylesheet element. Then use e.g. <li><xsl:value-of select="key('k1', #id)/title-fr"/></li>.
../our-news-categories-for-list-fr/entry/title-fr/text() instead of #handle should do it
Your problem is that you are in
<our-news-article-fr>
and need to reference
<our-news-categories-for-list-fr>
so I do a parent .. to walk up the tree and then down the entry nodes
Within the xsl:for-each repetition instruction, the context is our-news-article-fr/entry/categories/item. If you use . you select the current context, that's why you are receiving the english version there.
Another approach (not saying the simplest and the best one) is simply specify an XPath expression which locates the correct node. You can use the ancestor:: axis to go out from the current context to data and then use your test node. The needed predicate must match against the current context using current() function:
<xsl:value-of select="
ancestor::data[1]/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]
"/>
If data is the root of your document you can obviously use an absolute location path:
/
data/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]
Is it possible to check an elements ComplexType?
i have this (simplified):
complexType Record
complexType Customer extension of Record
complexType Person extension of Record
<xsl:template match="/">
<records>
<xsl:apply-templates />
</records>
</xsl:template>
<xsl:template match="!!! TYPECHECK FOR RECORD !!!" name="Record">
<record><xsl:value-of select="." /></record>
</xsl:template>
is it possible to check elementstype incl. inheritence?
i dont know the elements name only that they are a subtype of Record.
schema 1:
complexType name="Customer"
extension base="Record"
element name="customers"
element name="customer" type="Customer"
schema 2:
complexType name="Person"
extension base="Record"
element name="persons"
element name="person" type="Person"
schema ?:
complexType name="UnknownType"
extension base="Record"
element name="unknowns"
element name="unknown" type="UnknownType"
xml 1:
<customers>
<customer />
<customer />
</customers>
xml 2:
<persons>
<person />
<person />
</persons>
xml ?:
<?s>
<? />
<? />
</?s>
the xml input ist custom so i have to match by the type (i think)
In XPath 2.0 (and this means XSLT 2.0) one can use the instance-of operator:
. instance-of element(*, my:Record)
I'm not sure what you mean.
Different types have different tag names, and in xslt you should look at those to decide what type a node is.
Making sure a node indeed has a structure prescribed by your logic is not an xslt task. You should validate the document against a schema before passing it to an xslt processor to achieve this.
EDIT
I'm still not sure, but it seems like this question of mine could be of some help.