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]
- NONWINDOWS_CR =
"\n"
- WINDOWS_CR =
"\r\n"
Instance Attribute Summary collapse
-
#state ⇒ Object
readonly
Returns the value of attribute state.
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!().
-
#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 –>).
-
#commented_start(p1, p2 = nil) ⇒ Object
Writes a commented start tag for a given element name, but doesn’t terminate it.
-
#dtd(file_path) ⇒ Object
Writes a DOCTYPE that associates a DTD with the XML document.
-
#end! ⇒ 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_cr ⇒ Object
Gets the carriage return characters currently being used.
-
#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.
-
#no_indents_or_crs ⇒ Object
Don’t output indent output or write carriage returns.
-
#out(data) ⇒ Object
Writes the to_s value of an Object to the writer.
-
#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(obj, newline = false, escape = @check_me) ⇒ Object
Writes text inside the content of the current element.
-
#use_non_windows_cr ⇒ Object
Uses #@cr for carriage returns which is appropriate on every platform except Windows.
-
#use_windows_cr ⇒ Object
Uses r#@cr for carriage returns which is appropriate only on the Windows platform.
- #write(writer = $stdout, version = nil, &proc) ⇒ Object
-
#xslt(file_path) ⇒ Object
Writes an “xml-stylesheet” processing instruction.
Constructor Details
#initialize(writer = $stdout, version = nil) ⇒ WAX
Initializes new instances of this class.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/wax.rb', line 58 def initialize(writer=$stdout, version=nil) @attr_on_new_line = false @check_me = true @close_stream = writer != $stdout @dtd_specified = false @in_commented_start = false @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 @xslt_specified = false use_non_windows_cr write_xml_declaration(version) end |
Instance Attribute Details
#state ⇒ Object (readonly)
Returns the value of attribute state.
38 39 40 |
# File 'lib/wax.rb', line 38 def state @state 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 out $stdout. If the version isn’t specified then no XML declaration will be written.
45 46 47 48 49 50 51 |
# File 'lib/wax.rb', line 45 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 wax # return instance so caller can get configuration info. like @cr 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.
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 |
# File 'lib/wax.rb', line 84 def attr(p1, p2, p3=nil, p4=nil) if (p3 == nil) prefix, name, value, new_line = nil, p1, p2, @attr_on_new_line elsif (p4 == nil) prefix, name, value, new_line = p1, p2, p3, @attr_on_new_line 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_name(prefix) @pending_prefixes << prefix end XMLUtil.verify_name(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 out ' ' end out "#{qname}=\"#{value}\"" self end |
#blank_line ⇒ Object
Writes a blank line to increase readability of the XML.
126 127 128 |
# File 'lib/wax.rb', line 126 def blank_line nl_text "" end |
#cdata(text) ⇒ Object
Writes a CDATA section in the content of the current element.
131 132 133 134 135 136 137 |
# File 'lib/wax.rb', line 131 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!().
141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/wax.rb', line 141 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! 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.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/wax.rb', line 157 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!; 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 “–”.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/wax.rb', line 175 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 out "<!-- #{text} -->" out "#{@cr}" if will_indent and @parent_stack.size == 0 self end |
#commented_start(p1, p2 = nil) ⇒ Object
Writes a commented start tag for a given element name, but doesn’t terminate it.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/wax.rb', line 192 def commented_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 @in_commented_start = true start(prefix, name) @in_commented_start = false self end |
#dtd(file_path) ⇒ Object
Writes a DOCTYPE that associates a DTD with the XML document.
209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/wax.rb', line 209 def dtd(file_path) raise "can't specify more than one DTD" if @dtd_specified if @check_me bad_state("dtd") unless @state == :in_prolog XMLUtil.verify_uri(file_path) end @dtd_file_path = file_path @dtd_specified = true self end |
#end! ⇒ 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. The name of this method ends in ! because end is a keyword in Ruby.
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 |
# File 'lib/wax.rb', line 226 def end! 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 # Check for hypen at beginning of element name # which indicates that the commentedStart method was used. was_commented_start = name[0] == '-'[0] if @has_content write_indent if @has_indented_content out '</' out was_commented_start ? name[1..-1] : name out was_commented_start ? '-->' : '>' else out was_commented_start ? '/-->' : '/>' 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.
261 262 263 264 265 |
# File 'lib/wax.rb', line 261 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.
268 269 270 |
# File 'lib/wax.rb', line 268 def external_entity_def(name, file_path) entity_def(name + " SYSTEM", file_path) end |
#get_cr ⇒ Object
Gets the carriage return characters currently being used.
273 274 275 |
# File 'lib/wax.rb', line 273 def get_cr @cr end |
#get_indent ⇒ Object
Gets the indentation characters being used.
278 279 280 |
# File 'lib/wax.rb', line 278 def get_indent @indent end |
#is_trust_me ⇒ Object
Gets whether “trust me” mode is enabled.
300 301 302 |
# File 'lib/wax.rb', line 300 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.
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 |
# File 'lib/wax.rb', line 308 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_name(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 out ' ' end out "xmlns" out(':' + prefix) if has_prefix out "=\"#{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.
366 367 368 |
# File 'lib/wax.rb', line 366 def nl_text(text) text text, true, @check_me end |
#no_indents_or_crs ⇒ Object
Don’t output indent output or write carriage returns. Write out the XML on a single line.
372 373 374 |
# File 'lib/wax.rb', line 372 def no_indents_or_crs set_indent nil end |
#out(data) ⇒ Object
Writes the to_s value of an Object to the writer.
377 378 379 380 |
# File 'lib/wax.rb', line 377 def out(data) raise "attempting to out XML after close has been called" unless @writer @writer.write(data.to_s) end |
#processing_instruction(target, data) ⇒ Object
Writes a processing instruction.
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/wax.rb', line 383 def processing_instruction(target, data) if @check_me bad_state("pi") if @state == :after_root # Special handling for this processing instruction # since starting with "xml" is reserved. XMLUtil.verify_name(target) unless target == "xml-stylesheet" end @has_content = @has_indented_content = true terminate_start write_indent if @parent_stack.size > 0 out "<?#{target} #{data}?>" out("#{@cr}") 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 nil. Passing “” causes elements to be output on separate lines, but not indented. Passing nil causes all output to be on a single line.
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 451 452 453 454 |
# File 'lib/wax.rb', line 408 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 @check_me && count > 4 raise ArgumentError, "#{count} is an unreasonable indentation" end for i in 1..count; @indent << ' '; end return elsif indent.kind_of?(String) if @check_me # 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 if !valid or (indent != nil and indent.length > 4) raise ArgumentError, "invalid indent value #{indent}" end end @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.
465 466 467 |
# File 'lib/wax.rb', line 465 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.
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
# File 'lib/wax.rb', line 472 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_name(prefix) @pending_prefixes << prefix end XMLUtil.verify_name(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 if (@in_commented_start) out '<!--' + qname # Add a "marker" to the element name on the stack # so the end method knows to terminate the comment. @parent_stack.push('-' + qname) else out '<' + qname @parent_stack.push(qname) end # No namespace prefixes have been associated with this element yet. @prefixes_stack.push(nil) @state = :in_start_tag self end |
#text(obj, newline = false, escape = @check_me) ⇒ Object
Writes text inside the content of the current element.
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 |
# File 'lib/wax.rb', line 536 def text(obj, 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 text = obj.to_s if obj if obj != nil and text.length > 0 write_indent if newline text = XMLUtil.escape(text) if escape out text elsif newline out "#{@cr}" end self end |
#use_non_windows_cr ⇒ Object
Uses #@cr for carriage returns which is appropriate on every platform except Windows. Note that this is the default.
561 562 563 |
# File 'lib/wax.rb', line 561 def use_non_windows_cr @cr = NONWINDOWS_CR end |
#use_windows_cr ⇒ Object
Uses r#@cr for carriage returns which is appropriate only on the Windows platform. Note that this is not the default.
568 569 570 |
# File 'lib/wax.rb', line 568 def use_windows_cr @cr = WINDOWS_CR end |
#write(writer = $stdout, version = nil, &proc) ⇒ Object
53 54 55 |
# File 'lib/wax.rb', line 53 def write(writer=$stdout, version=nil, &proc) instance_eval(&proc) end |
#xslt(file_path) ⇒ Object
Writes an “xml-stylesheet” processing instruction.
666 667 668 669 670 671 672 673 674 675 676 677 678 |
# File 'lib/wax.rb', line 666 def xslt(file_path) raise "can't specify more than one XSLT" if @xslt_specified if @check_me bad_state("xslt") unless @state == :in_prolog XMLUtil.verify_uri(file_path) end processing_instruction("xml-stylesheet", "type=\"text/xsl\" href=\"#{file_path}\"") @xslt_specified = true end |