XSLT Template Matching from xml - xslt

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.

Related

XSLT - Lookup value from other part of xml inside foreach

I have the following XML.
<?xml version="1.0" ?>
<Root xmlns="http://1.local/1.xsd">
<Definitions>
<FileTypes>
<FileType ID="1" Name="FileType1"/>
<FileType ID="2" Name="FileType2"/>
<!--... - lots of file types-->
<FileTypes>
</Definitions>
<Files>
<File Name="File1" FileTypeID="1" />
<File Name="File2" FileTypeID="1" />
<File Name="File3" FileTypeID="2" />
<!--... - lots of files-->
<Files>
</Root>
For each file, I need to get its Name attribute and for its FileTypeID lookup corresponding file type name
So example output would be:
File name: File1
File type: FileType1
File name: File2
File type: FileType1
File name: File3
File type: FileType2
This is XSLT I have so far but I'm not sure how to lookup name of file type.
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:l="http://1.local/1.xsd"
exclude-result-prefixes="l"
version="1.0">
<xsl:output method="text" omit-xml-declaration="yes" media-type="text/plain" />
<xsl:template match="/">
<xsl:apply-templates select="l:Root/l:Files" />
</xsl:template>
<xsl:template match="l:Root/l:Files">
Why
<xsl:for-each select="l:File">
File name: <xsl:value-of select="#Name">
File type:
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Use a xsl:key here to look up the FileTypes
<xsl:key name="FileTypes" match="l:FileType" use="#ID" />
Then, to get the relevant FileType name, you would do this
<xsl:value-of select="key('FileTypes', #FileTypeID)/#Name" />
Try this XSLT
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:l="http://1.local/1.xsd"
exclude-result-prefixes="l"
version="1.0">
<xsl:output method="text" omit-xml-declaration="yes" media-type="text/plain" />
<xsl:key name="FileTypes" match="l:FileType" use="#ID" />
<xsl:template match="/">
<xsl:apply-templates select="l:Root/l:Files" />
</xsl:template>
<xsl:template match="l:Files">
<xsl:for-each select="l:File">
File name: <xsl:value-of select="#Name" />
File type: <xsl:value-of select="key('FileTypes', #FileTypeID)/#Name" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
(Note your XML is not well-formed, as you do not have correct closing tags for FileTypes and Files.)
If you want to work with XSLT you need to start with understanding its expression language XPath to navigate XML trees, you can select //l:FileType[#ID = current()/#FileTypeID]/#Name. Or in XSLT, as Tim has already posted, you can use a key to efficiently implement the lookup.
You can try the following XSL.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:l="http://1.local/1.xsd"
exclude-result-prefixes="l"
version="1.0">
<xsl:output method="text" omit-xml-declaration="yes" media-type="text/plain" />
<xsl:template match="/">
<xsl:apply-templates select="l:Root/l:Files" />
</xsl:template>
<xsl:template match="l:Root/l:Files">
Why
<xsl:for-each select="l:File">
File name: <xsl:value-of select="#Name"/>
File type: <xsl:value-of select="//l:FileTypes/l:FileType[#ID=current()/#FileTypeID]/#Name"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Also, you need to make sure you put the correct namespace in your xsl transformation to actually match the values in you XML, and you were missing a few close tags in your XML.
For completeness I've included the fixed XML that I used for the solution
<Root xmlns="http://1.local/1.xsd">
<Definitions>
<FileTypes>
<FileType ID="1" Name="FileType1"/>
<FileType ID="2" Name="FileType2"/>
<!--... - lots of file types-->
</FileTypes>
</Definitions>
<Files>
<File Name="File1" FileTypeID="1" />
<File Name="File2" FileTypeID="1" />
<File Name="File3" FileTypeID="2" />
<!--... - lots of files-->
</Files>
</Root>

Removing duplicates from a bag returned by xsl:key based on attribute value

