XSL / Schemas beginners question - c#

Been using XML for ages now for data storage & transfer, but have never had to validate or transform it. Currently starting a new project and making some design decisions and need to know some rudimentary things about XSL & Schemas.
Our XML is like this (excuse the boring book example :) ):
<Books>
<Book>
<ID>1</ID>
<Name>Book1</Name>
<Price>24.??</Price>
<Country>US</Country>
</Book>
<Book>
<ID>1</ID>
<Name></Name>
<Price>24.69</Price>
</Book>
</Books>
Our requirements:
Transformation
a) Turn "US" into United States
b) if Price > 20 create a new lLement <Expensive>True</Expensive>
I'm guessing this is done with XSLT, but can anyone give me some pointers on how to achieve this?
Validation
a) is ID an integer, is Price a float (the most important job to be honest)
b) Are all tags filled, e.g. the name tag is not filled (2nd most important)
c) Are all tags present, e.g. Country is missing for book 2
d) [Probably tricky] Is the ID element unique through all books? (nice to have)
From what I have read this is done with a Schema or Relax NG, but can the results of the validation be outputted to a simple HTML to display a list or errors?
e.g.
Book 1: Price "Price.??" is not float
Book 2: ID is not unique, Name empty, Country missing
Or would it be better do do these things programatically in C#?
Thanks.

This stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="map"
exclude-result-prefixes="m">
<xsl:key name="kTestIntID" match="Book"
use="number(ID)=number(ID) and not(contains(ID,'.'))"
m:message="Books with no integer ID"/>
<xsl:key name="kTestFloatPrice" match="Book"
use="number(Price)=number(Price) and contains(Price,'.')"
m:message="Books with no float Price"/>
<xsl:key name="kTestEmptyElement" match="Book"
use="not(*[not(node())])"
m:message="Books with empty element"/>
<xsl:key name="kTestAllElements" match="Book"
use="ID and Name and Price and Country"
m:message="Books with missing element"/>
<xsl:key name="kBookByID" match="Book" use="ID"/>
<m:map from="US" to="United States"/>
<m:map from="CA" to="Canada"/>
<xsl:variable name="vCountry" select="document('')/*/m:map"/>
<xsl:variable name="vKeys" select="document('')/*/xsl:key/#name
[starts-with(.,'kTest')]"/>
<xsl:variable name="vTestNotUniqueID"
select="*/*[key('kBookByID',ID)[2]]"/>
<xsl:template match="/" name="validation">
<xsl:param name="pKeys" select="$vKeys"/>
<xsl:param name="pTest" select="$vTestNotUniqueID"/>
<xsl:param name="pFirst" select="true()"/>
<xsl:choose>
<xsl:when test="$pTest and $pFirst">
<html>
<body>
<xsl:if test="$vTestNotUniqueID">
<h2>Books with no unique ID</h2>
<ul>
<xsl:apply-templates
select="$vTestNotUniqueID"
mode="escape"/>
</ul>
</xsl:if>
<xsl:variable name="vCurrent" select="."/>
<xsl:for-each select="$vKeys">
<xsl:variable name="vKey" select="."/>
<xsl:for-each select="$vCurrent">
<xsl:if test="key($vKey,'false')">
<h2>
<xsl:value-of
select="$vKey/../#m:message"/>
</h2>
<ul>
<xsl:apply-templates
select="key($vKey,'false')"
mode="escape"/>
</ul>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</body>
</html>
</xsl:when>
<xsl:when test="$pKeys">
<xsl:call-template name="validation">
<xsl:with-param name="pKeys"
select="$pKeys[position()!=1]"/>
<xsl:with-param name="pTest"
select="key($pKeys[1],'false')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Book" mode="escape">
<li>
<xsl:call-template name="escape"/>
</li>
</xsl:template>
<xsl:template match="*" name="escape" mode="escape">
<xsl:value-of select="concat('<',name(),'>')"/>
<xsl:apply-templates mode="escape"/>
<xsl:value-of select="concat('</',name(),'>')"/>
</xsl:template>
<xsl:template match="text()" mode="escape">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
<!-- Up to here, rules for validation.
From here, rules for transformation -->
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Country/text()">
<xsl:variable name="vMatch"
select="$vCountry[#from=current()]"/>
<xsl:choose>
<xsl:when test="$vMatch">
<xsl:value-of select="$vMatch/#to"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Price[. > 20]">
<xsl:call-template name="identity"/>
<Expensive>True</Expensive>
</xsl:template>
</xsl:stylesheet>
With your input, output:
<html>
<body>
<h2>Books with no unique ID</h2>
<ul>
<li><Book><ID>1</ID><Name>Book1</Name><Price>24.??</Price><Country>US</Country></Book></li>
<li><Book><ID>1</ID><Name></Name><Price>24.69</Price></Book></li>
</ul>
<h2>Books with no float Price</h2>
<ul>
<li><Book><ID>1</ID><Name>Book1</Name><Price>24.??</Price><Country>US</Country></Book></li>
</ul>
<h2>Books with empty element</h2>
<ul>
<li><Book><ID>1</ID><Name></Name><Price>24.69</Price></Book></li>
</ul>
<h2>Books with missing element</h2>
<ul>
<li><Book><ID>1</ID><Name></Name><Price>24.69</Price></Book></li>
</ul>
</body>
</html>
With proper input:
<Books>
<Book>
<ID>1</ID>
<Name>Book1</Name>
<Price>19.50</Price>
<Country>US</Country>
</Book>
<Book>
<ID>2</ID>
<Name>Book2</Name>
<Price>24.69</Price>
<Country>CA</Country>
</Book>
</Books>
Output:
<Books>
<Book>
<ID>1</ID>
<Name>Book1</Name>
<Price>19.50</Price>
<Country>United States</Country>
</Book>
<Book>
<ID>2</ID>
<Name>Book2</Name>
<Price>24.69</Price>
<Expensive>True</Expensive>
<Country>Canada</Country>
</Book>
</Books>
Note: Ussing keys for performance. This is proof of concept. In real life, the XHTML output should be wrapped into an xsl:message instruction. From http://www.w3.org/TR/xslt#message
The xsl:message instruction sends a
message in a way that is dependent on
the XSLT processor. The content of the
xsl:message instruction is a template.
The xsl:message is instantiated by
instantiating the content to create an
XML fragment. This XML fragment is the
content of the message.
NOTE:An XSLT processor might implement
xsl:message by popping up an alert box
or by writing to a log file.
If the terminate attribute has the
value yes, then the XSLT processor
should terminate processing after
sending the message. The default value
is no.
Edit: Compacting code and addressing country map issue.
Edit 2: In real life, with big XML documents and more enterprice tools, the best approach would be to run the transformation with XSLT 2.0 schema-aware processor for validating, or run validation independly with well-know schema validators. If for some reason these choices aren't aviable, don't go with my proof-of-concept answer because having keys for each validation rule make cause a lot of memory use for big documents. The better way for last case, is to add rules to catch validation errors ending transformation with message. As example, this stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="map"
exclude-result-prefixes="m">
<xsl:key name="kIDByValue" match="ID" use="."/>
<m:map from="US" to="United States"/>
<m:map from="CA" to="Canada"/>
<xsl:variable name="vCountry" select="document('')/*/m:map"/>
<xsl:template name="location">
<xsl:param name="pSteps" select="ancestor-or-self::*"/>
<xsl:if test="$pSteps">
<xsl:call-template name="location">
<xsl:with-param name="pSteps"
select="$pSteps[position()!=last()]"/>
</xsl:call-template>
<xsl:value-of select="concat('/',
name($pSteps[last()]),
'[',
count($pSteps[last()]/
preceding-sibling::*
[name()=
name($pSteps[last()])])
+1,
']')"/>
</xsl:if>
</xsl:template>
<xsl:template match="ID[not(number()=number() and not(contains(.,'.')))]">
<xsl:message terminate="yes">
<xsl:text>No integer ID at </xsl:text>
<xsl:call-template name="location"/>
</xsl:message>
</xsl:template>
<xsl:template match="Price[not(number()=number() and contains(.,'.'))]">
<xsl:message terminate="yes">
<xsl:text>No float Price at </xsl:text>
<xsl:call-template name="location"/>
</xsl:message>
</xsl:template>
<xsl:template match="Book/*[not(node())]">
<xsl:message terminate="yes">
<xsl:text>Empty element at </xsl:text>
<xsl:call-template name="location"/>
</xsl:message>
</xsl:template>
<xsl:template match="Book[not(ID and Name and Price and Country)]">
<xsl:message terminate="yes">
<xsl:text>Missing element at </xsl:text>
<xsl:call-template name="location"/>
</xsl:message>
</xsl:template>
<xsl:template match="ID[key('kIDByValue',.)[2]]">
<xsl:message terminate="yes">
<xsl:text>Duplicate ID at </xsl:text>
<xsl:call-template name="location"/>
</xsl:message>
</xsl:template>
<!-- Up to here, rules for validation.
From here, rules for transformation -->
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Country/text()">
<xsl:variable name="vMatch"
select="$vCountry[#from=current()]"/>
<xsl:choose>
<xsl:when test="$vMatch">
<xsl:value-of select="$vMatch/#to"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Price[. > 20]">
<xsl:call-template name="identity"/>
<Expensive>True</Expensive>
</xsl:template>
</xsl:stylesheet>
With your input, this message stops the transformation:
Duplicate ID ar /Books[1]/Book[1]/ID[1]
With proper input, outputs the same as before.

