Denormalize a document using XSLT - xslt

I am new to XSLT so excuse me if this is a noob question.
Let's say I have this XML document (one hotel element with 2 private_rates):
<hotel>
<private_rates>
<private_rate>
<id>1</id>
</private_rate>
<private_rate>
<id>2</id>
</private_rate>
</private_rates>
</hotel>
Is there any way to use XSLT to transform it into 2 hotel elements, each with one private rate ?
<hotel>
<private_rates>
<private_rate>
<id>1</id>
</private_rate>
</private_rates>
</hotel>
<hotel>
<private_rates>
<private_rate>
<id>2</id>
</private_rate>
</private_rates>
</hotel>
How would the XSLT for that look like? Any help will be greatly appreciated! thanks.

As an alternative to Jollymorphic's solution, my preference would be
<xsl:template match="private_rates">
<hotel>
<xsl:copy-of select="."/>
</hotel>
</xsl:template>

Since a legal XML document has to have a single, containing document element, I presume that this sequence of hotels is going inside something. That being said, how about this:
<xsl:template match="hotel">
<xsl:for-each select="private_rates/private_rate">
<hotel>
<private_rates>
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</private_rates>
</hotel>
</xsl:for-each>
</xsl:template>
The apply-templates usage here presumes that you also have an identity template in your stylesheet that will copy source content that isn't more specifically matched by other templates (such as this one).
Underscores are frowned upon in element and attribute names, incidentally.

Related

Xslt - How do you check for a grandchild node with a certain path name. (xpath 1.0)

What I want to do is given an element as context, I want to determine if it has a child with a given name and determine if that child has a node with a given name so I can do operations with it. It is important that I do this in XPath 1.0 syntax.
The code that I've gotten so far is this.
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'description')">
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'text')">
<xsl:value-of select="node()"/>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
It works, but it's big and ugly and I know that there's a way to condense it. The for-eachs there are unnecessary, since I'm only expecting one child node to be named description, and for it to only have one text node.
I feel like this solution should work
<xsl:for-each select="./description/text">
..
</xsl:for-each>
But it isn't, and I'm not really good enough with XPath Syntax to know why.
The reason I'm asking is because though I've found answers that detect whether a child node has a name, and I've found answers that can get to that child node's context, I haven't found an answer that combines the two, though maybe I just haven't been searching hard enough, in which case I apologize.
Edit: Woops, sorry yeah I forgot to mention that the contains() part of the code was also just a hack because I wasn't sure how to compare their values with equality.
Also as long as the answer is there, <xsl:for-each select="description/text"> does not work either.
A sample of the XML in question is this
<leaf>
<description>
<text> Various Words
</text>
</description>
</leaf>
where the context is the leaf and I am trying to get to the text node.
Edit: The Second Coming:
The problem for me was that my XSLT file was using a default namespace (in my case named a). If I had added that then Borodin's answer would have been correct.
To be specific, this is the code which ended up working for me in the end, in case anyone wants to know.
<xsl:for-each select="a:description/a:text>
<xsl:value-of select="node()"/>
</xsl:for-each>
Thanks Guys ^-^
Do you really want to check whether the element names contain those strings? Or, as your narrative says, do you want elements with that exact name?
To do something like what you have already written, use
<xsl:for-each select="*[contains(name(), 'description')]/*[contains(name(), 'text')]">
<xsl:value-of select="node()"/>
</xsl:for-each>
But if you know the complete names it is a lot neater:
<xsl:for-each select="description/text">
<xsl:value-of select="node()"/>
</xsl:for-each>
If that doesn't work then we need to see more of your source XML and your transform.
Update
If I use this XML
<leaf>
<description>
<text>Various Words</text>
</description>
<description>
<text>More Words</text>
</description>
<description>
<text>Other Words</text>
</description>
</leaf>
and apply this stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/leaf">
<xsl:for-each select="description/text">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
the output is the expected Various WordsMore WordsOther Words. I don't know how to help you unless you describe your situation better, except to say that transforms should be written with another template rather than for-each wherever possible. Like this variation which produces the same output as above.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/leaf">
<xsl:apply-templates select="description/text"/>
</xsl:template>
<xsl:template match="text">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>