I have the following xml file.
<Bank>
<Person personId="1" type="1071" deleted="0">
</Person>
<Person personId="2" type="1071" deleted="0">
</Person>
<Person personId="3" type="1071" deleted="0">
</Person>
<Account>
<Role personId="1" type="1025" />
</Account>
<Account>
<Role personId="1" type="1025" />
</Account>
<Account>
<Role personId="1" type="1018" />
</Account>
<Account>
<Role personId="3" type="1025" />
<Role personId="1" type="1018" />
</Account>
<Account>
<Role personId="2" type="1025" />
</Account>
</Bank>
and the following xsl transformation.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="ISO-8859-1" />
<xsl:strip-space elements="*" />
<xsl:key name="roleKey"
match="Role[(#type = '1025' or #type = '1018' or #type = '1022' or #type = '1023') and not(#validTo)]"
use="#personId" />
<xsl:template match="Person">
<xsl:value-of select="#personId" />
<xsl:variable name="roles" select="key('roleKey', #personId)" />
<xsl:for-each select="$roles">
<xsl:text>;</xsl:text><xsl:value-of select="#type" />
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
The actual result is following and I would like to remove the duplicated type values.
1;1025;1025;1018;1018
2;1025
3;1025
The expected results should be like follows...
1;1025;1018
2;1025
3;1025
I have tried the tips involving keyword following from this website and also the trick with the Muenchian method. They all do not work as they seem to be browsing through the whole document and matching the duplicates for each and every Person element, whereas I want to remove duplicates only in a Person context defined by personId attribute.
How do I remove those duplicates from the bag returned by key function? Or maybe there is a method that I can use in xsl:for-each to print only what I want to?
I can only use what there is available in XSLT 1.0. I do not have a possibility to use any XSLT 2.0 processor.
Ok, do not know if this is the best solution available but I achieved what I wanted by introducing such a key and using Muenchian method.
<xsl:key name="typeKey" match="Role" use="concat(#type, '|', #personId)" />
The whole transformation looks like that after the change...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="ISO-8859-1" />
<xsl:strip-space elements="*" />
<xsl:key name="roleKey"
match="Role[(#type = '1025' or #type = '1018' or #type = '1022' or #type = '1023') and not(#validTo)]"
use="#personId" />
<xsl:key name="typeKey" match="Role" use="concat(#type, '|', #personId)" />
<xsl:template match="Person">
<xsl:value-of select="#personId" />
<xsl:variable name="roles" select="key('roleKey', #personId)" />
<xsl:for-each select="$roles[generate-id() = generate-id(key('typeKey', concat(#type, '|', #personId)))]">
<xsl:text>;</xsl:text><xsl:value-of select="#type" />
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
And the actual result is now...
1;1025;1018
2;1025
3;1025

Namespace issue in xslt

I am trying to convert following xml into other xml but I am not getting the values for xCoordinate and yCoordinate. I would like to convert the structure from source - XML to Target-XML where the goocodes will match and the result would be assigned to x and y.
Source - XML
<?xml version="1.0"?>
<AddressResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" errorCode="0" errorDescription="">
<wrappedResultList xmlns="http://xlocate.xserver.ptvag.com">
<ResultAddress city="Amsterdam" city2="" country="NL" houseNumber="" postCode="1***" state="Noord-Holland" street="" adminRegion="Amsterdam" appendix="" classificationDescription="EXACT" countryCapital="Amsterdam" detailLevelDescription="CITY" totalScore="100">
<wrappedAdditionalFields />
<coordinates>
<kml xsi:nil="true" xmlns="http://common.xserver.ptvag.com" />
<point x="4.89327999999999" y="52.373090000000005" xmlns="http://common.xserver.ptvag.com" />
</coordinates>
</ResultAddress>
<ResultAddress city="Amsterdam-Zuidoost" city2="" country="NL" houseNumber="" postCode="110*" state="Noord-Holland" street="" adminRegion="Amsterdam" appendix="" classificationDescription="EXACT" countryCapital="Amsterdam" detailLevelDescription="CITY" totalScore="80">
<wrappedAdditionalFields />
<coordinates>
<kml xsi:nil="true" xmlns="http://common.xserver.ptvag.com" />
<point x="4.9513699999999838" y="52.316199999999988" xmlns="http://common.xserver.ptvag.com" />
</coordinates>
</ResultAddress>
<ResultAddress city="Nieuw-Amsterdam" city2="" country="NL" houseNumber="" postCode="7833" state="Drenthe" street="" adminRegion="Emmen" appendix="" classificationDescription="EXACT" countryCapital="Amsterdam" detailLevelDescription="CITY" totalScore="80">
<wrappedAdditionalFields />
<coordinates>
<kml xsi:nil="true" xmlns="http://common.xserver.ptvag.com" />
<point x="6.8528699999999994" y="52.716139999999982" xmlns="http://common.xserver.ptvag.com" />
</coordinates>
</ResultAddress>
</wrappedResultList>
</AddressResponse>
Target - XML
<GeoCodeResponse>
<geocordinate>
<xCordinate>4.89327999999999</xCordinate>
<yCordinate>52.716139999999982</yCordinate>
</geocordinate>
</GeoCodeResponse>
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xl="http://xlocate.xserver.ptvag.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xmlns:cm="http://common.xserver.ptvag.com" exclude-result-prefixes="xl xsi xsd cm" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<GeoCodeResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<geocordinate xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<xsl:for-each select="AddressResponse/xl:wrappedResultList/xl:ResultAddress">
<xsl:sort select="#xl:totalScore" order="descending" data-type="number"/>
<xsl:if test="position()= 1">
<xCordinate> <xsl:value-of select="/xl:coordinates/cm:point/cm:x" /></xCordinate>
<yCordinate> <xsl:value-of select="/xl:coordinates/cm:point/cm:y" /></yCordinate>
</xsl:if>
</xsl:for-each>
</geocordinate>
</GeoCodeResponse>
</xsl:template>
</xsl:stylesheet>
Please help what could be done in above xslt.
You were almost there. The coordinates are attributes, not nodes.
change it into this:
<xsl:if test="position()= 1">
<xCordinate>
<xsl:value-of select="xl:coordinates/cm:point/#x" />
</xCordinate>
<yCordinate>
<xsl:value-of select="xl:coordinates/cm:point/#y" />
</yCordinate>
</xsl:if>
You are also referencing the coordinates node from the root, but it should be relative. I changed /xl:coordinaties into xl:coordinates

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.

XSL cross reference

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"/>