How can I get the 'key' of a keyword from an XSLT TBB? - xslt

I am working on the XSLT TBB (using the XSLT Mediator on Tridion 2011 SP1) to retrieve the key Value from the Keyword.
My Keyword looks like this.
Value: Some Value
Key: Its ID is 123
It's a normal Keyword.
I have created a schema with a field. The values will be selected from List and from Category.
The component Source looks like this:
This is the Component source directly taken from the component of Tridion UI.
<Content xmlns="Some Name space">
<keywordlink xlink:href="tcm:202-9737-1024" xlink:title="Some Value"
xmlns:xlink="http://www.w3.org/1999/xlink">Some Value</keywordlink>
</Content>
When I observed the tcm:Component source from template builder, I observed that there are no attributes present for the field.
<Content xmlns="Some Name space">
<keywordlink>Some Value</keywordlink>
</Content>
I want to retrieve the Key value of the keyword.
I wrote a XSLT TBB like this. I am using XSLT mediator to execute XSLT TBBs.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:simple="Some Name space"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:tcm="http://www.tridion.com/ContentManager/5.0"
xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:transform-ext="urn:tridion:transform-ext"
xmlns="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="#default simple xh">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:apply-templates
select="tcm:Component/tcm:Data/tcm:Content/simple:Content" />
</xsl:template>
<xsl:template match="simple:Content">
<xsl:value-of select="simple:keywordlink/#*"/>
<xsl:value-of select=document(simple:keywordlink/#xlink:href)/>
</xsl:template>
<xsl:stylesheet>
I am getting blank output. I want to get the key value of a keyword.
I am getting blank output because in tcm:Component XML, there are no attributes.
I am not sure how can I navigate to that keyword.
I should retrieve the value of Key i.e. "Its ID is 123".
Can any one help how to do this?

It seems it's impossible to get an xlink:href to the referred keyword in a Keyword field using just the XSLT Mediator.
To overcome this I created a .NET compound that "inflates" the extra keyword info in the XML. You'll have to place this compound just before the XSLT compound.
Code:
namespace ContentManagement.TBB.Templates
{
[TcmTemplateTitle("Inflate Keyword Info")]
public class GetExtendedComponent : TemplateBase
{
public override void Transform(Engine engine, Package package)
{
Initialize(engine, package);
Component component = GetComponent();
XmlElement componentXml = component.ToXml();
XmlNamespaceManager ns = new XmlNamespaceManager(componentXml.OwnerDocument.NameTable);
ns.AddNamespace("ns", component.Schema.NamespaceUri);
ItemFields fields = new ItemFields(component.Content, component.Schema);
InflateKeywords(fields, (XmlElement)componentXml.SelectSingleNode(String.Format("//ns:{0}", component.Schema.RootElementName), ns));
ItemFields metaFields = new ItemFields(component.Metadata, component.MetadataSchema);
InflateKeywords(metaFields, (XmlElement)componentXml.SelectSingleNode("//ns:Metadata", ns));
Item xmlItem = package.CreateStringItem(ContentType.Component, componentXml.OuterXml);
package.Remove(package.GetByName(Package.ComponentName));
package.PushItem(Package.ComponentName, xmlItem);
}
private void InflateKeywords(ItemFields fields, XmlElement element)
{
XmlNamespaceManager ns = new XmlNamespaceManager(element.OwnerDocument.NameTable);
ns.AddNamespace("ns", element.NamespaceURI);
Logger.Debug("NS: " + element.NamespaceURI);
foreach (ItemField field in fields)
{
if (field is KeywordField)
{
KeywordField keywordField = (KeywordField)field;
XmlNodeList nodes = element.SelectNodes(String.Format("./ns:{0}", keywordField.Name), ns);
foreach (XmlNode node in nodes)
{
XmlElement kwelement = (XmlElement)node;
Logger.Debug(String.Format("Keyword titel: {0}", keywordField.Value.Title));
Logger.Debug(String.Format("Keyword Element Value: {0}", kwelement.InnerText));
kwelement.SetAttribute("href", "http://www.w3.org/1999/xlink", keywordField.Values.First(v => v.Title.Equals(kwelement.InnerText)).Id);
kwelement.SetAttribute("type", "http://www.w3.org/1999/xlink", "simple");
kwelement.SetAttribute("title", "http://www.w3.org/1999/xlink", kwelement.InnerText);
}
}
else if (field is EmbeddedSchemaField)
{
EmbeddedSchemaField embedField = (EmbeddedSchemaField)field;
XmlNodeList nodes = element.SelectNodes(String.Format("./ns:{0}", embedField.Name), ns);
int i = 0;
foreach (XmlNode node in nodes)
{
XmlElement embedElement = (XmlElement)node;
InflateKeywords(embedField.Values[i], embedElement);
i++;
}
}
}
}
}
}

The Key of the Keyword is not stored in the link (which really only contains the minimal information needed to look up the Keyword). So you'll have to load the Keyword and read it from there.
Yoav showed how to read other items from within your XSLT here:
http://yoavniran.wordpress.com/2009/07/11/implementing-the-xslt-mediator-part-1/
This snippet seems relevant:
<xsl:attribute name="alt">
<xsl:value-of select="document(simple:image/#xlink:href)/tcm:Component/tcm:Data/tcm:Metadata/image:Metadata/image:altText"/>
</xsl:attribute>
So the document() call loads the linked item (in this case a multimedia component) and the rest of the select then finds the value they are looking for.
A keyword XML looks like this:
<?xml version="1.0"?>
<tcm:Keyword xmlns:transform-ext="urn:tridion:transform-ext"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:tcm="http://www.tridion.com/ContentManager/5.0"
IsEditable="false" ID="tcm:1-233-1024">
<tcm:Context>
<tcm:Publication xlink:title="000 Parent Publication" xlink:href="tcm:0-1-1"
xlink:type="simple"/>
<tcm:OrganizationalItem xlink:title="Places" xlink:href="tcm:1-37-512"
xlink:type="simple"/>
</tcm:Context>
<tcm:Info>
<tcm:LocationInfo>
<tcm:WebDAVURL>/webdav/000%20Parent%20Publication/Places/New%20Keyword.tkw</tcm:WebDAVURL>
<tcm:Path>\000 Parent Publication\Places</tcm:Path>
</tcm:LocationInfo>
<tcm:BluePrintInfo>
<tcm:OwningPublication xlink:title="000 Parent Publication" xlink:href="tcm:0-1-1" xlink:type="simple"/>
<tcm:IsShared>false</tcm:IsShared>
<tcm:IsLocalized>false</tcm:IsLocalized>
</tcm:BluePrintInfo>
<tcm:VersionInfo>
<tcm:CreationDate>2012-06-11T09:09:03</tcm:CreationDate>
<tcm:RevisionDate>2012-06-11T09:09:03</tcm:RevisionDate>
<tcm:Creator xlink:title="TCMHOSTNAME\Administrator"
xlink:href="tcm:0-11-65552" xlink:type="simple"/>
</tcm:VersionInfo>
<tcm:AllowedActions>
<tcm:Actions Managed="0" Deny="96" Allow="268560384"/>
</tcm:AllowedActions>
</tcm:Info>
<tcm:Data>
<tcm:Title>New Keyword</tcm:Title>
<tcm:Description>New Keyword</tcm:Description>
<tcm:Key>Key</tcm:Key>
<tcm:IsAbstract>false</tcm:IsAbstract>
<tcm:ParentKeywords/>
<tcm:RelatedKeywords/>
<tcm:MetadataSchema xlink:title="" xlink:href="tcm:0-0-0" xlink:type="simple"/>
<tcm:Metadata/>
<tcm:IsRoot>true</tcm:IsRoot>
</tcm:Data>
</tcm:Keyword>

Implement this way:-
To get the Value field:-
document(simple:keywordlink/#xlink:href)/tcm:Keyword/tcm:Data/tcm:Title/text()
<xsl:value-of select="document(simple:keywordlink/#xlink:href)/tcm:Keyword/tcm:Data/tcm:Title/text()" />
Result:
Some Value
To get the Key field:-
document(simple:keywordlink/#xlink:href)/tcm:Keyword/tcm:Data/tcm:Key/text()
<xsl:value-of select="document(simple:keywordlink/#xlink:href)/tcm:Keyword/tcm:Data/tcm:Key/text()" />
Result:
Its ID is 123

Related

Query BaseX with xslt module error on [XPST0003] Expecting variable declaration

I'm using xslt module to query BaseX.
<query><text>
let $xml := doc("/dsn_ext/pcbtestxmldbdata001_test001.xml")
let $style := <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml" /><xsl:template match="/"><xsl:copy-of select="." /></xsl:template></xsl:stylesheet>
return xslt:transform-text($xml,$style)
</text>
</query>
I do the query on BaseX GUI and it works!!!
enter image description here
BUT! When I do the same query on postman to query BaseX on remote server by api ,it fails!
The error messages are:
Stopped at /srv/basex/webapp, 3/19:
[XPST0003] Expecting variable declaration.
enter image description here
I don't know why this error occured.Is there anybody could help me to solve this problem ?
Help!!!!!!!
My baseX versions on local and remote are both 9.5.0
I try it on BaseXGUI and it works correctly
but when I try it on postman then it fails!
I hope the query will work correctly on postman !
The image you show of the BaseX GUI actually shows the query
let $xml := doc("/dsn_ext/pcbtestxmldbdata001_test001.xml")
let $style := <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml" /><xsl:template match="/"><xsl:copy-of select="." /></xsl:template></xsl:stylesheet>
return xslt:transform-text($xml,$style)
(no wrapping <query><text>), which is a valid query.
I don't know Postman but I would expect that it wants the query to be the string value of the <text> element. To achieve that you will need to escape the contained elements, typically by wrapping them in a CDATA section:
<query><text><![CDATA[
let $xml := doc("/dsn_ext/pcbtestxmldbdata001_test001.xml")
let $style := <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml" /><xsl:template match="/"><xsl:copy-of select="." /></xsl:template></xsl:stylesheet>
return xslt:transform-text($xml,$style)
]]></text>
</query>

Please explain why I would get a no schema error when there is, in fact, an associated schema?

OK. So I am still learning the ins and outs of XSLT and associating schemas. In my company we use XSLT in a very specific way, to transform XML metadata from one schema to another. (i.e. Dublin Core to PBCore, our house standard metadata to METS, etc.) I have a plain XML file with our standard metadata tags. I transform it using an XSLT that has these declarations:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="/">
<reVTMD xmlns="http://nwtssite.nwts.nara/schema/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.archives.gov/preservation/products/reVTMD.xsd"
recordCreation="2016-03-24T18:13:51.0Z" profile="profile1"
version="version1">
The output XML includes this:
<?xml version="1.0" encoding="UTF-8"?>
<reVTMD xmlns="http://nwtssite.nwts.nara/schema/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.archives.gov/preservation/products/reVTMD.xsd"
recordCreation="2016-03-24T18:13:51.0Z"
profile="profile1"
version="version1">
at the top of the document. But I still get a "There is no schema or DTD associated with the document." in Oxygen when I try to validate the document against the reVTMD schema. What am I doing wrong?
The xsi:schemaLocation attribute as its value needs a list of pairs associating a namespace with a schema location so with the input being in the namespace http://nwtssite.nwts.nara/schema/ and the schema being in the location https://www.archives.gov/preservation/products/reVTMD.xsd you need
xsi:schemaLocation="http://nwtssite.nwts.nara/schema/ https://www.archives.gov/preservation/products/reVTMD.xsd"

BaseX: where to declare the XML document on which to perform a query

With the program BaseX I was able to use XPath and XQuery in order to query an XML document located at my home directory, but I have a problem with doing the same in XSLT.
The document I'm querying is BookstoreQ.xml.
XPath version, running totally fine:
doc("/home/ioannis/Desktop/BookstoreQ.xml")/Bookstore/Book/Title
XSLT code which I want to execute:
<xsl:stylesheet version = "2.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method= "xml" indent = "yes" omit-xml-declaration = "yes" />
<xsl:template match = "Book"></xsl:template>
</xsl:stylesheet>
I read BaseX' documentation on XSLT, but didn't manage to find a solution. How can I run given XSLT?
BaseX has no direct support for XSLT, you have to call it using XQuery functions (which is easy, though). There are two functions for doing this, one for returning XML nodes (xslt:transform(...)), one for returning text as a string (xslt:transform-text(...)). You need the second one.
xslt:transform-text(doc("/home/ioannis/Desktop/BookstoreQ.xml"),
<xsl:stylesheet version = "2.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method= "xml" indent = "yes" omit-xml-declaration = "yes" />
<xsl:template match = "Book"></xsl:template>
</xsl:stylesheet>
)
Both can either be called with the XSLT as nodes (used here), by passing it as a string or giving a path to a file containing the XSLT code.

XSLT transformation passing parameters

I am trying to pass parameters during an XSLT transformation. Here is the xsl stylesheet.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="param1" select="'defaultval1'" />
<xsl:param name="param2" select="'defaultval2'" />
<xsl:template match="/">
<xslttest>
<tagg param1="{$param1}"><xsl:value-of select="$param2" /></tagg>
</xslttest>
</xsl:template>
</xsl:stylesheet>
The following in the java code.
File xsltFile = new File("template.xsl");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document stylesheet = builder.parse("template.xsl");
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer xsltTransformer = transformerFactory.newTransformer(new DOMSource(stylesheet));
//Transformer xsltTransformer = transformerFactory.newTransformer(new StreamSource(xsltFile));
xsltTransformer.setParameter("param1", "value1");
xsltTransformer.setParameter("param2", "value2");
StreamResult result = new StreamResult(System.out);
xsltTransformer.transform(new DOMSource(builder.newDocument()), result);
I get following errors:-
ERROR: 'Variable or parameter 'param1' is undefined.'
FATAL ERROR: 'Could not compile stylesheet'
However, if i use the following line to create the transformer everything works fine.
Transformer xsltTransformer = transformerFactory.newTransformer(new StreamSource(xsltFile));
Q1. I just wanted to know whats wrong in using a DOMSource in creating a Transformer.
Q2. Is this one of the ideal ways to substitute values for placeholders in an xml document? If my placeholders were in a source xml document is there any (straightforward) way to substitute them using style sheets (and passing parameters)?
Q1: This is a namespace awareness problem. You need to make the DocumentBuilderFactory namespace aware:
factory.setNamespaceAware(true);
Q2: There are several ways to get the values from an external xml file. One way to do this is with the document function and a top level variable in the document:
<!-- Loads a map relative to the template. -->
<xsl:variable name="map" select="document('map.xml')"/>
Then you can select the values out of the map. For instance, if map.xml was defined as:
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<mapping key="value1">value2</mapping>
</mappings>
You could remove the second parameter from your template, then look up the value using this line:
<tagg param1="{$param1}"><xsl:value-of select="$map/mappings/mapping[#key=$param1]"/></tagg>
Be aware that using relative document URIs will require that the stylesheet has a system id specified, so you will need to update the way you create your DOMSource:
DOMSource source = new DOMSource();
source.setNode(stylesheet);
source.setSystemId(xsltFile.toURL().toString());
In general, I suggest looking at all of the options that are available in Java's XML APIs. Assume that all of the features available are set wrong for what you are trying to do. I also suggest reading the XML Information Set. That specification will give you all of the definitions that the API authors are using.

How keys work in muenchian grouping

I haven't found an answer yet on how keys are generated while doing muenchian-grouping in xslt. The examples I found all have very simple xml files. Given the following xml-file:
<?xml version="1.0" encoding="UTF-8"?>
<despatch-advice>
<message-id>2012041715435517181</message-id>
<message-creation>2012-04-17T15:43:55.000+02:00</message-creation>
<originatingFlow>DespatchAdvice</originatingFlow>
<shipment>
<transport-mode>Sea</transport-mode>
<transport-id>1111</transport-id>
<order-line>
<order>123</order>
<order-date>2012-01-17+01:00</order-date>
<selling-code>ME</selling-code>
</order-line>
</shipment>
<shipment>
<transport-mode>Sea</transport-mode>
<transport-id>2222</transport-id>
<order-line>
<order>456</order>
<order-date>2012-01-17+01:00</order-date>
<selling-code>ME</selling-code>
</order-line>
<order-line>
<order>789</order>
<order-date>2012-01-17+01:00</order-date>
<selling-code>ME</selling-code>
</order-line>
<order-line>
<order>832</order>
<order-date>2012-01-17+01:00</order-date>
<selling-code>XM</selling-code>
</order-line>
</shipment>
<shipment>
<transport-mode>Air</transport-mode>
<transport-id>333</transport-id>
<order-line>
<order>781</order>
<order-date>2012-01-17+01:00</order-date>
<selling-code>XM</selling-code>
</order-line>
<order-line>
<order>789</order>
<order-date>2012-01-17+01:00</order-date>
<selling-code>XM</selling-code>
</order-line>
</shipment>
What I would like to do is group the order-lines per selling-code in each shipment.
In my xslt file I define the key as follows:
<xsl:key name="groups" match="order-line" use="selling-code"/>
And after having created the first part of the xml-file I do the following:
<xsl:for-each select="order-line[generate-id(.)=generate-id(key('groups',selling-code)[1])]">
<ns0:order-line-group>
<xsl:for-each select="key('groups',selling-code)">
<ns0:order-line>
I would expect that the xml file would be parsed from the current <shipment> node and the keys built will only use the <order-line> nodes for that specific shipment. But instead I get three <shipment> nodes containg ALL <order-line> nodes in the file. So it seems that when the key is created the xml file is always parsed from the root? Is that so? Is there a way around this?
You need to define a key like
<xsl:key name="groups" match="order-line" use="concat(generate-id(..), '|', selling-code)"/>
and then you need to use
<xsl:for-each select="order-line[generate-id(.)=generate-id(key('groups',concat(generate-id(..), '|', selling-code))[1])]">
<ns0:order-line-group>
<xsl:for-each select="key('groups',concat(generate-id(..), '|', selling-code))">
<ns0:order-line>
to ensure you only process items for each shipment.
With XSLT 2.0 the key function has a third argument to look for items in a subtree but XSLT 1.0 does not have that, so you need to put the id of the node you want to restrict the search to in the key value. And of course XSLT 2.0 has for-each-group so for grouping you don't need keys at all.