diff options
Diffstat (limited to 'tclxslt')
-rw-r--r-- | tclxslt/process.tcl | 312 | ||||
-rw-r--r-- | tclxslt/resources.tcl | 388 | ||||
-rw-r--r-- | tclxslt/tclxslt.tcl | 30 | ||||
-rw-r--r-- | tclxslt/utilities.tcl | 144 | ||||
-rw-r--r-- | tclxslt/xsltcache.tcl | 379 |
5 files changed, 1253 insertions, 0 deletions
diff --git a/tclxslt/process.tcl b/tclxslt/process.tcl new file mode 100644 index 0000000..d38e5fb --- /dev/null +++ b/tclxslt/process.tcl @@ -0,0 +1,312 @@ +# process.tcl -- +# +# XSLT extension providing processing functions +# +# Copyright (c) 2007 Packaged Press +# http://www.packagedpress.com/ +# Copyright (c) 2002-2004 Zveno Pty Ltd +# http://www.zveno.com/ +# +# See the file "LICENSE" in this distribution for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# $Id: process.tcl,v 1.1.1.1 2009/01/16 22:11:49 joye Exp $ + +package provide xslt::process 1.1 + +package require uri 1.1 +package require xslt::cache 3.2 + +namespace eval xslt::process { + namespace export transform fop + namespace export transform-result + namespace export dtd-valid +} + +# Add support for the dom: URI scheme. +# +# This scheme allows a script to reference an in-memory DOM tree. + +proc ::uri::SplitDom url { + return [list dom $url] +} + +proc ::uri::JoinDom args { + array set components { + dom {} + } + array set components $args + + return dom:$components(dom) +} + +# xslt::process::transform -- +# +# Perform an XSL Transformation. +# +# TODO: +# Return messages +# Cache source and stylesheet documents. +# Generate dependency documents. +# +# Arguments: +# src Location of source document +# ssheet Location of stylesheet +# result Location for result document +# params Parameters (nodelist) +# args not needed +# +# Results: +# Returns empty string for success + +# This version forks a process +proc xslt::process::transform_fork {src ssheet result {params {}} args} { + if {[catch {exec tclxsltproc -config /Users/steve/scms/lib/config.tcl --xinclude -o $result $ssheet $src} out]} { + return $out + } else { + return {} + } +} + +# This version performs the transformation in-process. +proc xslt::process::transform:dbg {src ssheet result {params {}} args} { + puts stderr [list process::transform $src $ssheet $result $params $args] + if {[catch {eval transform:dbg [list $src $ssheet $result] $params $args} msg]} { + puts stderr "\nprocess::transform returned error $msg\nStack trace:$::errorInfo\n" + return -code error $msg + } else { + puts stderr [list process::transform ran OK] + return $msg + } +} +proc xslt::process::transform {srcNd ssheetNd resultNd {params {}} args} { + + # The filenames may be passed in as nodesets + set src $srcNd + catch {set src [dom::node stringValue [lindex $srcNd 0]]} + set ssheet $ssheetNd + catch {set ssheet [dom::node stringValue [lindex $ssheetNd 0]]} + set result $resultNd + catch {set result [dom::node stringValue [lindex $resultNd 0]]} + + # params will be a nodeset consisting of name/value pairs. + # These must be converted to strings + set parameterList {} + switch [llength $params] { + 1 { + puts stderr [list xslt::process::transform params nodeType [dom::node cget $params -nodeType]] + set pNdList [dom::node children $params] + } + default { + set pNdList $params + } + } + foreach paramNd $pNdList { + set name [set value {}] + foreach child [dom::node children $paramNd] { + set nameNd [dom::node selectNode $child name] + set name [dom::node stringValue $nameNd] + set valueNd [dom::node selectNode $child value] + set value [dom::node stringValue $valueNd] + } + if {[string compare $name {}]} { + lappend parameterList $name $value + } + } + + puts stderr [list xslt::process::transform parameters: $parameterList] + + set cleanup {} + + if {[catch {open $src} ch]} { + # eval $cleanup + return "unable to open source document \"$src\" for reading due to \"$ch\"" + } + if {[catch {::dom::parse [read $ch] -baseuri $src} sourcedoc]} { + # eval $cleanup + return "unable to parse source document \"$src\" due to \"$sourcedoc\"" + } + close $ch + + append cleanup "dom::destroy $sourcedoc" \n + + dom::xinclude $sourcedoc + + if {[catch {open $ssheet} ch]} { + eval $cleanup + return "unable to open stylesheet document \"$ssheet\" for reading due to \"$ch\"" + } + if {[catch {::dom::parse [read $ch] -baseuri $ssheet} styledoc]} { + eval $cleanup + return "unable to parse stylesheet document \"$ssheet\" due to \"$styledoc\"" + } + close $ch + + append cleanup "dom::destroy $styledoc" \n + + if {[catch {xslt::compile $styledoc} style]} { + eval $cleanup + return "unable to compile stylesheet \"$ssheet\" due to \"$style\"" + } + + append cleanup "rename $style {}" \n + + if {[catch {eval [list $style] transform [list $sourcedoc] $parameterList} resultdoc]} { + eval $cleanup + return "unable to transform document \"$src\" with stylesheet \"$ssheet\" due to \"$resultdoc\"" + } + + append cleanup "dom::destroy $resultdoc" \n + + if {[catch {open $result w} ch]} { + eval $cleanup + return "unable to save result document \"$result\" due to \"$ch\"" + } + + puts $ch [dom::serialize $resultdoc -method [$style cget -method]] + close $ch + + catch { + uplevel \#0 $cleanup + } + + return {} +} + +# xslt::process::transform-result -- +# +# Perform an XSL Transformation. +# This version returns the result document. +# +# Arguments: +# src Location of source document +# ssheet Location of stylesheet +# params Parameters (nodelist) +# args not needed +# +# Results: +# Returns result document. + +proc xslt::process::transform-result {srcNd ssheetNd {params {}} args} { + + # The filenames may be passed in as nodesets + set src $srcNd + catch {set src [dom::node stringValue [lindex $srcNd 0]]} + set ssheet $ssheetNd + catch {set ssheet [dom::node stringValue [lindex $ssheetNd 0]]} + + # params will be a nodeset consisting of name/value pairs. + # These must be converted to strings + set parameterList {} + foreach paramNd $params { + set name [set value {}] + foreach child [dom::node children $paramNd] { + set nameNd [dom::node selectNode $child name] + set name [dom::node stringValue $nameNd] + set valueNd [dom::node selectNode $child value] + set value [dom::node stringValue $valueNd] + } + if {[string compare $name {}]} { + lappend parameterList $name $value + } + } + + if {[catch {eval xslt::cache::transform [list $src $ssheet] $parameterList} rd]} { + return "unable to perform transformation due to \"$rd\"" + } + + return $rd +} + +# xslt::process::checkwffdoc -- +# +# Test a document for well-formedness +# +# Arguments: +# doc DOM token for document to check +# args not needed +# +# Results: +# Returns success message + +proc xslt::process::checkwffdoc {doc args} { + return "of course it's well-formed, it's a DOM tree!" +} + +# xslt::process::dtd-valid -- +# +# Test a document for (DTD) validity +# +# Arguments: +# uri URI for document to check, supports dom: scheme +# args not needed +# +# Results: +# Returns success/failure message + +proc xslt::process::dtd-valid {uri args} { + array set components [uri::split $uri] + + switch -- $components(scheme) { + file { + set ch [open $components(path)] + set xmldata [read $ch] + close $ch + set doc [dom::parse $xmldata -baseuri $uri] + set cleanup [list dom::destroy $doc] + } + dom { + set doc $components(dom) + set cleanup {} + } + default { + # TODO: support http: scheme + return -code error "unable to resolve entity $uri" + } + } + + if {[catch {dom::validate $doc} msg]} { + set result $msg + } else { + set result {document is valid} + } + + eval $cleanup + + return $result +} + +# xslt::process::fop -- +# +# Format an XSL FO document using FOP +# +# Arguments: +# fo Location of FO document +# pdf Location for PDF document +# params Parameters (nodelist) +# args not needed +# +# Results: +# Returns success message + +proc xslt::process::fop {fo pdf params args} { + return "format fo $fo to produce $pdf" +} + +# xslt::process::log -- +# +# Emit a log message. The application is expected to override this. +# +# Arguments: +# msg Log message +# args not needed +# +# Results: +# None + +proc xslt::process::log {msg args} { + Stderr Log:\ $msg + return {} +} + + diff --git a/tclxslt/resources.tcl b/tclxslt/resources.tcl new file mode 100644 index 0000000..b107a77 --- /dev/null +++ b/tclxslt/resources.tcl @@ -0,0 +1,388 @@ +# resources.tcl -- +# +# XSLT extension providing access to resources. +# +# Copyright (c) 2005-2008 Explain +# http://www.explain.com.au/ +# Copyright (c) 2001-2004 Zveno Pty Ltd +# http://www.zveno.com/ +# +# See the file "LICENSE" in this distribution for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# $Id: resources.tcl,v 1.1.1.1 2009/01/16 22:11:49 joye Exp $ + +catch { + package require base64 +} + +package provide xslt::resources 1.3 + +namespace eval xslt::resources { + namespace export list type exists modified +} + +# xslt::resources::list -- +# +# List the resources available at a given location +# +# Arguments: +# locn Resource path to list +# basedir Base directory +# args not needed +# +# Results: +# Returns list of resources + +proc xslt::resources::list {locnNd {baseNd {}} args} { + # What kind of resource is this? file, http, ftp, etc? + + if {[llength $args]} { + return -code error "too many arguments" + } + + set locn $locnNd + # The resource may be passed in as a nodeset + catch {set locn [dom::node stringValue [lindex $locnNd 0]]} + set base $baseNd + catch {set base [dom::node stringValue [lindex $baseNd 0]]} + + if {[string match /* $base]} { + regsub {^(/)} $locn {} locn + } + + set result {} + foreach entry [glob -nocomplain [file join $base $locn *]] { + lappend result [file tail $entry] + } + + return $result +} + +# xslt::resources::type -- +# +# Gives the type of the resource +# +# Arguments: +# locn Resource path to type +# args not needed +# +# Results: +# Returns string describing resource + +proc xslt::resources::type {locnNd args} { + + if {[llength $args]} { + return -code error "too many arguments" + } + + set locn $locnNd + catch {set locn [dom::node stringValue [lindex $locnNd 0]]} + + if {[file isdir $locn]} { + return directory + } elseif {[file isfile $locn]} { + return file + } else { + return other + } +} + +# xslt::resources::exists -- +# +# Check whether a resource exists +# +# Arguments: +# locn Resource path to type +# args not needed +# +# Results: +# Returns boolean + +proc xslt::resources::exists {locnNd args} { + + if {[llength $args]} { + return -code error "too many arguments" + } + + set locn $locnNd + catch {set locn [dom::node stringValue [lindex $locnNd 0]]} + + if {[file exists $locn]} { + return 1 + } else { + return 0 + } +} + +# xslt::resources::modified -- +# +# Report last modification time of a resource +# +# Arguments: +# locn Resource path +# args not needed +# +# Results: +# Returns ISO standard date-time string + +proc xslt::resources::modified {locnNd args} { + + if {[llength $args]} { + return -code error "too many arguments" + } + + set locn $locnNd + catch {set locn [dom::node stringValue [lindex $locnNd 0]]} + + if {[file exists $locn]} { + return [clock format [file mtime $locn] -format {%Y-%m-%dT%H:%M:%S}] + } else { + return {} + } +} + +# xslt::resources::mkdir -- +# +# Create a directory hierarchy. +# +# Arguments: +# locn Resource path for directory +# args not needed +# +# Results: +# Returns directory created or empty string if unsuccessful + +proc xslt::resources::mkdir {locnNd args} { + + if {[llength $args]} { + return {} + } + + set locn $locnNd + catch {set locn [dom::node stringValue [lindex $locnNd 0]]} + + set dir [file split $locn] + set current [lindex $dir 0] + set remaining [lrange $dir 1 end] + while {[llength $remaining]} { + set current [file join $current [lindex $remaining 0]] + set remaining [lrange $remaining 1 end] + if {[file exists $current]} { + if {![file isdir $current]} { + return {} + } + } elseif {[file isdir $current]} { + continue + } else { + if {[catch {file mkdir $current}]} { + return {} + } + } + } + + return $locn +} + +# xslt::resources::copy -- +# +# Copy a resource. +# +# Arguments: +# src Resource to copy +# dest Destination for resource +# args not needed +# +# Results: +# Resource copied + +proc xslt::resources::copy {srcNd destNd args} { + set src $srcNd + catch {set src [dom::node stringValue [lindex $srcNd 0]]} + set dest $destNd + catch {set dest [dom::node stringValue [lindex $destNd 0]]} + + if {[catch {file copy -force $src $dest} msg]} { + catch { + package require log + log::log error "copy failed due to \"$msg\"" + } + return 0 + } else { + return 1 + } +} + +# xslt::resources::move -- +# +# Move (rename) a resource. +# +# Arguments: +# src Resource to move +# dest Destination for resource +# args not needed +# +# Results: +# Resource renamed + +proc xslt::resources::move {srcNd destNd args} { + set src $srcNd + catch {set src [dom::node stringValue [lindex $srcNd 0]]} + set dest $destNd + catch {set dest [dom::node stringValue [lindex $destNd 0]]} + + if {[catch {file rename -force $src $dest}]} { + return 0 + } else { + return 1 + } +} + +# xslt::resources::file-attributes -- +# +# Change attributes of a resource. +# +# Arguments: +# src Resource to change +# what Attribute to change +# detail Attribute value +# args not needed +# +# Results: +# Resource attribute changed + +proc xslt::resources::file-set-attributes {srcNd whatNd detailNd args} { + set src $srcNd + catch {set src [dom::node stringValue [lindex $srcNd 0]]} + set what $whatNd + catch {set what [dom::node stringValue [lindex $whatNd 0]]} + set detail $detailNd + catch {set detail [dom::node stringValue [lindex $detailNd 0]]} + + if {[catch {file attributes $src -$what $detail} result]} { + return {} + } else { + return $result + } +} + +# xslt::resources::delete -- +# +# Delete a resource +# +# Arguments: +# locn Resource path to type +# args not needed +# +# Results: +# Returns boolean + +proc xslt::resources::delete {locnNd args} { + + if {[llength $args]} { + return -code error "too many arguments" + } + + set locn $locnNd + catch {set locn [dom::node stringValue [lindex $locnNd 0]]} + + if {[catch {file delete -force $locn} msg]} { + catch { + package require log + log::log error "delete failed due to \"$msg\"" + } + return 0 + } else { + return 1 + } +} + +# xslt::resources::link -- +# +# Link a resource. +# +# Arguments: +# from Link to create +# to Target of link +# args not needed +# +# Results: +# Symbolic link created + +proc xslt::resources::link {fromNd toNd args} { + set from $fromNd + catch {set from [dom::node stringValue [lindex $fromNd 0]]} + set to $toNd + catch {set to [dom::node stringValue [lindex $toNd 0]]} + + if {[catch {file link $from $to}]} { + return 0 + } else { + return 1 + } +} + +# xslt::resources::write-base64 -- +# +# Decode base64 encoded data and write the binary data to a file +# +# Arguments: +# fname Filename +# b64 base64 encoded data +# args not needed +# +# Results: +# File opened for writing and binary data written. +# Returns 1 if file successfully written, 0 otherwise. + +proc xslt::resources::write-base64 {fnameNd b64Nd args} { + set fname $fnameNd + catch {set fname [dom::node stringValue [lindex $fnameNd 0]]} + set b64 $b64Nd + catch {set b64 [dom::node stringValue [lindex $b64Nd 0]]} + + if {[catch {package require base64}]} { + return 0 + } + + if {[catch {open $fname w} ch]} { + return 0 + } else { + set binarydata [base64::decode $b64] + fconfigure $ch -trans binary -encoding binary + puts -nonewline $ch $binarydata + close $ch + return 1 + } +} + +# xslt::resources::read-base64 -- +# +# Read binary data from a file and base64 encode it +# +# Arguments: +# fname Filename +# args not needed +# +# Results: +# File opened for readng and contents read. +# Returns content as base64-encoded data. + +proc xslt::resources::read-base64 {fnameNd args} { + set fname $fnameNd + catch {set fname [dom::node stringValue [lindex $fnameNd 0]]} + + if {[catch {package require base64}]} { + return 0 + } + + if {[catch {open $fname} ch]} { + return 0 + } else { + fconfigure $ch -trans binary -encoding binary + set binarydata [read $ch] + close $ch + return [base64::encode $binarydata] + } +} + diff --git a/tclxslt/tclxslt.tcl b/tclxslt/tclxslt.tcl new file mode 100644 index 0000000..6aa6a2c --- /dev/null +++ b/tclxslt/tclxslt.tcl @@ -0,0 +1,30 @@ +# tclxslt.tcl -- +# +# Tcl library for TclXSLT package. +# +# Copyright (c) 2001-2003 Zveno Pty Ltd +# http://www.zveno.com/ +# +# See the file "LICENSE" in this distribution for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# $Id: tclxslt.tcl,v 1.1.1.1 2009/01/16 22:11:49 joye Exp $ + +namespace eval xslt { + namespace export getprocs +} + +proc xslt::getprocs ns { + set functions {} + set elements {} + foreach proc [info commands ${ns}::*] { + if {[regexp {::([^:]+)$} $proc discard name]} { + if {[string equal [lindex [info args $proc] end] "args"]} { + lappend functions $name + } else { + lappend elements $name + } + } + } + return [list $elements $functions] +} diff --git a/tclxslt/utilities.tcl b/tclxslt/utilities.tcl new file mode 100644 index 0000000..8459598 --- /dev/null +++ b/tclxslt/utilities.tcl @@ -0,0 +1,144 @@ +# utilities.tcl -- +# +# Miscellaneous extension functions for XSLT. +# +# Copyright (c) 2007 Explain +# http://www.explain.com.au/ +# Copyright (c) 2004 Zveno Pty Ltd +# http://www.zveno.com/ +# +# See the file "LICENSE" in this distribution for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# $Id: utilities.tcl,v 1.1.1.1 2009/01/16 22:11:49 joye Exp $ + +package provide xslt::utilities 1.2 + +namespace eval xslt::utilities { + namespace export character-first decode-base64 +} + +# xslt::utilities::character-first -- +# +# Returns the character that occurs first from a string +# of possible characters. +# +# Arguments: +# src source string +# chars characters to find +# args not needed +# +# Results: +# Returns a character or empty string. + +proc xslt::utilities::character-first {srcNd charsNd args} { + if {[llength $args]} { + return -code error "too many arguments" + } + + set src $srcNd + catch {set src [dom::node stringValue [lindex $srcNd 0]]} + set chars $charsNd + catch {set chars [dom::node stringValue [lindex $charsNd 0]]} + + regsub -all {([\\\[\]^$-])} $chars {\\\1} chars + if {[regexp [format {([%s])} $chars] $src dummy theChar]} { + return $theChar + } + + return {} +} + +# xslt::utilities::decode-base64 -- +# +# Returns decoded (binary) base64-encoded data. +# +# Arguments: +# src source string +# args not needed +# +# Results: +# Returns binary data. + +proc xslt::utilities::decode-base64 {srcNd args} { + if {[llength $args]} { + return -code error "too many arguments" + } + + if {[catch {package require base64}]} { + return {} + } + + set src $srcNd + catch {set src [dom::node stringValue [lindex $srcNd 0]]} + + return [base64::decode $src] +} + +# xslt::utilities::binary-document -- +# +# Writes binary data into a document +# (this should be an extension element) +# +# Arguments: +# fname filename +# data binary data +# args not needed +# +# Results: +# File opened for writing and data written. +# Returns 1 on success, 0 otherwise + +proc xslt::utilities::binary-document {fnameNd srcNd args} { + if {[llength $args]} { + return -code error "too many arguments" + } + + set fname $fnameNd + catch {set fname [dom::node stringValue [lindex $fnameNd 0]]} + set data $dataNd + catch {set data [dom::node stringValue [lindex $dataNd 0]]} + + if {[catch {open $fname w} ch]} { + return 0 + } + fconfigure $ch -trans binary -encoding binary + puts -nonewline $ch $data + close $ch + + return 1 +} + +# xslt::utilities::base64-binary-document -- +# +# Returns base64-encoded data from a file. +# +# Arguments: +# fname filename +# args not needed +# +# Results: +# Returns text. Returns empty string on error. + +proc xslt::utilities::base64-binary-document {fnameNd args} { + if {[llength $args]} { + return -code error "too many arguments" + } + + if {[catch {package require base64}]} { + return {} + } + + set fname $fnameNd + catch {set fname [dom::node stringValue [lindex $fnameNd 0]]} + + if {[catch {open $fname} ch]} { + return {} + } + fconfigure $ch -trans binary -encoding binary + set data [read $ch] + close $ch + + return [base64::encode $data] +} + diff --git a/tclxslt/xsltcache.tcl b/tclxslt/xsltcache.tcl new file mode 100644 index 0000000..9a3d8f7 --- /dev/null +++ b/tclxslt/xsltcache.tcl @@ -0,0 +1,379 @@ +# xsltcache.tcl -- +# +# Handles performing XSLT transformations, +# caching documents and results. +# +# Copyright (c) 2005-2007 Steve Ball +# http://www.packagedpress.com/staff/Steve.Ball +# Copyright (c) 2002-2003 Zveno Pty Ltd +# http://www.zveno.com/ +# +# See the file "LICENSE" in this distribution for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# $Id: xsltcache.tcl,v 1.1.1.1 2009/01/16 22:11:49 joye Exp $ + +package require xslt 3.2 +package require uri + +package provide xslt::cache 3.2 + +namespace eval xslt::cache { + namespace export transform transformdoc flush + namespace export parse_depend + namespace export loadstylesheet + + variable sources + array set sources {} + variable stylesheets + array set stylesheets {} + variable results + array set results {} +} + +# xslt::cache::transform -- +# +# Perform an XSLT transformation. +# +# Arguments: +# src Filename of source document +# ssheet Filename of stylesheet document +# args Configuration options, stylesheet parameters +# +# Results: +# Result document token + +proc xslt::cache::transform {src ssheet args} { + variable sources + variable stylesheets + variable results + + # Separate parameters from options + set parameters {} + set options {} + foreach {key value} $args { + switch -glob -- $key { + -* { + lappend options $key $value + } + default { + lappend parameters $key $value + } + } + } + + # Normalize the parameter list + array set paramArray $parameters + set parameters {} + foreach name [lsort [array names paramArray]] { + lappend parameters $name $paramArray($name) + } + + set hash $src.$ssheet.$parameters + + array set opts { + -xmlinclude 1 + } + array set opts $options + + set readSource [ReadXML $src -xmlinclude $opts(-xmlinclude)] + + set readStylesheet 1 + if {[info exists stylesheets($ssheet)]} { + if {[file mtime $ssheet] < $stylesheets($ssheet,time)} { + set readStylesheet 0 + } + } + if {$readStylesheet} { + catch {rename $stylesheets($ssheet) {}} + ReadXML $ssheet -xmlinclude $opts(-xmlinclude) + + set stylesheets($ssheet) [xslt::compile $sources($ssheet)] + set stylesheets($ssheet,time) [clock seconds] + } + + if {$readSource || $readStylesheet || ![info exists results($hash)]} { + + set results($hash) [eval [list $stylesheets($ssheet)] transform [list $sources($src)] $parameters] + set results($hash,time) [clock seconds] + } + + return $results($hash) +} + +# xslt::cache::loadstylesheet -- +# +# Read, parse and compile an XSLT stylesheet. +# +# Arguments: +# src Filename for the stylesheet document +# args options +# +# Results: +# Returns compiled stylesheet token. Adds reference to stylesheet to cache. + +proc xslt::cache::loadstylesheet {src args} { + variable sources + variable stylesheets + + array set options { + -keepsource 0 + -xmlinclude 0 + } + array set options $args + + eval ReadXML [list $src] [array get options -xmlinclude] + + set stylesheets($src) [xslt::compile $sources($src)] + set stylesheets($src,time) [clock seconds] + + if {!$options(-keepsource)} { + flush $src {} + } + + # TODO: set command trace so that if the stylesheet is deleted + # the cache is invalidated + + return $stylesheets($src) +} + +# xslt::cache::ReadXML -- +# +# Internal proc to manage parsing a document. +# Used for both source and stylesheet documents. +# +# Arguments: +# src Filename of source document +# args Configuration options +# +# Results: +# Returns 1 if document was read. Returns 0 if document is cached. + +proc xslt::cache::ReadXML {src args} { + variable sources + array set opts { + -xmlinclude 1 + } + array set opts $args + + set readSource 1 + if {[info exists sources($src)]} { + if {[file mtime $src] < $sources($src,time)} { + set readSource 0 + } + } + if {$readSource} { + catch {dom::destroy $sources($src)} + set ch [open $src] + set sources($src) [dom::parse [read $ch] -baseuri file://$src] + close $ch + if {$opts(-xmlinclude)} { + dom::xinclude $sources($src) + } + set sources($src,time) [clock seconds] + } + + return $readSource +} + +# xslt::cache::transformdoc -- +# +# Perform an XSLT transformation on a DOM document. +# +# Arguments: +# src DOM token of source document +# ssheet Filename of stylesheet document +# args Configuration options, stylesheet parameters +# +# Results: +# Result document token + +proc xslt::cache::transformdoc {src ssheet args} { + variable sources + variable stylesheets + + # Separate parameters from options + set parameters {} + set options {} + foreach {key value} $args { + switch -glob -- $key { + -* { + lappend options $key $value + } + default { + lappend parameters $key $value + } + } + } + + # Normalize the parameter list + array set paramArray $parameters + set parameters {} + foreach name [lsort [array names paramArray]] { + lappend parameters $name $paramArray($name) + } + + array set opts { + -xmlinclude 1 + } + array set opts $options + + set readStylesheet 1 + if {[info exists stylesheets($ssheet)]} { + if {[file mtime $ssheet] < $stylesheets($ssheet,time)} { + set readStylesheet 0 + } + } + if {$readStylesheet} { + catch {rename $stylesheets($ssheet) {}} + ReadXML $ssheet -xmlinclude $opts(-xmlinclude) + + set stylesheets($ssheet) [xslt::compile $sources($ssheet)] + set stylesheets($ssheet,time) [clock seconds] + } + + set result [eval [list $stylesheets($ssheet)] transform [list $src] $parameters] + + return $result +} + +# ::xslt::cache::parse_depend -- +# +# Parse a document while determining its dependencies. +# +# Arguments: +# uri Document's URI +# depVar Global variable name for dependency document +# +# Results: +# Returns parsed document token. +# Document token for dependency document is stored in depVar. + +proc xslt::cache::parse_depend {uri depVar} { + upvar #0 $depVar dep + + set dep [dom::create] + dom::document createElement $dep dependencies + + array set uriParsed [uri::split $uri] + + switch -- $uriParsed(scheme) { + file { + set ch [open $uriParsed(path)] + set doc [dom::parse [read $ch] -baseuri $uri -externalentitycommand [namespace code [list ParseDepend_Entity $depVar]]] + close $ch + + ParseDepend_XInclude $doc $depVar + ParseDepend_XSLT $doc $depVar + } + http { + return -code error "URI scheme \"http\" not yet implemented" + } + dom { + set doc $uriParsed(dom) + + # Can't determine external entities, but can find XInclude + # and XSL stylesheet includes/imports. + ParseDepend_XInclude $uriParsed(dom) $depVar + ParseDepend_XSLT $uriParsed(dom) $depVar + } + default { + return -code error "URI scheme \"$uriParsed(scheme)\" not supported" + } + } + + return $doc +} + +# xslt::cache::ParseDepend_Entity -- +# +# Callback for external entity inclusion. +# +# Arguments: +# depVar Global variable of dependency document +# pubId Public identifier +# sysId System identifier +# +# Results: +# Dependency added to dependency document + +proc xslt::cache::ParseDepend_Entity {depVar pubId sysId} { + upvar #0 $depVar dep + + dom::document createNode $dep /dependencies/external-entities/entity +} + +# ::xslt::cache::flush -- +# +# Flush the cache +# +# Arguments: +# src source document filename +# ssheet stylesheet document filename +# args parameters +# +# Results: +# Returns the empty string. +# If all arguments are given then all entries corresponding +# to that transformation are destroyed. +# If the source and/or stylesheet are given then all +# entries corresponding to those documents are destroyed. + +proc xslt::cache::flush {src ssheet args} { + variable sources + variable stylesheets + variable results + + # Normalize parameter list + array set paramArray $args + set parameters {} + foreach name [lsort [array names paramArray]] { + lappend parameters $name $paramArray($name) + } + + set hash $src.$ssheet.$parameters + + switch -glob [string length $src],[string length $ssheet],[llength $args] { + 0,0,* { + # Special case: flush all + unset sources + array set sources {} + unset stylesheets + array set stylesheets {} + unset results + array set results {} + } + + 0,*,0 { + # Flush all entries for the given stylesheet + catch {rename $stylesheets($ssheet) {}} + catch {unset stylesheets($ssheet)} + catch {unset stylesheets($ssheet,time)} + + foreach entry [array names results *.$ssheet.*] { + catch {dom::destroy $results($entry)} + catch {unset results($entry)} + catch {unset results($entry,time)} + } + } + + *,0,0 { + # Flush all entries for the given source document + catch {dom::destroy $sources($src)} + catch {unset sources($src)} + catch {unset sources($src,time)} + foreach entry [array names results $src.*] { + catch {dom::destroy $results($entry)} + catch {unset results($entry)} + catch {unset results($entry,time)} + } + } + + default { + # Flush specific entry + catch {dom::destroy $results($hash)} + catch {unset results($hash)} + catch {unset results($hash,time)} + } + } +} |