Here is the RelaxNG schema:
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<element name="Books">
<zeroOrMore>
<element name="Book">
<element name="ID"><data type="ID"/></element>
<element name="Name"><text/></element>
<element name="Price"><data type="decimal"/></element>
<element name="Country"><data type="NMTOKEN"/></element>
</element>
</zeroOrMore>
</element>
</start>
</grammar>
and this is the XML Schema version. (I think.)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Books">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="Book"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Book">
<xs:complexType>
<xs:sequence>
<xs:element ref="ID"/>
<xs:element ref="Name"/>
<xs:element ref="Price"/>
<xs:element ref="Country"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ID" type="xs:ID"/>
<xs:element name="Name" type="xs:string"/>
<xs:element name="Price" type="xs:decimal"/>
<xs:element name="Country" type="xs:NMTOKEN"/>
</xs:schema>
Couple of things to note here:
The ID simple type will cause validators to check for multiple occurrences of the identifier, and complain if there are any. A disadvantage of using ID tags however is that they cannot start with a number. So, A1, A2, ... An would be fine, but IDs like 1, 2, ...., n would be considered invalid anyway.
The price has been set to be of type decimal. Float is never a proper type for financial numbers, because of rounding errors.
Running this through xmllint with the original XML document as input (with modified identifiers) gives:
wilfred$ xmllint --noout --relaxng ./books.rng ./books.xml
./books.xml:5: element Price: Relax-NG validity error : Type decimal doesn't allow value '24.??'
./books.xml:5: element Price: Relax-NG validity error : Error validating datatype decimal
./books.xml:5: element Price: Relax-NG validity error : Element Price failed to validate content
./books.xml:8: element Book: Relax-NG validity error : Expecting an element , got nothing
./books.xml fails to validate

On general XSL education, you may find useful an XSL Primer I wrote some years back. It's not current on all the latest trends, but covers the basics of how the XML document is processed.

Related

How do I convert these embedded elements into attributes of the parent element?

