XSLT choose template - c#

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>

Related

How to transform XML using XSLT based on external parameter?

I have incoming XML message and each message have different schema. I want to transform that request into another schema using C#.Net as my XSLT processor. Here is simplified scenario of the situation I have.
Incoming request:
<?xml version="1.0" encoding="utf-8"?>
<Request xmlns="http://www.example.com/api">
<SourceId>SourceId1</SourceId>
<RequestId>RequestId1</RequestId>
<StatusEvent>
<TenderId>TenderId1</TenderId>
<EventCode>TENDER_STARTED</EventCode>
</StatusEvent>
</Request>
Translate to:
<?xml version="1.0" encoding="utf-8"?>
<TransactionStatus xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
<Event>TRANSACTION_STARTED</Event>
</TransactionStatus>
Incoming request:
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns="http://www.example.com/api">
<SourceId>SourceId1</SourceId>
<RequestId>RequestId1</RequestId>
<TenderCreated>
<TenderId>TenderId1</TenderId>
</TenderCreated>
</Response>
Translate to:
<?xml version="1.0" encoding="utf-8"?>
<TransactionStarted xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
</TransactionStarted>
Here is the XSLT I'm currently using to achieve above result,
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://www.example.com/api"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="ns0 xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="text()"/>
<xsl:template match="ns0:StatusEvent[1]">
<TransactionStatus
xmlns="http://www.example1.com/api">
<RequestId>
<xsl:value-of select="//ns0:RequestId"/>
</RequestId>
<TransactionId>
<xsl:value-of select="ns0:TenderId"/>
</TransactionId>
<Event>
<xsl:value-of select="ns0:EventCode"/>
</Event>
</TransactionStatus>
</xsl:template>
<xsl:template match="ns0:TenderCreated[1]">
<TransactionStarted
xmlns="http://www.example1.com/api">
<RequestId>
<xsl:value-of select="//ns0:RequestId"/>
</RequestId>
<TransactionId>
<xsl:value-of select="ns0:TenderId"/>
</TransactionId>
</TransactionStarted>
</xsl:template>
</xsl:stylesheet>
So Here is the two questions I have,
For the current scenario I'm getting correct result but, is there any better way to achieve this?
For some of the incoming request, I want to select template based on external parameter, how do I achieve that?
Update: More clarification on second question,
e.g: In 2nd Incoming request I might have TenderUpdated instead of TenderCreated and for that I want to translate that into either TransactionUpdated or TransactionCanceled depends on external string parameter.
so If incoming request is like,
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns="http://www.example.com/api">
<SourceId>SourceId1</SourceId>
<RequestId>RequestId1</RequestId>
<TenderUpdated>
<TenderId>TenderId1</TenderId>
</TenderUpdated>
</Response>
And parameter passed is Update, translate to
<?xml version="1.0" encoding="utf-8"?>
<TransactionUpdated xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
<Update/>
</TransactionUpdated>
And if parameter passed is Cancel , translate to
<?xml version="1.0" encoding="utf-8"?>
<TransactionCanceled xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
<Cancel/>
</TransactionCanceled>
This is simplified scenario, actual message have more xml tag and TransactionUpdated and TransactionCanceled differs much.
If you know all result elements should be in the namespace http://www.example1.com/api then you can put that on the xsl:stylesheet e.g.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.example1.com/api"
xmlns:ns0="http://www.example.com/api"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="ns0 xs">
As for the parameter, declare it as
<xsl:param name="transactionName" select="'Updated'"/>
and when you want to create an element using that parameter don't use a literal result element but xsl:element instead:
<xsl:element name="Transaction{$transactionName}">...</xsl:element>
Unfortunately in XSLT 1.0 the use of parameter or variables references inside of patterns is not allowed so to handle the condition you can only write a template matching on the element name and then you need to use xsl:choose/xsl:when to handle the different element names. Here is an example that you can hopefully extend:
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:api="http://www.example.com/api"
xmlns="http://www.example1.com/api"
exclude-result-prefixes="api">
<xsl:param name="transactionName" select="'Update'"/>
<xsl:output indent="yes"/>
<xsl:template match="api:Response">
<xsl:element name="Transaction{$transactionName}">
<xsl:apply-templates select="api:RequestId | api:TenderUpdated/api:TenderId"/>
<xsl:choose>
<xsl:when test="$transactionName = 'Update'">
<Update/>
</xsl:when>
<xsl:when test="$transactionName = 'Cancel'">
<Cancel/>
</xsl:when>
</xsl:choose>
</xsl:element>
</xsl:template>
<xsl:template match="api:RequestId">
<RequestId>
<xsl:apply-templates/>
</RequestId>
</xsl:template>
<xsl:template match="api:TenderId">
<TransactionId>
<xsl:apply-templates/>
</TransactionId>
</xsl:template>
</xsl:transform>
Online at http://xsltransform.net/94rmq5R.
If there are lots of differences between the input formats then I might be tempted to handle them by different stylesheets. If that is not possible then it might make sense to branch in the template for the root and use modes on templates to distinguish the handling e.g.
<xsl:template match="api:Response">
<xsl:choose>
<xsl:when test="$transactionName = 'Update'">
<xsl:apply-templates select="." mode="update"/>
</xsl:when>
<xsl:when test="$transactionName = 'Cancel'">
<xsl:apply-templates select="." mode="cancel"/>
</xsl:when>
</xsl:choose>
</xsl:element>
</xsl:template>
<xsl:template match="api:Response" mode="update">
<TransactionUpdate>
<xsl:apply-templates select="api:Foo | api:Bar" mode="update"/>
<Update/>
<TransactionUpdate>
</xsl:template>
<!-- now add templates for the other elements and for other mode(s) here -->

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 lower-case using .NET

