xsl:variable advanced select - xslt

I have several XML elements like this:
<testcase starttime="2012-09-03 10:41:29" timestamp=" 622.922000">
<testlogfile file="" />
<teststep timestamp=" 622.944000" level="0" type="user" ident="1" result="pass">Do something</teststep>
<teststep timestamp=" 622.965000" level="0" type="user" ident="2" result="pass">Do something</teststep>
<teststep timestamp=" 622.986000" level="0" type="user" ident="3" result="pass">Do something</teststep>
<verdict time="2012-09-03 10:41:30" timestamp=" 623.428000" endtime="2012-09-03 10:41:30" endtimestamp=" 623.428000" result="pass" />
<title>Title goes here</title>
<ident>TC100_06</ident>
<description>description goes here</description>
<extendedinfo type="test case status">Approved</extendedinfo>
<extendedinfo type="traceability">some requirement</extendedinfo>
<extendedinfo type="vehicle mode">Hibernate, Parked, Living, Accessory</extendedinfo>
<extendedinfo type="environment">Station with ATB</extendedinfo>
<extendedinfo type="variants">veh variants</extendedinfo>
</testcase>
I would like to make a xsl:variable select query to count testcases by "ident" and "test case status". I am able to implement 2 separate queries, but I dont know how to join them both:
<xsl:variable name="totalTc" select="count(//testcase[./ident!='text' and ./ident!='obsolete' and ./ident!='rtm' and ./ident!='status overview' and ./ident!='statistics'])"/>
<xsl:variable name="approvedTc" select="count(//extendedinfo[#type='test case status' and text()='Approved'])"/>
I expect joined query should look something like this, but I am unable to query attribute:
<xsl:variable name="totalTc" select="count(//testcase[./ident!='text' and ./ident!='obsolete' and ./ident!='rtm' and ./ident!='status overview' and ./ident!='statistics' and ./extendedinfo=='Approved'])"/>

