wax 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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