XSL cross reference - xslt

I am stuck with a XSLT 1.0 problem. I tried to find info on StackOverflow but I couldn't apply the examples.
Here is the structure of my XML:
<XML>
<PR>
<AS>
<ID_AS>AS-001</ID_AS>
<FIRST>
<ID_CATALOG>Id-001</ID_CATALOG>
<STATUS>NOK</STATUS>
</FIRST>
<SECOND>
<ID_CATALOG>Id-002</ID_CATALOG>
<STATUS>OK</STATUS>
</SECOND>
</AS>
<AS>
<ID_AS>AS-002</ID_AS>
<FIRST>
<ID_CATALOG>Id-003</ID_CATALOG>
<STATUS>OK</STATUS>
</FIRST>
<SECOND>
<ID_CATALOG>Id-004</ID_CATALOG>
<STATUS>OK</STATUS>
</SECOND>
</AS>
</PR>
<METADATA>
<ID_CATALOG>Id-001</ID_CATALOG>
<ANGLES>32.25</ANGLES>
</METADATA>
<METADATA>
<ID_CATALOG>Id-002</ID_CATALOG>
<ANGLES>18.75</ANGLES>
</METADATA>
<METADATA>
<ID_CATALOG>Id-003</ID_CATALOG>
<ANGLES>5.23</ANGLES>
</METADATA>
<METADATA>
<ID_CATALOG>Id-004</ID_CATALOG>
<ANGLES>12.41</ANGLES>
</METADATA>
</XML>
I want to display for each AS, the FIRST/ID_CATALOG, FIRST/STATUS and ANGLES corresponding to the ID_CATALOG, then SECOND/etc.
The output would be similar to:
AS-001
Id-001 NOK 32.25
Id-002 OK 18.75
AS-002
Id-003 OK 5.23
Id-004 OK 12.41
I tried the following XSL but I only get the ANGLES for the first item
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns="http://earth.google.com/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:hma="http://earth.esa.int/hma" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:output method="xml" indent="yes" encoding="ISO-8859-1"/>
<!--==================MAIN==================-->
<xsl:template match="/">
<html>
<body>
AS List:
<br/><br/>
<xsl:call-template name="ASandCo"/>
</body>
</html>
</xsl:template>
<!--==================TEMPLATES==================-->
<xsl:template name="ASandCo">
<AS>
<xsl:for-each select="XML/PR/AS">
<xsl:value-of select="ID_AS"/>
<br/>
<xsl:value-of select="FIRST/ID_CATALOG"/> - <xsl:value-of select="FIRST/STATUS"/> -
<xsl:if test="contains(/XML/METADATA/ID_CATALOG, FIRST/ID_CATALOG)">
<xsl:value-of select="/XML/METADATA/ANGLES"/>
</xsl:if>
<br/>
<xsl:value-of select="SECOND/ID_CATALOG"/> - <xsl:value-of select="SECOND/STATUS"/> -
<xsl:if test="contains(/XML/METADATA/ID_CATALOG, SECOND/ID_CATALOG)">
<xsl:value-of select="/XML/METADATA/ANGLES"/>
</xsl:if>
<br/><br/>
</xsl:for-each>
</AS>
</xsl:template>
</xsl:stylesheet>
This XSLT will be applied to very large XML files, so I am trying to find the most efficient way.
Thank you very much in advance!

It seems like you want to look up some metadata metadata based on the ID_CATALOG value.
An efficient way to do this is by using a key. You can define a key on the top level:
<xsl:key name="metadata-by-id_catalog" match="METADATA" use="ID_CATALOG"/>
And then you can look up the ANGLES value using the key for a given ID_CATALOG value like this:
<xsl:value-of select="key('metadata-by-id_catalog', FIRST/ID_CATALOG)/ANGLES"/>
and this:
<xsl:value-of select="key('metadata-by-id_catalog', SECOND/ID_CATALOG)/ANGLES"/>

Related

Is there a way to replace the for-each with apply-templates in an XSLT Transform?

