XSLT: Copy two files into one common structure - xslt

I try to merge result of SSIS Data Profiler Task for several tables into one XML for inspection of the results within one single file inside "Data Profiler Viewer". The whole problem shrinks to the stronly simplified XML-trasformation here:
File 1 (test_1.xml):
<a xmlns="http://schemas.microsoft.com/sqlserver/2008/DataDebugger/">
<b id="1"/>
<c>
<2: any other XML-structure to come here/>
</c>
</a>
File 2 (test_2.xml):
<a xmlns="http://schemas.microsoft.com/sqlserver/2008/DataDebugger/">
<b id="1"/>
<c>
<1: any other XML-structure to come here/>
</c>
</a>
(Element b is always exacly the same)
Expected result:
<a xmlns="http://schemas.microsoft.com/sqlserver/2008/DataDebugger/">
<b id="1"/>
<c>
<1: any other XML-structure to come here/>
<2: any other XML-structure to come here/>
</c>
</a>
Any help is stronly recommended! I will provide the solution to the original problem here.

Another try:
<?xml version='1.0' encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t="http://schemas.microsoft.com/sqlserver/2008/DataDebugger/"
version="1.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no" version="1.0" encoding="UTF-8"/>
<xsl:template match="t:c">
<xsl:element name="c" namespace="http://schemas.microsoft.com/sqlserver/2008/DataDebugger/">
<xsl:copy-of select="*" />
<xsl:copy-of select="document('test_2.xml')//t:c/node() " />
</xsl:element>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
checked with xalan (set classpath in environment)
java org.apache.xalan.xslt.Process -IN test1_1.xml -XSL test1.xslt -OUT test1_12.xml
and saxon (Change skript to Version = "1.1")
java -jar saxon-9.1.0.8j.jar -s:test_1.xml -xsl:test_1.xslt -o:test_12.xml

Related

How to not touch a record if text exist in an XML field but process the record in no text exist using XSLT 2