i use the following XSLT by using XMLSpy:
<?xml version="1.0" encoding="UTF-16"?>
<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-16" indent="yes"/>
<xsl:template match="*">
<xsl:element name="{lower-case(local-name())}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="* | text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{lower-case(local-name())}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
If i try to use it in my source (XslCompiledTransform) i get an exception telling me that the function 'lower-case()' is not part of the XSLT synthax.
So i changed the transformation a little bit:
fn:lower-case
Now my exception is that the script or external object prefixed by 'http://www.w3.org/2005/xpath-functions' can not be found.
Whats the matter here? How can i fix it?
Regards
.NET does not implement XSLT 2.0/XPath 2.0.
In XPath 1.0 one can use the following expression, instead of lower-case():
translate(yourString,
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')

XSLT transform creates StackoverflowException

I tried to perform XSLT transform of a XSD file. My goal is in the end to create SQL from XSD. So far so good, this is what I have:
void Convert()
{
XPathDocument xpathDoc = new XPathDocument(#"myschema.xsd");
string xslPath = #"convert.xsl";
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(xslPath, new XsltSettings(true, true), null);
using (FileStream fs = File.Create(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "output.sql")))
{
try
{
transform.Transform(xpathDoc, null, fs);
}
catch
{
fs.Close();
}
}
}
This is the XSLT file which is failing:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Get schema nodes from this schema and any included schemas -->
<xsl:variable name="contents" select="/|document(//xs:include/#schemaLocation)" />
<xsl:template match="*" >
<xsl:for-each select="$contents" >
<xsl:apply-templates select=".//xs:element" />
</xsl:for-each>
</xsl:template>
<xsl:template match="xs:element">
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
I always get a StackoverflowException in System.Data.SqlXml.dll. How can I stop the recursion? Shouldn't it stop if no xs:element remain?
EDIT:
The original code was from here and it already had the error. I tried to fix it by simplifying the XSLT until only the error remained.
the line
<xsl:apply-templates select=".//xs:element" />
sends the current node (xs:element) to the template it started from. Then it matches it in the for loop and sends itself again. Stack overflow is inevitable.
The problem that causes the endless recursion is here:
<xsl:template match="xs:element">
<xsl:apply-templates />
</xsl:template>
The <xsl:apply-templates> instruction will cause other elements than xs:element to be processed. For all such elements the following template is selected for processing:
<xsl:template match="*" >
<xsl:for-each select="$contents" >
<xsl:apply-templates select=".//xs:element" />
</xsl:for-each>
</xsl:template>
and this closes the loop and causes the endless recursion.
This problem can be avoided in the following way:
<xsl:template match="xs:include">
<xsl:apply-templates select="document(#schemaLocation)/*/>
</xsl:template>
No other special templates are necessary -- just add the templates that process specific xsd elements.
As Woody has answer, you have a circular call ("For every element... apply templates for elements"). So, the proper way is:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/" name="root">
<xsl:param name="schema" select="*/*"/>
<xsl:choose>
<xsl:when test="$schema[self::xs:include]">
<xsl:call-template name="root">
<xsl:with-param name="schema" select="$schema[not(self::xs:include)]|document($schema[self::xs:include]/#schemaLocation)/*/*"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="*/*">
<xsl:with-param name="schema" select="$schema"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this stylesheet you need to add your templates with param schema been your expanded schema. Also, you need to apply templates with a param schema as select="$schema".
EDIT: Sorry, a little mistake. Also, an explanation: when you process a modular schema you need to get first the complete expanded schema because otherwise you end up calling a recursion template for getting reference and type definitions in diferent schema modules every time. With my template you get the complete expanded schema in $schema param, so when you process a xs:element with #type="someType" you could continue the process with xsl:apply-templates select="$schema[self::xs:complexType[#name='someType']]".

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