I've been having a lot of trouble figuring this out.
What I want is a FAQ page that displays all the questions and answers on the same page. It gets the content from the questions and answers from subnode content.
So for example my tree looks like this:
FAQ
Question1
Question2
Question3
I want the template on FAQList to list the question and answer data from Question1...2... and 3 on the same page.
Every time I try to find examples of this being done I can only find examples that list the subpages as links. I don't want to link to the subpages. I want to actually print the content from them onto the parent page. Is that possible?
This is my attempt at it:
<xsl:for-each select="$currentPage/node">
Question: <xsl:value-of select="data [#alias = 'question']"/><br/>
Answer: <xsl:value-of select="data [#alias = 'answer']"/><br/>
</xsl:for-each>
But I had no results. Help me out here. I'm banging my head on this.
It all depends on the version of Umbraco you're running. There's a lot of documentation out there that refers to a much earlier version of Umbraco and simply won't work on more recent versions.
Assuming the document type alias of your questions is called 'FaqItem' and assuming that this XSLT is run on the respective content node (i.e. $currentPage is your FAQ parent node), you can use the following:
If you're using < Umbraco 4.5.1
<xsl:for-each select="$currentPage/child::node[#nodeTypeAlias='FaqItem']">
Question: <xsl:value-of select="./data[#alias='question']"/><br/>
Answer: <xsl:value-of select="./data[#alias='answer']"/><br/>
</xsl:for-each>
If you're using >= Umbraco 4.5.1
<xsl:for-each select="$currentPage/FaqItem">
Question: <xsl:value-of select="./question"/><br/>
Answer: <xsl:value-of select="./answer"/><br/>
</xsl:for-each>
For future reference
If you're familiar with XPath and want to figure out how Umbraco has stored the data, or to help with debugging. Look for a file called Umbraco.config (typically found in ~/App_Data/). This is the cached XML that all XSLTs will read from. Posting the relevant snippet from this file into your [Stack Overflow] question will increase the speed and chances of getting a response, as XSLT contributors will be able to help and not just Umbraco contributors.
Have to looked at razor? IMHO it is much easier to read and write.
#using System.Linq
#using System.Xml.Linq
#using umbraco.MacroEngines
#{
IEnumerable<DynamicNode> FAQs = new DynamicNode(Model.Id).Descendants("FaqItem").Items;
List<DynamicNode> faqList = FAQs.ToList();
#foreach(DynamicNode faq in faqList){
Question: #(faq.GetProperty("question").ToString())
Answer: #(faq.GetProperty("answer").ToString())
}
}
Related
System.Xml 'xsl:choose' cannot be a child of the 'xsl:call-template' element.
How do I know the order of such operators in xslt ?
XSLT is a publically documented W3C standard so check the relevant documents, for XSLT 3 at https://www.w3.org/TR/xslt-30/#element-call-template is saying e.g. <!-- Content: xsl:with-param* --> which explains the allowed content is a (possibly empty) sequence of xsl:with-param.
The spec also contains an XSD schema so any XML editor using that schema should assist you in showing the possible, allowed elements.
As well as the spec, for quick reference, you might like the MulberryTech reference cards at http://www.mulberrytech.com/quickref/
Alternatively, there are some good books on XSLT ;-)
If you're trying to supply a parameter conditionally, then instead of
<xsl:call-template
<xsl:choose
<xsl:with-param
use
<xsl:call-template
<xsl:with-param
<xsl:choose
The XML file I want to extract data from looks as below:
`<groups>
<group>approved</group>
<group>withdrawn</group>
</groups>`
I am using 'if' statement on this part of code to give me the data only if it is approved and don't give me the data if it has both groups(approved and withdrawn). I tried doing something but its not giving me the output. I tried the following:
<xsl:if test="groups/group='approved' and group!='withdrawn'">
<xsl:value-of select="name"/><xsl:text>
I also tried other things, but couldn't really get there. If anyone can help me with this simple question that will be really great. Thank you.
I am using 'if' statement on this part of code to give me the data
only if it is approved and don't give me the data if it has both
groups(approved and withdrawn).
I guess that you're looking for something like:
<xsl:if test="groups/group='approved' and not(groups/group='withdrawn')">
<!-- some stuff -->
</xsl:if>
This question actually asks something quite different. See the comments to #Tomalak's answer to understand what the OP really wanted. :(
Is there a way to store a variable/param during a for-each loop in a sort of array, and use it in another template, namely <xsl:template match="Foundation.Core.Classifier.feature">.
All the classname values that appear during the for-each should be stored. How would you implement that in XSLT? Here's my current code.
<xsl:for-each select="Foundation.Core.Class">
<xsl:for-each select="Foundation.Core.ModelElement.name">
<xsl:param name="classname">
<xsl:value-of select="Foundation.Core.ModelElement.name"/>
</xsl:param>
</xsl:for-each>
<xsl:apply-templates select="Foundation.Core.Classifier.feature" />
</xsl:for-each>
Here's the template in which the classname parameters should be used.
<xsl:template match="Foundation.Core.Classifier.feature">
<xsl:for-each select="Foundation.Core.Attribute">
<owl:DatatypeProperty rdf:ID="{Foundation.Core.ModelElement.name}">
<rdfs:domain rdf:resource="$classname" />
</owl:DatatypeProperty>
</xsl:for-each>
</xsl:template>
The input file can be found at http://krisvandenbergh.be/uml_pricing.xml
No, it is not possible to store a variable in a for-each loop and use it later.
This is because variables are write-once in XSLT (once set they are immutable) and they are strictly scoped within their parent element. Once processing leaves the for-each loop, the variable is gone.
XSLT does not work as an imperative programming language, but that's what you seem to be trying here. You don't need <xsl:for-each> in 98% of all cases and should not use it because it clogs your view of how XSLT works. To improve your XSLT code, get rid of all <xsl:for-each> loops you have (all of them, I mean it) and use templates instead:
<xsl:template match="Foundation.Core.Class">
<xsl:apply-templates select="
Foundation.Core.Classifier.feature/Foundation.Core.Attribute
" />
</xsl:template>
<xsl:template match="Foundation.Core.Attribute">
<owl:DatatypeProperty rdf:ID="{Foundation.Core.ModelElement.name}">
<rdfs:domain rdf:resource="{
ancestor::Foundation.Core.Class[1]/Foundation.Core.ModelElement.name[1]
}" />
</owl:DatatypeProperty>
</xsl:template>
(I'm not sure if the above is what you actually want, your question is rather ambiguous.)
Note the use of the XPath ancestor axis to refer to an element higher in the hierarchy (you seem to want the <Foundation.Core.ModelElement.name> of the parent class).
PS: Your XML is incredibly bloated and strongly redundant due to structured element names. Structure should come from... well... structure, not from elements like <Foundation.Core.Classifier.feature>. I'm not sure if you can do anything about it, though.
Addition:
To solve your xmi.id / xmi.idref problem, the best way is to use an XSL key:
<!-- this indexes all elements by their #xmi.id attribute -->
<xsl:key name="kElementByIdref" match="*[#xmi.id]" use="#xmi.id" />
<!-- now you can do this -->
<xsl:template match="Foundation.Core.DataType">
<dataTypeName>
<!-- pull out the corresponding element from the key, output its value -->
<xsl:value-of select="key('kElementByIdref', #xmi.idref)" />
</dataTypeName>
</xsl:template>
To better understand how keys work internally, you can read this answer I gave earlier. Don't bother too much with the question, just read the lower part of my answer, I explained keys in terms of JavaScript.
Ok, I now understand why for-each is not always needed. Consider the code below.
<Foundation.Core.DataType xmi.id="UID71848B1D-2741-447E-BD3F-BD606B7FD29E">
<Foundation.Core.ModelElement.name>int</Foundation.Core.ModelElement.name>
</Foundation.Core.DataType>
It has an id UID71848B1D-2741-447E-BD3F-BD606B7FD29E
Way elsewhere I have the following.
<Foundation.Core.StructuralFeature.type>
<Foundation.Core.DataType xmi.idref="UID71848B1D-2741-447E-BD3F-BD606B7FD29E"/>
</Foundation.Core.StructuralFeature.type>
As you can see, both codes have the same ID. Now I want to output "int", everytime this ID appears somewhere in the document. So basically idref="UID71848B1D-2741-447E-BD3F-BD606B7FD29" should be replaced by int, which can be easily derived from the Foundation.Core.ModelElement.name element.
Above is the main reason why I would like to store it in a variable. I don't get it how this can be dealt with using XSLT. If someone could elaborate on this, I hope there exists some kind of pattern to solve such a problem, since I need it quite often. What would be a good approach?
I understand this is maybe a bit off-topic, but I am willing to ask it in this thread anyway since this problem is very close to it.
My problem:
I have a wealth of atom RSS feed files which have many different atom entries in them and a few overlapping entries between files. I need to find and return an entry based on a URL from any one of the RSS feeds.
Technologies:
This code is being run through PHP 5.2.10's XLSTProcessor extension, which uses XSLT 1, has support for EXSLT and ability to run built in PHP functions. Saxan, Xalan or other similar solutions are not too helpful in this particular situation.
The following code is greatly simplified, but represents my situation.
rss-feed-names.xml:
<feeds>
<feed name="travel.xml"/>
<feed name="holidays.xml"/>
...
<feed name="summer.xml"/>
<feed name="sports.xml"/>
</feeds>
stylesheet.xsl
<xsl:stylesheet ...>
...
<func:function name="cozi:findPost">
<xsl:param name="post-url"/>
<xsl:variable name="blog-feeds" select="document('rss-feed-names.xml')/feeds"/>
<xsl:for-each select="$blog-feeds/feed">
<xsl:variable name="feed-file" select="document(#name)/atom:feed"/>
<xsl:variable name="feed-entry" select="$feed-file/atom:entry[atom:link[contains(#href, $post-url)]]"/>
<xsl:if test="$feed-entry">
<func:result select="$feed-entry"/><!-- this causes errors if more than one result is found -->
</xsl:if>
</xsl:for-each>
</func:function>
</xsl:stylesheet>
...
This code works just fine iff the atom entry that we're looking for appears in ONE of the files we look through. It may appear multiple times within that file, but as soon as it appears in two or more files, the code breaks because func:result was already instantiated and is being over-written, which is a no-no in XSLT.
If there is a way to ACTUALLY exit an EXSLT function or xsl:for-each "loop" (you can assign a return variable for a function, but the function continues; and for-each's are actually not loops, but more similar to function maps), that would be ideal but I have not found a way yet.
I have considered combining all feeds into one variable and removing the for-each loop altogether, but have had problems getting this to work from the beginning.
Any other possible solutions, ideas or pointers are much appreciated! The file relationship here and XML is pretty hard to change, so solutions suggesting such a change are not ideal.
Thanks in advance,
Tristan Eastburn
The general answer (as with Jim's response) is that you shouldn't put <func:result> inside <xsl:for-each>. My more specific solution for this case doesn't require you to use <xsl:for-each> or even <xsl:variable>. You can use just XPath alone:
<func:result select="(document
(document('rss-feed-names.xml')/feeds/#name)
/atom:feed
/atom:entry
[atom:link
[contains(#href, $post-url)]]
)[1]"/>
This works because document() can take a node-set. When it does, it goes and gets the document that's referenced by each node in the argument. Multiple input/multiple output.
That said, judicious use of explaining variables would be good to help readability. Still, you don't need to use <xsl:for-each>.
Since you can't force exit from the loop, you have to build the complete list and then return only the first element:
<func:function name="cozi:findPost">
<xsl:param name="post-url"/>
<xsl:variable name="blog-feeds" select="document('rss-feed-names.xml')/feeds"/>
<xsl:variable name="feedList">
<xsl:for-each select="$blog-feeds/feed">
<xsl:variable name="feed-file" select="document(#name)/atom:feed"/>
<xsl:variable name="feed-entry" select="$feed-file/atom:entry[atom:link[contains(#href, $post-url)]]"/>
<xsl:if test="$feed-entry">
<xsl:value-of select="$feed-entry"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<func:result select="$feedList[1]"/>
</func:function>
Handling of the empty-list condition is left as an exercise :-)
What is the best resource to learn the principles by which XSLT applies template rules?
Questions like this seem like they should be relatively easy to answer, and certainly so after some study. I'm almost embarrassed to post them. But I have looked at Kay's XSLT Programmer's Reference, the XSLT Cookbook, and Learning XSLT, and I still cannot find a clear explanation of how a node and its children will be processed by a set of rules. Maybe I'm an idiot, but I haven't found Python, Linux, Apache, MySQL, or bash to be anything like XSLT for sheer frustration.
UPDATE Thank you for your answers. I won't be able to pick this up again for several days, but I do appreciate the help.
This section in the specification on XSLT Template Rules is fairly straight-forward, and gives examples.
Don't think of XSLT as acting on your XML.
Think of your XML as flowing through XSLT.
XSLT starts at the root template, with an imaginary cursor at the root level of your document tree:
<xsl:template match="/">
...stuff in here...
</xsl:template>
In the middle, XSLT will start executing statements. If you have an <xsl:apply-templates select="...something..."/>, then it will find everything at the cursor that matches the XPath in the select=, and look for templates with a <xsl:template match="...something...">. The first one it finds, according to the precedence rules, it will execute.
While executing that new template, the same things apply, except the context is now the node that matched.
Those are the basics. So for example, if you have this XSLT program, then no matter what the input, you'll get <hello world="!"/> as the output:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<hello world="!"/>
</xsl:template>
</xsl:stylesheet>
But if you have input like this:
<people>
<name>Paige</name>
<name>Hayley</name>
<name>Hamlet</name>
</people>
and use this as a transform:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table>
<xsl:apply-templates select="/people/name[substring(., 1, 1) = 'H']"/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="name">
<tr>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
It will first set up an HTML file. It will then find all of the 'name' elements under the 'people' elements under the root element, and if they start with the letter 'H' a template will be searched for that matches 'name'. Since we have one, it will write out a table row with the name. It will execute that rule twice, and then the HTML elements will be closed, and the script will end.
Does this help?
The answers above all do a good job of explaining what happens if you have templates defined but it's very important to understand the built-in behaviour of XSLT as well.
Template processing is driven by the XSLT engine itself not (in general) by your code. In that way, it's very different to the procedural languages you've mentioned. If you have any background in functional programming that will help a great deal.
The initial behaviour of XSLT is to match the document node. The document node is an 'imaginary' node that acts as the parent of your xml document's root node. It represents the entire document. The built-in behaviour is effectively an xsl:apply-templates that looks like:
<xsl:apply-templates select='/|*'/>
The xpath statement matches the documentnode or any other element. It matches the root node and then traverses your document. Think of the document as a tree. At each element node it will execute exactly the same statement. XSLT traverses nodes in a left to right order (so if your root element has two children it will hit the first one in the document before the second). Since it's executing the select above it will then progress to the children of that node and do the same. This is a depth-first left-to-right traversal of the tree.
Now, at each element node the XSLT engine hits it will look for a matching template. The rules are relatively simple - it will choose to execute the most specific template. The built-in template is always the least specific. A template matching a full path is very specific:
<xsl:template match='/some/path/to/a/node'>...</xsl:template>.
A template matching just a node name is less specific:
<xsl:template match='node'>...</xsl:template>
If you have defined a template that the engine selects (any template you defined is going to get used in preference to a built-in), the default traversal above stops. It executes your template and stops unless your template starts a traversal again:
<xsl:template match='node'>
<p><xsl:value-of select='#text'/></p>
<xsl:apply-templates/>
</xsl:template>
That apply-templates above restarts our traversal (btw, apply-templates with no select attribute is the same as using select=''*.
I hope that helps. This is one of those situations where a diagram is the best possible approach.
If you have some money to spend on training, Ken Holman has an excellent set of XML/XSLT/XPATH/XSL-FO training courses.
http://www.cranesoftwrights.com/training/ptux/ptux-video.htm He links to some sample videos.
I have attended his training sessions in-person. He is very thorough and explains the processing model, functions, and aspects of XML/XSLT/XPATH. It is important to understand how the node trees are processed and how an XSLT engine "walks the tree". Then XSLT templates and the distinction between "push" and "pull" really make sense.
XSLT requires a different way of looking at things. Many programmers have a hard time adjusting or understanding XSLT because they keep thinking of things in terms of procedural code, rather than a more functional manner.