Using XSLT 2 how can I skip and not touch a record if a field contains text, in this case a date? I want to only process all the record that don't have a <SurveyDate> and don't touch record that already have a <SurveyDate>.
I tried using a choose statement with a test of "not(SurveyDate/text())" but this is not working. here is my complete XSL code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:lookup="lookup" xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="lookup exsl">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8" media-type="xml/plain" />
<xsl:strip-space elements="*" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Sub">
<!-- This is the final output -->
<xsl:choose>
<xsl:when test="not(SurveyDate/text())">
<xsl:if test= "count(Request/Phase/Status) = count(Request/Phase/Status[matches(. , 'Sup|Ser|Adm|Can')])">
<Request>
<xsl:copy-of select="Request/Code"/>
<SurveyDate>
<xsl:value-of select="format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H1]:[m01]:[s01]')"/>
</SurveyDate>
</Request>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<!-- just for testing remove when done -->
<Test>Do nothing</Test>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
And this is my test XML data.
<?xml version='1.0' encoding='UTF-8'?>
<document>
<businessobjects>
<Sub>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
<Request>
<Code>1.00</Code>
<Description>Test 1</Description>
<SurveyDate>2022-11-02T22:55:55</SurveyDate>
<Phase>
<Code>1.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
<Sub>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
<Request>
<Code>2.00</Code>
<Description>Test 2</Description>
<SurveyDate></SurveyDate>
<Phase>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>2.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
</businessobjects>
</document>
The result XML I need is this:
<document>
<businessobjects>
<Request>
<Code>2.00</Code>
<SurveyDate>2022-11-03T21:45:13</SurveyDate>
</Request>
</businessobjects>
</document>
My advice: forget using xsl:choose or xsl:if, and instead put the conditional logic into the template's match expression:
<xsl:template match="Sub[not(Request/SurveyDate/text())]">
<!-- handle Sub without SurveyDate -->
<!-- ... -->
</xsl:template>
Leave the case where a Sub does have a SurveyDate for the identity template to handle, if you want to copy it unchanged. If you want to remove it (it's not clear from your test code what you want to do with it), you could add another template to do so:
<xsl:template match="Sub"/>
Note that template would have a lower priority than the one above, because its match expression is simpler, so it would apply only to Sub elements which did have a SurveyDate descendant.

Checking multiple occurrences and manipulating them using XSLT

I have a requirement in which I need to check all four values of both occurrences in one condition and it is not given that hey1 and hello1 will come in one occurrence and hey2 and hello2 in the other.
I mean it is not guaranteed that hey1 or hey2 come in as first or second; the same applies for hello1 and hello2.
I am using the below code which gives me the output <output>ININ</output>, but I need it as <output>IN</output>.
I am trying this POC and I have a sample XML below:
<q>
<a>
<b>hey1</b>
<c>hello1</c>
</a>
<a>
<b>hey2</b>
<c>hello2</c>
</a>
</q>
Kindly provide me a solution which checks all four conditions and in which one if/when condition used for each <a> will occur twice or multiple times.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output encoding="UTF-8" version="1.0" method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="a" select="/q/a"/>
<output>
<xsl:for-each select="$a">
<xsl:if test="$a/b='hey1' and $a/c= 'hello1' and $a/b='hey2' and $a/c= 'hello2'">
<xsl:value-of select="'IN'"/>
</xsl:if>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
I hope that I my question is clear enough to answer it.
Thank you for any advice.
Try:
<xsl:template match="q">
<xsl:if test="a[b='hey1' and c='hello1'] and a[b='hey2' and c='hello2'] ">
<xsl:text>IN</xsl:text>
</xsl:if>
</xsl:template>
This will return "IN" for both:
<q>
<a>
<b>hey1</b>
<c>hello1</c>
</a>
<a>
<b>hey2</b>
<c>hello2</c>
</a>
</q>
and:
<q>
<a>
<b>hey2</b>
<c>hello2</c>
</a>
<a>
<b>hey1</b>
<c>hello1</c>
</a>
</q>
but not for:
<q>
<a>
<b>hey1</b>
<c>hello2</c>
</a>
<a>
<b>hey2</b>
<c>hello1</c>
</a>
</q>

Copy data from one XML doc to another using XSLT

I have to copy data of node element from file1.xml to file2.xml.
file1.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
<header>
<AsofDate>31-Dec-2012</AsofDate>
<FundName>This is Sample Fund</FundName>
<Description>This is test description</Description>
</header>
</root>
file2.xml
<?xml version="1.0" encoding="utf-8" ?>
<root id="1">
<header id="2">
<AsofDate id="3"/>
<FundName id="4" />
<Description id="5" />
</header>
</root>
after merging file1.xml into file2.xml, result should look below:
<?xml version="1.0" encoding="utf-8" ?>
<root id="1">
<header id="2">
<AsofDate id="3">31-Dec-2012</AsofDate>
<FundName id="4">This is Sample Fund</FundName>
<Description id="5">This is test description</Description>
</header>
</root>
I am using below XSLT to transform file.
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Below is the code is used to perform transformation:
XslCompiledTransform tf = new XslCompiledTransform();
tf.Load("TranFile.xsl");
tf.Transform("file1.xml", "file2.xml");
but above code is overwriting the file2 content with file1.xml content. This is just sample XML. In real case we don't know name of nodes and hierarchy of the xml file. But whatever structure would be will be same for both file and scenario will be exactly same. I am new to XSLT and not sure is this right approach to accomplish the result. Is it really possible to achieve result through XSLT.
The solution that I post is written having in mind the following:
The only thing that need to be merged are the attributes. Text and element nodes are copied as they appear from file1.xml.
The #id attributes are not numbered sequentially in file2.xml so the #id's in file2.xml could be (for example) 121 432 233 12 944 instead of 1 2 3 4 5. If the case is the latter then you would not need file2.xml to generate the desired output.
The document() function can be used to access files different than the current one. If XslCompiledTransform is giving an error when using the document function I would suggest to follow this using document() function in .NET XSLT generates error . I am using a different XSLT processor (xsltproc) and it works fine.
This solution is based on keeping a reference to the external file, so each time that we process an element in file1.xml the reference is moved to point at the same element in file2.xml. This can be done because according to the problem, both files present the same element hierarchy.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no"/>
<!-- Match the document node as an entry point for matching the files -->
<xsl:template match="/">
<xsl:apply-templates select="node()">
<xsl:with-param name="doc-context" select="document('file2.xml')/node()" />
</xsl:apply-templates>
</xsl:template>
<!-- In this template we copy the elements and text nodes from file1.xml and
we merge the attributes from file2.xml with the attributes in file1.xml -->
<xsl:template match="node()">
<!-- We use this parameter to keep track of where we are in file2.xml by
mimicking the operations that we do in the current file. So we are at
the same position in both files at the same time. -->
<xsl:param name="doc-context" />
<!-- Obtain current position in file1.xml so we know where to look in file2.xml -->
<xsl:variable name="position" select="position()" />
<!-- Copy the element node from the current file (file1.xml) -->
<xsl:copy>
<!-- Merge attributes from file1.xml with attributes from file2.xml -->
<xsl:copy-of select="#*|$doc-context[position() = $position]/#*" />
<!-- Copy text nodes and process children -->
<xsl:apply-templates select="node()">
<xsl:with-param name="doc-context" select="$doc-context/node()" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

with xslt how do I use choose/when to remove elements from document

Here is my example document
<a >
<b flag='foo'>
<c/>
<d/>
</b>
</a>
I am looking to remove the "c" element only when the flag attribute on b is 'bar'.
Ie if flag='foo' the "c" element should not be removed. I do not currently have an xsl debugging tool on my pc, and have been unable to find an online tool that shows xslt error information, and have been running the following test xsl transform on http://xslttest.appspot.com/ :
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" version="1.0" encoding="ISO-8859-1"/>
<!--Identity template to copy all content by default-->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:choose>
<xsl:when test="/a/b[#flag='bar']">
<xsl:template match="/a/b/c"/>
</xsl:when>
</xsl:choose>
</xsl:stylesheet>
When I run this I get Error: Failed to compile stylesheet. 3 errors detected.
I'm looking for help (1) fixing the problems with the xsl code and (2) anything out there like jsfiddle for xsl that can debug/test fragements of xsl code.
You can't put a choose outside a template, but you don't need to - you can use predicates in match expressions so just declare your no-op template to match the elements you want to remove:
<xsl:template match="b[#flag='bar']/c" />
or more generally, if the parent of the c element might have various names
<xsl:template match="c[../#flag='bar']" />
or
<xsl:template match="*[#flag='bar']/c" />

xslt apply-templates selects all remaining textnodes

I have this simplified xml:
<?xml version="1.0" encoding="UTF-8"?>
<a>
<b>
<c>
<d>1</d>
<e>2</e>
</c>
</b>
<f>
<g>3</g>
</f>
</a>
This is the xslt i try to apply:
<?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:template match="a">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="b">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="c">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="d">
</xsl:template>
</xsl:stylesheet>
When I apply this sheet, I get output 2 3, which are the remaining textnodes. I've read about the built-in templates which get applied if it can't find a matching template, but in this case, it should find a template?
What is going on?
Edit:
In this case, i would expect to see nothing, because the templates are empty. But i get 2 3 in stead.
When you do <xsl:template match="d">, you tell the processor to ignore all nodes under <d>.
All other nodes are processed with default rules, including the text() one, which is to print the text.
That's why you see 23, and not 1.
Start from the root:
<xsl:template match="/a">
And specify either a mode (so that the default template does not get called, because it does not find a template for e, f and g) or define your own * template which does nothing at the end of the stylesheet:
<xsl:template match="*"/>