# process.tcl --
#
#	XSLT extension providing processing functions
#
# Copyright (c) 2007-2009 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 2004/07/11 12:18:03 balls Exp $

package provide xslt::process 1.1

package require uri 1.1
package require xslt::cache 3.3

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 {}
}


