Can anyone please exlain how the below xsl works with an example?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Remove empty elements or attributes -->
<xsl:template match="#*|node()">
<xsl:if test=". != '' or ./#* != ''">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When i use above xsl for below xml which is not indented (Note it is showing indented below but consider it not indented. The input text box did not allow me to put not indented xml):
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<BOS>
<LIB>
<RequestorID ID="XXX" Type="10"/>
</LIB>
</BOS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Chapters>
<Chapter>
<Paragraphs>
<Paragraph NumberOfUnits="10" Lines="100">
<Rates>
<Rate EffectiveDate="2021-12-12" ExpireDate="2021-12-13" RateTimeUnit="Day" UnitMultiplier="1">
<Base AmountBeforeTax="145.90" CurrencyCode="USD"/>
</Rate>
</Rates>
</Paragraph>
</Paragraphs>
<Readers>
<Reader Age="10" Count="1"/>
</Readers>
<TimeSpan End="2021-12-13" Start="2021-12-12"/>
<BasicInfo BookCode="1310"/>
</Chapter>
</Chapters>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
<GlobalInfo>
<ReadIds>
<ReadId ReadID_Source="ZZZ" ReadID_Type="10" ReadID_Value="1234"/>
</ReadIds>
</GlobalInfo>
</Section>
</Sections>
</Book>
Then i get below output:
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<BOS>
<LIB>
<RequestorID ID="XXX" Type="10"/>
</LIB>
</BOS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
</Section>
</Sections>
</Book>
As seen above it removes everything inside the Chapters tag and Global info tag
But if i use above xsl for below xml which indented:
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<POS>
<Source>
<RequestorID ID="XXX" Type="10"/>
</Source>
</POS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Chapters>
<Chapter>
<Paragraphs>
<Paragraph NumberOfUnits="10" Lines="100">
<Rates>
<Rate EffectiveDate="2021-12-12" ExpireDate="2021-12-13" RateTimeUnit="Day" UnitMultiplier="1">
<Base AmountBeforeTax="145.90" CurrencyCode="USD"/>
</Rate>
</Rates>
</Paragraph>
</Paragraphs>
<Readers>
<Reader Age="10" Count="1"/>
</Readers>
<TimeSpan End="2021-12-13" Start="2021-12-12"/>
<BasicInfo BookCode="1310"/>
</Chapter>
</Chapters>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
<GlobalInfo>
<ReadIds>
<ReadId ReadID_Source="ZZZ" ReadID_Type="10" ReadID_Value="1234"/>
</ReadIds>
</GlobalInfo>
</Section>
</Sections>
</Book>
Then i get correct output:
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<BOS>
<LIB>
<RequestorID ID="XXX" Type="10"/>
</LIB>
</BOS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Chapters>
<Chapter>
<Paragraphs>
<Paragraph NumberOfUnits="10" Lines="100">
<Rates>
<Rate EffectiveDate="2021-12-12" ExpireDate="2021-12-13" RateTimeUnit="Day" UnitMultiplier="1">
<Base AmountBeforeTax="145.90" CurrencyCode="USD"/>
</Rate>
</Rates>
</Paragraph>
</Paragraphs>
<Readers>
<Reader Age="10" Count="1"/>
</Readers>
<TimeSpan End="2021-12-13" Start="2021-12-12"/>
<BasicInfo BookCode="1310"/>
</Chapter>
</Chapters>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
<GlobalInfo>
<ReadIds>
<ReadId ReadID_Source="ZZZ" ReadID_Type="10"
ReadID_Value="1234"/>
</ReadIds>
</GlobalInfo>
</Section>
</Sections>
</Book>
Can anyone explain if the xmls are same why does indentation give different output? Does proper indentation affect the xsl transformaion?
The code has a single template rule which matches all element, text, comment, processing instruction, and attribute nodes. If the node has a non-empty string value, or has an attribute with a non-empty string value, then it shallow-copies the node and processes its attributes and children recursively.
The overall effect is to copy the entire document except for elements that have no content and no non-empty attributes (such as <br/>) - plus a few other exceptions such as empty comments.
The XPath expression . != '' or ./#* != '' in the test might not behave as you expect. The specification for XPath 1.0 (usually used with XSLT 1.0), as indicated by version="1.0" says at https://www.w3.org/TR/1999/REC-xpath-19991116/#dt-string-value
For every type of node, there is a way of determining a string-value
for a node of that type. For some types of node, the string-value is
part of the node; for other types of node, the string-value is
computed from the string-value of descendant nodes.
and down in section "5.2 Element Nodes"
The string-value of an element node is the concatenation of the
string-values of all text node descendants of the element node
in document order.
So for an ordinary non-indented source element, string conversion involves recursively descending to elements and getting their values (ignoring attributes). In your case, for an element which also does not have attributes, the whole sub-tree gets eliminated (i.e. not copied to output) if it does not have any element with a value like <elem>value</elem> in it.
With indented source, you also have nodes representing the whitespace between the nodes. (XPath does not know that white-space is irrelevant for you, and assumes mixed content.) This causes to-string conversion of an element with (indented) sub-elements to have (at least) that whitespace in the result of the string conversion, rendering the . != '' XPath expression false.
I hope, this explains the difference in result depending on indentation in source.
You might want to have a look at https://www.w3.org/TR/1999/REC-xpath-19991116/#function-normalize-space for trimming the conversion result. Note that this would potentially also affect honoring of "real values" if these can be all whitespace.
Edit: Depending on what you want to achieve, you might consider having white-space eliminated by the XSLT processor by using xsl:strip-space.
Related
I am trying to replicate the example located here Click Here
However, I am unable to achieve the desired results.
The output is not at all getting generated as per desired.
The input I have used :
<?xml version="1.0" encoding="UTF-8" ?>
<Order xmlns="http://www.book.org">
<Bundle>
<authors>
<author>
<authorId>100</authorId>
<authorName>Kathisiera</authorName>
</author>
<author>
<authorId>200</authorId>
<authorName>Bates</authorName>
</author>
<author>
<authorId>300</authorId>
<authorName>Gavin King</authorName>
</author>
</authors>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
**The Schema I have used in for my transformation**
<!-- begin snippet: js hide: false console: true babel: false -->
The XSLT I am using for my transformation :
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:socket="http://www.oracle.com/XSL/Transform/java/oracle.tip.adapter.socket.ProtocolTranslator"
xmlns:oracle-xsl-mapper="http://www.oracle.com/xsl/mapper/schemas"
xmlns:dvm="http://www.oracle.com/XSL/Transform/java/oracle.tip.dvm.LookupValue"
xmlns:mhdr="http://www.oracle.com/XSL/Transform/java/oracle.tip.mediator.service.common.functions.MediatorExtnFunction"
xmlns:oraxsl="http://www.oracle.com/XSL/Transform/java"
xmlns:oraext="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
xmlns:ns0="http://www.book.org"
exclude-result-prefixes="oracle-xsl-mapper xsi xsd xsl ns0 socket dvm mhdr oraxsl oraext xp20 xref">
<oracle-xsl-mapper:schema>
<!--SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY.-->
<oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:source type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:source>
</oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:mapTargets>
<oracle-xsl-mapper:target type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:target>
</oracle-xsl-mapper:mapTargets>
<!--GENERATED BY ORACLE XSL MAPPER 12.2.1.2.0(XSLT Build 161003.0739.0018) AT [THU JAN 23 13:13:32 IST 2020].-->
</oracle-xsl-mapper:schema>
<!--User Editing allowed BELOW this line - DO NOT DELETE THIS LINE-->
<xsl:key name="k" match="ns0:Order/ns0:Bundle/ns0:books/ns0:book" use="ns0:orderId"/>
<xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:books/ns0:author" use="ns0:authorId"/>
<xsl:template match="/ns0:Order">
<xsl:copy>
<xsl:apply-templates select="//ns0:books"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//ns0:books">
<xsl:apply-templates select="ns0:book[generate-id(.) = generate-id(key('k', ns0:orderId))]"/>
</xsl:template>
<xsl:template match="ns0:book">
<Bundle>
<authors>
<xsl:apply-templates select="key('a', string(key('k', ns0:orderId)/ns0:bookAuthorId ))" />
</authors>
<books>
<xsl:copy-of select="key('k', ns0:orderId)"/>
</books>
</Bundle>
</xsl:template>
<xsl:template match="ns0:author">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However the output I am getting is :
<?xml version = '1.0' encoding = 'UTF-8'?>
<Order xmlns="http://www.book.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.book.org file:/C:/JDeveloper/mywork/SOADevelopment/XsltLearner/SOA/Schemas/BooksOrder.xsd">
<Bundle>
<authors/>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
<Bundle>
<authors/>
<books>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
The node is not getting populated.
I have tried using copy-of to see the output for the second key but it prints nothing.
Could you please suggest me where I am going wrong.
Any Idea what I am doing incorrect?
Please help
Thanks!
The pattern for the key would <xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:authors/ns0:author" use="ns0:authorId"/>, as far as I understand your posted document sample. I would also remove the string call in any use of the key function e.g. use <xsl:apply-templates select="key('a', key('k', ns0:orderId)/ns0:bookAuthorId )"/>.
I have a complex XML file structured by book title. Something like this, but with hundreds of books and sometimes many authors per book.
<Book>
<Title>Ken Lum</Title>
<Author>
<GivenName>Grant</GivenName>
<Surname>Arnold</Surname>
</Author>
</Book>
<Book>
<Title>Shore, Forest and Beyond</Title>
<Author>
<GivenName>Ian M.</GivenName>
<Surname>Thom</Surname>
</Author>
<Author>
<GivenName>Grant</GivenName>
<Surname>Arnold</Surname>
</Author>
</Book>
What I need to output is an alphabetized list of authors, and then a list of every book they worked on, also alphabetized, something like:
Arnold, Grant — Ken Lum; Shore, Forest and Beyond
Thom, Ian M. — Shore, Forest and Beyond
I have a version of the code working fairly well, but it is very slow, so I'm trying to optimize my approach. I recently learned of the Muenchian method of grouping from another user here and I'm trying to apply that.
The part I'm specifically stuck on right now is getting the list of titles per author. This is what I have right now:
<xsl:key name="books-by-author" match="Book"
use="concat(Author/GivenName, Contributor/Surname)" />
…
<xsl:template match="Author">
…
<xsl:apply-templates mode="ByAuthor" select=
"key('books-by-author',
concat(GivenName, Surname)
)">
<xsl:sort select="Title/TitleText"/>
</xsl:apply-templates>
</template>
But it seems that this is only matching Books where the Author is the first one listed, like:
Arnold, Grant — Ken Lum
Thom, Ian M. — Shore, Forest and Beyond
I figure the xsl:key is only using the first Author element, rather than checking every author. Is it possible to check every Author like that? Or is there a better approach?
I would suggest you look at this way:
XML
<Books>
<Book>
<Title>Ken Lum</Title>
<Author>
<GivenName>Grant</GivenName>
<Surname>Arnold</Surname>
</Author>
</Book>
<Book>
<Title>Shore, Forest and Beyond</Title>
<Author>
<GivenName>Ian M.</GivenName>
<Surname>Thom</Surname>
</Author>
<Author>
<GivenName>Grant</GivenName>
<Surname>Arnold</Surname>
</Author>
</Book>
</Books>
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:key name="author" match="Author" use="concat(Surname, ', ', GivenName)" />
<xsl:template match="/Books">
<Authors>
<!-- for each unique author -->
<xsl:for-each select="Book/Author[count(. | key('author', concat(Surname, ', ', GivenName))[1]) = 1]">
<xsl:sort select="Surname"/>
<xsl:sort select="GivenName"/>
<Author>
<!-- author's details-->
<xsl:copy-of select="Surname | GivenName"/>
<!-- list author's books -->
<Books>
<xsl:for-each select="key('author', concat(Surname, ', ', GivenName))/parent::Book">
<xsl:sort select="Title"/>
<xsl:copy-of select="Title"/>
</xsl:for-each>
</Books>
</Author>
</xsl:for-each>
</Authors>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<Authors>
<Author>
<GivenName>Grant</GivenName>
<Surname>Arnold</Surname>
<Books>
<Title>Ken Lum</Title>
<Title>Shore, Forest and Beyond</Title>
</Books>
</Author>
<Author>
<GivenName>Ian M.</GivenName>
<Surname>Thom</Surname>
<Books>
<Title>Shore, Forest and Beyond</Title>
</Books>
</Author>
</Authors>
Given the following xml inputs:
file1:
<?xml version="1.0" encoding="UTF-8"?>
<File1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<code code="file1_code" displayName="file1_display" codeSystem="file1_cs" codeSystemName="file1_csn"/>
<title>Title of file1</title>
<component typeCode="COMP">
<structuredBody classCode="DOCBODY">
<component typeCode="COMP">
<section>
<templateId root="someRoot_file1" assigningAuthorityName="someAuhthority_file1"/>
<code code="file1-sec1_code" displayName="file1_sec1_display" codeSystem="file1_sec1_cs" codeSystemName="file1_sec1_csn"/>
<title>Tile of sec 1 from file1</title>
<text>
<content styleCode="Italics">
Text of sec 1 from file1
</content>
</text>
<entry> file 1 sec 1
</entry>
</section>
</component>
<component typeCode="COMP">
<section classCode="DOCSECT">
<code code="file1_sec2_code" codeSystem="file2_sec2_cs" displayName="file2_sec2_display" codeSystemName="file2_sec2_csn"/>
<title>Tile from sec 2 file 1</title>
<text>
<content styleCode="Italics">
Text from file1 sec 2
</content>
</text>
<entry typeCode="test"> file2 sec 2
</entry>
</section>
</component>
</structuredBody>
</component>
</File1>
file2:
<?xml version="1.0"?>
<A>
<title value="Title of file2"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>File 2 Text</p>
</div>
</text>
<section>
<code>
<coding>
<system value="sec 1 file2 sys"/>
<code value="sec 1 file 2 code"/>
<display value="sec 1 file 2 display"/>
</coding>
</code>
<title>Title of sec 1 file2</title>
<text>
<content styleCode="Italics">Section 1 Text
</content>
</text>
<entry>
<someEntry>
</someEntry>
</entry>
</section>
<section>
<code>
<coding>
<system value="sec 2 file2 sys"/>
<code value="sec 2 file 2 code"/>
<display value="sec 2 file 2 display"/>
</coding>
</code>
<title>Title of sec 2 file2</title>
<text>
<content styleCode="Italics">Section 2 file2 Text
</content>
</text>
<entry>
<someEntry> entry sec 2 file 2
</someEntry>
</entry>
</section>
</A>
and the following xslt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:variable name="input" select="/" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<Bundle>
<id value="test"/>
<type value="document"/>
<entry>
<resource>
<xsl:apply-templates select="document('file2.xml')/*"/>
</resource>
</entry>
</Bundle>
</xsl:template>
<xsl:template match="text">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
</xsl:template>
<xsl:template match="title">
<xsl:apply-templates select="$input/File1/title"/>
</xsl:template>
<xsl:template match="section[1]">
<xsl:apply-templates select="$input/File1/component/structuredBody/component/section"/>
</xsl:template>
<xsl:template match="section[2]"/>
<xsl:template match="File1/title">
<title>
<xsl:attribute name="value">
<xsl:value-of select="." />
</xsl:attribute>
</title>
</xsl:template>
<xsl:template match = "File1/component/structuredBody/component/section">
<section>
<xsl:apply-templates/>
</section>
</xsl:template>
</xsl:stylesheet>
And this is the output:
<?xml version="1.0" encoding="UTF-8"?>
<Bundle>
<id value="test"/>
<type value="document"/>
<entry>
<resource>
<A>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<section>
<templateId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" root="someRoot_file1" assigningAuthorityName="someAuhthority_file1"/>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1-sec1_code" displayName="file1_sec1_display" codeSystem="file1_sec1_cs" codeSystemName="file1_sec1_csn"/>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</section>
<section>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1_sec2_code" codeSystem="file2_sec2_cs" displayName="file2_sec2_display" codeSystemName="file2_sec2_csn"/>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" typeCode="test"/>
</section>
</A>
</resource>
</entry>
</Bundle>
And this is the expected output:
<?xml version="1.0" encoding="UTF-8"?>
<Bundle>
<id value="test"/>
<type value="document"/>
<entry>
<resource>
<A>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<section>
<templateId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" root="someRoot_file1" assigningAuthorityName="someAuhthority_file1"/>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1-sec1_code" displayName="file1_sec1_display" codeSystem="file1_sec1_cs" codeSystemName="file1_sec1_csn"/>
<title>Tile of sec 1 from file1</title>
<text>
<content styleCode="Italics">
Text of sec 1 from file1
</content>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</section>
<section>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1_sec2_code" codeSystem="file2_sec2_cs" displayName="file2_sec2_display" codeSystemName="file2_sec2_csn"/>
<title>Tile from sec 2 file 1</title>
<text>
<content styleCode="Italics">
Text from file1 sec 2
</content>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" typeCode="test"/>
</section>
</A>
</resource>
</entry>
</Bundle>
I have the following questions:
Why is the title in the section elements coming from the main title (i.e. File1/title) when the apply templates is within File1/component/structuredBody/component/section? I was expecting that the title of the section will be output, which is what is desired. Even more confusing is that it does indeed output the elements in the section like code, entry and so on but title and text (see q2 below) seems to be treated differently and I can't for the life of me understand why.
Same with text. Why is the text for section not being output?
Here is my presumably false understanding of the process:
We start with the <xsl:template match="/"> and create elements Bundle, id etc. and then using <xsl:apply-templates select="document('file2.xml')/*"/> we match the top element of file2 (A) and since we don't have a template matching it explicitly, the identity template is called, copies it and process its child elements, which are text, title and section. For each of these child elements, it looks for a matching template. it finds them and matches them.
For element section however, it matches only the first section element because of <xsl:template match="section[1]"> and then because of <xsl:apply-templates select="$input/File1/component/structuredBody/component/section"/> in the template, it looks for a template matching children of section in FIle1, which are code, text, title and templateId. It finds no such explicitly defined template, so calls the identity templates for them, copies and processes them till the end. At least that is my understanding of it.
Why is the title in the section elements coming from the main title
Because any time the processor is instructed to apply templates to a title, it looks for the best-matching template to apply, and finds this:
<xsl:template match="title">
<xsl:apply-templates select="$input/File1/title"/>
</xsl:template>
This changes the context to the title in File1.xml, and the best-matching template for this one is:
<xsl:template match="File1/title">
<title>
<xsl:attribute name="value">
<xsl:value-of select="." />
</xsl:attribute>
</title>
</xsl:template>
and that is the result you see.
Same with text. Why is the text for section not being output?
-- edited in response to the following clarification: --
When I say text I am talking about text elements only.
The original text element (child of section in File1.xml) is not being output because you have a specific template matching it and outputting something else instead:
<xsl:template match="text">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
</xsl:template>
# michael.hor257k and #Michael Kay yeah, that was definitely the case, that I misunderstood how xsl:apply-templates works with regards to context . I thought because I called the xsl:apply-templates from within xsl:template match = "$input/File1/component/structuredBody/component/section"> that it will only look for match templates that match the children of section. In other words, I thought it will look for templates like “<xsl:template match=”File1/component/structuredBody/component/section/title"> but that is clearly not the case.
xsl:apply-templates simply looks for the children and then looks for a match template regardless of the context from which they were called. So, it will look for title or text template that matches and if it finds them, it will match them.
The easiest solution I could find that seems to solve the problem is to add a path to the title and text templates. In other words, instead of just <xsl:template match="text"> I should have <xsl:template match="A/text">. Same for title. This way, the xsl:apply-templates will not apply <xsl:template match="A/text"> as the title in section is not a child of A. So given that no matching explicit template is defined, the identity template will be applied and will output the title of section as desired.
I've got an XML file with the following structure (multiple "entity" nodes):
<!-- entities.xml -->
<root>
<entity template="foo-template" kind="foo" name="bar">
<groups>
<group id="1">
<definition id="1" name="foobar" />
</group>
</groups>
</entity>
</root>
Many entity nodes have similar attributes and children nodes. I'd like to allow users to create entity templates in a separate file. Referencing the template will be done as follows:
<entity template="foo-template" kind="foo" ... />
Every attribute and child node from "foo-template" should be copied into the entity, except for those that already exist (i.e. allow overriding the template).
I'm not very familiar with XSLT. Is it the right tool for this task, or am I better off implementing this without it?
I'm using C++ and RapidXml, but can use other XML libraries.
Edit: example.
Template file:
<!-- templates.xml -->
<templates>
<entity template="foo-template" name="n/a" model="baz">
<groups>
<group id="1">
<definition id="1" name="def1" />
<definition id="2" name="def2" />
</group>
<group id="2">
<definition id="1" name="def3" />
<definition id="2" name="def4" />
</group>
</groups>
</entity>
</templates>
Output file:
<!-- output.xml -->
<root>
<entity kind="foo" name="bar" model="baz">
<groups>
<group id="1">
<definition id="1" name="foobar" />
</group>
<group id="2">
<definition id="1" name="def3" />
<definition id="2" name="def4" />
</group>
</groups>
</entity>
</root>
So the output contains group 1 from "entities.xml" and group 2 from "templates.xml". No need to merge group nodes with the same id.
If you have a file templates.xml that looks like
<templates>
<entity template="foo-template" kind="foo" name="bar" model="baz" />
<!-- and other entity elements with different template="..." values -->
</templates>
then an XSLT such as the following would achieve what you're after
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="kEntityTemplate" match="entity" use="#template" />
<!-- identity template - copy everything not overridden by another template -->
<xsl:template match="#*|node">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="entity[#template]">
<xsl:variable name="thisEntity" select="." />
<!-- switch to templates doc -->
<xsl:for-each select="document('templates.xml')">
<xsl:variable name="template"
select="key('kEntityTemplate', $thisEntity/#template)" />
<entity>
<!-- copy template attributes that are not overridden -->
<xsl:for-each select="$template/#*">
<xsl:if test="not($thisEntity/#*[name() = name(current())])">
<!-- if not, copy the one from the template -->
<xsl:apply-templates select="." />
</xsl:if>
</xsl:for-each>
<!-- copy source attributes -->
<xsl:apply-templates select="$thisEntity/#*[name() != 'template']" />
<!-- deal with elements -->
<xsl:if test="$thisEntity/groups/group | $template/groups/group">
<groups>
<!-- here we select all group elements from the source plus
those group elements from the template that do not also exist
in the source, and sort the whole lot by id -->
<xsl:apply-templates select="$thisEntity/groups/group
| $template/groups/group[not(#id = $thisEntity/groups/group/#id)]">
<xsl:sort select="#id" data-type="number" />
</xsl:apply-templates>
</groups>
</xsl:if>
</entity>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The templates.xml file needs to be in the same directory as the stylesheet.
One option that you have outside of doing any kind of XML transformation is importing the other XML file and then referencing it from within the tags. See here for an example.
This would require your users to have separate template files for each template type which you may not want. However I would prefer the import approach because of the kiss principle. If you're not familiar with XSLT then importing is probably a better way to go as well.
I hope this helps!
I am converting a xml using xslt.
Original XML is
<Content>
<book>
<customData>
<CustomDataElement>
<title>book-name</title>
<value>Java</value>
</CustomDataElement>
<CustomDataElement>
<title>genre</title>
<value>Programming</value>
</CustomDataElement>
</customData>
</book>
<authors>
<author>
<name>authorOne</name>
<country>US</country>
</author>
</authors>
<book>
<customData>
<CustomDataElement>
<title>book-name</title>
<value>Stranger</value>
</CustomDataElement>
<CustomDataElement>
<title>genre</title>
<value>Fiction</value>
</CustomDataElement>
</customData>
</book>
<authors>
<author>
<name>authorthree</name>
<country>UK</country>
</author>
</authors>
</Content>
and my xslt is as follows
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/">
<xsl:for-each select="Content/book">
<media>
<book>
<xsl:apply-templates select="customData/CustomDataElement[title = 'book-name']" />
</book>
<genre>
<xsl:apply-templates select="customData/CustomDataElement[title = 'genre']" />
</genre>
<author>
<xsl:value-of select="../authors/author/name" />
</author>
</media>
</xsl:for-each>
</xsl:template>
<xsl:template match="CustomDataElement">
<xsl:value-of select="value" />
</xsl:template>
</xsl:stylesheet>
This gives me output as
<?xml version="1.0"?>
<media>
<book>Java</book>
<genre>Programming</genre>
<author>authorOne</author>
</media>
<media>
<book>Stranger</book>
<genre>Fiction</genre>
<author>authorOne</author>
</media>
I want the authors name from the tag 'authors\author' which follows the book tag.
what i am missing here ? pls help
Instead of
<xsl:value-of select="../authors/author/name" />
try
<xsl:value-of select="following-sibling::authors[1]/author/name" />
Since you are in the context of a book node, this xpath says to look for the first ([1]) following sibling authors node, and to select the author/name from that.