How to change root node's attribute values and namespace urls - xslt

I want to change an xml's namespace url values. I have an xml like the following
<catalog xmlns="http://someurl"
xmlns:some="http://someurl2"
xsi:schemaLocation="http://someurl some.3.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>
<name>Columbia</name>
</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
I am using identity transformation. Is there any way I can change the text in the namespace ursl? For example I want to change the urls 'http://someurl' to 'http://someur2'
and 'http://someurl some.3.0.xsd' to 'http://someurl some.4.0.xsd'

This should do it:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
xmlns:old="http://someurl" exclude-result-prefixes="old">
<!-- Identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- replace namespace of elements in old namespace -->
<xsl:template match="old:*">
<xsl:element name="{local-name()}" namespace="http://someurl2">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<!-- replace xsi:schemaLocation attribute -->
<xsl:template match="#xsi:schemaLocation">
<xsl:attribute name="xsi:schemaLocation">http://someurl some.4.0.xsd</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With the sample input, this gives:
<?xml version="1.0" encoding="utf-8"?>
<catalog xmlns="http://someurl2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://someurl some.4.0.xsd">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>
<name>Columbia</name>
</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
Explanation:
The last two templates, added to the identity transform, are more specific and therefore take higher default priority than the identity template. They override the identity template for elements in the "old" namespace, and xsl:schemaLocation attributes, respectively.
The template for "old:*" outputs an element with the same local-name as the one it's replacing (i.e. the name without the namespace), and gives it the new desired namespace.
Happily, the XSLT processor (or more properly, the serializer; I'm using Saxon 6.5.5 for my test) decided to make this new namespace the default, so it added a default namespace declaration for it to the output root element. We didn't ask it to do that, and theoretically it shouldn't matter whether this new namespace is the default or uses a prefix. But you seem to want it to be the default so that worked out well.

Related

Multiple transformations to an xml file using xslt 1

Newbie to this site and using xslt but running into a roadblock transforming a SSRS 2008v2 rendered xml file into another XSL raw format for a 3rd Party EDI transfer. I've been searching this site and others for a while now, but struggling putting it all together.I'm starting with the following raw xml data;
<Invoices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.spscommerce.com/RSX" xsi:schemaLocation="http://www.spscommerce.com/RSX http://tfl- sql01/ReportServer_SQL2012? %2FTesting%2FINTest&rs%3ACommand=Render&rs%3AFormat=XML&rs%3ASessionID= jn5ugdirg4m02nmodnm0hynq&rc%3ASchema=True" Name="INTest">
<Invoices1> ***need to remove***
<ivhID_Collection> ***need to remove***
<Invoices>...</Invoices>
<Invoices>...</Invoices>
<Invoices>
<Invoice>
<Header1>
<InvoiceHeader>...</InvoiceHeader>
<PaymentTerms>...</PaymentTerms>
<Dates>...</Dates>
<Address>...</Address>
<References>...</References>
<ChargesAllowances>...</ChargesAllowances>
<LineItem_Collection> ***need to remove and replace with </Header>***
<LineItem>
<InvoiceLine>...</InvoiceLine>
<ProductOrItemDescription>...</ProductOrItemDescription>
</LineItem>
<LineItem>
<InvoiceLine>...</InvoiceLine>
<ProductOrItemDescription>...</ProductOrItemDescription>
</LineItem>
</LineItem_Collection> ***need to remove***
<Summary>...</Summary>
</Header1> ***need to remove***
</Invoice>
</Invoices>
<Invoices>...</Invoices>
<Invoices>...</Invoices>
<Invoices>...</Invoices>
/ivhID_Collection> ***need to remove***
</Invoices1> ***need to remove***
</Invoices>
Trying to get it in this structure instead;
<Invoices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.spscommerce.com/RSX" xsi:schemaLocation="http://www.spscommerce.com/RSX http://tfl-sql01/ReportServer_SQL2012?%2FTesting%2FINDoItBest%20v7&rs%3ACommand=Render&rs%3AFormat=XML&rs%3ASessionID=jn5ugdirg4m02nmodnm0hynq&rc%3ASchema=True" Name="INDoItBest v7">
<Invoices>...</Invoices>
<Invoices>...</Invoices>
<Invoices>
<Invoice>
<Header>
<InvoiceHeader>...</InvoiceHeader>
<PaymentTerms>...</PaymentTerms>
<Dates>...</Dates>
<Address>...</Address>
<References>...</References>
<ChargesAllowances>...</ChargesAllowances>
</Header>
<LineItem>
<InvoiceLine>...</InvoiceLine>
<ProductOrItemDescription>...</ProductOrItemDescription>
</LineItem>
<LineItem>
<InvoiceLine>...</InvoiceLine>
<ProductOrItemDescription>...</ProductOrItemDescription>
</LineItem>
<Summary>...</Summary>
</Invoice>
</Invoices>
<Invoices>...</Invoices>
<Invoices>...</Invoices>
<Invoices>...</Invoices>
</Invoices>
I made some progress using this style sheet, but am stuck on the regrouping of the Header tag and the display of the element namespace.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t="http://www.spscommerce.com/RSX"
exclude-result-prefixes="t">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--rule to suppress the undesired nodes-->
<xsl:template match="t:Invoices1|t:ivhID_Collection">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="t:LineItem_Collection">
<xsl:apply-templates/>
</xsl:template>
<!--<xsl:template match="t:Invoice/t:Header1">
<xsl:apply-templates/>
</xsl:template>-->
<!-- Identity Transform -->
<xsl:template match="t:Header1">
<xsl:copy>
<xsl:element name="Header">
<xsl:apply-templates select="#*|t:InvoiceHeader|t:PaymentTerms|t:Dates|t:Address|t:References|t:ChargesAllowances"/>
</xsl:element>
<xsl:apply-templates select="#*|t:LineItem_Collection|t:Summary"/>
</xsl:copy>
</xsl:template>
<!-- Had to comment out -->
<!--<xsl:template match="t:Invoice/t:Header1">
<xsl:apply-templates/>
</xsl:template>-->
The stylesheet produced most of what I needed, but failed when I tried to remove the Header1 tag (code commented out). Also, struggling to understand why "exclude-result-prefixes" isn't working to remove the namespace from the new xml file.
<Invoices xmlns="http://www.spscommerce.com/RSX" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.spscommerce.com/RSX http://tfl-sql01/ReportServer_SQL2012?%2FTesting%2FINDoItBest%20v7&rs%3ACommand=Render&rs%3AFormat=XML&rs%3ASessionID=jn5ugdirg4m02nmodnm0hynq&rc%3ASchema=True" Name="INDoItBest v7">
<Invoices>...</Invoices>
<Invoices>...</Invoices>
<Invoices>
<Invoice>
<Header1>
<Header xmlns="">
<InvoiceHeader xmlns="http://www.spscommerce.com/RSX">... </InvoiceHeader>
<PaymentTerms xmlns="http://www.spscommerce.com/RSX">... </PaymentTerms>
<Dates xmlns="http://www.spscommerce.com/RSX">...</Dates>
<Address xmlns="http://www.spscommerce.com/RSX">...</Address>
<References xmlns="http://www.spscommerce.com/RSX">...</References>
<ChargesAllowances xmlns="http://www.spscommerce.com/RSX">... </ChargesAllowances>
</Header>
<LineItem>
<InvoiceLine>...</InvoiceLine>
<ProductOrItemDescription>...</ProductOrItemDescription>
</LineItem>
<LineItem>
<InvoiceLine>...</InvoiceLine>
<ProductOrItemDescription>...</ProductOrItemDescription>
</LineItem>
<Summary>
<TotalAmount>756.8400</TotalAmount>
<TotalSalesAmount>727.1600</TotalSalesAmount>
<TotalLineItemNumber>2</TotalLineItemNumber>
</Summary>
</Header1>
</Invoice>
</Invoices>
<Invoices>...</Invoices>
<Invoices>...</Invoices>
<Invoices>...</Invoices>
</Invoices>
Any advice or other options would be greatly appreciated!
You've already got a template matching t:Header1 in your XSLT, so you shouldn't add another one matching it, as only one can apply. (In your case, if you did add a template matching t:Invoice\t:Header1 then because of the parent being specified, it would have a higher priority as the one just matching t:Header1 and be used instead).
What you will need to do, is put all the logic in the single template. In this case, all you need to do is remove the xsl:copy from that template to avoid the Header1 being copied to the output tree. Additionally, when you create Header, you are creating it in no namespace, not in the namespace bound to the prefix "t". Therefore, the child elements will be given new namespace declarations because they will still be in that namespace.
One way to do it is simply add a "namespace" attribute to the xsl:element, like so:
<xsl:element name="Header" namespace="http://www.spscommerce.com/RSX">
Alternatively, you can create the element by just doing <Header> but you will need to add a default namespace declaration to the XSLT too, to ensure it gets output in the correct namespace.
Try this XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t="http://www.spscommerce.com/RSX"
xmlns="http://www.spscommerce.com/RSX"
exclude-result-prefixes="t">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--rule to suppress the undesired nodes-->
<xsl:template match="t:Invoices1|t:ivhID_Collection">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="t:LineItem_Collection">
<xsl:apply-templates/>
</xsl:template>
<!-- Identity Transform -->
<xsl:template match="t:Header1">
<xsl:apply-templates select="#*" />
<Header>
<xsl:apply-templates select="#*|t:InvoiceHeader|t:PaymentTerms|t:Dates|t:Address|t:References|t:ChargesAllowances"/>
</Header>
<xsl:apply-templates select="t:LineItem_Collection|t:Summary"/>
</xsl:template>
</xsl:stylesheet>
As a side note, in your XSLT you were also doing this immediately after creating the Header element
<xsl:apply-templates select="#*|t:LineItem_Collection|t:Summary"/>
This would fail if the Header1 had attributes you wanted to copy, as it is an error to try to add attributes to a parent element after you have created child elements. This is why in my XSLT I have split the statement into two.

Transforming the namespace child(sub) element using XSLT

XML
<?xml version="1.0" encoding="UTF-8"?>
<!-- Edited by XMLSpy -->
<h:catalog xmlns:h="http://www.w3.org/TR/html4/">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</h:catalog>
XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:h="http://www.w3.org/TR/html4/">
<xsl:template match="/h:catalog/cd">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="country"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
I am expecting below output from the above transformation, but i am not getting
*Need Your help to display in below format, Struggling a lot because of the namespace child element*
Expected Output:
<tr xmlns:h="http://www.w3.org/TR/html4/">
<td>Empire Burlesque</td>
<td>USA</td>
</tr>
This is just an example i gave my query is: How can i transform the child element, whose parent has the namespace? example:
<app:ApplicationRequest xmlns:app="http://*/applicationRequest">
<BusinessChannel>
<!-- SOME STUFF -->
</BusinessChannel>
</app:ApplicationRequest>.
I need to display the contents of BusinessChannel using
<xsl:template match="/app:ApplicationRequest/BusinessChannel">
But it is not working.
If you want to match an element disregarding its namespace, as described here you have to use local-name(), so in your case:
<xsl:template match="/app:ApplicationRequest/*[local-name()='BusinessChannel']">

Copy data from one XML doc to another using XSLT

I have to copy data of node element from file1.xml to file2.xml.
file1.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
<header>
<AsofDate>31-Dec-2012</AsofDate>
<FundName>This is Sample Fund</FundName>
<Description>This is test description</Description>
</header>
</root>
file2.xml
<?xml version="1.0" encoding="utf-8" ?>
<root id="1">
<header id="2">
<AsofDate id="3"/>
<FundName id="4" />
<Description id="5" />
</header>
</root>
after merging file1.xml into file2.xml, result should look below:
<?xml version="1.0" encoding="utf-8" ?>
<root id="1">
<header id="2">
<AsofDate id="3">31-Dec-2012</AsofDate>
<FundName id="4">This is Sample Fund</FundName>
<Description id="5">This is test description</Description>
</header>
</root>
I am using below XSLT to transform file.
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Below is the code is used to perform transformation:
XslCompiledTransform tf = new XslCompiledTransform();
tf.Load("TranFile.xsl");
tf.Transform("file1.xml", "file2.xml");
but above code is overwriting the file2 content with file1.xml content. This is just sample XML. In real case we don't know name of nodes and hierarchy of the xml file. But whatever structure would be will be same for both file and scenario will be exactly same. I am new to XSLT and not sure is this right approach to accomplish the result. Is it really possible to achieve result through XSLT.
The solution that I post is written having in mind the following:
The only thing that need to be merged are the attributes. Text and element nodes are copied as they appear from file1.xml.
The #id attributes are not numbered sequentially in file2.xml so the #id's in file2.xml could be (for example) 121 432 233 12 944 instead of 1 2 3 4 5. If the case is the latter then you would not need file2.xml to generate the desired output.
The document() function can be used to access files different than the current one. If XslCompiledTransform is giving an error when using the document function I would suggest to follow this using document() function in .NET XSLT generates error . I am using a different XSLT processor (xsltproc) and it works fine.
This solution is based on keeping a reference to the external file, so each time that we process an element in file1.xml the reference is moved to point at the same element in file2.xml. This can be done because according to the problem, both files present the same element hierarchy.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no"/>
<!-- Match the document node as an entry point for matching the files -->
<xsl:template match="/">
<xsl:apply-templates select="node()">
<xsl:with-param name="doc-context" select="document('file2.xml')/node()" />
</xsl:apply-templates>
</xsl:template>
<!-- In this template we copy the elements and text nodes from file1.xml and
we merge the attributes from file2.xml with the attributes in file1.xml -->
<xsl:template match="node()">
<!-- We use this parameter to keep track of where we are in file2.xml by
mimicking the operations that we do in the current file. So we are at
the same position in both files at the same time. -->
<xsl:param name="doc-context" />
<!-- Obtain current position in file1.xml so we know where to look in file2.xml -->
<xsl:variable name="position" select="position()" />
<!-- Copy the element node from the current file (file1.xml) -->
<xsl:copy>
<!-- Merge attributes from file1.xml with attributes from file2.xml -->
<xsl:copy-of select="#*|$doc-context[position() = $position]/#*" />
<!-- Copy text nodes and process children -->
<xsl:apply-templates select="node()">
<xsl:with-param name="doc-context" select="$doc-context/node()" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Passing xml as parameter to xslt and replacing the xml [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
XSLT for replace attribute in XML element if it exists in another XML?
I have two XML:
First XML:
<FirstXML>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
</FirstXML>
Second XML:
<SecondXML>
<catalog>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
</SecondXML>
I want to Transform my First XML using XSLT. The cd node of first xml should be replaced with cd node of second xml.
Resulting XML from XSLT transformation:
<FirstXML>
<catalog>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
</FirstXML>
Please help me with XSLT for this. I think we would need to pass the Second XML as a parameter to the XSLT, I am trying to do that. I am very new to XSLT so might not be coding it correctly.
Any inputs on how we can do this would be helpful. Thanks in Advance.
I think we would need to pass the Second XML as a parameter to the
XSLT.
This is possible, but not necessary at all.
A more convenient way is to pass to the transformation just the filepath to the document and use the XSLT document() function for parsing and accessing it:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pDocLink" select=
"'file:///c:/temp/delete/SecondXml.xml'"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="document($pDocLink)/*/*[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided "first" document:
<FirstXML>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
</FirstXML>
and the "second" document is placed at: c:\temp\delete\SecondXml.xml :
<SecondXML>
<catalog>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
</SecondXML>
the wanted, correct result is produced:
<FirstXML>
<catalog>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
</FirstXML>
There is a document function, use it to work with several input sources.
The description of your problem is too vague to make more detailed recommendations. Try to search some tutorials, e.g. http://www.ibm.com/developerworks/xml/library/x-tipcombxslt/
You should be looking at the document() function. Passing an XML tree in as a parameter works is some XML systems, not others.
If you have a variable such as
<xsl:variable name="source" select="document('filename.xml')//catalog"/>
then accessing the variable $source would give you the root of your CD branch
EDIT
Just for clarity of my answer to Dimitre Novatchev, and as I said you could do it, although in 90% of times you would want to use the document function, it is possible to pass a node fragment in through to a stylesheet from a C# application with AddParam, and could be used when you are generating that data from your own application, and don't need to write it (or where it isn't practical).
So for a stylesheet such as:
<?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:param name="bonnie_tyler"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cd[ancestor::FirstXML]">
<xsl:copy>
<xsl:apply-templates select="msxsl:node-set($bonnie_tyler)//cd/*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
the First and Second XML as per your original question, you could use the pass the nodes in such as:
try
{
XslCompiledTransform xt = new XslCompiledTransform();
Assembly ass = Assembly.GetEntryAssembly();
Stream strm = ass.GetManifestResourceStream("ConsoleApplication1.testparam.xslt");
XmlTextReader xmr = new XmlTextReader(strm);
xt.Load(xmr);
xmr.Close();
XmlDocument originalDocument = new XmlDocument();
strm = ass.GetManifestResourceStream("ConsoleApplication1.FirstXML.xml");
originalDocument.Load(strm);
XmlDocument replacementNode = new XmlDocument();
strm = ass.GetManifestResourceStream("ConsoleApplication1.SecondXML.xml");
replacementNode.Load(strm);
XsltArgumentList arg = new XsltArgumentList();
arg.AddParam("bonnie_tyler", "", replacementNode);
StringBuilder htmlOutput = new StringBuilder();
XmlWriter writer = XmlWriter.Create(htmlOutput);
xt.Transform(originalDocument, arg, writer);
Console.Out.Write(htmlOutput.ToString());
}
which would give you your desired output (although obviously you wouldn't be loading the files from disk as here)

XPath expression to select all XML child nodes except a specific list?

Here's the sample data:
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<customField1>Whatever</customField1>
<customField2>Whatever</customField2>
<customField3>Whatever</customField3>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<customField1>Whatever</customField1>
<customField2>Whatever</customField2>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<customField1>Whatever</customField1>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
Say I want to select everything except the price and year elements. I would expect to write something like the below, which obviously doesn't work.
<?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="/">
<html>
<body>
<xsl:for-each select="//cd/* except (//cd/price|//cd/year)">
Current node: <xsl:value-of select="current()"/>
<br />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Please help me find a way to exclude certain child elements.
<xsl:for-each select="//cd/*[not(self::price or self::year)]">
But actually this is bad and unnecessarily complicated. Better:
<xsl:template match="catalog">
<html>
<body>
<xsl:apply-templates select="cd/*" />
</body>
</html>
</xsl:template>
<!-- this is an empty template to mute any unwanted elements -->
<xsl:template match="cd/price | cd/year" />
<!-- this is to output wanted elements -->
<xsl:template match="cd/*">
<xsl:text>Current node: </xsl:text>
<xsl:value-of select="."/>
<br />
</xsl:template>
Avoid <xsl:for-each>. Almost all of the time it is the wrong tool and should be substituted by <xsl:apply-templates> and <xsl:template>.
The above works because of match expression specificity. match="cd/price | cd/year" is more specific than match="cd/*", so it is the preferred template for cd/price or cd/year elements. Don't try to exclude nodes, let them come and handle them by discarding them.
I'd start experimenting with something like
"//cd/*[(name() != 'price') and (name() != 'year')]"
Or you just do normal recursive template matching with <xsl:apply-templates/>, and then have empty templates for <price/> and <year/> elements:
<xsl:template match="price"/>
<xsl:template match="year"/>