Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Ideas on how to identify attribute nodes in tdom?

62 views
Skip to first unread message

tomás zerolo

unread,
May 12, 2012, 4:07:28 AM5/12/12
to

For a small visualization of XPath matching, I'm cobbling together a
little Tcl/Tk script (which'll end up in wiki.tcl.tk, I promise :-)

The idea is to draw a tree on a canvas representing the XML tree
structure (attribute nodes and all) and tag all the nodes with their Tcl
"node address" as returned by tdom (e.g. "domNode0x1eea720").

For element nodes this works pretty well: each element node has such one
unique address I can use to tag the little box on the canvas and to
highlight the result node set of an XPath query.

But the result of an XPath query matching attributes is just a list of
{name value} pairs for the matched attributes -- hardly a handy option
to "find" the corresponding "ink blobs" on the canvas.

Now I'm looking for some cheap cop out. I'm far from an XPath guru. Is
there a way of (a) finding whether this XPath will match attribute nodes
(maybe running the match and looking at the result is my best option)
and (b) modifying this XPath so that it spits out the parent nodes of
the attributes in question?

I'm a bit scared of tokenizing/parsing the XPath expression, but perhaps
it's easiear than I think (I'd like to keep the whole script below 200
lines or so).

I guess it's too late once I hold the results in my hands: the list of
{name value} pairs has lost its links to their parents, right?

Regards
-- tomás

Gerald W. Lester

unread,
May 12, 2012, 12:56:45 PM5/12/12
to tomás zerolo
Tomas,

In tdom, attributes are a part of a node (i.e. they are an attribute of a
node) and not a node themselves.

First off, instead of using a canvas, I'd suggest using ttk::treeview.

If you do use a canvas, then the correct way to display any given node is:

+-----------------------+
|Name: node_name |
|Value: text_value |
+.......................+
| Attr_1: attr_1_value |
| .... |
| Attr_N: attr_N_value |
+-----------------------+

Where for none end nodes you do not get the value.

The tags for an attribute would be NodeId_AttributeName.
--
+------------------------------------------------------------------------+
| Gerald W. Lester, President, KNG Consulting LLC |
| Email: Gerald...@kng-consulting.net |
+------------------------------------------------------------------------+

Gerald W. Lester

unread,
May 12, 2012, 12:57:56 PM5/12/12
to
A little XML example with a couple of your searches where you are having
issues would help a lot to ensure that everyone is talking about the same thing.

On 5/12/12 3:07 AM, tomás zerolo wrote:
>

Aric Bills

unread,
May 13, 2012, 12:00:45 AM5/13/12
to
On May 12, 2:07 am, to...@tuxteam.de (tomás zerolo) wrote:

> Now I'm looking for some cheap cop out. I'm far from an XPath guru. Is
> there a way of (a) finding whether this XPath will match attribute nodes
> (maybe running the match and looking at the result is my best option)
> and (b) modifying this XPath so that it spits out the parent nodes of
> the attributes in question?

The following proc should be a reliable, though not terribly
efficient, way to get the information you want. If a query does not
select attribute nodes, the proc returns the results of the query
unchanged. If a query selects attribute nodes (either exclusively or
mixed with other nodes [consider the query "//somenode|//@someattr",
for example]), the proc returns a modified version of the results,
where every attribute result is represented as a list with three
items: the ID of the parent node, the attribute name, and the
attribute value.

The proc takes two required arguments and one optional one. The first
required argument is the handle to a tDOM document object. The second
required argument is an XPath query. The optional argument is a
variable name; if provided, it will be set to the result type of the
query (empty, bool, number, string, nodes, attrnodes or mixed; this
functionality comes straight from the tDOM selectNodes method). You'll
have to modify the proc slightly if you want support for namespaces.

The magic happens as the proc loops over the query results, keeping
track of the index for each result. When an attribute result is
encountered, an additional query is performed, of the form "(Q)
[I]/..", where Q is the original query and I is the index of the
result in question.