Xpath is not working on variable having xml string

I am facing issue in writing xapth. Let me explain the problem.
I am writing xslt to transform some xml. The xslt also loads one xml file from disk into xslt variable.
PeopleXml.xml:
<TestXml>
<People>
<Person id="MSA1" name="Sachin">
<Profession>
<Role>Developer</Role>
</Profession>
</Person>
<Person id="ZAG4" name="Rahul">
<Profession>
<Role>Tester</Role>
</Profession>
</Person>
</People>
</TestXml>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://MyNamespace"
version="2.0">
<xsl:variable name="PeopleXml" select ="document('PeopleXml.xml')"/>
<xsl:variable name="peopleList" select="$PeopleXml/TestXml/People/Person"/>
<xsl:variable name="person1" select="MSA1"/>
<xsl:variable name="person" select="$peopleList/Person[#id=$person1]/#name"/>
<xsl:template match="/">
<xsl:value-of select="$person"/>
</xsl:template>
</xsl:stylesheet>
Issue: The xpath "$peopleList/Person[#id=$person1]/#name" is not returning anything. Infact, $peopleList/Person also does not work. However, I can see two person nodes in $peopleList variable when I debugged the code.
Could anyone help me, what I am doing wrong in xpath?
EDIT
Above xapth issue has been resolved after applying Daniel's solution. Now, only issue remained is with accessing child nodes of person based on some condition.
Following test does not work.
<xsl:variable name="roleDev" select="'Developer'"/>
<xsl:when test="$peopleList/Profession/Role=$roleDev">
<xsl:value-of select="We have atleast one Developer"/>
</xsl:when>
Your problem is here:
<xsl:variable name="person1" select="MSA1"/>
This results in having the $person1 variable empty.
Why?
Because the expression MSA1 is evaluated -- the current node doesn't have any children named "MSA1" and nothing is selected.
Solution:
Specify the wanted string as string literal:
<xsl:variable name="person1" select="'MSA1'"/>
Your Second Question:
Now, only issue remained is with accessing child nodes of person based
on some condition.
Use:
boolean($peopleList[Profession/Role = 'Developer'])
This produces true() exactly when there is a node in $peopleList such that it has at least one Profession/Role crand-child whose string value is the string "Developer"
Since the variable peopleList is already Person nodes, you should access them like this:
<xsl:variable name="person" select="$peopleList[#id=$person1]/#name"/>

How to map 2 different nodes into a repeating node using BizTalk mapper

I have this schema:
<Root>
<Customers>
<Customer>
<ID>123</ID>
<Name>John</Name>
</Customer>
</Customers>
<Order>
<ID>ABC</ID>
<Title>Boat</Title>
</Order>
</Root>
I need to map the two different records into one repeating record like this:
<Root>
<Data>
<ID>123</ID>
<Text>John</Text>
</Data>
<Data>
<ID>ABC</ID>
<Text>Boat</Text>
</Data>
</Root>
I tried to create two table loopings (one for Customer, one for Order) and got:
<Root>
<Data>
<ID>ABC</ID>
<Text>Boat</Text>
</Data>
</Root>
Tried one table looping with two rows, and got the same. (Tried also with the Gated option to check for existance which made no difference)
In reality the schemas are huge, the map is super complex (not built by me), has a lot of functoids, and many wires. So I would like to avoid creating a custom XSL, which will be easier for this task, but harder to maintain. This is the only part I need to change.
Anybody ?
Thanks.
For complex mapping, using a custom XSLT almost always ends up being simpler and more maintainable than the spider-web we often find in BizTalk maps. However, as you stated, you need to avoid re-coding the complete map, as you are only changing a small section.
You should be able to use the 'Inline XSLT Call Template' script type in the Scripting Functoid to combine the best of BizTalk maps and custom XSLT.
Extending from Sean B. Durkin's answer, you will need to set up 2 Call Template functoids, the first one wired to your output 'Data' node
<xsl:template name="DataTemplate">
<xsl:apply-templates select="//*[local-name()='Customer']|//*[local-name()='Order']" />
</xsl:template>
Your second Call Template will output the relevant data into the current output 'Data' node. Note, this second Functoid does not need to be wired to any node in your output document.
<xsl:template match="*[local-name()='Customer']|*[local-name()='Order']">
<xsl:element name="Data">
<xsl:element name="ID">
<xsl:value-of select="*[local-name()='ID']"/>
</xsl:element>
<xsl:element name="Text">
<xsl:value-of select="*[local-name()='Name']|*[local-name()='Title']" />
</xsl:element>
</xsl:element>
</xsl:template>
No need to use XSLT here. Simply drag a Looping functoid on the map. Connect both the Customer and the Order record as inputs to the functoid (yes, you can have multiple inputs). Connect the output of the functoid to the Data record. Then connect your fields directly (ID --> ID, Name --> Text). This will work.
The individual input records to a Looping functoid don't have to be repeating records in themselves. By connecting multiple inputs to the functoid, you are looping over the collection of instances.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Root>
<xsl:apply-templates select="*/Customers/Customer|*/Order"/>
</Root>
</xsl:template>
<xsl:template match="Customer|Order">
<Data>
<ID><xsl:value-of select="ID" /></ID>
<Text><xsl:value-of select="Name|Title" /></Text>
</Data>
</xsl:template>
</xsl:stylesheet>