I have a large XSD, with elements that look like this:
<xs:element name="ProgramName" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>PN</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="TestItem" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>TA</xs:documentation>
</xs:annotation>
</xs:element>
I would like to collapse the <documentation> element into an attribute of the grandparent element, like this:
<xs:element name="ProgramName" type="xs:string" minOccurs="0" code="PN">
</xs:element>
<xs:element name="TestItem" type="xs:string" minOccurs="0" code="TA">
</xs:element>
How could this be done with XSLT? Alternatively, is there a better (read: simpler) way to do it than XSLT?
The resulting XSD will be used with XSD.EXE to create a C# class, for serialization and deserialization purposes. The original XSD cannot be used this way, because XSD.EXE drops all of the annotation elements, so the information contained in those annotations is lost.
This is how I'd do it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[xs:annotation]">
<xsl:copy>
<xsl:attribute name="code"><xsl:value-of select="xs:annotation/xs:documentation"/></xsl:attribute>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xs:annotation"/>
</xsl:stylesheet>
This Just another answer :)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()[local-name()='element']">
<xsl:copy>
<xsl:apply-templates select="#*|node()[local-name()!='annotation']"/>
<xsl:for-each select="node()[local-name()='annotation']/node()[local-name()='documentation']">
<xsl:attribute name="code">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Indeed a very good thought of using XSLT for rearranging nodes rather than doing it manually :) I always make use of XSLT ..
We can even use it to generate sample XMLs from XSD .. if XSD is with bearable size :)

Parsing XSLT/Xpath data for a list of XML nodes