Bear in mind that due to XPath functions, not all queries will return
nodes. Your program will have to take into account the possibility of
queries like "count(//somenode)". You can use the typevar argument to
determine whether a query returned nodes or something else.

Anyway, I hope this helps.



package require tdom

proc runquery {doc query {typevar ""}} {
if {$typevar ne ""} {
upvar $typevar resulttype
}
set matches [$doc selectNodes $query resulttype]
if {$resulttype in {attrnodes mixed}} {
set matchnum 0
foreach match $matches {
incr matchnum
if {[regexp {^domNode0x[0-9a-f]+$} $match]} {
lappend newmatches $match
} else {
set query2 [format {(%s)[%d]/..} $query $matchnum]
set parentnode [$doc selectNodes $query2]
lappend newmatches [concat $parentnode $match]
}
}
set matches $newmatches
}
return $matches
}

tomás zerolo

unread,
May 13, 2012, 4:34:00 AM5/13/12
to
"Gerald W. Lester" <Gerald...@KnG-Consulting.net> writes:

> A little XML example with a couple of your searches where you are
> having issues would help a lot to ensure that everyone is talking
> about the same thing.

Here it goes (along with an idea which I'll be trying out). But first,
I'd like to re-state my targets, since they weren't very clear:

I wanted the user to enter an XPath expression and to show (visually)
which nodes would be selected, in some graphical representation of the
XML tree (whether that'd be a more traditional tree on a canvas or some
hierarchical list representation).

Since it's goig to be a demo, I'd strongly prefer a 99.5-percent
solution which is simple and easy to understand to a complex and messy
100-percent solution (which would be a 99.95% anyway ;-)

So consider this XML snippet:

--- example.xml ---------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<example size="small" useful="you bet">
<section useful="no">
<subsection useful="possibly">
With some text inside
<subsubsection useful="perhaps">
some text here, too
</subsubsection>
And another text here, just to be nasty
</subsection>
A loose text node here, just to show off
what a stupid idea it is to mis-use XML as
a data description language.
<subsection useful="sometimes" empty="yes" />
</section>
<section useful="definitely not">
A last fight here, horrified by the ubiquity of
XML data representations in my favourite distro.
<subsection useful="meh">
Just to be here
</subsection>
<subsection useful="never">
Because we can
</subsection>
</section>
</example>
-------------------------------------------------------

And this little XML program (the most important part is, of course in
the comments). Assume I've already rendered the whole XML tree on the
canvas, tagging each node representation there with its domNode ID.

--- example -------------------------------------------
#!/usr/bin/tclsh8.5
package require tdom
set doc [dom parse -channel [open "example.xml"]]
set docroot [$doc documentElement]

# (1) All subsection nodes, at any depth:
puts [$docroot selectNodes {//subsection}]
# => domNode0x1383470 domNode0x1383900 domNode0x1383ba0 domNode0x1383cd0
#
# Easy: highlight the nodes with the above tags

# All "useful" attributes, somewhere:
puts [$docroot selectNodes {//@useful}]
# => {useful {you bet}} {useful no} {useful possibly} {useful perhaps}
# {useful sometimes} {useful {definitely not}} {useful meh} {useful never}
#
# Argh. But "where" do they live?

puts [$docroot selectNodes {//@useful/..}]
# => domNode0x22d7170 domNode0x22d7350 domNode0x22d74d0 domNode0x22d76c0
# domNode0x22d7960 domNode0x22d7a80 domNode0x22d7c00 domNode0x22d7d30
#
# Ah, there they are! Highlight "domNode0x22d7170@useful" and so on
-------------------------------------------------------

So I tend to do the following.

(1) Eval the user-given XPath
(2) look out for results not matching "domNodeXXXXXX"
(3) If there are any, they must be attributes or text nodes (which are
nodes under the XPath model, but not under the tdom
model).
(4) Re-evaluate the XPath expression with "/.." slapped at the end. The
new result is a list of the parent nodes under (1)

Now, if I've been careful when tagging the node representations on the
canvas, giving, f.ex. to the attribute "useful" of domNode0x22d7170 the
tag "domNode0x22d7170@useful", things should kind of work, right? For
text nodes, I think domNode0x22d74d0#0 "With some..." or
domNode0x22d74d0#1 might do the job (since a node can have more than one
text child). It'll be difficult to differentiate text and attr nodes in
the result of [selectNodes] (yes, yes there is the typeVar there, but
for all practical purposes it's saying "mixed" :-(

There's of course the possibility that some nasty XML has some text
inside like:

<nasty-node>domNode0x22d76c0</nasty-node>

...but hey. You get what you pay for ;-)

Ideas welcome (remember -- I'd like to stay below about 200 lines of
code, even if the section "Known Issues" grows by a bit ;-)

Another problem is that the results are node sets: I didn't look what it
happens when the slapping of "/.." at the end isn't injective: do we get
repetitions (good) -- or does XPath weed out duplicates (bad).

Regards
-- tomás

Aric Bills

unread,
May 13, 2012, 5:38:51 AM5/13/12
to
On May 13, 2:34 am, to...@tuxteam.de (tomás zerolo) wrote:
[selectNodes] does indeed weed out duplicates:

set xml {
<testdocument>
<node1 attr1="value1" attr2="value2"/>
<node1 attr1="value3" attr2="value4"/>
</testdocument>
}

dom parse $xml doc
set attributes [$doc selectNodes {//@*}]
# returns {attr1 value1} {attr2 value2} {attr1 value3} {attr2
value4}
set parents [$doc selectNodes {//@*/..}]
# returns domNode0xdd9960 domNode0xdd9a40

Also, simply slapping "/.." on the end of a query will not always give
you the results you want; consider the query "//@attr1|//@attr2":

$doc selectNodes {//@attr1|//@attr2}
# returns {attr1 value1} {attr2 value2} {attr1 value3} {attr2
value4}
$doc selectNodes {//@attr1|//@attr2/..}
# returns domNode0xdd9960 {attr1 value1} domNode0xdd9a40 {attr1
value3}

tomás zerolo

unread,
May 17, 2012, 9:07:21 AM5/17/12
to
Aric Bills <aric....@gmail.com> writes:

> On May 12, 2:07 am, to...@tuxteam.de (tomás zerolo) wrote:
>
>> Now I'm looking for some cheap cop out [...]
>
> The following proc should be a reliable, though not terribly
> efficient, way to get the information you want [...]

Thanks! already playing with it (I was moving along a similar alley,
i.e. picking up each non-element node and trying to find its
parent). Efficiency isn't a consideration at this point.

> Anyway, I hope this helps.

It sure does -- and I learn a thing or three about XPath. So double
thanks. I'll let you know how it works out.

Regards
-- tomás

tomás zerolo

unread,
May 17, 2012, 10:18:22 AM5/17/12
to
Aric Bills <aric....@gmail.com> writes:

[...]

Thanks again -- I now had time to play around a bit and it works like a
charm :-)

> The magic happens as the proc loops over the query results, keeping
> track of the index for each result. When an attribute result is
> encountered, an additional query is performed, of the form "(Q)
> [I]/..", where Q is the original query and I is the index of the
> result in question.

As I mentioned in a previous post, I was gravitating towards a similar
solution (i.e. iterating over the non-node results), but your elegant
derived XPath "(Q)[I]/.." kept escaping from me. Plus your optimization
of bypassing the whole party when the result is only nodes, as indicated
by the result type.

> Bear in mind that due to XPath functions, not all queries will return
> nodes. Your program will have to take into account the possibility of
> queries like "count(//somenode)". You can use the typevar argument to
> determine whether a query returned nodes or something else.

I'm aware of that. That'll be the Proverbial Exercise For The Reader ;-)
(first of all one would have to make up his/her mind on how to visualize
that).

Thanks, regards
-- tomás
0 new messages