xsl generate attribute with prefix - xslt

I'm totally new to XSL and have the task of generating output xml with xmlns:A.B.C prefix/value as an attribute of the root node. Specifically like this:
<GetVersionResponse xmlns:A.B.C="www.somesite.co.uk/A/B/C">
<Class>GetVersionRequest</Class>
<Errors>
<Error>
<Text>Some message</Text>
</Error>
</Errors>
</GetVersionResponse>
This is my input:
<Error>
<Namespace>xmlns:A.B.C</Namespace>
<NamespaceValue>www.somesite.com/A/B/C</NamespaceValue>
<Class>GetVersionRequest</Class>
<Message>Some message</Message>
</Error>
This is the important snippet from the Error.xsl file:
<xsl:template match="Error">
<xsl:variable name="ResponseType">
<xsl:value-of select="concat(substring-before(Class, 'Request'),'Response')"/>
</xsl:variable>
<xsl:variable name="NS">
<xsl:value-of select="Namespace"/>
</xsl:variable>
<xsl:variable name="NSV">
<xsl:value-of select="NamespaceValue"/>
</xsl:variable>
<xsl:element name="{$ResponseType}" namespace="{$NSV}" xml:space="default">
...
</xsl:template
It generates the following error because I have a colon in the Namespace element of my input:
XSLT 2.0 Debugging Error: Unknown namespace prefix
If I replace the colon with an underscore I get the exact out put I require but only with an underscore of course. Like this:
<GetVersionResponse xmlns_A.B.C="www.somesite.co.uk/A/B/C">
<Class>GetVersionRequest</Class>
<Errors>
<Error>
<Text>Some message</Text>
</Error>
</Errors>
</GetVersionResponse>
I've trawled the web for hours and think I'm trying to insert the xmlns: as text instead of using the proper objects that will generate xmlns:MYTEXT but cannot figure out how to do it.
Many Thanks for any help !!!!

The complicating factor here is that xmlns:... namespace declarations are not attributes as far as the XPath data model is concerned, so you can't create them using xsl:attribute. However, in XSLT 2.0 you can create them using xsl:namespace.
<xsl:template match="Error">
<xsl:element name="{substring-before(Class, 'Request')}Response">
<xsl:namespace name="{substring-after(Namespace, 'xmlns:')}"
select="NamespaceValue" />
<xsl:copy-of select="Class" />
<Errors>
<Error>
<Text><xsl:value-of select="Message" /></Text>
</Error>
</Errors>
</xsl:element>
</xsl:template>
Live demo
The name attribute of xsl:namespace is the prefix you want to bind (so not including xmlns:), and the select gives the URI you want to bind it to.

Related

Using an xsl param as argument to XPath function

I've been trying to figure out a way to use a param/variable as an argument to a function.
At the very least, I'd like to be able to use basic string parameters as arguments as follows:
<xsl:param name="stringValue" default="'abcdef'"/>
<xsl:value-of select="substring(string($stringValue),1,3)"/>
The above code generates no output.
I feel like I'm missing a simple way of doing this. I'm happy to use exslt or some other extension if an xslt 1.0 processor does not allow this.
Edit:
I am using XSL 1.0 and transforming using Nokogiri, which supports XPATH 1.0 . Here is a more complete snippet of what I am trying to do:
I want to pass column numbers as parameters using nokogiri as follows
document = Nokogiri::XML(File.read('table.xml'))
template = Nokogiri::XSLT(File.read('extractTableData.xsl'))
transformed_document = template.transform(document,
["tableName","'Problems'", #Table Heading
"tablePath","'Table'", #Takes an absolute XPATH String
"nameColumnIndex","2", #column number
"valueColumnIndex","3"]) #column number
File.open('FormattedOutput.xml', 'w').write(transformed_document)
My xsl then wants to access every TD[valueColumnIndex] and and retrieve the first 3 characters at that position, which is why I am using a substring function. So I want to do something like:
<xsl:value-of select="substring(string(TD[$valueColumnIndex]),1,3)"/>
Since I was unable to do that, I tried to extract TD[$valueColumnIndex] to another param valueCode and then do substring(string(valueCode),1,3)
That did not work either (which is to say, no text was output, whereas <xsl:value-of select="$valueCode"/> gave me the expected output).
As a result, i decided to understand how to use parameters better, I would just use a hard coded string, as mentioned in my earlier question.
Things I have tried:
using single quotes around abcdef (and not) while
using string() around the param name (and not)
Based on the comments below, it seems I am handicapped in my ability to understand the error because Nokogiri does not report an error for these situations. I am in the process of installing xsltproc right now and seeing if I receive any errors.
Finally, here is my entire xsl. I use a separate template forLoop because of the valueCode param I am creating. The lines of interest are the last 5 or so. I cannot include the xml as there are data use issues involved.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:dyn="http://exslt.org/dynamic"
exclude-result-prefixes="ext dyn">
<xsl:param name="tableName" />
<xsl:param name="tablePath" />
<xsl:param name= "nameColumnIndex" />
<xsl:param name= "valueColumnIndex"/>
<xsl:template match="/">
<xsl:param name="tableRowPath">
<xsl:value-of select="$tablePath"/><xsl:text>/TR</xsl:text>
</xsl:param>
<!-- Problems -->
<section>
<name>
<xsl:value-of select="$tableName" />
</name>
<!-- <xsl:for-each select="concat($tablePath,'/TR')"> -->
<xsl:for-each select="dyn:evaluate($tableRowPath)">
<!-- Encode record section -->
<xsl:call-template name="forLoop"/>
</xsl:for-each>
</section>
</xsl:template>
<xsl:template name="forLoop">
<xsl:param name="valueCode">
<xsl:value-of select="./TD[number($valueColumnIndex)][text()]"/>
</xsl:param>
<xsl:param name="RandomString" select="'Try123'"/>
<section>
<name>
<xsl:value-of select="./TD[number($nameColumnIndex)]"/>
</name>
<code>
<short>
<xsl:value-of select="substring(string($valueCode),1,3)"/>
</short>
<long>
<xsl:value-of select="$valueCode"/>
</long>
</code>
</section>
</xsl:template>
</xsl:stylesheet>
Use it this way:
<xsl:param name="stringValue" select="'abcdef'"/>
<xsl:value-of select="substring($stringValue,1,3)"/>