I am searching for a lib or tool or even some simple code that can parse the Xpath/XSLT data in our XSLT files to produce a Dictionary/List/Tree of all the XML nodes that the XSLT is expecting to work on or find. Sadly everything I am finding is dealing with using XSLT to parse XML rather than parsing XSLT. And the real difficult part I'm dealing with is how flexible XPath is.
For example in the several XSLT files we work with an entry may select on
nodeX/nodeY/nodeNeeded;
OR
../nodeNeeded;
OR
select nodeX then select nodeY then select nodeNeeded;
and so forth.
What we would like to do is to be able to parse out that XSLT doc and get a data structure of sorts that explicitly tell us that the XSLT is looking for nodeNeeded in path nodeX, nodeY so that we can custom build the XML data in a minimalism fashion
Thanks!
Here is a mocked up sub-set of data for visualization purposes:
<server_stats>
<server name="fooServer">
<uptime>24d52m</uptime>
<userCount>123456</userCount>
<loggedInUsers>
<user name="AnnaBannana">
<created>01.01.2012:00.00.00</created>
<loggedIn>25</loggedIn>
<posts>3</posts>
</user>
</loggedInUsers>
<temperature>82F</temperature>
<load>72</load>
<mem_use>45</mem_use>
<visitors>
<current>42</current>
<browsers name="mozilla" version="X.Y.Z">22</browsers>
<popular_link name="index.html">39</popular_link>
<history>
<max_visitors>789</max_visitors>
<average_visitors>42</average_visitors>
</history>
</visitors>
</server>
</server_stats>
From this one customer may just want create an admin HTML page where they pull the hardware stats out of the tree, and perhaps run some load calculations from the visitor count. Another customer may just want to pull just the visitor count information to display as information on their public site. To have each of these customers system load to be as small as possible we would like to parse their stat selecting XSLT and provide them with just the data they need (which has been requested). Obviously the issue is that one customer may perform a direct select on the visitor count node and another may select the visitors node and select each of the child nodes they want etc.
The 2 hypothetical customers looking for the "current" node in "visitors" might have XSLT looking like:
<xsl:template match="server_stats/server/visitors">
<xsl:value-of select="current"/>
</xsl:template>
OR
<xsl:template match="server_stats">
<xsl:for-each select="server">
<xsl:value-of select="visitors/current"/>
<xsl:value-of select="visitors/popular_link"/>
</xsl:for-each>
</xsl:template>
In this example both are trying to select the same node but the way they do it is different and "current" is not all that specific so we also need the path they used to get there since "current" could be nodes for several items. This hurts us from just looking for "current" in their XSLT and because the way they access the path can be very different we cant just search for the whole path either.
So the result we would like is to parse their XSLT and give us say a List of stats:
Customer 1:
visitors/current
Customer 2:
visitors/current
visitors/popular_link
etc.
Some example selects that break the solution provided below which we will be working on solving:
<xsl:variable name="fcolor" select="'Black'"/> results in a /'Black' entry
<xsl:for-each select="server"> we get the entry, but its children don't show it anymore
<xsl:value-of select="../../#name"/> This was kind of expected, we can try to figure out how to skip attribute based selections but the relative paths show up as I thought they would
<xsl:when test="substring(someNode,1,2)=0 and substring(someNode,4,2)=0 and substring(someNode,7,2)>30"> This one is kind of throwing me, because this shows up as a path item, it's due to the when check in the solution but I don't see any nice solution since the same basic statement could have been checking for a branching path, so this might just be one of those cases we need to post-process or something of that nature.
It is unrealistic to try reconstructing the structure of the source XML document from just looking at an XSLT transformation that operates on this document.
Most XSLT transformations operate on a class of XML documents -- fore than one specific document type.
For example, the following is one of the most used XSLT transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Nothing can be deduced from this transformation about the structure of the XML document(s) that it processes.
There is a huge variety of transformations that just override the template from the above transformation.
For example, this is a useful transformation that renames any element having a particular name, specified in an external parameter:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pName"/>
<xsl:param name="pNewName"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:if test="not(name() = $pName)">
<xsl:call-template name="identity"/>
</xsl:if>
<xsl:element name="{$pNewName}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Once again, absolutely nothing can be said about the names and structure of the source XML document.
UPDATE:
Perhaps something like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="xsl:template[#match]">
<xsl:variable name="vPath" select="string(#match)"/>
<xsl:value-of select="concat('
', $vPath)"/>
<xsl:apply-templates select="*">
<xsl:with-param name="pPath" select="$vPath"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*">
<xsl:param name="pPath"/>
<xsl:apply-templates select="*">
<xsl:with-param name="pPath" select="$pPath"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="xsl:for-each">
<xsl:param name="pPath"/>
<xsl:variable name="vPath">
<xsl:choose>
<xsl:when test="starts-with(#select, '/')">
<xsl:value-of select="#select"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($pPath, '/', #select)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat('
', $vPath)"/>
<xsl:apply-templates select="*">
<xsl:with-param name="pPath" select="$vPath"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="xsl:if | xsl:when">
<xsl:param name="pPath"/>
<xsl:variable name="vPath">
<xsl:choose>
<xsl:when test="starts-with(#test, '/')">
<xsl:value-of select="#test"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($pPath, '/', #test)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat('
', $vPath)"/>
<xsl:apply-templates select="*">
<xsl:with-param name="pPath" select="$pPath"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*[#select]">
<xsl:param name="pPath"/>
<xsl:variable name="vPath">
<xsl:choose>
<xsl:when test="starts-with(#select, '/')">
<xsl:value-of select="#select"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($pPath, '/', #select)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat('
', $vPath)"/>
<xsl:apply-templates select="*">
<xsl:with-param name="pPath" select="$pPath"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XSLT stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="server_stats">
<xsl:for-each select="server">
<xsl:value-of select="visitors/current"/>
<xsl:value-of select="visitors/popular_link"/>
<xsl:for-each select="site">
<xsl:value-of select="defaultPage/Url"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
the following wanted result is produced:
/
server_stats
server_stats/server
server_stats/visitors/current
server_stats/visitors/popular_link
server_stats/site
server_stats/defaultPage/Url
Do Note: Not only is such analysis incomplete, but it must be regarded with a grain of salt. These are results of static analysis. It may happen in practice that out of 100 paths only 5-6 of these are accessed in 99% of the time. Static analysis cannot give you such information. Dynamic analysis tools (similar to profilers) can return much more precise and useful information.
That's going to be challenging, because XSLT is so context-dependent. You're right to call this "parsing" because you're going to have to duplicate a lot of the logic that would go into a parser.
My suggestion would be to start with a brute-force approach, and refine it as you find more test cases that it can't handle. Look at a couple of XSLT files and write code that can find the structures you're looking for. Look at a few more and if any new structures appear, refine your code to find those, too.
This will not find every possible way that XSLT and XPath can be used, as a purely empirical approach to parsing these files would, but it will be a much smaller project and will find the structures that whoever developed the files tended to use.

XSLT choose template

I have an application written in C# that needs to apply a template name to an xml file that is defined in an XSLT.
Example XML:
<Data>
<Person>
<Name>bob</Name>
<Age>43</Age>
</Person>
<Thing>
<Color>Red</Color>
</Thing>
</Data>
Example XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="TargetName" />
<xsl:param name="RootPath" />
<xsl:Template Name="AgeGrabber">
<xsl:value-of select="/Person/Age" />
</xsl:Template>
<xsl:Template Name="ColorGrabber">
<xsl:value-of select="/Color" />
</xsl:Template>
</xsl:stylesheet>
Say I wanted to run the template "ColorGrabber" with path "/Data/Thing" and then run another transform with the template "AgeGrabber" with path "/Data". Is this possible? I was thinking I could pass in the path and the template name (hense the 2 params at the top) and then do some type of switch but it looks like xsl:call-template can not take a parameter as the name attribute.
How can I achieve this behaviour?
There are a number of issues with this question:
<xsl:stylesheet version="2.0" ... is specified, however, at present >NET doesn't natively support XSLT 2.0.
Thecode example is not too meaningful, because a single XML document cannot contain both /Person/Age and /Color elements -- a wellformed XML document has only a single top element and it can be either Person or Color, but not both.
In case there was a more meaningful example:
<Person>
<Age>27</Age>
<HairColor>blond</HairColor>
</Person>
one simple solution is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pProperty" select="'Age'"/>
<xsl:template match="/">
<xsl:value-of select="/*/*[name()=$pProperty]"/>
</xsl:template>
</xsl:stylesheet>
and when this transformation is applied on the above XML document, it produces the wanted result:
27
In case the nestedness of the elements of interest can be arbitrary and/or we need to do different processing on the different elements, then an appropriate solution is to use matching templates (not named ones):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pProperty" select="'HairColor'"/>
<xsl:template match="Age">
<xsl:if test="$pProperty = 'Age'">
This person is <xsl:value-of select="."/> old.
</xsl:if>
</xsl:template>
<xsl:template match="HairColor">
<xsl:if test="$pProperty = 'HairColor'">
This person has <xsl:value-of select="."/> hair.
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), again the correct result is produced:
This person has blond hair.
Finally, if you really want to simulate higher-order functions (HOF) in XSLT 1.0 or XSLT 2.0, see this answer: https://stackoverflow.com/a/8363249/36305 , or learn about FXSL.
Much simpler: prepare two apply-templates rules (for Age and Color elements) and conditionally send proper node to transform - //Person/Age or //Thing/Color
You got it backwards. You ought to create templates, matching the nodes you want to use.
<xsl:stylesheet>
<xsl:template match="Person|Thing">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Person">
<xsl:value-of select="Age" />
</xsl:template>
<xsl:template match="Thing">
<xsl:value-of select="Color" />
</xsl:template>
</xsl:stylesheet>

Generate C# code for Oracle table

I have this XML for a table structure in Oracle (export option in PL/SQL developer). How can I generate code in C# for get entity class?
<?xml version="1.0" encoding="utf-8"?>
<ROWDATA>
<ROW>
<Name>ID_TRANSACCION</Name>
<Type>NUMBER(12)</Type>
<Nullable></Nullable>
<Default></Default>
<Comments>Identificador unico de la transacción.</Comments>
</ROW>
<ROW>
<Name>ID_RECIBO</Name>
<Type>NUMBER(12)</Type>
<Nullable></Nullable>
<Default></Default>
<Comments>Identificador unico del recibo.</Comments>
</ROW>
<ROW>
<Name>IMPORTE_COBRAR</Name>
<Type>NUMBER(10,2)</Type>
<Nullable>Y</Nullable>
<Default></Default>
<Comments>Importe a cobrar</Comments>
</ROW>
</ROWDATA>
Thanks!
Look into xsd.exe - this tool allows you to create an xsd schema from a sample xml document (your export form Oracle) and create a .net class from a given xsd schema.
Now that your sample code is visible it's clear that you would need to transform the xml before you could use it in connection with xsd.exe.
If you are looking for oracle support of the .NET EntityFramework you need to use a specific Oracle provider. One example would be DataDirect.
sample xslt for schema generation
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:template match="/">
<xsl:element name="xsd:schema">
<xsl:attribute name="attributeFormDefault">
qualified
</xsl:attribute>
<xsl:attribute name="elementFormDefault">
qualified
</xsl:attribute>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="ROWDATA">
<xsl:element name="xsd:element">
<xsl:attribute name="name">
<xsl:value-of select="local-name()"/>
</xsl:attribute>
<xsl:element name="xsd:complexType">
<xsl:element name="xsd:sequence">
<xsl:apply-templates />
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="ROW">
<xsl:element name="xsd:element">
<xsl:attribute name="name">
<xsl:value-of select="./Name"/>
</xsl:attribute>
<xsl:attribute name="nillable">
<xsl:value-of select="contains(./Nullable, 'Y')"/>
</xsl:attribute>
<xsl:if test="./Default != ''">
<xsl:attribute name="default">
<xsl:value-of select="./Default"/>
</xsl:attribute>
</xsl:if>
<xsl:element name="xsd:annotation">
<xsl:element name="xsd:documentation">
<xsl:value-of select="./Comments"/>
</xsl:element>
</xsl:element>
<xsl:element name="xsd:simpleType">
<xsl:element name="xsd:restriction">
<xsl:attribute name="base">xsd:decimal</xsl:attribute>
<!-- elaborate data type here -->
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The resulting schema document can be used as an input for xsd.exe.

Comparing 2 XML docs and applying the changes to source document

Here's my problem.I have 2 xmlfiles with identical structure, with the second xml containing only few node compared to first.
File1
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta></delta>
</root>
File2
<root>
<beta>XX</beta>
<delta>XX</delta>
</root>
This's what the result should look like
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta>XX</delta>
</root>
Basically if the node contents of any node in File1 is blank then it should read the values from File2(if it exists, that is).
I did try my luck with Microsoft XmlDiff API but it didn't work out for me(the patch process didn't apply changes to the source doc). Also I'm a bit worried about the DOM approach that it uses, because of the size of the xml that I'll be dealing with.
Can you please suggest a good way of doing this.
I'm using C# 2
Here is a little bit simpler and more efficient solution that that proposed by Alastair (see my comment to his solution).
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vFile2"
select="document('File2.xml')"/>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(text())]">
<xsl:copy>
<xsl:copy-of
select="$vFile2/*/*[name() = name(current())]/text()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta></delta>
</root>
produces the wanted result:
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta>XX</delta>
</root>
In XSLT you can use the document() function to retrieve nodes from File2 if you encounter an empty node in File1. Something like:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="root/*[.='']">
<xsl:variable name="file2node">
<xsl:copy-of select="document('File2.xml')/root/*[name()=name(current())]"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$file2node != ''">
<xsl:copy-of select="$file2node"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This merge seems very specific.
If that is the case, just write some code to load both xml files and apply the changes as you described.

Categories

Resources