Forum OpenACS Q&A: How to upvar an array in a recursive procedure

This past weekend I started writing some procedures that parse an RSS feed. The code is experimental and has nothing to do with OpenACS yet:
proc get_node {node_id element parent} {
    set element [ns_xml node name $element]
    set parent [ns_xml node name $parent]
    set content [ns_xml node getcontent $node_id]
    if { $element == "description" && $parent == "channel" } {
        ns_write "$content ($element - $parent)"
    }
}
proc get_tree {children element parent level} {
    upvar $parent p
    foreach child_id $children {
        get_node $child_id $element $p($level)
        set new_children [ns_xml node children $child_id]
        if { [llength $new_children] > 0 } {
            set level [expr $level + 1]
            set p($level) $element
            set element $child_id
            get_tree $new_children $element $p($level) $level
        }
    }
}
proc get_doc {doc_id} {
    set level 2
    set parent($level) [list [ns_xml doc root $doc_id]]
    set elements [ns_xml node children $parent($level)]
    for {set i 0} {$i < [llength $elements]} {incr i} {
        set children [ns_xml node children [lindex $elements $i]]
        get_tree $children [lindex $elements $i] $parent($level) $level
    }
}
I get this error, though:
can't read "p(2)": no such variable
    while executing
"get_node $child_id $element $p($level)"
    (procedure "get_tree" line 4)
In other words, the array $parents isn't passed correctly.

Perhaps I pass it incorrectly with:

get_tree $children [lindex $elements $i] $parent($level) $level
Or perhaps it's because of the get_tree procedure being recursive and so the array exists on a higher level?

Can anyone help out?

/Simon

Collapse
Posted by Dave Bauer on
Simon,

I am not sure that is the best way to do what you want.

You might want to look at ns_xmlrpc http://dev.openacs.org:8000/cvs/ns_xmlrpc/ to see how it decodes the values passed in an XMLRPC call.

Collapse
Posted by David Walker on
But in case it is what you want to do

In this line you are passing an element of the array
"get_tree $new_children $element $p($level) $level"
when you want to pass the whole array.
"get_tree $new_children $element $p $level"

I think.

Collapse
Posted by Simon Carstensen on
Dave Bauer's code looks like something I'll want to go with, but for the sake of learning, let's figure out what's wrong with my code.
get_tree $children [lindex $elements $i] $parent $level
gives me this error:
can't read "parent": variable is array
Your suggestion makes sense to me and I've tried it before. Right now I'm passing one element and I guess the question comes down to how I pass the whole array (of course likewise with the recursive call to get_tree).
Collapse
Posted by Steffen Christensen on

Simon,

You can't pass arrays (I don't think so...). Instead you may want to try using a list as the $arr_parent_list argument:

get_tree $children [lindex $elements $i] [array get parent] $level

and afterwards:

array set parent $arr_parent_list

/stc

Collapse
Posted by Jeff Davis on
One thing is that for upvar things are passed around by reference (i.e. their name, not their value). Here is an example:
set x(10) foo
proc y { v } {
  puts $v
  upvar $v zim
  puts $zim
}
%  y x(10)
x(10)
foo
% y x
x
can't read "zim": variable is array
% y $x(10)
foo
can't read "zim": no such variable
% y $x
can't read "x": variable is array
So when you call
get_tree $children [lindex $elements $i] $parent $level
$parent is not a string it's an array. What you really want is the string literal "parent" (or some other string literal I did not read the code that closely).
Collapse
Posted by Dave Bauer on
I found this old code I was using to parse RSS files. It is not very robust, but it is a start.

It uses ns_xml and some help procs I wrote.

Collapse
Posted by Dave Bauer on
Some of those help procs might conflict with the ones already in OpenACS for the query dispatcher.
Collapse
Posted by David Walker on
You don't actually need to pass the array at all.

Change "upvar $parent p" to "upvar p p" and you can stop passing in the parent variable.

Collapse
Posted by Tom Jackson on

I think the line:

 get_node $child_id $element $p($level)

probably needs to be changed to:

 get_node $child_id $element p($level)

since p is now the name of the array, I think that is what Jeff was refering to?

Anyway, the practice (which you followed) of passing in the name of the upvar'd array is good practice: always let you api users choose the names of their vars, and pass them to you.

If David is right, and you need to be passing the whole array, you just pass p, not $p.

Collapse
Posted by Brian Fenton on
Rob Mayoff's excellent Guidelines for Using upvar and uplevel may be useful to you.
Collapse
Posted by Brad Duell on
Not sure if this helps, but here's what I used to export url vars with array values in our old ACS implementation...

proc_doc nca_export_url_vars {
    {args ""}
} {
} {
    set params {}
    foreach var $args {
    upvar 1 $var value
    if { [array exists value] } {
        # Array
        foreach {index array_value} [array get value] {
        if { [info exists array_value] && ![empty_string_p $array_value] } {
            lappend params "$var\($index\)=[ns_urlencode $array_value]"
        }
        }
    } else {
        # Normal Variable
        if { [info exists value] } {
        lappend params "$var=[ns_urlencode $value]"
        }
    }
    }
    return [join $params "&"]
}