How about...
<xsl:variable name="totalTc" select="count(
//testcase[
ident!='text' and
ident!='obsolete' and
ident!='rtm' and
ident!='status overview' and
ident!='statistics' and
extendedinfo[
#type='test case status' and
.='Approved']
])"/>
Options
Note in XSLT 2.0, you could instead use:
<xsl:variable name="totalTc" select="count(
//testcase[
not (ident in ('text','obsolete','rtm','status overview','statistics')) and
extendedinfo[#type='test case status' and .='Approved']
])"/>
Also, instead of an expression of the form ...
testcase[ A and B]
... one could instead write....
testcase[A][B]
The former case may be ever so slightly more efficient, but I think personal style should also factor in, in relation to your choice of expression. So for example, one could also put...
<xsl:variable name="totalTc" select="count(//testcase
[not (ident in ('text','obsolete','rtm','status overview','statistics'))]
[extendedinfo[#type='test case status'][.='Approved']])"/>

Use:
count(
//testcase
[extendedinfo[#type='test case status']='Approved'
and
not(teststep
[contains('|text|obsolete|rtm|status overview|statistics',
concat('|',#ident,'|')
)
]
)
]
)

Related

Saxon XSLT-Compiler omits allowances and charges

I am using the Saxon XsltTransformer to transform the XML of electronic invoices ("XRechnungen", the German adaption).
The invoices are XML files with "un/cefact CII" or "UBL" syntax and both need to be translated in the same XR syntax.
For UBL I use the files "ubl_invoice_xr.xsl" and "ubl_creditnore_xr.xsl" and this works without any problems.
For CII I use the "cii_xr.xsl" from
https://github.com/itplr-kosit/xrechnung-visualization/tree/master/src/xsl
but the result omits the allowances and charges (both on invoice_line-level and document-level).
At https://github.com/itplr-kosit/xrechnung-testsuite/tree/master/src/test/business-cases/standard
I found a lot of test invoices ("uncefact" suffix) bot none of them is transformed correctly.
Looking at the "cii-xr.xsl" you find:
<xsl:template mode="BG-20"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator='false']">
<xsl:variable name="bg-contents" as="item()*"><!--Der Pfad /rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator='false'] der Instanz in konkreter Syntax wird auf 7 Objekte der EN 16931 abgebildet. -->
<xsl:apply-templates mode="BT-92" select="./ram:ActualAmount"/>
<xsl:apply-templates mode="BT-93" select="./ram:BasisAmount"/>
<xsl:apply-templates mode="BT-94" select="./ram:CalculationPercent"/>
<xsl:apply-templates mode="BT-95" select="./ram:CategoryTradeTax/ram:CategoryCode"/>
<xsl:apply-templates mode="BT-96" select="./ram:CategoryTradeTax/ram:RateApplicablePercent"/>
<xsl:apply-templates mode="BT-97" select="./ram:Reason"/>
<xsl:apply-templates mode="BT-98" select="./ram:ReasonCode"/>
</xsl:variable>
<xsl:if test="$bg-contents">
<xr:DOCUMENT_LEVEL_ALLOWANCES>
<xsl:attribute name="xr:id" select="'BG-20'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:sequence select="$bg-contents"/>
</xr:DOCUMENT_LEVEL_ALLOWANCES>
</xsl:if>
</xsl:template>
<xsl:template mode="BT-92"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge/ram:ActualAmount">
<xr:Document_level_allowance_amount>
<xsl:attribute name="xr:id" select="'BT-92'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:call-template name="amount"/>
</xr:Document_level_allowance_amount>
</xsl:template>
<xsl:template mode="BT-93"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge/ram:BasisAmount">
<xr:Document_level_allowance_base_amount>
<xsl:attribute name="xr:id" select="'BT-93'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:call-template name="amount"/>
</xr:Document_level_allowance_base_amount>
</xsl:template>
<xsl:template mode="BT-94"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge/ram:CalculationPercent">
<xr:Document_level_allowance_percentage>
<xsl:attribute name="xr:id" select="'BT-94'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:call-template name="percentage"/>
</xr:Document_level_allowance_percentage>
</xsl:template>
<xsl:template mode="BT-95"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge/ram:CategoryTradeTax/ram:CategoryCode">
<xr:Document_level_allowance_VAT_category_code>
<xsl:attribute name="xr:id" select="'BT-95'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:call-template name="code"/>
</xr:Document_level_allowance_VAT_category_code>
</xsl:template>
<xsl:template mode="BT-96"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge/ram:CategoryTradeTax/ram:RateApplicablePercent">
<xr:Document_level_allowance_VAT_rate>
<xsl:attribute name="xr:id" select="'BT-96'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:call-template name="percentage"/>
</xr:Document_level_allowance_VAT_rate>
</xsl:template>
<xsl:template mode="BT-97"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge/ram:Reason">
<xr:Document_level_allowance_reason>
<xsl:attribute name="xr:id" select="'BT-97'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:call-template name="text"/>
</xr:Document_level_allowance_reason>
</xsl:template>
<xsl:template mode="BT-98"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode">
<xr:Document_level_allowance_reason_code>
<xsl:attribute name="xr:id" select="'BT-98'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:call-template name="code"/>
</xr:Document_level_allowance_reason_code>
</xsl:template>
So the output file should contain "<xr:DOCUMENT_LEVEL_ALLOWANCES>" tags with (for example)
"</xr:Document_level_allowance_amount>" children, but it does not.
No matter if the input file has allowances/charges in the "invoice_lines" or at "document_level".
Since I use vb.NET I use the .NET wrapper of saxon-HE:
Public Shared Function Cii2UnifiedXML(ByVal vsXmlFilePath As String) As Result
Dim cproc As New Saxon.Api.Processor
Dim cComp As Saxon.Api.XsltCompiler = cproc.NewXsltCompiler
Dim exe As Saxon.Api.XsltExecutable
Dim inputStream As New StreamReader(vsXmlFilePath)
#Disable Warning BC40000 ' I know, XmlDataDocument is deprecated
Dim xmldoc As New XmlDataDocument()
#Enable Warning BC40000
Dim cResult As New Result
Dim ctrans As Saxon.Api.XsltTransformer
Dim seri As Saxon.Api.Serializer
Dim sPath As String = String.Empty
Dim sFilename As String = String.Empty
Dim outStream As FileStream = Nothing
Try
exe = cComp.Compile(New MemoryStream(Encoding.UTF8.GetBytes(My.Resources.cii_xr)))
ctrans = exe.Load
seri = cproc.NewSerializer
sPath = Path.GetDirectoryName(vsXmlFilePath)
sFilename = Path.Combine(sPath, $"{Path.GetFileNameWithoutExtension(vsXmlFilePath)}_Unified.xml")
outStream = New FileStream(sFilename, FileMode.Create, FileAccess.Write)
ctrans.SetInputStream(inputStream.BaseStream, New Uri(sPath))
seri.SetOutputStream(outStream)
ctrans.Run(seri)
cResult.Success = True
cResult.Obj = sFilename
Catch ex As Exception
cResult.Exception = ex
cResult.Success = False
cResult.ErrorMsg = ex.Message
Finally
xmldoc = Nothing
If outStream IsNot Nothing Then outStream.Close()
If inputStream IsNot Nothing Then inputStream.Close()
End Try
Return cResult
End Function
I don't know if I don't understand how the XsltCompiler works (and therefore I am too stupid) or if it's a bug, since the translation of UBL invoices works great.
The output file of UBL translations contains the elements "DOCUMENT_LEVEL_ALLOWANCES", "DOCUMENT_LEVEL_CHARGES", "INVOICE_LINE_ALLOWANCES" and "INVOICE_LINE_CHARGES".
All files can be found at https://github.com/itplr-kosit
Thanks a lot
It's hard to make much headway on debugging this for you because I don't understand the XML vocabulary or the logic of the stylesheet. However, I tried it on one sample document from the test set, and the template rule you show us with
<xsl:template mode="BG-20" match="/rsm:CrossIndustryInvoice
/rsm:SupplyChainTradeTransaction
/ram:ApplicableHeaderTradeSettlement
/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator='false']">
doesn't get executed because the ApplicableHeaderTradeSettlement element in the source document doesn't have a child called SpecifiedTradeAllowanceCharge.
Of course I've no idea what this means or how you fix it, but there's no evidence that the stylesheet isn't doing what it was designed to do on the one source document that I tested.
The match is /rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator='false'] looking for the value false in the predicate while your input sample seems to have the value true. So that could be one reason why the templates you have cited are not applied.
Another reason could be the wrong predicate or the lack of <xsl:strip-space/> although the online XSLT seems to use the right predicate [ram:ChargeIndicator/udt:Indicator='false'], i.e. with the additional child.
the problem was found, you won't believe what caused it...
we tried several XR-visualizer that showed the charges and allowances correctly.
We tried several translation tools (web + desktop) but none of them created the output file correctly (with allowances and charges).
Most validator tools for "XPath" showed the invoices as "correct" but only ONE told us, that there is a line break between the "indicator" element and it's parent element "ChargeIndicator".
Of course... there are hundreds of line breaks everywhere?
This block in the cii invoice file does NOT work:
<ram:ChargeIndicator>
<udt:Indicator>true</udt:Indicator>
</ram:ChargeIndicator>
but this works:
<ram:ChargeIndicator><udt:Indicator>true</udt:Indicator></ram:ChargeIndicator>
I tried to contact the kosit several times but they have no telephone hotline for development issues and I did not get a reply to the mails I wrote 8, 5 and 2 weeks ago.
That's funny, because the version XRechnung 2.0 will be mandatory to use by 1st jan. 2021.
Thanks for all your ideas guys

