I'm trying to get the value of iWantToGetThis.jpg and put it into an <img> during my XSL transformation. This is how my xml is structures:
<article>
<info>
<mediaobject>
<imageobject>
<imagedata fileref='iWantToGetThis.jpg'>
Here's what I've come up with for the XSL:
<xsl:template name="user.header.content">
<xsl:stylesheet xmlns:d='http://docbook.org/ns/docbook'>
<img><xsl:attribute name="src">../icons/<xsl:value-of select='ancestor-or-self::d:article/info/mediaobject/imageobject/imagedata/#fileref' /></xsl:attribute></img>
</xsl:stylesheet>
</xsl:template>
The image is being added to the output, but the src attribute is set to "../icons/", so I'm assuming it's not finding the fileref attribute in the XML. This looks perfectly valid to me, so I'm not sure what I'm missing.
I am not sure how you can get anything back at all, because that does not look like a valid XSLT document (I would expect the error "Keyword xsl:stylesheet may not contain img.").
However, it may be you are just showing a fragment of the code. If this is the case, your issue may be that you have only specified the namespace for the article element, when you really need to specify it for all elements in your xpath. Try this
<xsl:value-of
select="ancestor-or-self::d:article/d:info/d:mediaobject/d:imageobject/d:imagedata/#fileref"/>
Another possible problem may be because you are using the 'ancestor-or-self' xpath axis to find the attribute. This would only work if your current context was already on the article element, or one of its descendants.
As a side note, you can simplify the code by making use of Attribute Value Templates here
<img src="../icons/{ancestor-or-self::d:article/d:info/d:mediaobject/d:imageobject/d:imagedata/#fileref}" />
Related
I need to pass a node as a parameter to an XSL stylesheet. The issue is that the parameter gets sent as a string. I have seen the several SO questions regarding this topic, and I know that the solution (in XSLT 1.0) is to use an external node-set() function to transform the string to a node set.
My issue is that I am using eXist DB I cannot seem to be able to get its XSLT processor to locate any such function. I have tried the EXSLT node-set() from the namespace http://exslt.org/common as well as both the Saxon and Xalan version (I think eXist used to use Xalan but now it might be Saxon).
Are these extensions even allowed in the XSLT processor used by eXist? If not, is there something else I can do?
To reference or transform documents from the database, you should pass the path as a parameter to the transformation, and then refer to it using a parameter and variable
(: xquery :)
let $path-to-document := "/db/test/testa.xml"
let $stylesheet :=
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="source" required="no"/>
<xsl:variable name="error"><error>doc not available</error></xsl:variable>
<xsl:variable name="theDoc" select="if (doc-available($source)) then doc($source) else $error"/>
<xsl:template match="/">
<result><xsl:value-of select="$source"/> - <xsl:value-of select="node-name($theDoc/*)"/></result>
</xsl:template>
</xsl:stylesheet>
return transform:transform(<dummy/>,$stylesheet, <parameters><param name="source" value="xmldb:exist://{$path-to-document}"/></parameters>)
As per Martin Honnen's comments I don't think it is possible to pass an XML node via the <parameters> structure of the transform:transform() function in eXist. The function seems to strip away any XML tags passed to it as a value.
As a workaround I will wrap both my input XML and my parameter XML into a root element and pass that as input to the transform function.
I have to trasform the raw response of any OData feed (ATOM) in the form of a tree with expandable/collapsable nodes. For this purpose I am converting the raw response into HTML using XSLT transformation.
The problem is that response from some services have the feed element with namespace declarations as attributes. (eg: feed xmlns:d= ..., xmlns:m= ...).In my final output these namespace declarations are not displayed.
The XSLT processor ignores them while processing the attributes.(I am using the XPath expression "#*".) Is there a way to extract them using XSLT and display the namespace declaration content as-is in the trasformed output ?
Note that I get to know about these namespace declaration attributes at runtime in the OData response. I have no information before the query executes.
UPDATE:
Input : (RAW XML Entry)
<?xml version="1.0" encoding="utf-8"?><entry xml:base="http://services.odata.org/Northwind/Northwind.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><id>http://services.odata.org/Northwind/Northwind.svc/Regions(1)</id><category term="NorthwindModel.Region" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><link rel="edit" title="Region" href="Regions(1)" /><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Territories" type="application/atom+xml;type=feed" title="Territories" href="Regions(1)/Territories" /><title /><updated>2014-03-17T10:24:14Z</updated><author><name /></author><content type="application/xml"><m:properties><d:RegionID m:type="Edm.Int32">1</d:RegionID><d:RegionDescription xml:space="preserve">Eastern </d:RegionDescription></m:properties></content></entry>
Desired Output: (The same ATOM entry,as a XML tree, pretty printed with expandable/collapsable nodes)
-<entry xml:base="http://services.odata.org/Northwind/Northwind.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
-<id>
http://services.odata.org/Northwind/Northwind.svc/Regions(1)
</id>
<category term="NorthwindModel.Region" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<link rel="edit" title="Region" href= "Regions(1)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Territories" type="application/atom+xml;type=feed" title="Territories" href= "Regions(1)/Territories" />
<title/>
<updated>2014-03-17T10:06:25Z</updated>
-<author>
<name/>
</author>
-<content type="application/xml">
-<m:properties>
<d:RegionID m:type="Edm.Int32">1</d:RegionID>
<d:RegionDescription xml:space="preserve">Eastern </d:RegionDescription>
</m:properties>
</content>
</entry>
Output which I am getting.
-<entry xml:base="http://services.odata.org/Northwind/Northwind.svc/">
-<id>
http://services.odata.org/Northwind/Northwind.svc/Regions(1)
</id>
<category term="NorthwindModel.Region" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<link rel="edit" title="Region" href= "Regions(1)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Territories" type="application/atom+xml;type=feed" title="Territories" href= "Regions(1)/Territories" />
<title/>
<updated>2014-03-17T10:06:25Z</updated>
-<author>
<name/>
</author>
-<content type="application/xml">
-<m:properties>
<d:RegionID m:type="Edm.Int32">1</d:RegionID>
<d:RegionDescription xml:space="preserve">Eastern </d:RegionDescription>
</m:properties>
</content>
</entry>
Please note the missing name space declarations in the output's "entry" root element.
The output is a HTML which displays pretty-printed xml, with expandable/collapsable nodes and since it should display the data as-is, namespace declarations are required to be displayed in the output HTML.
Clicking on the "-" symbols collapses the nodes.
If a declaration is already in scope (remember, namespace bindings are inherited to children), it will not need to be redeclared and most XML serializers will not generate it. You haven't shown us an example yet, but I'd bet your output is fine as it is.
Namespace declarations in your source XML appear in the XPath data model as namespace nodes (not attribute nodes).
You need to make clear what you want to do with the namespaces. You're saying that you are generating HTML, but you also say you want to copy the namespaces to the output. That seems inconsistent. Show us you input and desired output.
I haven't managed to spot the difference between your actual output and your desired output (never was very good at "spot the ball" competitions), but from your revised description of the problem it sounds as if you should be using the namespace axis to find the in-scope namespaces for an element (in the same way as you are currently using the attribute axis).
The only tricky part is that the namespace axis will give you all the namespaces for an element and you probably only want to show those that are different from the namespaces of the parent element.
The details depend on whether you are using XSLT 1.0 or 2.0 - you need to tell us!
EDIT -
I've figured out the solution to my problem and posted a Q&A here.
I'm looking to process XML conforming to the Library of Congress EAD standard (found here). Unfortunately, the standard is very loose regarding the structure of the XML.
For example the <bioghist> tag can exist within the <archdesc> tag, or within a <descgrp> tag, or nested within another <bioghist> tag, or a combination of the above, or can be left out entirely. I've found it to be very difficult to select just the bioghist tag I'm looking for without also selecting others.
Below are a few different possible EAD XML documents my XSLT might have to process:
First example
<ead>
<eadheader>
<archdesc>
<bioghist>one</bioghist>
<dsc>
<c01>
<descgrp>
<bioghist>two</bioghist>
</descgrp>
<c02>
<descgrp>
<bioghist>
<bioghist>three</bioghist>
</bioghist>
</descgrp>
</c02>
</c01>
</dsc>
</archdesc>
</eadheader>
</ead>
Second example
<ead>
<eadheader>
<archdesc>
<descgrp>
<bioghist>
<bioghist>one</bioghist>
</bioghist>
</descgrp>
<dsc>
<c01>
<c02>
<descgrp>
<bioghist>three</bioghist>
</descgrp>
</c02>
<bioghist>two</bioghist>
</c01>
</dsc>
</archdesc>
</eadheader>
</ead>
Third example
<ead>
<eadheader>
<archdesc>
<descgrp>
<bioghist>one</bioghist>
</descgrp>
<dsc>
<c01>
<c02>
<bioghist>three</bioghist>
</c02>
</c01>
</dsc>
</archdesc>
</eadheader>
</ead>
As you can see, an EAD XML file might have a <bioghist> tag almost anywhere. The actual output I'm suppose to produce is too complicated to post here. A simplified example of the output for the above three EAD examples might be like:
Output for First example
<records>
<primary_record>
<biography_history>first</biography_history>
</primary_record>
<child_record>
<biography_history>second</biography_history>
</child_record>
<granchild_record>
<biography_history>third</biography_history>
</granchild_record>
</records>
Output for Second example
<records>
<primary_record>
<biography_history>first</biography_history>
</primary_record>
<child_record>
<biography_history>second</biography_history>
</child_record>
<granchild_record>
<biography_history>third</biography_history>
</granchild_record>
</records>
Output for Third example
<records>
<primary_record>
<biography_history>first</biography_history>
</primary_record>
<child_record>
<biography_history></biography_history>
</child_record>
<granchild_record>
<biography_history>third</biography_history>
</granchild_record>
</records>
If I want to pull the "first" bioghist value and put that in the <primary_record>, I can't simply <xsl:apply-templates select="/ead/eadheader/archdesc/bioghist", as that tag might not be a direct descendant of the <archdesc> tag. It might be wrapped by a <descgrp> or a <bioghist> or a combination thereof. And I can't select="//bioghist", because that will pull all the <bioghist> tags. I can't even select="//bioghist[1]" because there might not actually be a <bioghist> tag there and then I'll be pulling the value below the <c01>, which is "Second" and should be processed later.
This is already a long post, but one other wrinkle is that there can be an unlimited number of <cxx> nodes, nested up to twelve levels deep. I'm currently processing them recursively. I've tried saving the node I'm currently processing (<c01> for example) as a variable called 'RN', then running <xsl:apply-templates select=".//bioghist [name(..)=name($RN) or name(../..)=name($RN)]">. This works for some forms of EAD, where the <bioghist> tag isn't nested too deeply, but it will fail if it ever has to process an EAD file created by someone who loves wrapping tags in other tags (which is totally fine according to the EAD Standard).
What I'd love is someway of saying
Get any <bioghist> tag anywhere below the current node but
don't dig deeper if you hit a <c??> tag
I hope that I've made the situation clear. Please let me know if I've left anything ambiguous. Any assistance you can provide would be greatly appreciated. Thanks.
As the requirements are rather vague, any answer only reflects the guesses its author has made.
Here is mine:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:names>
<n>primary_record</n>
<n>child_record</n>
<n>grandchild_record</n>
</my:names>
<xsl:variable name="vNames" select="document('')/*/my:names/*"/>
<xsl:template match="/">
<xsl:apply-templates select=
"//bioghist[following-sibling::node()[1]
[self::descgrp]
]"/>
</xsl:template>
<xsl:template match="bioghist">
<xsl:variable name="vPos" select="position()"/>
<xsl:element name="{$vNames[position() = $vPos]}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<ead>
<eadheader>
<archdesc>
<bioghist>first</bioghist>
<descgrp>
<bioghist>first</bioghist>
<bioghist>
<bioghist>first</bioghist></bioghist>
</descgrp>
<dsc>
<c01>
<bioghist>second</bioghist>
<descgrp>
<bioghist>second</bioghist>
<bioghist>
<bioghist>second</bioghist></bioghist>
</descgrp>
<c02>
<bioghist>third</bioghist>
<descgrp>
<bioghist>third</bioghist>
<bioghist>
<bioghist>third</bioghist></bioghist>
</descgrp>
</c02>
</c01>
</dsc>
</archdesc>
</eadheader>
</ead>
the wanted result is produced:
<primary_record>first</primary_record>
<child_record>second</child_record>
<grandchild_record>third</grandchild_record>
I worked out a solution on my own and posted it at this Q&A because the solution is quite specific to a certain XML standard and seemed out of the scope of this question. If people feel it would be best to post it here as well, I can update this answer with a copy.
I am working with XML files that are generated by a digital video camera. The camera allows the user to save all of the camera's settngs to an SD card so that the settings can be recalled or loaded into another camera. The XSL stylesheet I am writing will allow users to view the camera's settings, as saved to the SD card in a web browser.
While most of the values in the XML file -- as formatted by my stylesheet -- make sense to humans, some do not. What I would like to do is have the stylesheet display text that is based on the value in the XML file but more easily understood by humans.
My sample XML file may be viewed here: http://josephthomas.info/Alexa/Setup_120511_140322.xml
A few lines down the page you will see:
Color GAMMA-SxS Rec_Log
While "Rec_Log" is a value that the cameras understand, it is not a value that the camera's users will understand. What I would like for the stylesheet to do is to display "LogC" instead.
In the XML file this value is defined thusly:
<DteLut lowerLimit="0" upperLimit="2">Rec_Log</DteLut>
The XSL formatting the sample page for this value is:
<tr>
<td class="title_column">Color GAMMA-SxS</td><td><xsl:value-of select="Settings/Groups/Recording/DteLut"/>
</td>
</tr>
So what I hope to do is have "LogC" displayed on the page rather than Rec_Log.
It seems to me that the "when" conditional statement is the correct approach, but I am not familiar enough with the syntax to cause this to happen. There are other values in the XML file that want replacing but the above is a good example of my mission.
What you could do is make use of make use of template matching, to match the exceptions to what you want to change. Firstly, add the following template to your XSL
<xsl:template match="DteLut[. = 'Rec_Log']">
<xsl:text>LogC</xsl:text>
</xsl:template>
Then, instead of the following line
<xsl:value-of select="Settings/Groups/Recording/DteLut"/>
Do the following line
<xsl:apply-templates select="Settings/Groups/Recording/DteLut"/>
When the value of*DteLut* is "Rec_Log", then the custom template will be matched to output "LogC" instead. When there is not a match, the default behaviour will kick in which will be to just output the text value as-is.
I would use a data-driven approach. Have a mapping file that gives all the translations:
<translations>
<translate from="Rec_log" to="LogC"/>
<translate .../>
</translations>
then define a key:
<xsl:key name="trans" match="translate" use="#from"/>
and then change
<xsl:value-of select="Settings/Groups/Recording/DteLut"/>
to
<xsl:value-of select="key('trans', Settings/Groups/Recording/DteLut,
doc('translations.xml'))/#to"/>
if using XSLT 2.0, or
<xsl:variable name="val" select="Settings/Groups/Recording/DteLut"/>
<xsl:for-each select="document('translations.xml')">
<xsl:value-of select="key('trans', $val)/#to"/>
</xsl:for-each>
if you're stuck with 1.0.
For some reason, YQL's XSLT table can't parse my stylesheet. I have used the stylesheet successfully with the W3C's XSLT service. Here's an example of the problem in YQL Console. Why does this not work in YQL?
Also, I have yet to figure out how to pass the results of a YQL query to the XSLT table as the XML to be transformed while also specifying a stylesheet url. Current workaround is to abuse the W3C's service.
Your stylesheet is defined as 1.0 but you're using replace() and tokenize() which is part of the 2.0 standard. However it is a fully valid XSLT/XPath 2.0 stylesheet.
As an addition to Per T answer, change this:
<xsl:variable name="r">
<xsl:value-of select="replace(tr/td/p/a/following-sibling::text(),
'\s*-\s*(\d+)\.(\d+)\.(\d+)\s*',
'$1,$2,$3')" />
</xsl:variable>
With this:
<xsl:variable name="r"
select="translate(tr/td/p/a/following-sibling::text(),'. -',',')">
These:
tokenize($r,',')[1]
tokenize($r,',')[2]
tokenize($r,',')[3]
With these:
substring-before($r,',')
substring-before(substring-after($r,','),',')
substring-after(substring-after($r,','),',')
Note: This is just in case you don't know the amount of digit in advance, otherwise you could do:
substring($r,1,2)
substring($r,4,2)
substring($r,7)
Also, this
replace(tr/td/p[#class='t11bold']/a,'\s+',' ')
It should be just this:
normalize-space(tr/td/p[#class='t11bold']/a)
And finaly this:
replace($d,'^[^\[]*\[\s*(\d+:\d{2})?\s*-?\s*([^\]]*)\]\s*$','$2')
Could be:
normalize-space(substring-after(substring-before(substring-after($d,'['),']'),'-'))