Environment: XSLT 1.0
The transform will take each element in partOne section and lookup #field attribute in partTwo section using #find attribute and then output #value attribute.
I'm using a for-each loop and was wondering if apply-templates could work?
xml
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="file.xslt"?>
<xml>
<partOne>
<target field="hello"/>
<target field="world"/>
</partOne>
<partTwo>
<number input="2" find="hello" value="valone" />
<number input="2" find="world" value="valtwo" />
<number input="2" find="hello" value="valthree" />
<number input="2" find="world" value="valfour" />
</partTwo>
</xml>
xsl
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="/xml/partOne/target">
,<xsl:value-of select="#field"/>
<xsl:for-each select="/xml/partTwo/number[#find=current()/#field]">
,<xsl:value-of select="#value"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
,hello
,valone
,valthree
,world
,valtwo
,valfour
Well, it seems straight-forward to change
<xsl:for-each select="/xml/partTwo/number[#find=current()/#field]">
,<xsl:value-of select="#value"/>
</xsl:for-each>
to
<xsl:apply-templates select="/xml/partTwo/number[#find=current()/#field]"/>
with a template
<xsl:template match="partTwo/number">
,<xsl:value-of select="#value"/>
</xsl:template>
As your root template so far processes all elements you need to change it to
<xsl:template match="/">
<xsl:apply-templates select="xml/partOne"/>
</xsl:template>
to avoid processing the partTwo element(s) twice.
For the cross-reference you might want to use a key in both versions:
<xsl:key name="ref" match="partTwo/number" use="#find"/>
and then select="key('ref', #field)" instead of select="/xml/partTwo/number[#find=current()/#field]" for the apply-templates or for-each.

XSLT Template Matching from xml

I have the following xml code that I need to transform into a text file. I'm struggling massively with the namespaces in XSLT. I'm used to doing Export/Record and simple default namespace matches. I have the following XML code:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPerson xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MultiCountryIntegrationService.Core.Entities.Dto">
<Person>
<Addresses>
<Address>
<City>London</City>
<Country>GB</Country>
<County>London</County>
<CreatedDate xmlns:d5p1="http://schemas.datacontract.org/2004/07/System">
<d5p1:DateTime>2017-02-21T11:05:08.8387752Z</d5p1:DateTime>
<d5p1:OffsetMinutes>0</d5p1:OffsetMinutes>
</CreatedDate>
<DeletedDate xmlns:d5p1="http://schemas.datacontract.org/2004/07/System" i:nil="true" />
<EndDate xmlns:d5p1="http://schemas.datacontract.org/2004/07/System" i:nil="true" />
<Extension i:nil="true" />
<Id>8e5b30d0</Id>
<ModifiedDate xmlns:d5p1="http://schemas.datacontract.org/2004/07/System" i:nil="true" />
<Number>8</Number>
<StartDate xmlns:d5p1="http://schemas.datacontract.org/2004/07/System">
<d5p1:DateTime>2016-06-30T22:00:00Z</d5p1:DateTime>
<d5p1:OffsetMinutes>120</d5p1:OffsetMinutes>
</StartDate>
<Street>Somewhere</Street>
<Type>Primary</Type>
<ZipCode>L1 1LL</ZipCode>
</Address>
</Addresses>
<PersonLocalReferences xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</Person>
<Person>
<Addresses>
<Address>
<City>Birmingham</City>
<Country>ETC...</Country>
</Address>
</Addresses>
<PersonLocalReferences xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</Person>
</ArrayOfPerson>
My XSLT - As you can see I've tried several approaches, done countless hours on google and stackoverflow. If I remove the i: and xmlns from the XML I can get the stylesheet to work, but I'm not in a position to change the XML. I'm using Visual Studio 2016 to create and run the xslt. If I use "#* | node()" I get everything, and I only want to output certain bits of information into a text file.
If I run the xslt below, I just get the header and EOF, so it looks like the <xsl:apply-templates select="ArrayOfPerson/Person"/> isn't selecting the right level of data.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance" exclude-result-prefixes="i">
<xsl:output omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:call-template name="header" />
<xsl:text>Person</xsl:text>
<xsl:apply-templates select="ArrayOfPerson/Person"/>
<xsl:value-of select="$newline" />
<xsl:text>EOF</xsl:text>
</xsl:template>
<xsl:template match="Person">
<xsl:value-of select="ArrayOfPerson/Person/Addresses/Address/City"/>
<xsl:value-of select="Person/Addresses/Address/City"/>
<xsl:value-of select="Addresses/Address/City"/>
<xsl:value-of select="."/>
<xsl:value-of select="$newline" />
<xsl:text>match</xsl:text>
<xsl:value-of select="$newline" />
</xsl:template>
</xsl:stylesheet>
As Michael wrote in his comment, add xpath-default-namespace="http://schemas.datacontract.org/2004/07/MultiCountryIntegrationService.Core.Entities.Dto"
to your xsl:stylesheet tag.
Note that you must include full namespace.