Want to get the preceding-sibling

I am parsing the XML like below,
<Service>
<ServiceName>
<Plans>
<Plan>
<PlanNumber>A</PlanNumber>
....
</Plan>
</Plans>
</Service>
<Service>
<ServieName>
<Plans>
<Plan>
<PlanNumber>B</PlanNumber>
....
</Plan>
</Plans>
</Service>
I am iterating through for-each Plans/Plan and trying to print preceding-sibling::Plan/PlanNumber, I am not getting anything.
I want this to iterate through the unique plan number so I am using an if statement next to the for-each. <xsl-if test="not(PlanNumber = preceding-sibling::Plan/PlanNumber)">
I am not getting through the PlanNumbers.
In your example, there is only one Plan in Plans (and only one Plans in Service). This means that Plan has no siblings and your attempt preceding-sibling::Plan/PlanNumber selects nothing.
To get the preceding PlanNumber from the context of Plan, you need to do:
../../preceding-sibling::Service/Plans/Plan/PlanNumber
or:
preceding::PlanNumber
<xsl:foreach select="Plan">
<xsl:foreach select="PlanNumber">
<xsl:if test="position()!=1">
<xsl:value-of select="preceding-sibling::text()"/>
</xsl:if>
</xsl:foreach>
</xsl:foreach>

Unable to get the value of 'xml:lang' attribute

