XSL to produce all element and attribute names
I need two XSL style sheets that evaluate any XML document and returns:
1) all element names within their hierarchy;
2) all attribute names associated with each element; and
3) one style sheet that output the result to XML; one style sheet that outputs the result to text.
I will transform this in SSIS, which I have done quite a few times including with basic XSL style sheets that I've created. I will be loading this into a table within a database.
Please note that I possess minimal knowledge of XSL/XML. I may use incorrect terms. Also I may overlook something obvious in my request. Therefore I would rely on you to apply your insights.
Example XML:
<BOOK id="1" chapters="9">
<AUTHOR gender="Male" age="43">
<NAME>John Smith</NAME>
</AUTHOR>
<TITLE>Just a book</TITLE>
</BOOK>
Desired text output (two columns delimited by tab or some character):
element attribute
/BOOK id
/BOOK chapters
/BOOK/AUTHOR gender
/BOOK/AUTHOR age
/BOOK/AUTHOR/NAME
/BOOK/TITLE
Desired XML output (more or less? not sure; open to suggestions):
<ROOT>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>id</ATTRIBUTE>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>chapters</ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>gender<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>age<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
<ELEMENT>/BOOK/TITLE</ELEMENT>
</ROOT>
I don't want to be a mooch and this is more than a little question so I would be willing to pay for the answer.
Thanks.
xml xslt
add a comment |
I need two XSL style sheets that evaluate any XML document and returns:
1) all element names within their hierarchy;
2) all attribute names associated with each element; and
3) one style sheet that output the result to XML; one style sheet that outputs the result to text.
I will transform this in SSIS, which I have done quite a few times including with basic XSL style sheets that I've created. I will be loading this into a table within a database.
Please note that I possess minimal knowledge of XSL/XML. I may use incorrect terms. Also I may overlook something obvious in my request. Therefore I would rely on you to apply your insights.
Example XML:
<BOOK id="1" chapters="9">
<AUTHOR gender="Male" age="43">
<NAME>John Smith</NAME>
</AUTHOR>
<TITLE>Just a book</TITLE>
</BOOK>
Desired text output (two columns delimited by tab or some character):
element attribute
/BOOK id
/BOOK chapters
/BOOK/AUTHOR gender
/BOOK/AUTHOR age
/BOOK/AUTHOR/NAME
/BOOK/TITLE
Desired XML output (more or less? not sure; open to suggestions):
<ROOT>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>id</ATTRIBUTE>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>chapters</ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>gender<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>age<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
<ELEMENT>/BOOK/TITLE</ELEMENT>
</ROOT>
I don't want to be a mooch and this is more than a little question so I would be willing to pay for the answer.
Thanks.
xml xslt
You haven't stated it in the requirement, but my guess is that you only want distinct element/attribute name pairs. @MadsHansen interpreted your requirements differently, which just goes to show how careful you need to be with requirements.
– Michael Kay
Nov 15 '18 at 8:17
add a comment |
I need two XSL style sheets that evaluate any XML document and returns:
1) all element names within their hierarchy;
2) all attribute names associated with each element; and
3) one style sheet that output the result to XML; one style sheet that outputs the result to text.
I will transform this in SSIS, which I have done quite a few times including with basic XSL style sheets that I've created. I will be loading this into a table within a database.
Please note that I possess minimal knowledge of XSL/XML. I may use incorrect terms. Also I may overlook something obvious in my request. Therefore I would rely on you to apply your insights.
Example XML:
<BOOK id="1" chapters="9">
<AUTHOR gender="Male" age="43">
<NAME>John Smith</NAME>
</AUTHOR>
<TITLE>Just a book</TITLE>
</BOOK>
Desired text output (two columns delimited by tab or some character):
element attribute
/BOOK id
/BOOK chapters
/BOOK/AUTHOR gender
/BOOK/AUTHOR age
/BOOK/AUTHOR/NAME
/BOOK/TITLE
Desired XML output (more or less? not sure; open to suggestions):
<ROOT>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>id</ATTRIBUTE>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>chapters</ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>gender<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>age<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
<ELEMENT>/BOOK/TITLE</ELEMENT>
</ROOT>
I don't want to be a mooch and this is more than a little question so I would be willing to pay for the answer.
Thanks.
xml xslt
I need two XSL style sheets that evaluate any XML document and returns:
1) all element names within their hierarchy;
2) all attribute names associated with each element; and
3) one style sheet that output the result to XML; one style sheet that outputs the result to text.
I will transform this in SSIS, which I have done quite a few times including with basic XSL style sheets that I've created. I will be loading this into a table within a database.
Please note that I possess minimal knowledge of XSL/XML. I may use incorrect terms. Also I may overlook something obvious in my request. Therefore I would rely on you to apply your insights.
Example XML:
<BOOK id="1" chapters="9">
<AUTHOR gender="Male" age="43">
<NAME>John Smith</NAME>
</AUTHOR>
<TITLE>Just a book</TITLE>
</BOOK>
Desired text output (two columns delimited by tab or some character):
element attribute
/BOOK id
/BOOK chapters
/BOOK/AUTHOR gender
/BOOK/AUTHOR age
/BOOK/AUTHOR/NAME
/BOOK/TITLE
Desired XML output (more or less? not sure; open to suggestions):
<ROOT>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>id</ATTRIBUTE>
<ELEMENT>/BOOK</ELEMENT><ATTRIBUTE>chapters</ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>gender<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>age<ATTRIBUTE></ATTRIBUTE>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
<ELEMENT>/BOOK/TITLE</ELEMENT>
</ROOT>
I don't want to be a mooch and this is more than a little question so I would be willing to pay for the answer.
Thanks.
xml xslt
xml xslt
asked Nov 15 '18 at 1:11
digest806digest806
113
113
You haven't stated it in the requirement, but my guess is that you only want distinct element/attribute name pairs. @MadsHansen interpreted your requirements differently, which just goes to show how careful you need to be with requirements.
– Michael Kay
Nov 15 '18 at 8:17
add a comment |
You haven't stated it in the requirement, but my guess is that you only want distinct element/attribute name pairs. @MadsHansen interpreted your requirements differently, which just goes to show how careful you need to be with requirements.
– Michael Kay
Nov 15 '18 at 8:17
You haven't stated it in the requirement, but my guess is that you only want distinct element/attribute name pairs. @MadsHansen interpreted your requirements differently, which just goes to show how careful you need to be with requirements.
– Michael Kay
Nov 15 '18 at 8:17
You haven't stated it in the requirement, but my guess is that you only want distinct element/attribute name pairs. @MadsHansen interpreted your requirements differently, which just goes to show how careful you need to be with requirements.
– Michael Kay
Nov 15 '18 at 8:17
add a comment |
2 Answers
2
active
oldest
votes
You could generate the desired XML with the following stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<ROOT>
<xsl:apply-templates select="//*[not(@*)]|//@*" />
</ROOT>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
</xsl:stylesheet>
And then enhance it to be able to generate either the desired XML or text output by setting the value of the format parameter to "text". Setting the format parameter value to anything else will generate the XML output. You can adjust the delimiter value by setting a different value for the delim parameter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="format" select="'text'"/>
<xsl:param name="delim" select="' '"/>
<xsl:template match="/">
<!--generate XML report and assign to a variable-->
<xsl:variable name="report">
<ROOT>
<!--Push elements that don't have attributes, and all attributes.
Elements that have attributes will be transformed when
transforming the attributes -->
<xsl:apply-templates select="//*[not(@*)] | //@*" />
</ROOT>
</xsl:variable>
<!--depending upon the value of the format parameter,
either transform that XML into the text report,
or return the generated XML -->
<xsl:choose>
<xsl:when test="$format='text'">
<xsl:apply-templates select="$report/ROOT"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$report"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<!--for each attribute, generate an ELEMENT with the path and an ATTRIBUTE element-->
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
<!--templates to transform the generated XML into the text output -->
<xsl:template match="ROOT">
<xsl:sequence select="string-join(('element', 'attribute'), $delim)"/>
<xsl:apply-templates select="ELEMENT"/>
</xsl:template>
<xsl:template match="ELEMENT">
<xsl:value-of select="'
'"/>
<xsl:value-of select="string-join((., following-sibling::*[1][self::ATTRIBUTE]), $delim)"/>
</xsl:template>
</xsl:stylesheet>
I am grateful for your response. This returned the following error: "Expected end of the expression, found '('.".
– digest806
Nov 16 '18 at 23:06
This is an XSLT 2.0 solution. It sounds as if you may be using a 1.0 engine. It would be helpful to state in the question if you are limited to XSLT 1.0. Tag it withxslt-1.0
– Mads Hansen
Nov 17 '18 at 2:49
add a comment |
Firstly, requirements:
(a) your example doesn't have any instances where there is more than one element or attribute with the same path, so it's not clear whether or not you want to eliminate duplicates.
(b) your output XML is quite tricky to process, because the element paths and attribute names aren't connected except by relative position. It would be better to add a wrapper element, say <PATH>
.
(c) in your output, the number of times an element path appears is N where N is the number of attributes found, except that it is 1 when there are no attributes. This seems a little inconsistent to me. I would suggest output of the form
<ROOT>
<PATH>
<ELEMENT>/BOOK</ELEMENT>
<ATTRIBUTE>id</ATTRIBUTE>
<ATTRIBUTE>chapters</ATTRIBUTE>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>
<ATTRIBUTE>gender<ATTRIBUTE>
<ATTRIBUTE>age</ATTRIBUTE>
<PATH>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR/TITLE</ELEMENT>
</PATH>
</ROOT>
If I'm right in thinking that you want to eliminate duplicates, then this is a grouping problem and therefore much easier in XSLT 2.0+. You didn't actually state any constraints on XSLT version, but I'm going to assume XSLT 2.0. Note that there are many XSLT processors that only support 1.0, even though 2.0 has been out for over ten years.
First we want a function that gives the path to an element:
<xsl:function name="f:path" as="xs:string">
<xsl:param name="node" as="node()"/>
<xsl:choose>
<xsl:when test="exists($node/..)">
<xsl:sequence select="concat(f:path($node/..), '/', local-name($node)"/>
</xsl:when>
<xsl:otherwise>/</xsl:otherwise>
</xsl:choose>
</xsl:function>
Now the grouping commences:
<xsl:for-each-group select="//*" group-by="f:path(.)">
<PATH>
<ELEMENT><xsl:value-of select="current-grouping-key()"/></ELEMENT>
<xsl:for-each-group select="current-group()/@*" group-by="local-name()">
<ATTRIBUTE><xsl:value-of select="local-name()"/></ATTRIBUTE>
</xsl:for-each-group>
</PATH>
</xsl:for-each-group>
I am grateful for your response. This only produced: "John SmithJust a book".
– digest806
Nov 16 '18 at 23:04
When the result of a transformation is just the text node content of the input, that usually suggests that none of the code in the stylesheet is being executed. I didn't give you a complete stylesheet, just the critical parts; I suspect there's something wrong in the code that you added.
– Michael Kay
Nov 17 '18 at 21:55
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53311064%2fxsl-to-produce-all-element-and-attribute-names%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
You could generate the desired XML with the following stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<ROOT>
<xsl:apply-templates select="//*[not(@*)]|//@*" />
</ROOT>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
</xsl:stylesheet>
And then enhance it to be able to generate either the desired XML or text output by setting the value of the format parameter to "text". Setting the format parameter value to anything else will generate the XML output. You can adjust the delimiter value by setting a different value for the delim parameter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="format" select="'text'"/>
<xsl:param name="delim" select="' '"/>
<xsl:template match="/">
<!--generate XML report and assign to a variable-->
<xsl:variable name="report">
<ROOT>
<!--Push elements that don't have attributes, and all attributes.
Elements that have attributes will be transformed when
transforming the attributes -->
<xsl:apply-templates select="//*[not(@*)] | //@*" />
</ROOT>
</xsl:variable>
<!--depending upon the value of the format parameter,
either transform that XML into the text report,
or return the generated XML -->
<xsl:choose>
<xsl:when test="$format='text'">
<xsl:apply-templates select="$report/ROOT"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$report"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<!--for each attribute, generate an ELEMENT with the path and an ATTRIBUTE element-->
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
<!--templates to transform the generated XML into the text output -->
<xsl:template match="ROOT">
<xsl:sequence select="string-join(('element', 'attribute'), $delim)"/>
<xsl:apply-templates select="ELEMENT"/>
</xsl:template>
<xsl:template match="ELEMENT">
<xsl:value-of select="'
'"/>
<xsl:value-of select="string-join((., following-sibling::*[1][self::ATTRIBUTE]), $delim)"/>
</xsl:template>
</xsl:stylesheet>
I am grateful for your response. This returned the following error: "Expected end of the expression, found '('.".
– digest806
Nov 16 '18 at 23:06
This is an XSLT 2.0 solution. It sounds as if you may be using a 1.0 engine. It would be helpful to state in the question if you are limited to XSLT 1.0. Tag it withxslt-1.0
– Mads Hansen
Nov 17 '18 at 2:49
add a comment |
You could generate the desired XML with the following stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<ROOT>
<xsl:apply-templates select="//*[not(@*)]|//@*" />
</ROOT>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
</xsl:stylesheet>
And then enhance it to be able to generate either the desired XML or text output by setting the value of the format parameter to "text". Setting the format parameter value to anything else will generate the XML output. You can adjust the delimiter value by setting a different value for the delim parameter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="format" select="'text'"/>
<xsl:param name="delim" select="' '"/>
<xsl:template match="/">
<!--generate XML report and assign to a variable-->
<xsl:variable name="report">
<ROOT>
<!--Push elements that don't have attributes, and all attributes.
Elements that have attributes will be transformed when
transforming the attributes -->
<xsl:apply-templates select="//*[not(@*)] | //@*" />
</ROOT>
</xsl:variable>
<!--depending upon the value of the format parameter,
either transform that XML into the text report,
or return the generated XML -->
<xsl:choose>
<xsl:when test="$format='text'">
<xsl:apply-templates select="$report/ROOT"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$report"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<!--for each attribute, generate an ELEMENT with the path and an ATTRIBUTE element-->
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
<!--templates to transform the generated XML into the text output -->
<xsl:template match="ROOT">
<xsl:sequence select="string-join(('element', 'attribute'), $delim)"/>
<xsl:apply-templates select="ELEMENT"/>
</xsl:template>
<xsl:template match="ELEMENT">
<xsl:value-of select="'
'"/>
<xsl:value-of select="string-join((., following-sibling::*[1][self::ATTRIBUTE]), $delim)"/>
</xsl:template>
</xsl:stylesheet>
I am grateful for your response. This returned the following error: "Expected end of the expression, found '('.".
– digest806
Nov 16 '18 at 23:06
This is an XSLT 2.0 solution. It sounds as if you may be using a 1.0 engine. It would be helpful to state in the question if you are limited to XSLT 1.0. Tag it withxslt-1.0
– Mads Hansen
Nov 17 '18 at 2:49
add a comment |
You could generate the desired XML with the following stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<ROOT>
<xsl:apply-templates select="//*[not(@*)]|//@*" />
</ROOT>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
</xsl:stylesheet>
And then enhance it to be able to generate either the desired XML or text output by setting the value of the format parameter to "text". Setting the format parameter value to anything else will generate the XML output. You can adjust the delimiter value by setting a different value for the delim parameter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="format" select="'text'"/>
<xsl:param name="delim" select="' '"/>
<xsl:template match="/">
<!--generate XML report and assign to a variable-->
<xsl:variable name="report">
<ROOT>
<!--Push elements that don't have attributes, and all attributes.
Elements that have attributes will be transformed when
transforming the attributes -->
<xsl:apply-templates select="//*[not(@*)] | //@*" />
</ROOT>
</xsl:variable>
<!--depending upon the value of the format parameter,
either transform that XML into the text report,
or return the generated XML -->
<xsl:choose>
<xsl:when test="$format='text'">
<xsl:apply-templates select="$report/ROOT"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$report"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<!--for each attribute, generate an ELEMENT with the path and an ATTRIBUTE element-->
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
<!--templates to transform the generated XML into the text output -->
<xsl:template match="ROOT">
<xsl:sequence select="string-join(('element', 'attribute'), $delim)"/>
<xsl:apply-templates select="ELEMENT"/>
</xsl:template>
<xsl:template match="ELEMENT">
<xsl:value-of select="'
'"/>
<xsl:value-of select="string-join((., following-sibling::*[1][self::ATTRIBUTE]), $delim)"/>
</xsl:template>
</xsl:stylesheet>
You could generate the desired XML with the following stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<ROOT>
<xsl:apply-templates select="//*[not(@*)]|//@*" />
</ROOT>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
</xsl:stylesheet>
And then enhance it to be able to generate either the desired XML or text output by setting the value of the format parameter to "text". Setting the format parameter value to anything else will generate the XML output. You can adjust the delimiter value by setting a different value for the delim parameter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="format" select="'text'"/>
<xsl:param name="delim" select="' '"/>
<xsl:template match="/">
<!--generate XML report and assign to a variable-->
<xsl:variable name="report">
<ROOT>
<!--Push elements that don't have attributes, and all attributes.
Elements that have attributes will be transformed when
transforming the attributes -->
<xsl:apply-templates select="//*[not(@*)] | //@*" />
</ROOT>
</xsl:variable>
<!--depending upon the value of the format parameter,
either transform that XML into the text report,
or return the generated XML -->
<xsl:choose>
<xsl:when test="$format='text'">
<xsl:apply-templates select="$report/ROOT"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$report"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<ELEMENT><xsl:apply-templates select="." mode="path"/></ELEMENT>
</xsl:template>
<!--for each attribute, generate an ELEMENT with the path and an ATTRIBUTE element-->
<xsl:template match="@*">
<xsl:apply-templates select=".." />
<ATTRIBUTE><xsl:value-of select="name()"/></ATTRIBUTE>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:text>/</xsl:text>
<xsl:value-of select="ancestor-or-self::*/name(.)" separator="/"/>
</xsl:template>
<!--templates to transform the generated XML into the text output -->
<xsl:template match="ROOT">
<xsl:sequence select="string-join(('element', 'attribute'), $delim)"/>
<xsl:apply-templates select="ELEMENT"/>
</xsl:template>
<xsl:template match="ELEMENT">
<xsl:value-of select="'
'"/>
<xsl:value-of select="string-join((., following-sibling::*[1][self::ATTRIBUTE]), $delim)"/>
</xsl:template>
</xsl:stylesheet>
answered Nov 15 '18 at 3:05
Mads HansenMads Hansen
44.9k1195122
44.9k1195122
I am grateful for your response. This returned the following error: "Expected end of the expression, found '('.".
– digest806
Nov 16 '18 at 23:06
This is an XSLT 2.0 solution. It sounds as if you may be using a 1.0 engine. It would be helpful to state in the question if you are limited to XSLT 1.0. Tag it withxslt-1.0
– Mads Hansen
Nov 17 '18 at 2:49
add a comment |
I am grateful for your response. This returned the following error: "Expected end of the expression, found '('.".
– digest806
Nov 16 '18 at 23:06
This is an XSLT 2.0 solution. It sounds as if you may be using a 1.0 engine. It would be helpful to state in the question if you are limited to XSLT 1.0. Tag it withxslt-1.0
– Mads Hansen
Nov 17 '18 at 2:49
I am grateful for your response. This returned the following error: "Expected end of the expression, found '('.".
– digest806
Nov 16 '18 at 23:06
I am grateful for your response. This returned the following error: "Expected end of the expression, found '('.".
– digest806
Nov 16 '18 at 23:06
This is an XSLT 2.0 solution. It sounds as if you may be using a 1.0 engine. It would be helpful to state in the question if you are limited to XSLT 1.0. Tag it with
xslt-1.0
– Mads Hansen
Nov 17 '18 at 2:49
This is an XSLT 2.0 solution. It sounds as if you may be using a 1.0 engine. It would be helpful to state in the question if you are limited to XSLT 1.0. Tag it with
xslt-1.0
– Mads Hansen
Nov 17 '18 at 2:49
add a comment |
Firstly, requirements:
(a) your example doesn't have any instances where there is more than one element or attribute with the same path, so it's not clear whether or not you want to eliminate duplicates.
(b) your output XML is quite tricky to process, because the element paths and attribute names aren't connected except by relative position. It would be better to add a wrapper element, say <PATH>
.
(c) in your output, the number of times an element path appears is N where N is the number of attributes found, except that it is 1 when there are no attributes. This seems a little inconsistent to me. I would suggest output of the form
<ROOT>
<PATH>
<ELEMENT>/BOOK</ELEMENT>
<ATTRIBUTE>id</ATTRIBUTE>
<ATTRIBUTE>chapters</ATTRIBUTE>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>
<ATTRIBUTE>gender<ATTRIBUTE>
<ATTRIBUTE>age</ATTRIBUTE>
<PATH>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR/TITLE</ELEMENT>
</PATH>
</ROOT>
If I'm right in thinking that you want to eliminate duplicates, then this is a grouping problem and therefore much easier in XSLT 2.0+. You didn't actually state any constraints on XSLT version, but I'm going to assume XSLT 2.0. Note that there are many XSLT processors that only support 1.0, even though 2.0 has been out for over ten years.
First we want a function that gives the path to an element:
<xsl:function name="f:path" as="xs:string">
<xsl:param name="node" as="node()"/>
<xsl:choose>
<xsl:when test="exists($node/..)">
<xsl:sequence select="concat(f:path($node/..), '/', local-name($node)"/>
</xsl:when>
<xsl:otherwise>/</xsl:otherwise>
</xsl:choose>
</xsl:function>
Now the grouping commences:
<xsl:for-each-group select="//*" group-by="f:path(.)">
<PATH>
<ELEMENT><xsl:value-of select="current-grouping-key()"/></ELEMENT>
<xsl:for-each-group select="current-group()/@*" group-by="local-name()">
<ATTRIBUTE><xsl:value-of select="local-name()"/></ATTRIBUTE>
</xsl:for-each-group>
</PATH>
</xsl:for-each-group>
I am grateful for your response. This only produced: "John SmithJust a book".
– digest806
Nov 16 '18 at 23:04
When the result of a transformation is just the text node content of the input, that usually suggests that none of the code in the stylesheet is being executed. I didn't give you a complete stylesheet, just the critical parts; I suspect there's something wrong in the code that you added.
– Michael Kay
Nov 17 '18 at 21:55
add a comment |
Firstly, requirements:
(a) your example doesn't have any instances where there is more than one element or attribute with the same path, so it's not clear whether or not you want to eliminate duplicates.
(b) your output XML is quite tricky to process, because the element paths and attribute names aren't connected except by relative position. It would be better to add a wrapper element, say <PATH>
.
(c) in your output, the number of times an element path appears is N where N is the number of attributes found, except that it is 1 when there are no attributes. This seems a little inconsistent to me. I would suggest output of the form
<ROOT>
<PATH>
<ELEMENT>/BOOK</ELEMENT>
<ATTRIBUTE>id</ATTRIBUTE>
<ATTRIBUTE>chapters</ATTRIBUTE>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>
<ATTRIBUTE>gender<ATTRIBUTE>
<ATTRIBUTE>age</ATTRIBUTE>
<PATH>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR/TITLE</ELEMENT>
</PATH>
</ROOT>
If I'm right in thinking that you want to eliminate duplicates, then this is a grouping problem and therefore much easier in XSLT 2.0+. You didn't actually state any constraints on XSLT version, but I'm going to assume XSLT 2.0. Note that there are many XSLT processors that only support 1.0, even though 2.0 has been out for over ten years.
First we want a function that gives the path to an element:
<xsl:function name="f:path" as="xs:string">
<xsl:param name="node" as="node()"/>
<xsl:choose>
<xsl:when test="exists($node/..)">
<xsl:sequence select="concat(f:path($node/..), '/', local-name($node)"/>
</xsl:when>
<xsl:otherwise>/</xsl:otherwise>
</xsl:choose>
</xsl:function>
Now the grouping commences:
<xsl:for-each-group select="//*" group-by="f:path(.)">
<PATH>
<ELEMENT><xsl:value-of select="current-grouping-key()"/></ELEMENT>
<xsl:for-each-group select="current-group()/@*" group-by="local-name()">
<ATTRIBUTE><xsl:value-of select="local-name()"/></ATTRIBUTE>
</xsl:for-each-group>
</PATH>
</xsl:for-each-group>
I am grateful for your response. This only produced: "John SmithJust a book".
– digest806
Nov 16 '18 at 23:04
When the result of a transformation is just the text node content of the input, that usually suggests that none of the code in the stylesheet is being executed. I didn't give you a complete stylesheet, just the critical parts; I suspect there's something wrong in the code that you added.
– Michael Kay
Nov 17 '18 at 21:55
add a comment |
Firstly, requirements:
(a) your example doesn't have any instances where there is more than one element or attribute with the same path, so it's not clear whether or not you want to eliminate duplicates.
(b) your output XML is quite tricky to process, because the element paths and attribute names aren't connected except by relative position. It would be better to add a wrapper element, say <PATH>
.
(c) in your output, the number of times an element path appears is N where N is the number of attributes found, except that it is 1 when there are no attributes. This seems a little inconsistent to me. I would suggest output of the form
<ROOT>
<PATH>
<ELEMENT>/BOOK</ELEMENT>
<ATTRIBUTE>id</ATTRIBUTE>
<ATTRIBUTE>chapters</ATTRIBUTE>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>
<ATTRIBUTE>gender<ATTRIBUTE>
<ATTRIBUTE>age</ATTRIBUTE>
<PATH>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR/TITLE</ELEMENT>
</PATH>
</ROOT>
If I'm right in thinking that you want to eliminate duplicates, then this is a grouping problem and therefore much easier in XSLT 2.0+. You didn't actually state any constraints on XSLT version, but I'm going to assume XSLT 2.0. Note that there are many XSLT processors that only support 1.0, even though 2.0 has been out for over ten years.
First we want a function that gives the path to an element:
<xsl:function name="f:path" as="xs:string">
<xsl:param name="node" as="node()"/>
<xsl:choose>
<xsl:when test="exists($node/..)">
<xsl:sequence select="concat(f:path($node/..), '/', local-name($node)"/>
</xsl:when>
<xsl:otherwise>/</xsl:otherwise>
</xsl:choose>
</xsl:function>
Now the grouping commences:
<xsl:for-each-group select="//*" group-by="f:path(.)">
<PATH>
<ELEMENT><xsl:value-of select="current-grouping-key()"/></ELEMENT>
<xsl:for-each-group select="current-group()/@*" group-by="local-name()">
<ATTRIBUTE><xsl:value-of select="local-name()"/></ATTRIBUTE>
</xsl:for-each-group>
</PATH>
</xsl:for-each-group>
Firstly, requirements:
(a) your example doesn't have any instances where there is more than one element or attribute with the same path, so it's not clear whether or not you want to eliminate duplicates.
(b) your output XML is quite tricky to process, because the element paths and attribute names aren't connected except by relative position. It would be better to add a wrapper element, say <PATH>
.
(c) in your output, the number of times an element path appears is N where N is the number of attributes found, except that it is 1 when there are no attributes. This seems a little inconsistent to me. I would suggest output of the form
<ROOT>
<PATH>
<ELEMENT>/BOOK</ELEMENT>
<ATTRIBUTE>id</ATTRIBUTE>
<ATTRIBUTE>chapters</ATTRIBUTE>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR</ELEMENT>
<ATTRIBUTE>gender<ATTRIBUTE>
<ATTRIBUTE>age</ATTRIBUTE>
<PATH>
<ELEMENT>/BOOK/AUTHOR/NAME</ELEMENT>
</PATH>
<PATH>
<ELEMENT>/BOOK/AUTHOR/TITLE</ELEMENT>
</PATH>
</ROOT>
If I'm right in thinking that you want to eliminate duplicates, then this is a grouping problem and therefore much easier in XSLT 2.0+. You didn't actually state any constraints on XSLT version, but I'm going to assume XSLT 2.0. Note that there are many XSLT processors that only support 1.0, even though 2.0 has been out for over ten years.
First we want a function that gives the path to an element:
<xsl:function name="f:path" as="xs:string">
<xsl:param name="node" as="node()"/>
<xsl:choose>
<xsl:when test="exists($node/..)">
<xsl:sequence select="concat(f:path($node/..), '/', local-name($node)"/>
</xsl:when>
<xsl:otherwise>/</xsl:otherwise>
</xsl:choose>
</xsl:function>
Now the grouping commences:
<xsl:for-each-group select="//*" group-by="f:path(.)">
<PATH>
<ELEMENT><xsl:value-of select="current-grouping-key()"/></ELEMENT>
<xsl:for-each-group select="current-group()/@*" group-by="local-name()">
<ATTRIBUTE><xsl:value-of select="local-name()"/></ATTRIBUTE>
</xsl:for-each-group>
</PATH>
</xsl:for-each-group>
answered Nov 15 '18 at 8:35
Michael KayMichael Kay
111k663119
111k663119
I am grateful for your response. This only produced: "John SmithJust a book".
– digest806
Nov 16 '18 at 23:04
When the result of a transformation is just the text node content of the input, that usually suggests that none of the code in the stylesheet is being executed. I didn't give you a complete stylesheet, just the critical parts; I suspect there's something wrong in the code that you added.
– Michael Kay
Nov 17 '18 at 21:55
add a comment |
I am grateful for your response. This only produced: "John SmithJust a book".
– digest806
Nov 16 '18 at 23:04
When the result of a transformation is just the text node content of the input, that usually suggests that none of the code in the stylesheet is being executed. I didn't give you a complete stylesheet, just the critical parts; I suspect there's something wrong in the code that you added.
– Michael Kay
Nov 17 '18 at 21:55
I am grateful for your response. This only produced: "John SmithJust a book".
– digest806
Nov 16 '18 at 23:04
I am grateful for your response. This only produced: "John SmithJust a book".
– digest806
Nov 16 '18 at 23:04
When the result of a transformation is just the text node content of the input, that usually suggests that none of the code in the stylesheet is being executed. I didn't give you a complete stylesheet, just the critical parts; I suspect there's something wrong in the code that you added.
– Michael Kay
Nov 17 '18 at 21:55
When the result of a transformation is just the text node content of the input, that usually suggests that none of the code in the stylesheet is being executed. I didn't give you a complete stylesheet, just the critical parts; I suspect there's something wrong in the code that you added.
– Michael Kay
Nov 17 '18 at 21:55
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53311064%2fxsl-to-produce-all-element-and-attribute-names%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You haven't stated it in the requirement, but my guess is that you only want distinct element/attribute name pairs. @MadsHansen interpreted your requirements differently, which just goes to show how careful you need to be with requirements.
– Michael Kay
Nov 15 '18 at 8:17