I have DB tables stored in XML format that have a FK based on two columns (Table2 has FK to Table1 based on ID and TYPE).
Table1.xml
<Table>
<Row>
<ID>1</ID>
<TYPE>A</TYPE>
<CONFIG>Y</CONFIG>
...
</Row>
<Row>
<ID>2</ID>
<TYPE>A</TYPE>
<CONFIG>Z</CONFIG>
...
</Row>
<Row>
<ID>1</ID>
<TYPE>B</TYPE>
<CONFIG>X</CONFIG>
...
</Row>
<Row>
<ID>3</ID>
<TYPE>A</TYPE>
<CONFIG>Z</CONFIG>
...
</Row>
</Table>
Table2.xml
<Table>
<Row>
<ID>1</ID>
<TYPE>A</TYPE>
...
</Row>
<Row>
<ID>2</ID>
<TYPE>A</TYPE>
...
</Row>
<Row>
<ID>1</ID>
<TYPE>B</TYPE>
...
</Row>
<Row>
<ID>3</ID>
<TYPE>A</TYPE>
...
</Row>
</Table>
I will have two XSLT files to delete rows in each XML file. Table2 will be processed first. I want to delete the row in Table2 where when joined with Table1 CONFIG=Z (ie, delete rows where (ID=2 and Type=A) and (ID=3 and Type=A), but I need to figure this out only knowing I want to delete records where CONFIG=Z). Table1 will then be processed to delete rows where CONFIG=Z, which I was able to figure out.
I think the XSLT that will be applied to Table2 needs to read in Table1 XML (xsl:variable name="table1Rows" select="document('Table1.xml')/Table/Row"/>). After that I'm lost on how to delete rows in Table2 where CONFIG=Z. I've tried several things based on examples I saw, but couldn't get anything to work.
With XSLT 2.0 define a key and cross-reference the elements, then simply do an identity transformation to copy nodes plus a template that suppresses the copying for those Row elements where the key function call finds a Row in the other document with the CONFIG being Z:
<xsl:variable name="table1" select="doc('Table1.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[key('r-by-id-and-type', concat(ID, '|', TYPE), $table1)/CONFIG = 'Z']"/>
[edit] For completeness, I tested the following complete sample with both Saxon 9.4 as well as AltovaXML successfully:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:variable name="table1" select="doc('table1.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[key('r-by-id-and-type', concat(ID, '|', TYPE), $table1)/CONFIG = 'Z']"/>
</xsl:stylesheet>
On request in comment I also add an XSLT 1.0 stylesheet:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:variable name="table1" select="document('test2012100102.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row">
<xsl:variable name="this" select="."/>
<xsl:for-each select="$table1">
<xsl:if test="not(key('r-by-id-and-type', concat($this/ID, '|', $this/TYPE))/CONFIG = 'Z')">
<xsl:copy-of select="$this"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Martin's solution is correct, given the original question and should be accepted.
In reference to the OP's request for an additional XSLT 1.0 solution, here is a polyglot. This style-sheet, a minor variation of Martin's solution, works on XSLT 2.0 processors and probably most XSLT 1.0 processors.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:variable name="table1" select="doc('table1.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#*|node()" name="ident">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template
match="Row"
use-when="number(system-property('xsl:version')) < 2" priority="2">
<xsl:variable name="row" select="." />
<xsl:variable name="id-type" select="concat(ID, '|', TYPE)" />
<xsl:for-each select="$table1">
<xsl:if test="not( key('r-by-id-and-type', $id-type))">
<xsl:for-each select="$row">
<xsl:call-template name="ident" />
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template
match="Row"
use-when="number(system-property('xsl:version')) >= 2" priority="1">
<xsl:if test="not( key('r-by-id-and-type', concat(ID, '|', TYPE), $table1))">
<xsl:call-template name="ident" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Caveat
This style-sheet was not tested.
The answers provided by Martin work and are probably the best solutions possible. For XSLT 1.0 I had come up with the following that seems to run faster, but is not as elegant. For this solution I knew that the only possible TYPE for CONFIG=Z is 'A'. (Note there could be a typo below since I'm running the XSLT on another machine and retyped it here with the mock column names/values.)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="table1_Z_rows" select="document('table1.xml')/Table/Row[CONFIG='Z']"/>
<xsl:template match="Row">
<xsl:choose>
<xsl:when test="TYPE != 'A'">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:if test="not(ID = $table1_Z_rows/ID)">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Related
I have the following XML:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Matches>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Matches>
</Row>
</Document>
I want to use XSLT to return the nested /Matches/Row/Period when the Document/Row/Period is undefined (as it is in the second Row of the XML)
So I have the following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:variable name="period" select="/Period" />
<xsl:choose>
<xsl:when test="$period = null">
<xsl:copy>
<xsl:copy-of select="KEY | /Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
But it returns the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
</Row>
</Document>
(Note how it is not returning the nested /Matches/Row/Period in the second /Row.
I expect to get the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Document>
What am I doing wrong?
undefined or null are not checked in XSLT/XPath using expression = null, you would rather use (for node-sets) <xsl:when test="expression"> e.g. <xsl:when test="Period"> that there is at least one Period child element for the context node or test="not(Period)" to check there is no Period child.
In the end I would suggest to use template matching based on the identity transformation template and put any conditions into match pattern (predicates), but that is a different issue.
Got it working with this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:choose>
<xsl:when test="not(Period)">
<xsl:copy>
<xsl:copy-of select="KEY | Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
Thanks to #MartinHonnen's guidance.
I believe it could be simply:
XSLT 1.0
<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="/Document">
<xsl:copy>
<xsl:for-each select="Row">
<xsl:copy>
<xsl:copy-of select="KEY | descendant::Period[1]" />
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I have to generate an xml file where certain grouped elements (documents which group lines) are numbered with a sequence. In turn, these groups belong within another group (by document type). The numbering must be sequential during all the document, regardless of the group.
See the attribute DocId in the Documents element in the example output:
INPUT
<Entries>
<Entry>
<UserID>1</UserID>
<DocNumber>1002</DocNumber>
<Description>An invoice</Description>
<Amount>3103.2000</Amount>
<DocType>INVOICE</DocType>
</Entry>
<Entry>
<UserID>2</UserID>
<DocNumber>3001</DocNumber>
<Description>Some receipt</Description>
<Amount>2352.0000</Amount>
<DocType>RECEIPT</DocType>
</Entry>
<Entry>
<UserID>1</UserID>
<DocNumber>1002</DocNumber>
<Description>An invoice</Description>
<Amount>2861.8400</Amount>
<DocType>INVOICE</DocType>
</Entry>
<Entry>
<UserID>2</UserID>
<DocNumber>3001</DocNumber>
<Description>Some receipt</Description>
<Amount>2352.0000</Amount>
<DocType>RECEIPT</DocType>
</Entry>
<Entry>
<UserID>5</UserID>
<DocNumber>1004</DocNumber>
<Description>Another invoice</Description>
<Amount>2.34</Amount>
<DocType>INVOICE</DocType>
</Entry>
</Entries>
XSLT 2.0
<xsl:stylesheet exclude-result-prefixes="xs xdt err fn" version="2.0" xmlns:err="http://www.w3.org/2005/xqt-errors" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xdt="http://www.w3.org/2005/xpath-datatypes" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ser="http://webservice">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/Entries">
<ser:Request>
<xsl:for-each-group select="Entry" group-by="DocType">
<xsl:sort select="current-grouping-key()"/>
<ser:ItemFile ImportName="{DocType}" Date="{current-date()}">
<xsl:for-each-group select="current-group()" group-by="DocNumber">
<xsl:sort select="current-grouping-key()"/>
<ser:Documents DocId="{position()}" DocRef="{DocNumber}" Desc="{Description}" >
<xsl:for-each select="current-group()">
<Line amount="{Amount}"/>
</xsl:for-each>
</ser:Documents>
</xsl:for-each-group>
</ser:ItemFile>
</xsl:for-each-group>
</ser:Request>
</xsl:template>
</xsl:stylesheet>
OUTPUT
<ser:Request xmlns:ser="http://webservice">
<ser:ItemFile ImportName="INVOICE" Date="2017-10-13+02:00">
<ser:Documents DocId="1" DocRef="1002" Desc="An invoice">
<Line amount="3103.2000"/>
<Line amount="2861.8400"/>
</ser:Documents>
<ser:Documents DocId="2" DocRef="1004" Desc="Another invoice">
<Line amount="2.34"/>
</ser:Documents>
</ser:ItemFile>
<ser:ItemFile ImportName="RECEIPT" Date="2017-10-13+02:00">
<ser:Documents DocId="1" DocRef="3001" Desc="Some receipt">
<Line amount="2352.0000"/>
<Line amount="2352.0000"/>
</ser:Documents>
</ser:ItemFile>
</ser:Request>
In this example, the desired output for last DocId attribute would be 3, following the sequence.
I noticed if I use the position() function, the numbering restarts in each group, which is not what I want. I also tried the xsl:number element with no success. I considered counting the elements in the first group and adding it to position(), which worked for me for a limited number of groups, but couldn't do it generically for a variable number of groups.
Is there some relatively simple way of achieving this? Thank you in advance.
I would store the newly created elements in a variable and then push it through templates that copy everything but that DocId attribute where you can then use xsl:number to set it:
<xsl:stylesheet exclude-result-prefixes="xs xdt err fn" version="2.0" xmlns:err="http://www.w3.org/2005/xqt-errors" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xdt="http://www.w3.org/2005/xpath-datatypes" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ser="http://webservice">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/Entries">
<ser:Request>
<xsl:variable name="result">
<xsl:for-each-group select="Entry" group-by="DocType">
<xsl:sort select="current-grouping-key()"/>
<ser:ItemFile ImportName="{DocType}" Date="{current-date()}">
<xsl:for-each-group select="current-group()" group-by="DocNumber">
<xsl:sort select="current-grouping-key()"/>
<ser:Documents DocId="" DocRef="{DocNumber}" Desc="{Description}" >
<xsl:for-each select="current-group()">
<Line amount="{Amount}"/>
</xsl:for-each>
</ser:Documents>
</xsl:for-each-group>
</ser:ItemFile>
</xsl:for-each-group>
</xsl:variable>
<xsl:apply-templates select="$result"/>
</ser:Request>
</xsl:template>
<xsl:template match="ser:Documents/#DocId">
<xsl:attribute name="{name()}">
<xsl:number select=".." level="any"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With XSLT 2.0, I am trying to create a list of relations between all children of given elements, in a document such as:
<doc>
<part1>
<name>John</name>
<name>Paul</name>
<name>George</name>
<name>Ringo</name>
<place>Liverpool</place>
</part1>
<part2>
<name>Romeo</name>
<name>Romeo</name>
<name>Juliet</name>
<fam>Montague</fam>
<fam>Capulet</fam>
</part2>
</doc>
The result I would like to obtain, ideally by conflating and weighing the identical relations, would be (in whatever order) something like:
<doc>
<part1>
<rel><name>John</name><name>Paul</name></rel>
<rel><name>John</name><name>George</name></rel>
<rel><name>John</name><name>Ringo</name></rel>
<rel><name>Paul</name><name>George</name></rel>
<rel><name>Paul</name><name>Ringo</name></rel>
<rel><name>George</name><name>Ringo</name></rel>
<rel><name>John</name><place>Liverpool</place></rel>
<rel><name>Paul</name><place>Liverpool</place></rel>
<rel><name>George</name><place>Liverpool</place></rel>
<rel><name>Ringo</name><place>Liverpool</place></rel>
</part1>
<part2>
<rel weight="2"><name>Romeo</name><name>Juliet</name></rel>
<rel weight="2"><name>Romeo</name><fam>Montague</fam></rel>
<rel weight="2"><name>Romeo</name><fam>Capulet</fam></rel>
<rel><name>Juliet</name><fam>Montague</fam></rel>
<rel><name>Juliet</name><fam>Capulet</fam></rel>
<rel><fam>Montague</fam><fam>Capulet</fam></rel>
</part2>
</doc>
—but I'm not sure how to proceed. Many thanks in advance for your help.
You still haven't explained the logic that needs to be applied here, so this is based largely on a guess:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="doc/*">
<!-- first pass-->
<xsl:variable name="unique-items">
<xsl:for-each-group select="*" group-by="concat(name(), '|', .)">
<item name="{name()}" count="{count(current-group())}" value="{.}"/>
</xsl:for-each-group>
</xsl:variable>
<!-- output -->
<xsl:copy>
<xsl:for-each select="$unique-items/item">
<xsl:variable name="left" select="."/>
<xsl:for-each select="following-sibling::item">
<xsl:variable name="weight" select="$left/#count * #count" />
<rel>
<xsl:if test="$weight gt 1">
<xsl:attribute name="weight" select="$weight"/>
</xsl:if>
<xsl:apply-templates select="$left | ." />
</rel>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:element name="{#name}">
<xsl:value-of select="#value"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The idea here is to remove duplicates in the first pass, then enumerate all combinations in the second (final) pass. The weight is computed by multiplying the number of occurrences of each member of a combination pair and shown only when it exceeds 1.
At least the combinatoric part of your problem could be solved with the following XSLT script. It does not solve the elimination of duplicates, but that could possibly be done in a second transformation.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- standard copy template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="doc/*">
<xsl:copy>
<xsl:variable name="l" select="./*"/>
<xsl:for-each select="$l">
<xsl:variable name="a" select="."/>
<xsl:variable name="posa" select="position()"/>
<xsl:variable name="namea" select="name()"/>
<xsl:for-each select="$l">
<xsl:if test="position() > $posa and (. != $a or name() != $namea)">
<rel>
<xsl:copy-of select="$a"/>
<xsl:copy-of select="."/>
</rel>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to the first part of your example, this produces:
<part1>
<rel><name>John</name><name>Paul</name></rel>
<rel><name>John</name><name>George</name></rel>
<rel><name>John</name><name>Ringo</name></rel>
<rel><name>John</name><place>Liverpool</place></rel>
<rel><name>Paul</name><name>George</name></rel>
<rel><name>Paul</name><name>Ringo</name></rel>
<rel><name>Paul</name><place>Liverpool</place></rel>
<rel><name>George</name><name>Ringo</name></rel>
<rel><name>George</name><place>Liverpool</place></rel>
<rel><name>Ringo</name><place>Liverpool</place></rel>
</part1>
Which seems about correct. If have no idea if the duplicate elimination (or weighting, as you call it) could be done in the same transformation.
I have got a source XML
<Records>
<Data>
<RecordType>New</RecordType>
<Number>4734122946</Number>
<DateOfBirth>20160506</DateOfBirth>
<Title>Mr</Title>
<ChangeTimeStamp>20160101010001</ChangeTimeStamp>
<SerialChangeNumber>01</SerialChangeNumber>
</Data>
<Data>
<RecordType>New</RecordType>
<Number>4734122946</Number>
<LastName>Potter</LastName>
<DateOfBirth>20160506</DateOfBirth>
<ChangeTimeStamp>20160101010002</ChangeTimeStamp>
<SerialChangeNumber>01</SerialChangeNumber>
</Data>
</Records>
I want to use XSLT 1.0 to produce the below output
<Contact>
<Number>4734122946</Number>
<Title>Mr</Title>
<LastName>Potter</LastName>
<BirthDate>20160506</BirthDate>
<ChangeTimeStamp>20160101010002</ChangeTimeStamp>
</Contact>
The XSLT has to group and merge the child nodes of Data records into one based on the Number field. Also if there are same elements present, then it should use the ChangeTimeStamp element to figure out the latest change and use that element.
I tried the below XSLT. But I am nowhere close to the output.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes" />
<xsl:key name="groups" match="Data" use="Number"/>
<xsl:key name="sortGroup" match="Data" use ="ChangeTimeStamp"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Records">
<xsl:for-each select="Data[generate-id() = generate-id(key('groups',Number))]">
<Contact>
<Number>
<xsl:value-of select="Number"/>
</Number>
<xsl:for-each select="key('groups',Number)">
<xsl:for-each select="key('sortGroup',ChangeTimeStamp)">
<xsl:sort select="sortGroup" order="ascending"/>
<xsl:if test="Title">
<Title>
<xsl:value-of select ="Title"/>
</Title>
</xsl:if>
<xsl:if test="LastName">
<LastName>
<xsl:value-of select="LastName"/>
</LastName>
</xsl:if>
<BirthDate>
<xsl:value-of select="DateOfBirth"/>
</BirthDate>
</xsl:for-each>
</xsl:for-each>
</Contact>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Appreciate your help.
The XSLT has to group and merge the child nodes of Data records into
one based on the Number field. Also if there are same elements
present, then it should use the ChangeTimeStamp element to figure out
the latest change and use that element.
For that, I believe you would want to do something like:
<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:key name="group" match="Data" use="Number"/>
<xsl:key name="item" match="Data/*" use="concat(../Number, '|', name())"/>
<xsl:template match="/Records">
<root>
<xsl:for-each select="Data[generate-id() = generate-id(key('group', Number)[1])]">
<Contact>
<xsl:for-each select="key('group', Number)/*[generate-id() = generate-id(key('item', concat(../Number, '|', name()))[1])]">
<xsl:for-each select="key('item', concat(../Number, '|', name()))">
<xsl:sort select="../ChangeTimeStamp" data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</Contact>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
You will have to make some adjustments if you want to include only some data items and/or if you want them to appear in particular order. If you have a list of all possible data item names (e.g. Title, LastName, DateOfBirth, etc.) then this could be simpler.
I have to add one attribute Publisher="Penguin" to the nodes from a NodeList : The input xml looks like:
<Rack RackNo="1">
<Rows>
<Row RowNo="1" NoOfBooks="10"/>
<Row RowNo="2" NoOfBooks="15"/>
<Rows>
</Rack>
The output xml lookslike:
<Rack RackNo="1">
<Rows>
<Row RowNo="1" NoOfBooks="10" Publisher="Penguin"/>
<Row RowNo="2" NoOfBooks="15" Publisher="Penguin"/>
<Rows>
</Rack>
The xsl i wrote is :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<Order>
<xsl:copy-of select = "Rack/#*"/>
<xsl:for-each select="Rows/Row">
<OrderLine>
<xsl:copy-of select = "Row/#*"/>
<xsl:attribute name="Publisher"></xsl:attribute>
<xsl:copy-of select = "Row/*"/>
</OrderLine>
</xsl:for-each>
<xsl:copy-of select = "Rack/*"/>
</Order>
</xsl:template>
</xsl:stylesheet>
This doesnt return the desired output.
Any help will be much appreciated.
Thanks in advance guys.
This is a job for the XSLT identity transform. On its own it simple creates a copy of all the nodes in your input XML
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
All you need to do is add an extra template to match Row element, and add a Publisher attribute to it. It might be good to first parameterise the publisher you wish to add
<xsl:param name="publisher" select="'Penguin'" />
You then create the matching template as follows:
<xsl:template match="Row">
<OrderLine Publisher="{$publisher}">
<xsl:apply-templates select="#*|node()"/>
</OrderLine>
</xsl:template>
Note the use of "Attribute Value Templates" to create the Publisher attribute. The curly braces indicate it is an expression to be evaluated. Also note in your XSLT it looks like you are renaming the elements too, so I have done this in my XSLT as well. (If this is not the case, simply replace OrderLine back with Row.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="publisher" select="'Penguin'" />
<xsl:template match="Rack">
<Order>
<xsl:apply-templates />
</Order>
</xsl:template>
<xsl:template match="Rows">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Row">
<OrderLine Publisher="{$publisher}">
<xsl:apply-templates select="#*|node()"/>
</OrderLine>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<Order>
<OrderLine Publisher="Penguin" RowNo="1" NoOfBooks="10"></OrderLine>
<OrderLine Publisher="Penguin" RowNo="2" NoOfBooks="15"></OrderLine>
</Order>