summaryrefslogtreecommitdiffstats
path: root/tclxml/tclxslt/xsltcache.tcl
blob: 9a3d8f7b1e3329e95ebb1b2ba533e06c8600fce4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
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)}
	}
    }
}