XSLT not generating expected output

Input XML:
<derivatives>
<derivative id="4" name="Audio Content">
<operator id="1" name="Reliance">
<referenceCode code="62033815">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
<operator id="22" name="Aircel">
<referenceCode code="811327">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
</derivative>
</derivatives>
Expected Output XML:
<hellotune>
<operator>Aircel</operator>
<vcode>811327</vcode>
</hellotune>
Current output (which is wrong):
<hellotune>
<operator>Aircel</operator>
<vcode/>
</hellotune>
XSL (which is not working):
<xsl:if test="derivatives/derivative/operator[#name='Aircel']">
<hellotune>
<operator>Aircel</operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:if>
Note: Using XSL v1.0. Not mentioned the complete XSL for brevity.
Based on the XSL you provided, it can be presumed that the context node is the root node, but starting from the root node, the path referenceCode/#code does not match anything in your input. Appending derivatives/derivative/operator/ before that path would succeed in finding a referenceCode #code attribute, but it would find the wrong one. Try this push-style approach:
<xsl:template match="/">
<xsl:apply-templates select="derivatives/derivative/operator[#name='Aircel']" />
</xsl:template>
<xsl:template match="operator">
<hellotune>
<operator><xsl:value-of select="#name" /></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:template>
The xpath xpression in the element <vcode> is not referring to any node in the input document. The best way to match all the nodes is to use
//
like in your code you can use
//referenceCode/#code
(but this is only for information purpose and using same can't get your result).
you can try this way:
<xsl:template match="/">
<hellotune>
<xsl:for-each select="//operator">
<xsl:if test="./#name='Aircel'">
<operator><xsl:value-of select="#name"/></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</xsl:if>
</xsl:for-each>
</hellotune>
</xsl:template>
Hope this helps :)

xml to xml (shorter version) with XSL

ORIGINAL - this is my original XML:
<course acad_year="2012" cat_num="85749" offered="N" next_year_offered="2013">
<term term_pattern_code="4" fall_term="Y" spring_term="Y">full year</term>
<department code="VES">
<dept_long_name>Department of Visual and Environmental Studies</dept_long_name>
<dept_short_name>Visual and Environmental Studies</dept_short_name>
</department>
</course>
DESIRED RESULT - I am trying to create another shorter version of the original XML that will group and list all department codes like in the example below:
<departments>
<department code="some_code" name="some_name"/>
<department code="some_code" name="some_name"/>
<department code="some_code" name="some_name"/>
</departments>
This is what I am trying and not working:
<xsl:template match="/">
<departments>
<xsl:for-each-group select="fas_courses/course" group-by="department[#code]">
<xsl:text disable-output-escaping="yes"> <department code=" </xsl:text>
<xsl:value-of select="department/#code"/>
<xsl:text>" name="</xsl:text>
<xsl:value-of select="dept_short_name"/>
<xsl:text disable-output-escaping="yes">"><department/></xsl:text>
</xsl:for-each-group>
</departments>
</xsl:template>
The error I am getting
F [Saxon-PE 9.4.0.3] The value of attribute "code" associated with an element type "department" must not contain the '<' character.
From the error message, I understand the '<' character is giving me an error inside of the xsl:text element, but how do I put that character then?, I am already using disable-output-escaping="yes", is there anything else???
Thanks!
Using disable-output-escaping is never a good idea, and you really don't need it here. Try this:
<xsl:template match="/">
<departments>
<xsl:for-each-group select="fas_courses/course" group-by="department/#code">
<department code="{department/#code}" name="{department/dept_short_name}" />
</xsl:for-each-group>
</departments>
</xsl:template>
You need to write your text inside <[DATA[...]]> so it doesn't get parsed or use < and > for the tags that you output.

