CMcC: TclSVG -- an oo object hierarchy to parse, manipulate, and generate SVG.
Simple test: call it as a tcl file, with the arguments: file somefile.svg ... it should produce a near-copy of its input.
Take 2: Decided to cleave more closely to tdom rather than use Tcl as an intermediate form for collections and attributes. Resulted in cleaner, smaller code. Old version is in history.
# SVG.tcl -- an oo hierarchy to parse, mutate and generate SVG
package require TclOO
package require tdom
package provide SVG 2.0
oo::class create ::SVG::element {
variable node doc
# turn tdom's attributes into something resembling sanity
method Attr {n} {
# the list that [domNode attributes] returns
# is {prefix localname namespaceURI}
if {[llength $n] == 3} {
lassign $n name ns uri
if {$uri eq ""} continue
set n $ns:$name
}
return $n
}
# get/set element attribute(s)
method attr {args} {
switch [llength $args] {
0 {
set result {}
foreach n [$node attributes] {
set n [my Attr $n]
lappend result $n [$node getAttribute $n]
}
return $result
}
1 {
return [$node getAttribute [lindex $args 0]]
}
default {
if {[dict exists $args id]} {
if {[$node hasAttribute id]} {
if {[dict get $args id] ne [$node getAttribute id]} {
catch {$doc del_id2obj [$node getAttribute id]}
}
}
$doc id2obj [dict get $args id] [self]
}
$node setAttribute {*}$args
}
}
}
# descend element subtree applying script
method descend {script {n ""}} {
if {$n eq ""} {
set $n $node
}
set r [::apply $script $n]
set children [$n childNodes]
if {[llength $children]} {
foreach child $children {
lappend result [my descend $script $n]
}
return [list $r $result]
} else {
return $r
}
}
# type - this node's 'tag'
method type {} {
return [$node nodeName]
}
# nodeValue - as of Text nodes
method value {args} {
return [$node nodeValue {*}$args]
}
# parent of this element
method parent {} {
return [$doc node2obj [$node parentNode]]
}
# children of this element
method children {} {
set result {}
foreach child [$node childNodes] {
if {![catch {
$doc node2obj $child
} obj]} {
lappend result $obj
}
}
return $result
}
# element as XML
method xml {{indent 4}} {
return [$node asXML -indent $indent]
}
# element as a nested list
method list {} {
return [$node asList]
}
# add children to this node from their XML representations
method add {args} {
foreach obj $args {
set child [$node appendXML [$obj xml]]
lappend children [$doc explore $child]
}
return $children
}
# delete children from this node
method del {args} {
set children [my children]
foreach obj $args {
if {$obj in $chilren} {
$obj destroy
}
}
}
destructor {
foreach o [my children] {
catch {$o destroy} ;# if we have children, destroy them
}
catch {
# remove interp-wide node->object mapping
$doc del_node2obj $node
}
catch {
# remove document-wide id->object mapping
$doc del_id2obj [my attr id]
}
}
constructor {n d} {
set node $n ;# we need to know our tdom node
set doc $d ;# we need to know our SVG document
}
}
oo::class create ::SVG {
variable doc id2obj
# we maintain an interp-wide map of node->object.
# elements themselves maintain their corresponding node
method node2obj {node} {
return [dict get [set [info object namespace [info object class [self]]]::node2obj] $node]
}
method del_node2obj {node} {
dict unset [info object namespace [info object class [self]]]::node2obj $node
}
# we maintain a document-wide map of id->object.
# id is just an attribute of elements
# id is guaranteed unique within documents by means of this map.
method id2obj {id obj} {
dict set id2obj $id $obj
}
method del_id2obj {id} {
dict unset id2obj $id
}
method by_id {id} {
return [dict get $id2obj $id]
}
# traverse SVG tdom tree, generating Tcl SVG element object hierarchy
method explore {node} {
if {[$node nodeType] eq "ELEMENT_NODE"} {
set obj [::SVG::element new $node [self]]
dict set [info object namespace [info object class [self]]]::node2obj $node $obj
if {![$node hasAttribute id]
|| [dict exists $id2obj [$node getAttribute id]]
} {
# no id or id collision - change id to something unique
$obj attr id id[incr [info object namespace [info object class [self]]]::_unique_id]
}
foreach child [$node childNodes] {
my explore $child
}
return $obj ;# return element representation
} else {
return "" ;# this has no element representation
}
}
# generate XML form of document with appropriate verbiage
method xml {{indent 4}} {
append result "<?xml version='1.0' standalone='no'?>" \n
append result "<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 20001102//EN' 'http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd'>" \n
append result [next $indent] \n
return $result
}
superclass ::SVG::element ;# the document is itself an element
destructor {
$doc del ;# delete the tdom parsed doc
next ;# delete the corresponding doc element
}
constructor {args} {
set id2obj {} ;# map id->object
if {[dict exists $args file]} {
# create document from xml text in named file
package require fileutil
set xml [::fileutil::cat [dict get $args file]]
set doc [dom parse $xml]
} elseif {[dict exists $args xml]} {
# create a document from xml text
set doc [dom parse [dict get $args xml]]
} else {
# create an empty document
set doc [dom createDocument svg]
}
# construct [self] as an element
set root [$doc documentElement]
next [$doc documentElement] [self]
my explore $root ;# traverse tdom tree, creating svg elements
}
}
if {[info exists argv0] && $argv0 eq [info script]} {
if {[lindex $argv 0] eq "examples"} {
foreach file [glob /usr/share/inkscape/examples/*.svg] {
puts $file
set svg [SVG new file $file]
puts [$svg xml]
$svg destroy
}
return
}
# test - plug in an SVG constructor (e.g. file FILE.svg) and watch it work
set svg [SVG new {*}$argv]
puts [$svg xml]
}
errordeveloper - 2010-07-18 14:38:56
i have just written this: http://github.com/errordeveloper/svgdom
i think for a generator it's all you would really need -
pass it canvas size and list of object-tags and there it is !!