<xoe:documents xmlns:xoe="http://xxxxxx" count="1">
<xocs:doc xmlns:xocs="xxxxxx" xmlns:xsi="yyyyyyy" xsi:schemaLocation="zzzzzz">
<xocs:meta>...</xocs:meta>
<xocs:serial-item>
<!-- this line -->
<article xmlns:sa="www.google.hgy" xmlns="http://www.xyzq1.org/xml/ja/dtd" version="5.4" xml:lang="pl" docsubtype="rev">
<article-info>
</article-info>
</article>
</xocs:serial-item>
</xocs:doc>
</xoe:documents>
I am unable to get the value of 'xml:lang' attribute. Even thought I tried with the below xpath
<xsl:variable name="rootPath" select="/xoe:documents/xocs:doc/xocs:serial-item"/>
<xsl:variable name="lang" select="$rootPath/ja:article[#xml:lang]"/>
or
<xsl:variable name="lang" select="$rootPath/ja:article/#xml:lang"/>
here ja is already defined in my xslt code
xmlns:ja="http://www.xyzq1.org/xml/ja/dtd"
Can some one please help?
First, you need to declare these:
xmlns:xoe="http://xxxxxx"
xmlns:xocs="xxxxxx"
xmlns:ya="www.yahoo.mkt"
Then you can get the value of the xml:lang attribute using:
<xsl:value-of select="/xoe:documents/xocs:doc/xocs:serial-item/ya:article/#xml:lang"/>
Note that the URIs in your stylesheet namespace declarations must be the same URIs that appear in your source XML. The prefixes can be anything you like.

XSLT 1.0: selecting based on attribute of preceding siblings of current() node

I have a base xml file like:
<base>
...
<s l="something" />
<s l="somethingelse" />
<s>
...
</base>
In my xslt, I use the document() function on each <s> to pull in another file structured like:
<otherbase>
...
<r l="something" />
<r l="somethingelse" />
<r l="evenmore" />
...
</otherbase>
At <s> in the base file, I am trying to select all <r>s whose #l does not match any preceding-sibling::*/#l of the current <s>. Thus for the third <s> with no attribute, it would match <r l="evenmore" /> etc.
Something like:
<xsl:for-each select="document('mydoc.xml')//r[#l != preceding-sibling::*/#l]">
...
</xsl:for-each>
would work if I were matching within the <r> attributes I believe, but I need preceding-siblings of current() ... I haven't been able to figure out the syntax.
Unfortunately there are no 2.0 processors in my environment, stuck in xslt 1.0.
You need to use the current() function when you want to refer to the current s node within a predicate. Try:
select="document('mydoc.xml')//r[not(#l = current()/preceding-sibling::s/#l)]"
Note also the difference between $a != $b and not($a = $b).

Retrieving sub-element attribute's value based on uniq-key with xslt ant

My xml looks something like this:
<someRoot
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../my.xsd">
<position name="quality">
<db>
<server name="blah-blah" type="xyz"/>
</db>
<some-more-element value="12345"/>
<my-needy-element>
<my-needy-sub-element name="uk1"
value="a,b,c"
more-attr="some-val"/>
<my-needy-sub-element name="uk2"
value="x,b,y,c"
more-attr="some-diff-val"/>
</my-needy-element>
</position>
</someRoot>
Retrieving say my single db-server name works after I do xmlproperty & samy something like : ${db.server(name)}. Now I am trying to retrieve value for sub elements - but it always
gives me all row values matching it. eg: ${my-needy-element.my-needy-sub-element(more-attr)} will print some-val,some-diff-val . How do I get attribute value matching
to paricular unique name , something like {my-needy-element.my-needy-sub-element(more-attr) with name="uk1"}
Adding editing comment: Is there any way even if I use xslt ant-task by passing 'uk1' & get required sub-element attribute value? with xsl:if or key match
Amending more:
Have tried with xsl & then an xslt ant , but it doesnot seem to give what I am looking for:
<xsl:template match="/someRoot/my-needy-element/my-needy-sub-element">
<someRoot>
<xsl:key name="my-needy-sub-element" match="text()" use="#name"/>
<xsl:apply-templates select="key('name',$xslParamPassed)"/>
</someRoot>
</xsl:template>
thanks,
You can't do this with xmlproperty but you might consider the third-party xmltask which lets you use full-fledged XPath expressions:
<xmltask source="file.xml">
<copy path="/someRoot/position/my-needy-element/my-needy-sub-element[#name='uk1']/#more-attr"
property="more.attr" />
</xmltask>
<echo>${more.attr}</echo>