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
|
# -*- tcl -*-
# [expand] utilities for generating XML.
#
# Copyright (C) 2001 Joe English <jenglish@sourceforge.net>.
# Freely redistributable.
#
# Copyright (C) 2019 Andreas Kupries <andreas_kupries@sourceforge.net>
######################################################################
# Handling XML delimiters in content:
#
# Plain text is initially passed through unescaped;
# internally-generated markup is protected by preceding it with \1.
# The final PostProcess step strips the escape character from
# real markup and replaces markup characters from content
# with entity references.
#
variable attvalMap { {&} & {<} < {>} > {"} " {'} ' } ; # "
variable markupMap { {&} {\1&} {<} {\1<} {>} {\1>} }
variable finalMap { {\1&} {&} {\1<} {<} {\1>} {>}
{&} & {<} < {>} > }
proc fmt_postprocess {text} {
variable finalMap
return [string trim [string map $finalMap $text]]\n
}
# markup text --
# Protect markup characters in $text with \1.
# These will be stripped out in PostProcess.
#
proc markup {text} {
variable markupMap
return [string map $markupMap $text]
}
# attlist { n1 v1 n2 v2 ... } --
# Return XML-formatted attribute list.
# Does *not* escape markup -- the result must be passed through
# [markup] before returning it to the expander.
#
proc attlist {nvpairs} {
variable attvalMap
if {[llength $nvpairs] == 1} { set nvpairs [lindex $nvpairs 0] }
set attlist ""
foreach {name value} $nvpairs {
append attlist " $name='[string map $attvalMap $value]'"
}
return $attlist
}
# startTag gi ?attname attval ... ? --
# Return start-tag for element $gi with specified attributes.
#
proc startTag {gi args} {
return [markup "<$gi[attlist $args]>"]
}
# endTag gi --
# Return end-tag for element $gi.
#
proc endTag {gi} {
return [markup "</$gi>"]
}
# emptyElement gi ?attribute value ... ?
# Return empty-element tag.
#
proc emptyElement {gi args} {
return [markup "<$gi[attlist $args]/>"]
}
# xmlComment text --
# Return XML comment declaration containing $text.
# NB: if $text includes the sequence "--", it will be mangled.
#
proc xmlComment {text} {
return [markup "<!-- [string map {-- { - - }} $text] -->"]
}
# wrap content gi --
# Returns $content wrapped inside <$gi> ... </$gi> tags.
#
proc wrap {content gi} {
return "[startTag $gi]${content}[endTag $gi]"
}
# wrap? content gi --
# Same as [wrap], but returns an empty string if $content is empty.
#
proc wrap? {content gi} {
if {![string length [string trim $content]]} { return "" }
return "[startTag $gi]${content}[endTag $gi]"
}
# wrapLines? content gi ? gi... ?
# Same as [wrap?], but separates entries with newlines
# and supports multiple nesting levels.
#
proc wrapLines? {content args} {
if {![string length $content]} { return "" }
foreach gi $args {
set content [join [list [startTag $gi] $content [endTag $gi]] "\n"]
}
return $content
}
# sequence args --
# Handy combinator.
#
proc sequence {args} { join $args "\n" }
######################################################################
# XML context management.
#
variable elementStack [list]
# start gi ?attribute value ... ? --
# Return start-tag for element $gi
# As a side-effect, pushes $gi onto the element stack.
#
proc start {gi args} {
if {[llength $args] == 1} { set args [lindex $args 0] }
variable elementStack
lappend elementStack $gi
return [startTag $gi $args]
}
# xmlContext {gi1 ... giN} ?default? --
# Pops elements off the element stack until one of
# the specified element types is found.
#
# Returns: sequence of end-tags for each element popped.
#
# If none of the specified elements are found, returns
# a start-tag for $default.
#
proc xmlContext {gis {default {}}} {
variable elementStack
set origStack $elementStack
set endTags [list]
while {[llength $elementStack]} {
set current [lindex $elementStack end]
if {[lsearch $gis $current] >= 0} {
return [join $endTags \n]
}
lappend endTags [endTag $current]
set elementStack [lreplace $elementStack end end]
}
# Not found:
set elementStack $origStack
if {![string length $default]} {
set where "[join $elementStack /] - [info level 1]"
puts_stderr "Warning: Cannot start context $gis ($where)"
set default [lindex $gis 0]
}
lappend elementStack $default
return [startTag $default]
}
# end ? gi ? --
# Generate markup to close element $gi, including end-tags
# for any elements above it on the element stack.
#
# If element name is omitted, closes the current element.
#
proc end {{gi {}}} {
variable elementStack
if {![string length $gi]} {
set gi [lindex $elementStack end]
}
set prefix [xmlContext $gi]
set elementStack [lreplace $elementStack end end]
return [join [list $prefix [endTag $gi]] "\n"]
}
######################################################################
# Utilities for multi-pass processing.
#
# Not really XML-related, but I find them handy.
#
variable PassProcs
variable Buffers
# pass $passNo procName procArgs { body } --
# Specifies procedure definition for pass $n.
#
proc pass {pass proc arguments body} {
variable PassProcs
lappend PassProcs($pass) $proc $arguments $body
}
proc setPassProcs {pass} {
variable PassProcs
foreach {proc args body} $PassProcs($pass) {
proc $proc $args $body
}
}
# holdBuffers buffer ? buffer ...? --
# Declare a list of hold buffers,
# to collect data in one pass and output it later.
#
proc holdBuffers {args} {
variable Buffers
foreach arg $args {
set Buffers($arg) [list]
}
}
# hold buffer text --
# Append text to named buffer
#
proc hold {buffer entry} {
variable Buffers
lappend Buffers($buffer) $entry
return
}
# held buffer --
# Returns current contents of named buffer and empty the buffer.
#
proc held {buffer} {
variable Buffers
set content [join $Buffers($buffer) "\n"]
set Buffers($buffer) [list]
return $content
}
#*EOF*
|