XSLT: find first element above selected element

I want to get the first heading (h1) before a table in a docx.
I can get all headings with:
<xsl:template match="w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<context>
<xsl:value-of select="." />
</context>
</p>
</xsl:template>
and I can also get all tables
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
But unfortunetly the processor does not accept
<xsl:template match="w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
Here is a reduced XML file extracted from a docx: http://pastebin.com/KbUyzRVv
I want something like that as a result:
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>We’re in the middle of something</context> <- my heading
<table>data</table>
Thanks to Daniel Haley I was able to find a solution for that problem. I'll post it here, so it is independend of the pastebin I postet below.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:v="urn:schemas-microsoft-com:vml" exclude-result-prefixes="xsl w v">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="w:tbl">
<context>
<xsl:value-of select="(preceding-sibling::w:p[w:pPr/w:pStyle[#w:val = 'berschrift1']])[last()]"/>
</context>
<table>
<xsl:value-of select="."/>
</table>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Hard to answer without a Minimal, Complete, and Verifiable example, but try this:
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="(preceding::w:p[w:pPr/w:pStyle[#w:val='berschrift1']])[last()]"/>
</table>
</p>
</xsl:template>
Assuming you can use XSLT 2.0 (and most people can, nowadays), I find a useful technique here is to have a global variable that selects all the relevant nodes:
<xsl:variable name="special"
select="//w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']][1]"/>
and then use this variable in a template rule:
<xsl:template match="w:p[. intersect $special]"/>
In XSLT 3.0 you can reduce this to
<xsl:template match="$special"/>

XSLT Appending incremented value to existing attribute value

For my input XML, I have written the XSLT , But I cannot make the XSLT to generate the <mynewtag> correctly. Please help.
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
Expected Output:
<?xml version="1.0" encoding="UTF-8"?>
<newbooks>
<newbook>
<mynewtag id="book1" />
<title>charithram</title>
<author>sarika</author>
</newbook>
<newbook>
<mynewtag id="book2" />
<title>doublebell</title>
<author>psudarsanan</author>
</newbook>
</newbooks>
XSLT that I tried: [I understand the syntax is incorrect for <mynewtag> ]. But I don't know to fix it to get the desired output.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:template match="/">
<newbooks>
<xsl:for-each select="books/child::*">
<newbook>
<mynewtag id="book<xsl:number value='position()' format='1' />" />
<title>
<xsl:value-of select="title" />
</title>
<author>
<xsl:value-of select="author" />
</author>
</newbook>
</xsl:for-each>
</newbooks>
</xsl:template>
</xsl:stylesheet>
Trying in Online XSLT transformer , http://www.freeformatter.com/xsl-transformer.html
I tried assigning the position to a variable, but still I face the same problem of not knowing how to append it with the attribute value book .
<xsl:variable name="cnt">
<xsl:number value='position()' format='1' />
</xsl:variable>
<xsl:value-of select = "$cnt" />
Note: If I remove the <xsl:number value='position()' format='1' /> from XSL, then the syntax is correct, but then I won't be able to generate book1 book2 etc as <mynewtag> attribute values.
Please help.
Added: <mynewtag> is a required element. It is like any other XML element like <title> that is required in output. It is not an element just to hold the attribute id. Sorry if there is a confusion on this.
Adding Solution here, from the answers obtained to summarize:
<mynewtag>
<xsl:attribute name="id">
<xsl:text>book</xsl:text>
<xsl:number value='position()'/>
</xsl:attribute>
</mynewtag>
or shortly:
<mynewtag id="book{position()}" />"
or
<newbook>
<xsl:variable name="cnt">
<xsl:number value='position()' format='1' />
</xsl:variable>
<mynewtag id="book{$cnt}" />
..........
Also the attribute value templates that IanRoberts mentioned.
You can't place a tag inside another tag. Try either:
<mynewtag>
<xsl:attribute name="id">
<xsl:text>book</xsl:text>
<xsl:number value='position()'/>
</xsl:attribute>
</mynewtag>
or shortly:
<mynewtag id="book{position()}" />"
ADDED:
Not directly related to your question, but I believe the id attribute should be applied to the parent <newbook> element, instead of creating an artificial child element to hold it.