XSLT: how to write redundant xmlns?

I need the following output for bizzare system which expects same xmlns declared in parent and child and refuses to work otherwise. I.e that's what expected:
<root xmlns="http://something">
<element xmlns="http://something" />
</root>
I can create xmlns in root with
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:element name="root" namespace="http://something">
<xsl:element name="node" namespace="http://something" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
However it doesn't add xmlns into childnode because node's parent has the same xmlns. How to force XSLT to write xmlns disregarding parent?
The XML schema specification expressly prohibits attributes named xmlns, so an XSLT stylesheet cannot create such attributes directly using <xsl:attribute>. I can only see two options for you...
One option is to create dummy attributes using a different name (e.g. xmlnsx):
<xsl:template match="/">
<xsl:element name="root">
<xsl:attribute name="xmlnsx">http://something</xsl:attribute>
<xsl:element name="node">
<xsl:attribute name="xmlnsx">http://something</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:template>
... and then replace all occurrences of the attribute xmlnsx with xmlns in some post-processing step (such as a SAX filter or other stream editor). However, this solution involves inserting a non-XSLT step into the pipeline.
The other option is pure, if ugly, XSLT. You could generate the required XML directly, using xsl:text and disable-output-escaping, like this:
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><root xmlns="http://something"></xsl:text>
<xsl:text disable-output-escaping="yes"><node xmlns="http://something"></xsl:text>
<xsl:text disable-output-escaping="yes"></root></xsl:text>
</xsl:template>
Note that the XSLT 1.0 specification is pretty loose when it comes to serialization, so a particular XSLT processor could still conceivable strip the redundant namespace declarations from this second solution. However, it worked in the four processors that I tried (namely Saxon, MSXML, MSXML.NET and LIBXML).

Merge two xml schemas using XSLT

I'm transforming an XML Schema using XSLT 2.0. The first schema (s1.xsd) imports a second schema (s2.xsd) as follows:
Content of s1.xsd
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema.xsd"
xmlns:ns1="URI1" targetNamespace="URI2"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<import namespace="URI1" schemaLocation="s2.xsd"/>
<element name="element1"/>
<element name="element2"/>
</schema>
and content of s2.xsd
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:ns1="URI1" targetNamespace="URI1">
<attribute name="attr1"/>
<schema>
My XSLT declares the XS namespace as follows:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
I would like to merge the nodes of s2.xsd into the <schema>-element of s1.xsd. So far, I've tried
<xsl:template name="merge_imported_schemas">
<xsl:for-each select="/schema/import[#namespace = //namespace::*]">
<!-- file exists? -->
<xsl:choose>
<xsl:when test="boolean(document(#schemaLocation))">
<!-- schema found -->
<xsl:copy-of select="document(#schemaLocation)/*/node()"/>
</xsl:when>
<xsl:otherwise>
<!-- schema not found -->
<xsl:message terminate="yes">
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
but I don't get the desired result. Could anyone please tell me what I'm doing wrong? I suspect there is a namespace-collision here, but honestly I find using namespaces a little confusing. Thanks!
You need to qualify the elements in your XPath. At the moment select="/schema/import[#namespace = //namespace::*]"> doesn't match anything at all, because there is no element /schema. The XPath is trying to match elements with no namespace.
Change it to select="/xs:schema/xs:import[#namespace = //namespace::*]"> and it should work.
Remember, namespace prefixes are an alias for the namespace URI, and if you have a default namespace (as in your xsd files), elements with no prefix are still namespace-qualified.
As an aside, instead of <xsl:for-each select="/schema/import[#namespace = //namespace::*]">, you might have more success using <xsl:apply-templates select="/xs:schema/node()", and defining different templates for the different kinds of node that you wish to copy into the output tree.