wax 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ This package provides a simple API for writing XML documents
2
+ It is particularly well-suited to writing large XML documents
3
+ because it doesn't require storing them in a DOM-like structure
4
+ before outputting them. XML libraries that use that approach can
5
+ cause the JVM to run out of memory when outputting large XML documents.
6
+
7
+ For more information, see http://ociweb.com/wax.
@@ -0,0 +1,16 @@
1
+ require 'wax'
2
+
3
+ url = "http://www.ociweb.com"
4
+ WAX.write($stdout, "1.0") do
5
+ comment "This is one of my favorite CDs!"
6
+ dtd "#{url}/xml/cd.dtd"
7
+ xslt "cd.xslt"
8
+ start "cd"
9
+ attr "year", 2008
10
+ namespace nil, "#{url}/music", "#{url}/xml/cd.xsd"
11
+ namespace "date", "#{url}/date", "#{url}/xml/date.xsd"
12
+ start "artist"
13
+ attr "name", "Gardot, Melody"
14
+ child "title", "Worrisome Heart"
15
+ child "date", "purchaseDate", "4/3/2008"
16
+ end
@@ -0,0 +1,32 @@
1
+ # This solution was found at
2
+ # http://www.jroller.com/abstractScope/entry/
3
+ # passing_parameters_to_an_instance
4
+
5
+ # Add a bind method to the Proc class.
6
+ class Proc
7
+
8
+ # This creates an UnboundMethod (from a temporary instance method)
9
+ # and binds it to a given object.
10
+ # In the case of Object#instance_exec (below), the method is bound to self.
11
+ def bind(object)
12
+ block, time = self, Time.now
13
+ (class << object; self end).class_eval do
14
+ method_name = "__bind_#{time.to_i}_#{time.usec}"
15
+ define_method(method_name, &block)
16
+ method = instance_method(method_name)
17
+ remove_method(method_name)
18
+ method
19
+ end.bind(object)
20
+ end
21
+ end
22
+
23
+ # Add an instance_exec method to the Object class unless it's already there.
24
+ class Object
25
+ unless defined? instance_exec # a new method in Ruby 1.9
26
+ # This binds a block to an arbitrary scope and
27
+ # calls it (with or without arguments).
28
+ def instance_exec(*arguments, &block)
29
+ block.bind(self)[*arguments]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,190 @@
1
+ require 'wax'
2
+
3
+ # This serves as a tutorial for using WAX.
4
+
5
+ def out(text)
6
+ puts "\n\n#{text}\n"
7
+ end
8
+
9
+ # When the no-arg WAX constructor is used, XML is written to stdout.
10
+ # There are also WAX constructors that take an IO object.
11
+ wax = WAX.new
12
+
13
+ out "Only a root element:"
14
+ wax.start("car").close
15
+ # <car/>
16
+
17
+ # After a WAX object is closed,
18
+ # a new one must be created to write more XML.
19
+ wax = WAX.new
20
+
21
+ out "A root element with some text inside:"
22
+ wax.start("car").text("Prius").close
23
+ # <car>Prius</car>
24
+
25
+ out "Text inside a child element:"
26
+ wax = WAX.new
27
+ wax.start("car").start("model").text("Prius").close
28
+ # <car>
29
+ # <model>Prius</model>
30
+ # </car>
31
+
32
+ out "The same with the \"child\" convenience method:"
33
+ wax = WAX.new
34
+ wax.start("car").child("model", "Prius").close
35
+ # <car>
36
+ # <model>Prius</model>
37
+ # </car>
38
+
39
+ out "Text in a CDATA section:"
40
+ wax = WAX.new
41
+ wax.start("car").start("model").cdata("1<2>3&4'5\"6").close
42
+ # <car>
43
+ # <model>
44
+ # <![CDATA[1<2>3&4'5\"6]]>
45
+ # </model>
46
+ # </car>
47
+
48
+ out "Without indentation, on a single line:"
49
+ wax = WAX.new
50
+ wax.set_indent(nil)
51
+ wax.start("car").child("model", "Prius").close
52
+ # <car><model>Prius</model></car>
53
+
54
+ out "Indent with four spaces instead of the default of two:"
55
+ wax = WAX.new
56
+ wax.set_indent(" ")
57
+ wax.start("car").child("model", "Prius").close
58
+ # <car>
59
+ # <model>Prius</model>
60
+ # </car>
61
+
62
+ out "Add an attribute:"
63
+ wax = WAX.new
64
+ wax.start("car").attr("year", 2008).child("model", "Prius").close
65
+ # <car year="2008">
66
+ # <model>Prius</model>
67
+ # </car>
68
+
69
+ out "XML declaration:"
70
+ wax = WAX.new($stdout, "1.0")
71
+ wax.start("car").attr("year", 2008).child("model", "Prius").close
72
+ # <?xml version="1.0" encoding="UTF-8"?>
73
+ # <car year="2008">
74
+ # <model>Prius</model>
75
+ # </car>
76
+
77
+ out "Comment:"
78
+ WAX.write do
79
+ comment "This is a hybrid car."
80
+ start "car"
81
+ child "model", "Prius"
82
+ end
83
+ # <!-- This is a hybrid car. -->
84
+ # <car>
85
+ # <model>Prius</model>
86
+ # </car>
87
+
88
+ out "Processing instruction:"
89
+ WAX.write do
90
+ processing_instruction "target", "data"
91
+ start "car"
92
+ attr "year", 2008
93
+ child "model", "Prius"
94
+ end
95
+ # <?target data?>
96
+ # <car year="2008">
97
+ # <model>Prius</model>
98
+ # </car>
99
+
100
+ out "Associate an XSLT stylesheet:"
101
+ WAX.write do
102
+ xslt "car.xslt"
103
+ start "car"
104
+ attr "year", 2008
105
+ child "model", "Prius"
106
+ end
107
+ # <?xml-stylesheet type="text/xsl" href="car.xslt"?>
108
+ # <car year="2008">
109
+ # <model>Prius</model>
110
+ # </car>
111
+
112
+ out "Associate a default namespace:"
113
+ WAX.write do
114
+ xslt "car.xslt"
115
+ start "car"
116
+ attr "year", 2008
117
+ namespace "http://www.ociweb.com/cars"
118
+ child "model", "Prius"
119
+ end
120
+ # <car year="2008"
121
+ # xmlns="http://www.ociweb.com/cars">
122
+ # <model>Prius</model>
123
+ # </car>
124
+
125
+ out "Associate a non-default namespace with the XML:"
126
+ prefix = "c"
127
+ WAX.write do
128
+ start prefix, "car"
129
+ attr "year", 2008
130
+ namespace prefix, "http://www.ociweb.com/cars"
131
+ child prefix, "model", "Prius"
132
+ end
133
+ # <c:car year="2008"
134
+ # xmlns:c="http://www.ociweb.com/cars">
135
+ # <c:model>Prius</c:model>
136
+ # </c:car>
137
+
138
+ out "Associate an XML Schema:"
139
+ WAX.write do
140
+ start "car"
141
+ attr "year", 2008
142
+ namespace nil, "http://www.ociweb.com/cars", "car.xsd"
143
+ child "model", "Prius"
144
+ end
145
+ # <car year="2008"
146
+ # xmlns="http://www.ociweb.com/cars"
147
+ # xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
148
+ # xsi:schemaLocation="http://www.ociweb.com/cars car.xsd">
149
+ # <model>Prius</model>
150
+ # </car>
151
+
152
+ out "Associate multiple XML Schemas:"
153
+ WAX.write do
154
+ start "car"
155
+ attr "year", 2008
156
+ namespace nil, "http://www.ociweb.com/cars", "car.xsd"
157
+ namespace "m", "http://www.ociweb.com/model", "model.xsd"
158
+ child "m", "model", "Prius"
159
+ end
160
+ # <car year="2008"
161
+ # xmlns="http://www.ociweb.com/cars"
162
+ # xmlns:m="http://www.ociweb.com/model"
163
+ # xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
164
+ # xsi:schemaLocation="http://www.ociweb.com/cars car.xsd
165
+ # http://www.ociweb.com/model model.xsd">
166
+ # <m:model>Prius</m:model>
167
+ # </car>
168
+
169
+ out "Associate a DTD:"
170
+ WAX.write do
171
+ dtd "car.dtd"
172
+ start "car"
173
+ attr "year", 2008
174
+ child "model", "Prius"
175
+ end
176
+ # <!DOCTYPE car SYSTEM "car.dtd">
177
+ # <car year="2008">
178
+ # <model>Prius</model>
179
+ # </car>
180
+
181
+ out "Entity definitions in DOCTYPE:"
182
+ String url = "http://www.ociweb.com/xml/";
183
+ WAX.write do
184
+ entity_def "oci", "Object Computing, Inc."
185
+ external_entity_def "moreData", url + "moreData.xml"
186
+ start "root"
187
+ text "The author works at &oci; in St. Louis, Missouri.",
188
+ true, false # turning escaping off for entity reference
189
+ text "&moreData;", true, false
190
+ end
@@ -0,0 +1,600 @@
1
+ require 'instance_exec'
2
+ require 'xml_util'
3
+
4
+ # This class provides methods that make outputting XML
5
+ # easy, fast and efficient in terms of memory utilization.
6
+ #
7
+ # A WAX object should not be used from multiple threads!
8
+ #
9
+ # For more information, see http://www.ociweb.com/wax.
10
+ #
11
+ # Copyright 2008 R. Mark Volkmann
12
+ # This file is part of WAX.
13
+ #
14
+ # WAX is free software. You can redistribute it and/or modify it
15
+ # under the terms of the GNU Lesser General Public License as published
16
+ # by the Free Software Foundation, either version 3 of the License,
17
+ # or (at your option) any later version.
18
+ #
19
+ # WAX is distributed in the hope that it will be useful,
20
+ # but WITHOUT ANY WARRANTY; without even the implied warranty
21
+ # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22
+ # See the GNU Lesser General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU Lesser General Public License
25
+ # along with WAX. If not, see http://www.gnu.org/licenses.
26
+ #
27
+ # R. Mark Volkmann, Object Computing, Inc.
28
+ class WAX
29
+
30
+ # The current state of XML output is used to verify that methods
31
+ # in this class aren't called in an illogical order.
32
+ # If they are, a RuntimeError is raised.
33
+ STATES = [:in_prolog, :in_start_tag, :in_element, :after_root]
34
+
35
+ # Creates a WAX object, invokes the specified block on it
36
+ # and calls close on the WAX object.
37
+ # The writer can be a String file path, an IO object such as a File,
38
+ # or unspecified to write $stdout.
39
+ # If the version isn't specified then no XML declaration will be written.
40
+ def self.write(writer=$stdout, version=nil, &proc)
41
+ writer = File.new(writer, "w") if writer.kind_of?(String)
42
+ wax = WAX.new(writer, version)
43
+ wax.instance_exec(&proc)
44
+ wax.close
45
+ end
46
+
47
+ # Initializes new instances of this class.
48
+ def initialize(writer=$stdout, version=nil)
49
+ @attr_on_new_line = false
50
+ @check_me = true
51
+ @close_stream = writer != $stdout
52
+ @dtd_file_path = nil
53
+ @entity_defs = []
54
+ @has_content = @has_indented_content = false
55
+ @indent = ' '
56
+ @namespace_uri_to_schema_path_map = {}
57
+ @parent_stack = []
58
+ @pending_prefixes = []
59
+ @prefixes_stack = []
60
+ @state = :in_prolog
61
+ @writer = writer
62
+ write_xml_declaration(version)
63
+ end
64
+
65
+ # Writes an attribute for the currently open element start tag.
66
+ # If two parameters are specified, they are name and value.
67
+ # If three parameters are specified, they are prefix, name and value.
68
+ # If four parameters are specified, they are prefix, name, value and a flag
69
+ # to indicate whether the attribute should be written on a new line.
70
+ def attr(p1, p2, p3=nil, p4=nil)
71
+ if (p3 == nil)
72
+ prefix, name, value, new_line = nil, p1, p2, false
73
+ elsif (p4 == nil)
74
+ prefix, name, value, new_line = p1, p2, p3, false
75
+ else
76
+ prefix, name, value, new_line = p1, p2, p3, p4
77
+ end
78
+
79
+ if @check_me
80
+ bad_state("attr") unless @state == :in_start_tag
81
+
82
+ unless prefix == nil
83
+ XMLUtil.verify_nmtoken(prefix)
84
+ @pending_prefixes << prefix
85
+ end
86
+
87
+ XMLUtil.verify_nmtoken(name)
88
+ value = XMLUtil.escape(value)
89
+ end
90
+
91
+ has_prefix = prefix != nil and prefix.length > 0
92
+ qname = has_prefix ? prefix + ':' + name : name
93
+
94
+ if new_line
95
+ write_indent
96
+ else
97
+ write ' '
98
+ end
99
+
100
+ write "#{qname}=\"#{value}\""
101
+
102
+ self
103
+ end
104
+
105
+ # Raises a RuntimeError that indicates
106
+ # the method that was called and the current state that was invalid.
107
+ def bad_state(method_name)
108
+ raise "can't call #{method_name} when state is #{@state}"
109
+ end
110
+
111
+ # Writes a blank line to increase readability of the XML.
112
+ def blank_line
113
+ nl_text ""
114
+ end
115
+
116
+ # Writes a CDATA section in the content of the current element.
117
+ def cdata(text)
118
+ if @check_me
119
+ bad_state("cdata") if @state == :in_prolog or @state == :after_root
120
+ end
121
+
122
+ text("<![CDATA[" + text + "]]>", true, false)
123
+ end
124
+
125
+ # A convenience method that is a shortcut for
126
+ # start(prefix, name).text(text).end_element().
127
+ def child(p1, p2, p3=nil)
128
+ if (p3 == nil)
129
+ # only specified element name and text
130
+ prefix, name, text = nil, p1, p2
131
+ else
132
+ # specified element namespace prefix, name and text
133
+ prefix, name, text = p1, p2, p3
134
+ end
135
+
136
+ bad_state("child") if @check_me and @state == :after_root
137
+ start(prefix, name).text(text).end_element
138
+ end
139
+
140
+ # Terminates all unterminated elements,
141
+ # closes the Writer that is being used to output XML,
142
+ # and insures that nothing else can be written.
143
+ def close
144
+ raise "already closed" unless @writer
145
+ bad_state("close") if @check_me and @state == :in_prolog
146
+
147
+ # End all the unended elements.
148
+ while @parent_stack.size > 0; end_element; end
149
+
150
+ if @close_stream
151
+ @writer.close
152
+ else
153
+ @writer.flush
154
+ end
155
+
156
+ @writer = nil
157
+ end
158
+
159
+ # Writes a comment (&lt;!-- text --&gt;).
160
+ # The comment text cannot contain "--".
161
+ def comment(text)
162
+ # Comments can be output in any state.
163
+
164
+ XMLUtil.verify_comment(text) if @check_me
165
+
166
+ @has_content = @has_indented_content = true
167
+ terminate_start
168
+ write_indent if @parent_stack.size > 0
169
+
170
+ write "<!-- #{text} -->"
171
+ write "\n" if will_indent and @parent_stack.size == 0
172
+
173
+ self
174
+ end
175
+
176
+ # Writes a DOCTYPE that associates a DTD with the XML document.
177
+ def dtd(file_path)
178
+ if @check_me
179
+ bad_state("dtd") unless @state == :in_prolog
180
+ XMLUtil.verify_uri(file_path)
181
+ end
182
+
183
+ @dtd_file_path = file_path
184
+ self
185
+ end
186
+
187
+ # Terminates the current element.
188
+ # It does so in the shorthand way (/&gt;) if the element has no content,
189
+ # and in the long way (&lt;/name&gt;) if it does.
190
+ def end_element
191
+ if @check_me
192
+ bad_state("end") if @state == :in_prolog or @state == :after_root
193
+ verify_prefixes
194
+ end
195
+
196
+ write_schema_locations
197
+
198
+ name = @parent_stack.pop
199
+
200
+ # Namespace prefixes that were in scope for this element
201
+ # are no longer in scope.
202
+ @prefixes_stack.pop
203
+
204
+ if @has_content
205
+ write_indent if @has_indented_content
206
+ write "</#{name}>"
207
+ else
208
+ write "/>"
209
+ end
210
+
211
+ @has_content = @has_indented_content = true # new setting for parent
212
+
213
+ @state = @parent_stack.size == 0 ? :after_root : :in_element
214
+
215
+ self
216
+ end
217
+
218
+ # Adds an entity definition to the internal subset of the DOCTYPE.
219
+ def entity_def(name, value)
220
+ bad_state("entity") if @check_me and @state != :in_prolog
221
+ @entity_defs << "#{name} \"#{value}\""
222
+ self
223
+ end
224
+
225
+ # Adds an external entity definition to the internal subset of the DOCTYPE.
226
+ def external_entity_def(name, file_path)
227
+ entity_def(name + " SYSTEM", file_path)
228
+ end
229
+
230
+ # Gets the indentation characters being used.
231
+ def get_indent
232
+ @indent
233
+ end
234
+
235
+ # Determines whether a given namespace prefix is currently in scope.
236
+ def is_in_scope_prefix(prefix)
237
+ @prefixes_stack.each do |prefixes|
238
+ next if prefixes == nil
239
+
240
+ # Check for the special case where we are testing for the
241
+ # default namespace and that's the only namespace in scope.
242
+ return true if prefix.length == 0 and prefixes.length == 0
243
+
244
+ prefixes.split(',').each do |token|
245
+ return true if token == prefix
246
+ end
247
+ end
248
+
249
+ false
250
+ end
251
+
252
+ # Gets whether "trust me" mode is enabled.
253
+ def is_trust_me
254
+ !@check_me
255
+ end
256
+
257
+ # Writes a namespace declaration in the start tag of the current element.
258
+ # If one parameter is specified, it is the default namespace uri.
259
+ # If two parameters are specified, they are prefix and uri.
260
+ # If three parameters are specified, they are prefix, uri and schema location.
261
+ def namespace(p1, p2=nil, p3=nil)
262
+ if (p2 == nil)
263
+ # only specified the default namespace uri
264
+ prefix, uri, schema_path = nil, p1, nil
265
+ elsif (p3 == nil)
266
+ # only specified a namespace prefix and uri
267
+ prefix, uri, schema_path = p1, p2, nil
268
+ else
269
+ # specified namespace prefix, uri and schema location
270
+ prefix, uri, schema_path = p1, p2, p3
271
+ end
272
+
273
+ prefix = "" if prefix == nil
274
+ has_prefix = prefix.length > 0
275
+
276
+ if @check_me
277
+ bad_state("namespace") unless @state == :in_start_tag
278
+
279
+ XMLUtil.verify_nmtoken(prefix) if has_prefix
280
+ XMLUtil.verify_uri(uri)
281
+ XMLUtil.verify_uri(schema_path) unless schema_path == nil
282
+ end
283
+
284
+ # Verify that the prefix isn't already defined in the current scope.
285
+ if is_in_scope_prefix(prefix)
286
+ raise ArgumentError,
287
+ "The namespace prefix \"#{prefix}\" is already in scope."
288
+ end
289
+
290
+ if will_indent
291
+ write_indent
292
+ else
293
+ write ' '
294
+ end
295
+
296
+ write "xmlns"
297
+ write(':' + prefix) if has_prefix
298
+ write "=\"#{uri}\""
299
+
300
+ if schema_path != nil
301
+ @namespace_uri_to_schema_path_map[uri] = schema_path
302
+ end
303
+
304
+ # Add this prefix to the list of those in scope for this element.
305
+ prefixes = @prefixes_stack.pop
306
+ if prefixes == nil
307
+ prefixes = prefix
308
+ else
309
+ prefixes << ',' + prefix
310
+ end
311
+ @prefixes_stack.push(prefixes)
312
+
313
+ @attr_on_new_line = true # for the next attribute
314
+
315
+ self
316
+ end
317
+
318
+ # Writes text preceded by a newline.
319
+ def nl_text(text)
320
+ text text, true, @check_me
321
+ end
322
+
323
+ # Writes a processing instruction.
324
+ def processing_instruction(target, data)
325
+ if @check_me
326
+ bad_state("pi") if @state == :after_root
327
+ XMLUtil.verify_nmtoken(target)
328
+ end
329
+
330
+ @has_content = @has_indented_content = true
331
+ terminate_start
332
+ write_indent if @parent_stack.size > 0
333
+
334
+ write "<?#{target} #{data}?>"
335
+ write("\n") if will_indent and @parent_stack.size == 0
336
+
337
+ self
338
+ end
339
+
340
+ # Sets the indentation characters to use.
341
+ # The only valid values are
342
+ # a single tab, one or more spaces, an empty string, or null.
343
+ # Passing "" causes elements to be output on separate lines,
344
+ # but not indented.
345
+ # Passing null causes all output to be on a single line.
346
+ def set_indent(indent)
347
+ if indent == nil
348
+ @indent = indent
349
+
350
+ elsif indent.kind_of?(Fixnum)
351
+ count, @indent = indent, ''
352
+
353
+ if count < 0
354
+ raise ArgumentError, "can't indent a negative number of spaces"
355
+ end
356
+
357
+ if count > 4
358
+ raise ArgumentError, "#{count} is an unreasonable indentation"
359
+ end
360
+
361
+ for i in 1..count; @indent << ' '; end
362
+
363
+ return
364
+
365
+ elsif indent.kind_of?(String)
366
+ # Note that the parens on the next line are necessary
367
+ # because the assignment operator has higher precedence than "or".
368
+ valid = (indent == nil or indent.length == 0 or indent == "\t")
369
+
370
+ unless valid
371
+ # It can only be valid now if every character is a space.
372
+ valid = true
373
+ for i in 0...indent.length
374
+ unless indent[i] == 32 # space
375
+ valid = false
376
+ break
377
+ end
378
+ end
379
+ end
380
+
381
+ raise ArgumentError, "invalid indent value #{indent}" unless valid
382
+
383
+ @indent = indent
384
+
385
+ else
386
+ raise ArgumentError, "invalid indent value #{indent}"
387
+ end
388
+ end
389
+
390
+ # Gets whether "trust me" mode is enabled.
391
+ # When disabled (the default),
392
+ # proper order of method calls is verified,
393
+ # method parameter values are verified,
394
+ # element and attribute names are verified to be NMTokens,
395
+ # and reserved characters in element/attribute text
396
+ # are replaced by built-in entity references.
397
+ # The main reason to enable "trust me" mode is for performance
398
+ # which is typically good even when disabled.
399
+ def set_trust_me(trust_me)
400
+ @check_me = !trust_me
401
+ end
402
+
403
+ # Writes the start tag for a given element name, but doesn't terminate it.
404
+ # If one parameter is specified, it is the element name.
405
+ # If two parameters are specified, they are prefix and name.
406
+ def start(p1, p2=nil)
407
+ if (p2 == nil)
408
+ # only specified element name
409
+ prefix, name = nil, p1
410
+ else
411
+ # specified element namespace prefix, name and text
412
+ prefix, name = p1, p2
413
+ end
414
+
415
+ @has_content = @has_indented_content = true
416
+ terminate_start
417
+ @has_content = false
418
+
419
+ if @check_me
420
+ bad_state("start") if @state == :after_root
421
+ if prefix != nil
422
+ XMLUtil.verify_nmtoken(prefix)
423
+ @pending_prefixes << prefix
424
+ end
425
+ XMLUtil.verify_nmtoken(name)
426
+ end
427
+
428
+ # If this is the root element ...
429
+ write_doctype(name) if @state == :in_prolog
430
+
431
+ # Can't add to pendingPrefixes until
432
+ # previous start tag has been terminated.
433
+ @pending_prefixes << prefix if @check_me and prefix != nil
434
+
435
+ write_indent if @parent_stack.size > 0
436
+
437
+ has_prefix = prefix != nil and prefix.length > 0
438
+ qname = has_prefix ? prefix + ':' + name : name
439
+
440
+ write '<' + qname
441
+
442
+ @parent_stack.push(qname)
443
+
444
+ # No namespace prefixes have been associated with this element yet.
445
+ @prefixes_stack.push(nil)
446
+
447
+ @state = :in_start_tag
448
+
449
+ self
450
+ end
451
+
452
+ # Closes the start tag, with &gt; or /&gt;, that had been kept open
453
+ # waiting for more namespace declarations and attributes.
454
+ def terminate_start
455
+ verify_prefixes if @check_me
456
+ return if @state != :in_start_tag
457
+ write_schema_locations
458
+ write '>'
459
+ @attr_on_new_line = false # reset
460
+ @state = :in_element
461
+ end
462
+
463
+ # Writes text inside the content of the current element.
464
+ def text(text, newline=false, escape=@check_me)
465
+ if @check_me
466
+ bad_state("text") if @state == :in_prolog or @state == :after_root
467
+ end
468
+
469
+ @has_content = true
470
+ @has_indented_content = newline
471
+ terminate_start
472
+
473
+ if text != nil and text.length > 0
474
+ write_indent if newline
475
+ text = XMLUtil.escape(text) if escape
476
+ write text
477
+ elsif newline
478
+ write "\n"
479
+ end
480
+
481
+ self
482
+ end
483
+
484
+ # Verifies that all the pending namespace prefix are currently in scope.
485
+ # ArgumentError is raises if any aren't in scope.
486
+ def verify_prefixes
487
+ @pending_prefixes.each do |prefix|
488
+ if !is_in_scope_prefix(prefix)
489
+ raise ArgumentError,
490
+ "The namespace prefix \"#{prefix}\" isn't in scope."
491
+ end
492
+ end
493
+
494
+ @pending_prefixes.clear
495
+ end
496
+
497
+ # Determines whether XML should be indented.
498
+ def will_indent
499
+ @indent != nil
500
+ end
501
+
502
+ # Writes the to_s value of an Object to the writer.
503
+ def write(data)
504
+ raise "attempting to write XML after close has been called" unless @writer
505
+ @writer.write(data.to_s)
506
+ end
507
+
508
+ # Writes a DOCTYPE.
509
+ def write_doctype(root_element_name)
510
+ return if @dtd_file_path == nil and @entity_defs.empty?
511
+
512
+ write "<!DOCTYPE #{root_element_name}"
513
+ write " SYSTEM \"#{@dtd_file_path}\"" unless @dtd_file_path == nil
514
+
515
+ if not @entity_defs.empty?
516
+ write " ["
517
+
518
+ @entity_defs.each do |entity_def|
519
+ write "\n" + @indent if will_indent
520
+ write "<!ENTITY #{entity_def}>"
521
+ end
522
+
523
+ write "\n" if will_indent
524
+ write ']'
525
+
526
+ @entity_defs.clear
527
+ end
528
+
529
+ write '>'
530
+ write "\n" if will_indent
531
+ end
532
+
533
+ # Writes the proper amount of indentation
534
+ # given the current nesting of elements.
535
+ def write_indent
536
+ return unless will_indent
537
+ write "\n"
538
+ for i in 0...@parent_stack.size
539
+ write @indent
540
+ end
541
+ end
542
+
543
+ # Writes the namespace declaration for the XMLSchema-instance namespace
544
+ # and writes the schemaLocation attribute
545
+ # which associates namespace URIs with schema locations.
546
+ def write_schema_locations
547
+ return if @namespace_uri_to_schema_path_map.empty?
548
+
549
+ # Write the attributes needed to associate XML Schemas with this XML.
550
+ schema_location = ""
551
+ @namespace_uri_to_schema_path_map.each_pair do |uri, path|
552
+ if schema_location.length > 0 # not first pair output
553
+ if will_indent
554
+ schema_location << "\n"
555
+ for i in 0..@parent_stack.size
556
+ schema_location << @indent
557
+ end
558
+ else
559
+ schema_location << ' '
560
+ end
561
+ end
562
+
563
+ schema_location << uri + ' ' + path
564
+ end
565
+
566
+ namespace("xsi", XMLUtil::XMLSCHEMA_INSTANCE_NS)
567
+ attr("xsi", "schemaLocation", schema_location, will_indent)
568
+ @attr_on_new_line = true # for the next attribute
569
+ @namespace_uri_to_schema_path_map.clear
570
+ end
571
+
572
+ # Writes an XML declaration.
573
+ # Note that regardless of indentation,
574
+ # a newline is always written after this.
575
+ def write_xml_declaration(version)
576
+ return if version == nil
577
+ XMLUtil.verify_version(version) if @check_me
578
+
579
+ write "<?xml version=\"#{version}\" " +
580
+ "encoding=\"#{XMLUtil::DEFAULT_ENCODING}\"?>\n"
581
+ end
582
+
583
+ # Writes an "xml-stylesheet" processing instruction.
584
+ def xslt(file_path)
585
+ if @check_me
586
+ bad_state("xslt") unless @state == :in_prolog
587
+ XMLUtil.verify_uri(file_path)
588
+ end
589
+
590
+ @state = :in_prolog
591
+
592
+ processing_instruction("xml-stylesheet",
593
+ "type=\"text/xsl\" href=\"#{file_path}\"")
594
+ end
595
+
596
+ private :bad_state, :is_in_scope_prefix, :terminate_start,
597
+ :verify_prefixes, :will_indent, :write_doctype, :write_indent,
598
+ :write_schema_locations, :write_xml_declaration
599
+
600
+ end