View Full Version : XSLT to emulate DOM Node.cloneNode(true)?
Alex Vincent
10-22-2002, 03:19 AM
I've been thinking about how nice the cloneNode() method of the Document Object Model is, and the fact that SVG has the <use /> element which essentially does the same thing.
So I'm thinking it's high time I asked how to create a similar effect in XSLT. (XML documents typically aren't scripted, according to one person who disagrees with me...)
That way, I can just have an element:
<html:p id="Hello">Hello World</html:p>
<jsl:clone xlink:href="#Hello" />
And the XSLT processor would output:
<html:p id="Hello">Hello World</html:p>
<html:p>Hello World</html:p>
Ideas, anyone?
... You could probably take a substr(1) from that xlink:href attribute, and match it against an attribute selector...
I might post something a little later to that effect.
<xsl:template match="//clone">
<xsl:copy-of select="id(substring(@href, 2))"/>
</xsl:template>
This is completely ignoring namespaces. To be honest, I don't think I recall how XPath deals with namespaces.... someone with some knowledge want to clear it up? :)
Conversation between Alex and I over this (useless parts edited out) in case anyone is interested (some interesting stuff if you want to read through the poorly copied text from the IRC chat):
..........
jasonkarldavis I don't know if XPath respects namespaces when matching attributes and tags or not
jasonkarldavis but the code itself it surprisingly simple
jasonkarldavis I thought it was gonna be way more complicated
WeirdAl well I was primarily concerned about matching by ID
jasonkarldavis but XPath has the nice id() function.... :)
jasonkarldavis which I didn't know about until 3 minutes ago
WeirdAl I figured it'd be simple
WeirdAl I just don't mess with XSLT ;)
jasonkarldavis I figure it is something worth knowing well... so I try to spend time with it now and then to keep fresh
WeirdAl :) -- well, as I said, it's somthing I figured you'd be the one to nail
WeirdAl goes to pull up XPath 1.0
WeirdAl -- I thought you'd use XPointer though
jasonkarldavis why?
jasonkarldavis XPointer is just like anchors for XML...
jasonkarldavis you can't actually grab content with it
WeirdAl http://www.w3.org/TR/xpath
WeirdAl :) I can't exactly tell the diff
jasonkarldavis picture XPointer as a replacement for <a name="myanchor">
WeirdAl http://www.w3.org/TR/xpath#namespace-nodes
jasonkarldavis recommends /XML in a Nutshell/ by O'Reilly
jasonkarldavis best reference I've ever gotten
.........
jasonkarldavis but XML In a Nutshell is *nice*
jasonkarldavis hehe
WeirdAl anyway check out that link I just gave you and tell me if it helps
jasonkarldavis that's not *quite* what I was looking for
WeirdAl try the expanded-names link
jasonkarldavis namespace nodes are one of the things Mozilla's Transformiix doesn't support
WeirdAl :(
WeirdAl section 4.1 of the same doc, what about that?
jasonkarldavis that expanded names thing is what I wanted I think
jasonkarldavis :)
WeirdAl if Moz supports it...
WeirdAl when in doubt, go get the spec
WeirdAl oh, and can you please make sure that the copy itself doesn't have any id attributes?
WeirdAl -- or better yet, lets me set a new id attribute?
WeirdAl in the original element
WeirdAl for the top element being copied?
WeirdAl -- that would be a killer XSLT app
jasonkarldavis umm
jasonkarldavis hmm
WeirdAl :) too much to swallow?
jasonkarldavis :: thinking ::
jasonkarldavis <xsl:attribute/> would be beautiful... but copied node isn't the "context node"
WeirdAl ?
jasonkarldavis yeah... I'm trying to think of how to access the copied fragment tree
WeirdAl -- maybe a *second* XSLT stylesheet
WeirdAl -- applied after the first!
jasonkarldavis does XSLT operate on itself? Like, once it figures out how to insert content, does it do it all at once?
jasonkarldavis or does it do it in order?
WeirdAl *shrug* I dunno
jasonkarldavis if it is in order... then you could just find the first-child of //clone
WeirdAl -- but that could be a text node or null
jasonkarldavis well, first element node
jasonkarldavis (and only)
WeirdAl :)
WeirdAl -- would it be guaranteed to be the intended node?
jasonkarldavis you can't select document fragments until XPointer works... therefore you are guarenteed that the <xsl:copy-of select="blabla"/> is an element node
jasonkarldavis besides, id() in this case only can return a single element node
WeirdAl -- in theory
jasonkarldavis as only elements can have id's
WeirdAl ah, but then we run into docs with two elems sharing the same id
jasonkarldavis which are invalid HTML/XHTML, but still well-formed XML....
jasonkarldavis id() can take multiple arguments
jasonkarldavis and will return a node-set of all matching nodes
WeirdAl -- hence why I'm trying to make sure we keep cloned id's out
jasonkarldavis or if multiple elements have the same id(), it returns a node-et of them
WeirdAl XSLT 1.0, Section 5.7: Modes allow an element to be processed multiple times, each time producing a different result.
jasonkarldavis does this reprocessing also pick up on previously inserted XSLT-generated templates?
WeirdAl read it
WeirdAl seems to
jasonkarldavis well then, create another template after this one
WeirdAl not me :)
jasonkarldavis is this my project now? ;) :D
jasonkarldavis j/k
WeirdAl -- I'm trying to figure out if XSLT lets us remove things instead of just creating them
jasonkarldavis I can "reset" the id attribute
WeirdAl :) that'd be cool -- if you reset them all to "" for the copy
jasonkarldavis <xsl:template match="//clone/*[position() = 1]">
jasonkarldavis <xsl:attribute name="id"/>
WeirdAl ?
jasonkarldavis </xsl:template>
WeirdAl hm
jasonkarldavis the content of <attribute/> is the new attribute value
jasonkarldavis in this case, I don't put anything in
WeirdAl ahhhhhhhhh
jasonkarldavis ? :)
WeirdAl that makes life interesting -- especially if you use a conditional to determine if it's the element actually being cloned
WeirdAl -- I think
jasonkarldavis well, we are assuming that the only children of <clone/> are what we put there via XSLT
jasonkarldavis which we become sure of it just a single element
WeirdAl which for the moment they would be
jasonkarldavis unless we have multiple same id;'s
WeirdAl uh oh... :)
jasonkarldavis which probably implies we are inserting content at a sibling level as a child of <clone/>?
WeirdAl hm -- yeah, that would be the safe bet
jasonkarldavis instead of only having firstChild, we have childNodes
WeirdAl oh, wait
jasonkarldavis which then makes:
jasonkarldavis match="//clone/*[position() = 1]"
jasonkarldavis become:
WeirdAl -- what I don't get is why we can't just replace <jsl:clone />
jasonkarldavis match="//clone/*"
jasonkarldavis simply replace the node?
WeirdAl exactly
WeirdAl that's safer imho
jasonkarldavis it would be easier to keep everything as children of it
jasonkarldavis plus, this way is more in line with the SVG spec
jasonkarldavis which you mentioned
WeirdAl -- does use create childnodes?
jasonkarldavis <use/> basically inserts almost anonymous content
jasonkarldavis the DOM only sees <use/>, but you can access its content through a property
WeirdAl heh, I'm rusty on the SVG DOM :)
jasonkarldavis UseElement.hasChildNodes() should be false
(Edited by Alex to remove a few slightly improper words that should not be posted here)
(Because Alex is a naughty moderator. :p - jkd )
Some food for thought:
<xsl:template match="//clone">
<xsl:for-each select="id(substring(@href, 2))">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="//clone/*">
<xsl:attribute name="id"/>
</xsl:template>
That should resolve multiple-same id problems.
I am still unsure of whether or not the second template will do anything though, but I intend to start testing it sometime later today...
Here is what I'm using:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//clone">
<clone>
<xsl:for-each select="id(substring(@href, 2))">
<xsl:copy-of select="."/>
</xsl:for-each>
</clone>
</xsl:template>
<xsl:template match="//clone/*">
<xsl:attribute name="id"/>
</xsl:template>
<xsl:template match="/node()">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
And applying it to:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="html.xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink" xml:lang="en">
<head>
<title>XSLT clone test</title>
</head>
<body>
<div id="reuse">cloned content?</div>
<clone xlink:type="simple" xlink:href="#reuse"/>
<hr/>
<clone xlink:type="simple" xlink:href="#reuse"/>
</body>
</html>
Doesn't result in *any* change to the document structure.
I think something is conceptually wrong with our approach... I don't know how to voice it clearly, but we need to:
1. Copy the entire document
2. While copying, replace <clone/> with <clone><!-- inserted content --></clone>
But to copy a document and maintain its original structure, while also looking for <clone/>, we are basically going to need to implement a DOM2 Traversal TreeWalker in XSLT?
Alex Vincent
11-14-2002, 05:31 AM
I don't think Mozilla supports what we're looking for directly. There was a FAQ I read which showed how to copy all elements but one, and it didn't work in Mozilla. Looks like I'll have to use DOM.
Alex Vincent
12-15-2002, 03:25 AM
<xsl:template match="@*|node()"><xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy></xsl:template><xsl:template match="edit:remove" xmlns:edit="http://foo.org/bar"/>
This is courtesy of Jonas Sicking, mozilla.org volunteer. :cool:
For removing a node.
Resurrecting dead threads is fun, particularly if a partial solution has been found.
Ok, so this removes a node... call me narrow, but I'm trying to wrap my head around how it will help us out...
Alex Vincent
12-17-2002, 12:57 AM
Well, if we can insert an XSLT element that will copy the targeted node as a child of the second template element, we should be in business.
So I'm an awful thread digger, but I recently needed this capability and finally solved the problem:
identity.xml:
<?xml version="1.0"?>
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
<template match="node()|@*">
<copy><apply-templates select="node()|@*"/></copy>
</template>
</stylesheet>
cloner.xml:
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:jkd="http://www.jasonkarldavis.com/xml">
<import href="identity.xml"/>
<output method="xml"
indent="yes"
omit-xml-declaration="no"
media-type="application/xhtml+xml"
doctype-public="-//W3C//DTD XHTML 1.1//EN"
doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
/>
<template match="jkd:clone">
<apply-templates select="id(substring(@xlink:href, 2))" mode="clone"/>
</template>
<template match="*" mode="clone">
<copy><apply-templates select="node()|@*[name()!='id']"/></copy>
</template>
</stylesheet>
And of course, the sample document:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="cloner.xml"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:jkd="http://www.jasonkarldavis.com/xml" xml:lang="en">
<head>
<title>XSLT clone test</title>
</head>
<body>
<div id="reuse">cloned content?</div>
<jkd:clone xlink:type="simple" xlink:href="#reuse"/>
<hr/>
<jkd:clone xlink:type="simple" xlink:href="#reuse"/>
</body>
</html>
Which outputs:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<?xml-stylesheet type="text/xsl" href="clone.xml"?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>XSLT clone test</title>
</head>
<body>
<div id="reuse">cloned content?</div>
<div>cloned content?</div>
<hr/>
<div>cloned content?</div>
</body>
</html>
M@rco
06-29-2004, 11:52 PM
Rather interesting! :)
vBulletin® v3.8.2, Copyright ©2000-2009, Jelsoft Enterprises Ltd.