Class: WAX
Overview
This class provides methods that make outputting XML easy, fast and efficient in terms of memory utilization.
A WAX object should not be used from multiple threads!
For more information, see www.ociweb.com/wax.
Copyright 2008 R. Mark Volkmann This file is part of WAX.
WAX is free software. You can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
WAX is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with WAX. If not, see www.gnu.org/licenses.
-
Mark Volkmann, Object Computing, Inc.
Constant Summary collapse
- STATES =
The current state of XML output is used to verify that methods in this class aren’t called in an illogical order. If they are, a RuntimeError is raised.
[:in_prolog, :in_start_tag, :in_element, :after_root]
Class Method Summary collapse
-
.write(writer = $stdout, version = nil, &proc) ⇒ Object
Creates a WAX object, invokes the specified block on it and calls close on the WAX object.
Instance Method Summary collapse
-
#attr(p1, p2, p3 = nil, p4 = nil) ⇒ Object
Writes an attribute for the currently open element start tag.
-
#blank_line ⇒ Object
Writes a blank line to increase readability of the XML.
-
#cdata(text) ⇒ Object
Writes a CDATA section in the content of the current element.
-
#child(p1, p2, p3 = nil) ⇒ Object
A convenience method that is a shortcut for start(prefix, name).text(text).end_element().
-
#close ⇒ Object
Terminates all unterminated elements, closes the Writer that is being used to output XML, and insures that nothing else can be written.
-
#comment(text) ⇒ Object
Writes a comment (<!– text –>).
-
#dtd(file_path) ⇒ Object
Writes a DOCTYPE that associates a DTD with the XML document.
-
#end_element ⇒ Object
Terminates the current element.
-
#entity_def(name, value) ⇒ Object
Adds an entity definition to the internal subset of the DOCTYPE.
-
#external_entity_def(name, file_path) ⇒ Object
Adds an external entity definition to the internal subset of the DOCTYPE.
-
#get_indent ⇒ Object
Gets the indentation characters being used.
-
#initialize(writer = $stdout, version = nil) ⇒ WAX
constructor
Initializes new instances of this class.
-
#is_trust_me ⇒ Object
Gets whether “trust me” mode is enabled.
-
#namespace(p1, p2 = nil, p3 = nil) ⇒ Object
Writes a namespace declaration in the start tag of the current element.
-
#nl_text(text) ⇒ Object
Writes text preceded by a newline.
-
#processing_instruction(target, data) ⇒ Object
Writes a processing instruction.
-
#set_indent(indent) ⇒ Object
Sets the indentation characters to use.
-
#set_trust_me(trust_me) ⇒ Object
Gets whether “trust me” mode is enabled.
-
#start(p1, p2 = nil) ⇒ Object
Writes the start tag for a given element name, but doesn’t terminate it.
-
#text(text, newline = false, escape = @check_me) ⇒ Object
Writes text inside the content of the current element.
-
#write(data) ⇒ Object
Writes the to_s value of an Object to the writer.
-
#xslt(file_path) ⇒ Object
Writes an “xml-stylesheet” processing instruction.
Constructor Details
#initialize(writer = $stdout, version = nil) ⇒ WAX
Initializes new instances of this class.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/wax.rb', line 48 def initialize(writer=$stdout, version=nil) @attr_on_new_line = false @check_me = true @close_stream = writer != $stdout @dtd_file_path = nil @entity_defs = [] @has_content = @has_indented_content = false @indent = ' ' @namespace_uri_to_schema_path_map = {} @parent_stack = [] @pending_prefixes = [] @prefixes_stack = [] @state = :in_prolog @writer = writer write_xml_declaration(version) end |
Class Method Details
.write(writer = $stdout, version = nil, &proc) ⇒ Object
Creates a WAX object, invokes the specified block on it and calls close on the WAX object. The writer can be a String file path, an IO object such as a File, or unspecified to write $stdout. If the version isn’t specified then no XML declaration will be written.
40 41 42 43 44 45 |
# File 'lib/wax.rb', line 40 def self.write(writer=$stdout, version=nil, &proc) writer = File.new(writer, "w") if writer.kind_of?(String) wax = WAX.new(writer, version) wax.instance_exec(&proc) wax.close end |
Instance Method Details
#attr(p1, p2, p3 = nil, p4 = nil) ⇒ Object
Writes an attribute for the currently open element start tag. If two parameters are specified, they are name and value. If three parameters are specified, they are prefix, name and value. If four parameters are specified, they are prefix, name, value and a flag to indicate whether the attribute should be written on a new line.
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 |
# File 'lib/wax.rb', line 70 def attr(p1, p2, p3=nil, p4=nil) if (p3 == nil) prefix, name, value, new_line = nil, p1, p2, false elsif (p4 == nil) prefix, name, value, new_line = p1, p2, p3, false else prefix, name, value, new_line = p1, p2, p3, p4 end if @check_me bad_state("attr") unless @state == :in_start_tag unless prefix == nil XMLUtil.verify_nmtoken(prefix) @pending_prefixes << prefix end XMLUtil.verify_nmtoken(name) value = XMLUtil.escape(value) end has_prefix = prefix != nil and prefix.length > 0 qname = has_prefix ? prefix + ':' + name : name if new_line write_indent else write ' ' end write "#{qname}=\"#{value}\"" self end |
#blank_line ⇒ Object
Writes a blank line to increase readability of the XML.
112 113 114 |
# File 'lib/wax.rb', line 112 def blank_line nl_text "" end |
#cdata(text) ⇒ Object
Writes a CDATA section in the content of the current element.
117 118 119 120 121 122 123 |
# File 'lib/wax.rb', line 117 def cdata(text) if @check_me bad_state("cdata") if @state == :in_prolog or @state == :after_root end text("<![CDATA[" + text + "]]>", true, false) end |
#child(p1, p2, p3 = nil) ⇒ Object
A convenience method that is a shortcut for start(prefix, name).text(text).end_element().
127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/wax.rb', line 127 def child(p1, p2, p3=nil) if (p3 == nil) # only specified element name and text prefix, name, text = nil, p1, p2 else # specified element namespace prefix, name and text prefix, name, text = p1, p2, p3 end bad_state("child") if @check_me and @state == :after_root start(prefix, name).text(text).end_element end |
#close ⇒ Object
Terminates all unterminated elements, closes the Writer that is being used to output XML, and insures that nothing else can be written.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/wax.rb', line 143 def close raise "already closed" unless @writer bad_state("close") if @check_me and @state == :in_prolog # End all the unended elements. while @parent_stack.size > 0; end_element; end if @close_stream @writer.close else @writer.flush end @writer = nil end |
#comment(text) ⇒ Object
Writes a comment (<!– text –>). The comment text cannot contain “–”.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/wax.rb', line 161 def comment(text) # Comments can be output in any state. XMLUtil.verify_comment(text) if @check_me @has_content = @has_indented_content = true terminate_start write_indent if @parent_stack.size > 0 write "<!-- #{text} -->" write "\n" if will_indent and @parent_stack.size == 0 self end |
#dtd(file_path) ⇒ Object
Writes a DOCTYPE that associates a DTD with the XML document.
177 178 179 180 181 182 183 184 185 |
# File 'lib/wax.rb', line 177 def dtd(file_path) if @check_me bad_state("dtd") unless @state == :in_prolog XMLUtil.verify_uri(file_path) end @dtd_file_path = file_path self end |
#end_element ⇒ Object
Terminates the current element. It does so in the shorthand way (/>) if the element has no content, and in the long way (</name>) if it does.
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 |
# File 'lib/wax.rb', line 190 def end_element if @check_me bad_state("end") if @state == :in_prolog or @state == :after_root verify_prefixes end write_schema_locations name = @parent_stack.pop # Namespace prefixes that were in scope for this element # are no longer in scope. @prefixes_stack.pop if @has_content write_indent if @has_indented_content write "</#{name}>" else write "/>" end @has_content = @has_indented_content = true # new setting for parent @state = @parent_stack.size == 0 ? :after_root : :in_element self end |
#entity_def(name, value) ⇒ Object
Adds an entity definition to the internal subset of the DOCTYPE.
219 220 221 222 223 |
# File 'lib/wax.rb', line 219 def entity_def(name, value) bad_state("entity") if @check_me and @state != :in_prolog @entity_defs << "#{name} \"#{value}\"" self end |
#external_entity_def(name, file_path) ⇒ Object
Adds an external entity definition to the internal subset of the DOCTYPE.
226 227 228 |
# File 'lib/wax.rb', line 226 def external_entity_def(name, file_path) entity_def(name + " SYSTEM", file_path) end |
#get_indent ⇒ Object
Gets the indentation characters being used.
231 232 233 |
# File 'lib/wax.rb', line 231 def get_indent @indent end |
#is_trust_me ⇒ Object
Gets whether “trust me” mode is enabled.
253 254 255 |
# File 'lib/wax.rb', line 253 def is_trust_me !@check_me end |
#namespace(p1, p2 = nil, p3 = nil) ⇒ Object
Writes a namespace declaration in the start tag of the current element. If one parameter is specified, it is the default namespace uri. If two parameters are specified, they are prefix and uri. If three parameters are specified, they are prefix, uri and schema location.
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 |
# File 'lib/wax.rb', line 261 def namespace(p1, p2=nil, p3=nil) if (p2 == nil) # only specified the default namespace uri prefix, uri, schema_path = nil, p1, nil elsif (p3 == nil) # only specified a namespace prefix and uri prefix, uri, schema_path = p1, p2, nil else # specified namespace prefix, uri and schema location prefix, uri, schema_path = p1, p2, p3 end prefix = "" if prefix == nil has_prefix = prefix.length > 0 if @check_me bad_state("namespace") unless @state == :in_start_tag XMLUtil.verify_nmtoken(prefix) if has_prefix XMLUtil.verify_uri(uri) XMLUtil.verify_uri(schema_path) unless schema_path == nil end # Verify that the prefix isn't already defined in the current scope. if is_in_scope_prefix(prefix) raise ArgumentError, "The namespace prefix \"#{prefix}\" is already in scope." end if will_indent write_indent else write ' ' end write "xmlns" write(':' + prefix) if has_prefix write "=\"#{uri}\"" if schema_path != nil @namespace_uri_to_schema_path_map[uri] = schema_path end # Add this prefix to the list of those in scope for this element. prefixes = @prefixes_stack.pop if prefixes == nil prefixes = prefix else prefixes << ',' + prefix end @prefixes_stack.push(prefixes) @attr_on_new_line = true # for the next attribute self end |
#nl_text(text) ⇒ Object
Writes text preceded by a newline.
319 320 321 |
# File 'lib/wax.rb', line 319 def nl_text(text) text text, true, @check_me end |
#processing_instruction(target, data) ⇒ Object
Writes a processing instruction.
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/wax.rb', line 324 def processing_instruction(target, data) if @check_me bad_state("pi") if @state == :after_root XMLUtil.verify_nmtoken(target) end @has_content = @has_indented_content = true terminate_start write_indent if @parent_stack.size > 0 write "<?#{target} #{data}?>" write("\n") if will_indent and @parent_stack.size == 0 self end |
#set_indent(indent) ⇒ Object
Sets the indentation characters to use. The only valid values are a single tab, one or more spaces, an empty string, or null. Passing “” causes elements to be output on separate lines, but not indented. Passing null causes all output to be on a single line.
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 380 381 382 383 384 385 386 387 388 |
# File 'lib/wax.rb', line 346 def set_indent(indent) if indent == nil @indent = indent elsif indent.kind_of?(Fixnum) count, @indent = indent, '' if count < 0 raise ArgumentError, "can't indent a negative number of spaces" end if count > 4 raise ArgumentError, "#{count} is an unreasonable indentation" end for i in 1..count; @indent << ' '; end return elsif indent.kind_of?(String) # Note that the parens on the next line are necessary # because the assignment operator has higher precedence than "or". valid = (indent == nil or indent.length == 0 or indent == "\t") unless valid # It can only be valid now if every character is a space. valid = true for i in 0...indent.length unless indent[i] == 32 # space valid = false break end end end raise ArgumentError, "invalid indent value #{indent}" unless valid @indent = indent else raise ArgumentError, "invalid indent value #{indent}" end end |
#set_trust_me(trust_me) ⇒ Object
Gets whether “trust me” mode is enabled. When disabled (the default), proper order of method calls is verified, method parameter values are verified, element and attribute names are verified to be NMTokens, and reserved characters in element/attribute text are replaced by built-in entity references. The main reason to enable “trust me” mode is for performance which is typically good even when disabled.
399 400 401 |
# File 'lib/wax.rb', line 399 def set_trust_me(trust_me) @check_me = !trust_me end |
#start(p1, p2 = nil) ⇒ Object
Writes the start tag for a given element name, but doesn’t terminate it. If one parameter is specified, it is the element name. If two parameters are specified, they are prefix and name.
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/wax.rb', line 406 def start(p1, p2=nil) if (p2 == nil) # only specified element name prefix, name = nil, p1 else # specified element namespace prefix, name and text prefix, name = p1, p2 end @has_content = @has_indented_content = true terminate_start @has_content = false if @check_me bad_state("start") if @state == :after_root if prefix != nil XMLUtil.verify_nmtoken(prefix) @pending_prefixes << prefix end XMLUtil.verify_nmtoken(name) end # If this is the root element ... write_doctype(name) if @state == :in_prolog # Can't add to pendingPrefixes until # previous start tag has been terminated. @pending_prefixes << prefix if @check_me and prefix != nil write_indent if @parent_stack.size > 0 has_prefix = prefix != nil and prefix.length > 0 qname = has_prefix ? prefix + ':' + name : name write '<' + qname @parent_stack.push(qname) # No namespace prefixes have been associated with this element yet. @prefixes_stack.push(nil) @state = :in_start_tag self end |
#text(text, newline = false, escape = @check_me) ⇒ Object
Writes text inside the content of the current element.
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'lib/wax.rb', line 464 def text(text, newline=false, escape=@check_me) if @check_me bad_state("text") if @state == :in_prolog or @state == :after_root end @has_content = true @has_indented_content = newline terminate_start if text != nil and text.length > 0 write_indent if newline text = XMLUtil.escape(text) if escape write text elsif newline write "\n" end self end |
#write(data) ⇒ Object
Writes the to_s value of an Object to the writer.
503 504 505 506 |
# File 'lib/wax.rb', line 503 def write(data) raise "attempting to write XML after close has been called" unless @writer @writer.write(data.to_s) end |
#xslt(file_path) ⇒ Object
Writes an “xml-stylesheet” processing instruction.
584 585 586 587 588 589 590 591 592 593 594 |
# File 'lib/wax.rb', line 584 def xslt(file_path) if @check_me bad_state("xslt") unless @state == :in_prolog XMLUtil.verify_uri(file_path) end @state = :in_prolog processing_instruction("xml-stylesheet", "type=\"text/xsl\" href=\"#{file_path}\"") end |