XSLT transformation with dynamic namespace

I need to transform an XML file to another XML file, where the source file has a dynamic namespace set to xmlns="whatever". My XSLT runs fine without the namespace being in the file, but I get no output with the namespace. How can I cause the schema of the source file to be applied to the destination file?
All help is appreciated and thanks in advance!
EDIT:
I'm trying to copy the namespace uri over to the resulting file:
<xsl:param name="schema">
<xsl:value-of select="namespace-uri()" />
</xsl:param>
<xsl:element name="root" namespace="$schema">
I have verified that schema is holding the correct value, but the problem is that the program appears to take this too literally:
<root xmlns="$schema">
Is this the right way to go about this?
EDIT x2:
I've implemented Alejandro's suggestion of:
<xsl:element name="root" namespace="{$schema}"/>
And that works for the most part, except for the fact that I have to put the namespace on every element or else I get the following structure in the result:
<root xmlns="NAMESPACE">
<foo xmlns="">
etc.
Is there a way to blanket all of the elements with this namespace, other than putting namespace={$schema} on every single line? Bounty and accept for the best answer!
EDIT x3:
Better example:
If I do:
<xsl:element name="root" namespace="{namespace-uri()}>
<xsl:element name="foo">
<xsl:element name="bar">
<!--etc-->
</xsl:element>
</xsl:element>
</xsl:element>
I get:
<root xmlns="NAMESPACE">
<foo xmlns="">
<bar>
<!--etc-->
</bar>
</foo>
<root>
I would like to have them all under namespace NAMESPACE, so I did:
<xsl:element name="root" namespace="{namespace-uri()}>
<xsl:element name="foo" namespace="{namespace-uri()}>
<xsl:element name="bar" namespace="{namespace-uri()}>
<!--etc-->
</xsl:element>
</xsl:element>
</xsl:element>
However this is ugly and tedious to type. Is there an easier way to blanket the namespace over all elements? (hopefully this clarifies what I need)
Suppose you have this XML input:
<root xmlns="survivors">
<louis/>
<francis/>
</root>
Meaning that every element is under default namespace wich its URI is "survivors".
As Welbog wrote you can select francis element with:
/*/*[local-name()='francis']
or
/*[local-name()='root']/*[local-name()='francis']
But, that also select francis element from these XML inputs:
<root xmlns="survivors" xmlns:n="no-survivors">
<louis/>
<n:francis/>
</root>
or
<root xmlns="survivors">
<louis/>
<francis xmlns="no-survivors"/>
</root>
You could also strengthen the predicate with some namespace URI. But, wich one? An option could be the default namespace for root element like:
/*/*[local-name()='francis'][namespace-uri()=namespace-uri(/*)]
Surely this make XPath expression very verbose.
In XSLT 2.0 you could use xsl:xpath-default-namespace attribute like:
<xsl:value-of select="/root/francis" xpath-default-namespace="survivors"/>
But that's not good for your case because you don't know the URI in advance.
EDIT: xsl:element 's attributes are AVT (Attribute Value Template) so you need this:
<xsl:element name="root" namespace="{$schema}"/>
Also, I recomend you to declare the param as a string data type (not RTF like now), something like:
<xsl:param name="schema" select="namespace-uri()"/>
EDIT 2: Maybe I was not clear. You don't need xsl:element/#namespace in every case. Following your statement that every element is in only one default namespace, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='bar']">
<xsl:element name="newbar" namespace="{namespace-uri()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
With this input:
<root xmlns="whatever">
<foo/>
<bar/>
</root>
Output:
<root xmlns="whatever">
<foo></foo>
<newbar></newbar>
</root>
Edit 2: I was showing you that when you are copying an element you are also copying the namespace needed for expand the QName. So, if want to transform this:
<root xmlns="whatever">
<foo/>
<bar/>
</root>
Into this:
<root xmlns="whatever">
<foo>
<bar/>
</foo>
</root>
You can use this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="*[1]|following-sibling::*[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Since the namespace is part of the full name of any elements your XPath is referencing, and since you don't know the namespace of the source file in advance, you'll have to use the local names of the elements you're accessing instead of their full names.
Let's say you have a source file like this:
<root xmlns="survivors">
<louis/>
<francis/>
</root>
One way to access these elements using XSLT is to specify a namespace that matches the source file's default namespace:
<xsl:stylesheet
version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:surv="survivors"
>
<xsl:template match="surv:louis">
<!-- ETC -->
That way works when you know the namespace. When you don't know the namespace, you can ignore it using the XPath function local-name() like this:
<xsl:stylesheet
version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="*[local-name() = 'louis']">
<!-- ETC -->
Using local-name() means you can ignore the namespace in the source document. You'll have to be careful if there are multiple elements with the same local name in different namespace, though. It's not exactly a robust solution, but if you can't trust your namespace then you don't have that many options anyway.
I'd imagine that having a variable namespace is a bigger problem in and of itself. If that's under your control, you should correct it. If it's not under your control, you should push to have it corrected.
XSLT is rarely executed in a vaccum. It's almost always a part of some other application that loads the XSLT, loads the source document, and then produces the output.
Assuming the above is your scenario, I wouldn't solve this problem directly with XSLT. Instead, I'd use standard XML technologies to examine the source XML document and discover the default namespace. Then, I'd load the XSLT document and use string substitution or some other technique to inject the resultant namespace at the appropriate point in the transform. Then I'd go ahead and run the transform normally.
This will make your XSLT much more natural to write and maintain. I'm a pro with XSLT and use it constantly, and this is how I would solve it. I couldn't imagine the ugliness of a stylesheet that had to use local-name() comparisons constantly. What a pain. (Of course, in much more complex scenarios there may be no choice. Fortunately yours isn't one of them.)
If you don't have this option, I sympathize.

XSLT - Filtering

I have a repeating xml tree like this -
<xml>
<head>this is a sample xml file</head>
<item><color>yellow</color><color>red</color></item>
<item><color>blue</color></item>
<item><color>grey</color><color>red</color><color>blue</color></item>
</xml>
As you can see, each item can have a varying number of color tags.
I wish to get all the color tags for the first two items only.
<xsl:template match="xml">
<xsl:apply-templates select="item[position() < 3]/color" />
</xsl:template>
<xsl:template match="color">
<xsl:copy-of select="." />
</xsl:template>
Applied to your XML this yields:
<color>yellow</color>
<color>red</color>
<color>blue</color>
One potential possible way to get the items which is technically perfectly correct and in no way makes assumptions about the structure of your document with respect to namespacing, future requirements or template construction is just a simple:
/xml/item[position() < 3]/color
Try this...
/xml/item[ position() < 3 ]/color
Add an ordinal field to each item and select the first two.