1# -*- tcl -*- 2# [expand] utilities for generating XML. 3# 4# Copyright (C) 2001 Joe English <jenglish@sourceforge.net>. 5# Freely redistributable. 6# 7# Copyright (C) 2019 Andreas Kupries <andreas_kupries@sourceforge.net> 8###################################################################### 9 10# Handling XML delimiters in content: 11# 12# Plain text is initially passed through unescaped; 13# internally-generated markup is protected by preceding it with \1. 14# The final PostProcess step strips the escape character from 15# real markup and replaces markup characters from content 16# with entity references. 17# 18 19variable attvalMap { {&} & {<} < {>} > {"} " {'} ' } ; # " 20variable markupMap { {&} {\1&} {<} {\1<} {>} {\1>} } 21variable finalMap { {\1&} {&} {\1<} {<} {\1>} {>} 22 {&} & {<} < {>} > } 23 24proc fmt_postprocess {text} { 25 variable finalMap 26 return [string trim [string map $finalMap $text]]\n 27} 28 29# markup text -- 30# Protect markup characters in $text with \1. 31# These will be stripped out in PostProcess. 32# 33proc markup {text} { 34 variable markupMap 35 return [string map $markupMap $text] 36} 37 38# attlist { n1 v1 n2 v2 ... } -- 39# Return XML-formatted attribute list. 40# Does *not* escape markup -- the result must be passed through 41# [markup] before returning it to the expander. 42# 43proc attlist {nvpairs} { 44 variable attvalMap 45 if {[llength $nvpairs] == 1} { set nvpairs [lindex $nvpairs 0] } 46 set attlist "" 47 foreach {name value} $nvpairs { 48 append attlist " $name='[string map $attvalMap $value]'" 49 } 50 return $attlist 51} 52 53# startTag gi ?attname attval ... ? -- 54# Return start-tag for element $gi with specified attributes. 55# 56proc startTag {gi args} { 57 return [markup "<$gi[attlist $args]>"] 58} 59 60# endTag gi -- 61# Return end-tag for element $gi. 62# 63proc endTag {gi} { 64 return [markup "</$gi>"] 65} 66 67# emptyElement gi ?attribute value ... ? 68# Return empty-element tag. 69# 70proc emptyElement {gi args} { 71 return [markup "<$gi[attlist $args]/>"] 72} 73 74# xmlComment text -- 75# Return XML comment declaration containing $text. 76# NB: if $text includes the sequence "--", it will be mangled. 77# 78proc xmlComment {text} { 79 return [markup "<!-- [string map {-- { - - }} $text] -->"] 80} 81 82# wrap content gi -- 83# Returns $content wrapped inside <$gi> ... </$gi> tags. 84# 85proc wrap {content gi} { 86 return "[startTag $gi]${content}[endTag $gi]" 87} 88 89# wrap? content gi -- 90# Same as [wrap], but returns an empty string if $content is empty. 91# 92proc wrap? {content gi} { 93 if {![string length [string trim $content]]} { return "" } 94 return "[startTag $gi]${content}[endTag $gi]" 95} 96 97# wrapLines? content gi ? gi... ? 98# Same as [wrap?], but separates entries with newlines 99# and supports multiple nesting levels. 100# 101proc wrapLines? {content args} { 102 if {![string length $content]} { return "" } 103 foreach gi $args { 104 set content [join [list [startTag $gi] $content [endTag $gi]] "\n"] 105 } 106 return $content 107} 108 109# sequence args -- 110# Handy combinator. 111# 112proc sequence {args} { join $args "\n" } 113 114###################################################################### 115# XML context management. 116# 117 118variable elementStack [list] 119 120# start gi ?attribute value ... ? -- 121# Return start-tag for element $gi 122# As a side-effect, pushes $gi onto the element stack. 123# 124proc start {gi args} { 125 if {[llength $args] == 1} { set args [lindex $args 0] } 126 variable elementStack 127 lappend elementStack $gi 128 return [startTag $gi $args] 129} 130 131# xmlContext {gi1 ... giN} ?default? -- 132# Pops elements off the element stack until one of 133# the specified element types is found. 134# 135# Returns: sequence of end-tags for each element popped. 136# 137# If none of the specified elements are found, returns 138# a start-tag for $default. 139# 140proc xmlContext {gis {default {}}} { 141 variable elementStack 142 set origStack $elementStack 143 set endTags [list] 144 while {[llength $elementStack]} { 145 set current [lindex $elementStack end] 146 if {[lsearch $gis $current] >= 0} { 147 return [join $endTags \n] 148 } 149 lappend endTags [endTag $current] 150 set elementStack [lreplace $elementStack end end] 151 } 152 # Not found: 153 set elementStack $origStack 154 if {![string length $default]} { 155 set where "[join $elementStack /] - [info level 1]" 156 puts_stderr "Warning: Cannot start context $gis ($where)" 157 set default [lindex $gis 0] 158 } 159 lappend elementStack $default 160 return [startTag $default] 161} 162 163# end ? gi ? -- 164# Generate markup to close element $gi, including end-tags 165# for any elements above it on the element stack. 166# 167# If element name is omitted, closes the current element. 168# 169proc end {{gi {}}} { 170 variable elementStack 171 if {![string length $gi]} { 172 set gi [lindex $elementStack end] 173 } 174 set prefix [xmlContext $gi] 175 set elementStack [lreplace $elementStack end end] 176 return [join [list $prefix [endTag $gi]] "\n"] 177} 178 179###################################################################### 180# Utilities for multi-pass processing. 181# 182# Not really XML-related, but I find them handy. 183# 184 185variable PassProcs 186variable Buffers 187 188# pass $passNo procName procArgs { body } -- 189# Specifies procedure definition for pass $n. 190# 191proc pass {pass proc arguments body} { 192 variable PassProcs 193 lappend PassProcs($pass) $proc $arguments $body 194} 195 196proc setPassProcs {pass} { 197 variable PassProcs 198 foreach {proc args body} $PassProcs($pass) { 199 proc $proc $args $body 200 } 201} 202 203# holdBuffers buffer ? buffer ...? -- 204# Declare a list of hold buffers, 205# to collect data in one pass and output it later. 206# 207proc holdBuffers {args} { 208 variable Buffers 209 foreach arg $args { 210 set Buffers($arg) [list] 211 } 212} 213 214# hold buffer text -- 215# Append text to named buffer 216# 217proc hold {buffer entry} { 218 variable Buffers 219 lappend Buffers($buffer) $entry 220 return 221} 222 223# held buffer -- 224# Returns current contents of named buffer and empty the buffer. 225# 226proc held {buffer} { 227 variable Buffers 228 set content [join $Buffers($buffer) "\n"] 229 set Buffers($buffer) [list] 230 return $content 231} 232 233#*EOF* 234