Setting disable-output-escaping="yes" for every xsl:text tag in the xml

say I have the following xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<xsl:for-each select="logline_t">
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_1" <xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_2" <xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_3" <xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:for-each>
</display>
</xsl:template>
</xsl:stylesheet>
Is there a way to set disable-output-escaping="yes" to all of the xsl:text that appear in the document?
I know there is an option to put
< xsl:output method="text"/ >
and every time something like
& lt;
appears, a < will appear, but the thing is that sometimes in the values of line_1, line_2 or line_3, there is a "$lt;" that I don't want changed (this is, I only need whatever is between to be changed)
This is what I'm trying to accomplish. I have this xml:
<readlog_l>
<logline_t>
<hora>16:01:09</hora>
<texto>Call-ID: 663903<hola>396#127.0.0.1</texto>
</logline_t>
</readlog_l>
And this translation:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<screen name="<xsl:value-of select="name(.)"/>">
<xsl:for-each select="logline_t">
< field name="<xsl:for-each select="*"><xsl:value-of select="."/></xsl:for-each>" value="" type="label"/>
</xsl:for-each>
</screen>
</display>
</xsl:template>
</xsl:stylesheet>
I want this to be the output:
<?xml version="1.0"?>
<display>
<screen name="readlog_l">
<field name="16:01:09 Call-ID: 663903<hola>396#127.0.0.1 " value="" type="label">
</screen>
</display>
Note that I need the "<" inside the field name not to be escaped, this is why I can't use output method text.
Also, note that this is an example and the translations are much bigger, so this is why I'm trying to find out how not to write disable-output-escaping for every '<' or '>' I need.
Thanks!
Thanks for clarifying the question. In this case, I'm fairly sure there's no need to disable output escaping. XSLT was designed to accomplish what you're doing:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<screen name="{name(.)}">
<xsl:for-each select="logline_t">
<xsl:variable name="nameContent">
<xsl:for-each select="*">
<xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
<field name="{$nameContent}" value="" type="label" />
</xsl:for-each>
</screen>
</display>
</xsl:template>
</xsl:stylesheet>
I'm a bit unclear on this point:
Note that I need the "<" inside the field name not to be escaped, this is why I can't use output method text.
Which < are you referring to? Is it the < and > around "hola"? If you left those unescaped you would wind up with invalid XML. It also looks like the name attribute in your sample output have a lot of values that aren't in the input XML. Where did those come from?
Given your expected output you don't need d-o-e at all for this. Here is a possible solution that doesn't use d-o-e, and is based on templates rather than for-each:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/*">
<display>
<screen name="{name(.)}">
<xsl:apply-templates select="logline_t"/>
</screen>
</display>
</xsl:template>
<xsl:template match="logline_t">
<field value="" type="label">
<xsl:attribute name="name">
<xsl:apply-templates select="*" mode="fieldvalue"/>
</xsl:attribute>
</field>
</xsl:template>
<xsl:template match="*[last()]" mode="fieldvalue">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="*" mode="fieldvalue">
<xsl:value-of select="." />
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
If you want to set d-o-e on everything, that suggests you are trying to generate markup "by hand". I don't think that's a particularly good idea (in fact, I think it's a lousy idea), but if it's what you want to do, I would suggest using the text output method instead of the xml output method. That way, no escaping of special characters takes place, and therefore it doesn't need to be disabled.