htree 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data.tar.gz.sig +4 -0
  2. data/Makefile +20 -0
  3. data/Manifest +58 -0
  4. data/README +61 -0
  5. data/Rakefile +37 -0
  6. data/htree.gemspec +32 -0
  7. data/init.rb +1 -0
  8. data/install.rb +112 -0
  9. data/lib/htree.rb +97 -0
  10. data/lib/htree/container.rb +8 -0
  11. data/lib/htree/context.rb +69 -0
  12. data/lib/htree/display.rb +46 -0
  13. data/lib/htree/doc.rb +149 -0
  14. data/lib/htree/elem.rb +262 -0
  15. data/lib/htree/encoder.rb +217 -0
  16. data/lib/htree/equality.rb +219 -0
  17. data/lib/htree/extract_text.rb +37 -0
  18. data/lib/htree/fstr.rb +32 -0
  19. data/lib/htree/gencode.rb +193 -0
  20. data/lib/htree/htmlinfo.rb +672 -0
  21. data/lib/htree/inspect.rb +108 -0
  22. data/lib/htree/leaf.rb +92 -0
  23. data/lib/htree/loc.rb +369 -0
  24. data/lib/htree/modules.rb +49 -0
  25. data/lib/htree/name.rb +122 -0
  26. data/lib/htree/output.rb +212 -0
  27. data/lib/htree/parse.rb +410 -0
  28. data/lib/htree/raw_string.rb +127 -0
  29. data/lib/htree/regexp-util.rb +19 -0
  30. data/lib/htree/rexml.rb +131 -0
  31. data/lib/htree/scan.rb +176 -0
  32. data/lib/htree/tag.rb +113 -0
  33. data/lib/htree/template.rb +961 -0
  34. data/lib/htree/text.rb +115 -0
  35. data/lib/htree/traverse.rb +497 -0
  36. data/test-all.rb +5 -0
  37. data/test/assign.html +1 -0
  38. data/test/template.html +4 -0
  39. data/test/test-attr.rb +67 -0
  40. data/test/test-charset.rb +79 -0
  41. data/test/test-context.rb +29 -0
  42. data/test/test-display_xml.rb +45 -0
  43. data/test/test-elem-new.rb +101 -0
  44. data/test/test-encoder.rb +53 -0
  45. data/test/test-equality.rb +55 -0
  46. data/test/test-extract_text.rb +18 -0
  47. data/test/test-gencode.rb +27 -0
  48. data/test/test-leaf.rb +25 -0
  49. data/test/test-loc.rb +60 -0
  50. data/test/test-namespace.rb +147 -0
  51. data/test/test-output.rb +133 -0
  52. data/test/test-parse.rb +115 -0
  53. data/test/test-raw_string.rb +17 -0
  54. data/test/test-rexml.rb +70 -0
  55. data/test/test-scan.rb +153 -0
  56. data/test/test-security.rb +37 -0
  57. data/test/test-subnode.rb +142 -0
  58. data/test/test-template.rb +313 -0
  59. data/test/test-text.rb +43 -0
  60. data/test/test-traverse.rb +69 -0
  61. metadata +166 -0
  62. metadata.gz.sig +1 -0
@@ -0,0 +1,115 @@
1
+ require 'htree/modules'
2
+ require 'htree/raw_string'
3
+ require 'htree/htmlinfo'
4
+ require 'htree/encoder'
5
+ require 'htree/fstr'
6
+ require 'iconv'
7
+
8
+ module HTree
9
+ class Text
10
+ # :stopdoc:
11
+ class << self
12
+ alias new_internal new
13
+ end
14
+ # :startdoc:
15
+
16
+ def Text.new(arg)
17
+ arg = arg.to_node if HTree::Location === arg
18
+ if Text === arg
19
+ new_internal arg.rcdata, arg.normalized_rcdata
20
+ elsif String === arg
21
+ arg2 = arg.gsub(/&/, '&amp;')
22
+ arg = arg2.freeze if arg != arg2
23
+ new_internal arg
24
+ else
25
+ raise TypeError, "cannot initialize Text with #{arg.inspect}"
26
+ end
27
+ end
28
+
29
+ def initialize(rcdata, normalized_rcdata=internal_normalize(rcdata)) # :notnew:
30
+ init_raw_string
31
+ @rcdata = rcdata && HTree.frozen_string(rcdata)
32
+ @normalized_rcdata = @rcdata == normalized_rcdata ? @rcdata : normalized_rcdata
33
+ end
34
+ attr_reader :rcdata, :normalized_rcdata
35
+
36
+ def internal_normalize(rcdata)
37
+ # - character references are decoded as much as possible.
38
+ # - undecodable character references are converted to decimal numeric character refereces.
39
+ result = rcdata.gsub(/&(?:#([0-9]+)|#x([0-9a-fA-F]+)|([A-Za-z][A-Za-z0-9]*));/o) {|s|
40
+ u = nil
41
+ if $1
42
+ u = $1.to_i
43
+ elsif $2
44
+ u = $2.hex
45
+ elsif $3
46
+ u = NamedCharacters[$3]
47
+ end
48
+ if !u || u < 0 || 0x7fffffff < u
49
+ '?'
50
+ elsif u == 38 # '&' character.
51
+ '&#38;'
52
+ elsif u <= 0x7f
53
+ [u].pack("C")
54
+ else
55
+ begin
56
+ Iconv.conv(Encoder.internal_charset, 'UTF-8', [u].pack("U"))
57
+ rescue Iconv::Failure
58
+ "&##{u};"
59
+ end
60
+ end
61
+ }
62
+ HTree.frozen_string(result)
63
+ end
64
+ private :internal_normalize
65
+
66
+ # HTree::Text#to_s converts the text to a string.
67
+ # - character references are decoded as much as possible.
68
+ # - undecodable character reference are converted to `?' character.
69
+ def to_s
70
+ @normalized_rcdata.gsub(/&(?:#([0-9]+));/o) {|s|
71
+ u = $1.to_i
72
+ if 0 <= u && u <= 0x7f
73
+ [u].pack("C")
74
+ else
75
+ '?'
76
+ end
77
+ }
78
+ end
79
+
80
+ def empty?
81
+ @normalized_rcdata.empty?
82
+ end
83
+
84
+ def strip
85
+ rcdata = @normalized_rcdata.dup
86
+ rcdata.sub!(/\A(?:\s|&nbsp;)+/, '')
87
+ rcdata.sub!(/(?:\s|&nbsp;)+\z/, '')
88
+ if rcdata == @normalized_rcdata
89
+ self
90
+ else
91
+ rcdata.freeze
92
+ Text.new_internal(rcdata, rcdata)
93
+ end
94
+ end
95
+
96
+ # HTree::Text.concat returns a text which is concatenation of arguments.
97
+ #
98
+ # An argument should be one of follows.
99
+ # - String
100
+ # - HTree::Text
101
+ # - HTree::Location which points HTree::Text
102
+ def Text.concat(*args)
103
+ rcdata = ''
104
+ args.each {|arg|
105
+ arg = arg.to_node if HTree::Location === arg
106
+ if Text === arg
107
+ rcdata << arg.rcdata
108
+ else
109
+ rcdata << arg.gsub(/&/, '&amp;')
110
+ end
111
+ }
112
+ new_internal rcdata
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,497 @@
1
+ require 'htree/doc'
2
+ require 'htree/elem'
3
+ require 'htree/loc'
4
+ require 'htree/extract_text'
5
+ require 'uri'
6
+
7
+ module HTree
8
+ module Traverse
9
+ def doc?() Doc::Trav === self end
10
+ def elem?() Elem::Trav === self end
11
+ def text?() Text::Trav === self end
12
+ def xmldecl?() XMLDecl::Trav === self end
13
+ def doctype?() DocType::Trav === self end
14
+ def procins?() ProcIns::Trav === self end
15
+ def comment?() Comment::Trav === self end
16
+ def bogusetag?() BogusETag::Trav === self end
17
+
18
+ def get_subnode(*indexes)
19
+ n = self
20
+ indexes.each {|index|
21
+ n = n.get_subnode_internal(index)
22
+ }
23
+ n
24
+ end
25
+ end
26
+
27
+ module Container::Trav
28
+ # +each_child+ iterates over each child.
29
+ def each_child(&block) # :yields: child_node
30
+ children.each(&block)
31
+ nil
32
+ end
33
+
34
+ # +each_child_with_index+ iterates over each child.
35
+ def each_child_with_index(&block) # :yields: child_node, index
36
+ children.each_with_index(&block)
37
+ nil
38
+ end
39
+
40
+ # +find_element+ searches an element which universal name is specified by
41
+ # the arguments.
42
+ # It returns nil if not found.
43
+ def find_element(*names)
44
+ traverse_element(*names) {|e| return e }
45
+ nil
46
+ end
47
+
48
+ # +traverse_element+ traverses elements in the tree.
49
+ # It yields elements in depth first order.
50
+ #
51
+ # If _names_ are empty, it yields all elements.
52
+ # If non-empty _names_ are given, it should be list of universal names.
53
+ #
54
+ # A nested element is yielded in depth first order as follows.
55
+ #
56
+ # t = HTree('<a id=0><b><a id=1 /></b><c id=2 /></a>')
57
+ # t.traverse_element("a", "c") {|e| p e}
58
+ # # =>
59
+ # {elem <a id="0"> {elem <b> {emptyelem <a id="1">} </b>} {emptyelem <c id="2">} </a>}
60
+ # {emptyelem <a id="1">}
61
+ # {emptyelem <c id="2">}
62
+ #
63
+ # Universal names are specified as follows.
64
+ #
65
+ # t = HTree(<<'End')
66
+ # <html>
67
+ # <meta name="robots" content="index,nofollow">
68
+ # <meta name="author" content="Who am I?">
69
+ # </html>
70
+ # End
71
+ # t.traverse_element("{http://www.w3.org/1999/xhtml}meta") {|e| p e}
72
+ # # =>
73
+ # {emptyelem <{http://www.w3.org/1999/xhtml}meta name="robots" content="index,nofollow">}
74
+ # {emptyelem <{http://www.w3.org/1999/xhtml}meta name="author" content="Who am I?">}
75
+ #
76
+ def traverse_element(*names, &block) # :yields: element
77
+ if names.empty?
78
+ traverse_all_element(&block)
79
+ else
80
+ name_set = {}
81
+ names.each {|n| name_set[n] = true }
82
+ traverse_some_element(name_set, &block)
83
+ end
84
+ nil
85
+ end
86
+
87
+ def each_hyperlink_attribute
88
+ traverse_element(
89
+ '{http://www.w3.org/1999/xhtml}a',
90
+ '{http://www.w3.org/1999/xhtml}area',
91
+ '{http://www.w3.org/1999/xhtml}link',
92
+ '{http://www.w3.org/1999/xhtml}img',
93
+ '{http://www.w3.org/1999/xhtml}object',
94
+ '{http://www.w3.org/1999/xhtml}q',
95
+ '{http://www.w3.org/1999/xhtml}blockquote',
96
+ '{http://www.w3.org/1999/xhtml}ins',
97
+ '{http://www.w3.org/1999/xhtml}del',
98
+ '{http://www.w3.org/1999/xhtml}form',
99
+ '{http://www.w3.org/1999/xhtml}input',
100
+ '{http://www.w3.org/1999/xhtml}head',
101
+ '{http://www.w3.org/1999/xhtml}base',
102
+ '{http://www.w3.org/1999/xhtml}script') {|elem|
103
+ case elem.name
104
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:base|a|area|link)\z}i
105
+ attrs = ['href']
106
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:img)\z}i
107
+ attrs = ['src', 'longdesc', 'usemap']
108
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:object)\z}i
109
+ attrs = ['classid', 'codebase', 'data', 'usemap']
110
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:q|blockquote|ins|del)\z}i
111
+ attrs = ['cite']
112
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:form)\z}i
113
+ attrs = ['action']
114
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:input)\z}i
115
+ attrs = ['src', 'usemap']
116
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:head)\z}i
117
+ attrs = ['profile']
118
+ when %r{\{http://www.w3.org/1999/xhtml\}(?:script)\z}i
119
+ attrs = ['src', 'for']
120
+ end
121
+ attrs.each {|attr|
122
+ if hyperlink = elem.get_attribute(attr)
123
+ yield elem, attr, hyperlink
124
+ end
125
+ }
126
+ }
127
+ end
128
+ private :each_hyperlink_attribute
129
+
130
+ # +each_hyperlink_uri+ traverses hyperlinks such as HTML href attribute
131
+ # of A element.
132
+ #
133
+ # It yields HTree::Text (or HTree::Loc) and URI for each hyperlink.
134
+ #
135
+ # The URI objects are created with a base URI which is given by
136
+ # HTML BASE element or the argument ((|base_uri|)).
137
+ # +each_hyperlink_uri+ doesn't yields href of the BASE element.
138
+ def each_hyperlink_uri(base_uri=nil) # :yields: hyperlink, uri
139
+ base_uri = URI.parse(base_uri) if String === base_uri
140
+ links = []
141
+ each_hyperlink_attribute {|elem, attr, hyperlink|
142
+ if %r{\{http://www.w3.org/1999/xhtml\}(?:base)\z}i =~ elem.name
143
+ base_uri = URI.parse(hyperlink.to_s)
144
+ else
145
+ links << hyperlink
146
+ end
147
+ }
148
+ if base_uri
149
+ links.each {|hyperlink| yield hyperlink, base_uri + hyperlink.to_s }
150
+ else
151
+ links.each {|hyperlink| yield hyperlink, URI.parse(hyperlink.to_s) }
152
+ end
153
+ end
154
+
155
+ # +each_hyperlink+ traverses hyperlinks such as HTML href attribute
156
+ # of A element.
157
+ #
158
+ # It yields HTree::Text or HTree::Loc.
159
+ #
160
+ # Note that +each_hyperlink+ yields HTML href attribute of BASE element.
161
+ def each_hyperlink # :yields: text
162
+ links = []
163
+ each_hyperlink_attribute {|elem, attr, hyperlink|
164
+ yield hyperlink
165
+ }
166
+ end
167
+
168
+ # +each_uri+ traverses hyperlinks such as HTML href attribute
169
+ # of A element.
170
+ #
171
+ # It yields URI for each hyperlink.
172
+ #
173
+ # The URI objects are created with a base URI which is given by
174
+ # HTML BASE element or the argument ((|base_uri|)).
175
+ def each_uri(base_uri=nil) # :yields: URI
176
+ each_hyperlink_uri(base_uri) {|hyperlink, uri| yield uri }
177
+ end
178
+ end
179
+
180
+ # :stopdoc:
181
+ module Doc::Trav
182
+ def traverse_all_element(&block)
183
+ children.each {|c| c.traverse_all_element(&block) }
184
+ end
185
+ end
186
+
187
+ module Elem::Trav
188
+ def traverse_all_element(&block)
189
+ yield self
190
+ children.each {|c| c.traverse_all_element(&block) }
191
+ end
192
+ end
193
+
194
+ module Leaf::Trav
195
+ def traverse_all_element
196
+ end
197
+ end
198
+
199
+ module Doc::Trav
200
+ def traverse_some_element(name_set, &block)
201
+ children.each {|c| c.traverse_some_element(name_set, &block) }
202
+ end
203
+ end
204
+
205
+ module Elem::Trav
206
+ def traverse_some_element(name_set, &block)
207
+ yield self if name_set.include? self.name
208
+ children.each {|c| c.traverse_some_element(name_set, &block) }
209
+ end
210
+ end
211
+
212
+ module Leaf::Trav
213
+ def traverse_some_element(name_set)
214
+ end
215
+ end
216
+ # :startdoc:
217
+
218
+ module Traverse
219
+ # +traverse_text+ traverses texts in the tree
220
+ def traverse_text(&block) # :yields: text
221
+ traverse_text_internal(&block)
222
+ nil
223
+ end
224
+ end
225
+
226
+ # :stopdoc:
227
+ module Container::Trav
228
+ def traverse_text_internal(&block)
229
+ each_child {|c| c.traverse_text_internal(&block) }
230
+ end
231
+ end
232
+
233
+ module Leaf::Trav
234
+ def traverse_text_internal
235
+ end
236
+ end
237
+
238
+ module Text::Trav
239
+ def traverse_text_internal
240
+ yield self
241
+ end
242
+ end
243
+ # :startdoc:
244
+
245
+ module Container::Trav
246
+ # +filter+ rebuilds the tree without some components.
247
+ #
248
+ # node.filter {|descendant_node| predicate } -> node
249
+ # loc.filter {|descendant_loc| predicate } -> node
250
+ #
251
+ # +filter+ yields each node except top node.
252
+ # If given block returns false, corresponding node is dropped.
253
+ # If given block returns true, corresponding node is retained and
254
+ # inner nodes are examined.
255
+ #
256
+ # +filter+ returns an node.
257
+ # It doesn't return location object even if self is location object.
258
+ #
259
+ def filter(&block)
260
+ subst = {}
261
+ each_child_with_index {|descendant, i|
262
+ if yield descendant
263
+ if descendant.elem?
264
+ subst[i] = descendant.filter(&block)
265
+ else
266
+ subst[i] = descendant
267
+ end
268
+ else
269
+ subst[i] = nil
270
+ end
271
+ }
272
+ to_node.subst_subnode(subst)
273
+ end
274
+ end
275
+
276
+ module Doc::Trav
277
+ # +title+ searches title and return it as a text.
278
+ # It returns nil if not found.
279
+ #
280
+ # +title+ searchs following information.
281
+ #
282
+ # - <title>...</title> in HTML
283
+ # - <title>...</title> in RSS
284
+ # - <title>...</title> in Atom
285
+ def title
286
+ e = find_element('title',
287
+ '{http://www.w3.org/1999/xhtml}title',
288
+ '{http://purl.org/rss/1.0/}title',
289
+ '{http://my.netscape.com/rdf/simple/0.9/}title',
290
+ '{http://www.w3.org/2005/Atom}title',
291
+ '{http://purl.org/atom/ns#}title')
292
+ e && e.extract_text
293
+ end
294
+
295
+ # +author+ searches author and return it as a text.
296
+ # It returns nil if not found.
297
+ #
298
+ # +author+ searchs following information.
299
+ #
300
+ # - <meta name="author" content="author-name"> in HTML
301
+ # - <link rev="made" title="author-name"> in HTML
302
+ # - <dc:creator>author-name</dc:creator> in RSS
303
+ # - <dc:publisher>author-name</dc:publisher> in RSS
304
+ # - <author><name>author-name</name></author> in Atom
305
+ def author
306
+ traverse_element('meta',
307
+ '{http://www.w3.org/1999/xhtml}meta') {|e|
308
+ begin
309
+ next unless e.fetch_attr('name').downcase == 'author'
310
+ author = e.fetch_attribute('content').strip
311
+ return author if !author.empty?
312
+ rescue IndexError
313
+ end
314
+ }
315
+
316
+ traverse_element('link',
317
+ '{http://www.w3.org/1999/xhtml}link') {|e|
318
+ begin
319
+ next unless e.fetch_attr('rev').downcase == 'made'
320
+ author = e.fetch_attribute('title').strip
321
+ return author if !author.empty?
322
+ rescue IndexError
323
+ end
324
+ }
325
+
326
+ if channel = find_element('{http://purl.org/rss/1.0/}channel')
327
+ channel.traverse_element('{http://purl.org/dc/elements/1.1/}creator') {|e|
328
+ begin
329
+ author = e.extract_text.strip
330
+ return author if !author.empty?
331
+ rescue IndexError
332
+ end
333
+ }
334
+ channel.traverse_element('{http://purl.org/dc/elements/1.1/}publisher') {|e|
335
+ begin
336
+ author = e.extract_text.strip
337
+ return author if !author.empty?
338
+ rescue IndexError
339
+ end
340
+ }
341
+ end
342
+
343
+ ['http://www.w3.org/2005/Atom', 'http://purl.org/atom/ns#'].each {|xmlns|
344
+ each_child {|top|
345
+ next unless top.elem?
346
+ if top.name == "{#{xmlns}}feed"
347
+ if feed_author = find_element("{#{xmlns}}author")
348
+ feed_author.traverse_element("{#{xmlns}}name") {|e|
349
+ begin
350
+ author = e.extract_text.strip
351
+ return author if !author.empty?
352
+ rescue IndexError
353
+ end
354
+ }
355
+ end
356
+ end
357
+ }
358
+ }
359
+
360
+ nil
361
+ end
362
+
363
+ end
364
+
365
+ module Doc::Trav
366
+ # +root+ searches root element.
367
+ # If there is no element on top level, it raise HTree::Error.
368
+ # If there is two or more elements on top level, it raise HTree::Error.
369
+ def root
370
+ es = []
371
+ children.each {|c| es << c if c.elem? }
372
+ raise HTree::Error, "no element" if es.empty?
373
+ raise HTree::Error, "multiple top elements" if 1 < es.length
374
+ es[0]
375
+ end
376
+
377
+ # +has_xmldecl?+ returns true if there is an XML declaration on top level.
378
+ def has_xmldecl?
379
+ children.each {|c| return true if c.xmldecl? }
380
+ false
381
+ end
382
+ end
383
+
384
+ module Elem::Trav
385
+ # +name+ returns the universal name of the element as a string.
386
+ #
387
+ # p HTree('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>').root.name
388
+ # # =>
389
+ # "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}RDF"
390
+ #
391
+ def name() element_name.universal_name end
392
+
393
+ # +qualified_name+ returns the qualified name of the element as a string.
394
+ #
395
+ # p HTree('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>').root.qualified_name
396
+ # # =>
397
+ # "rdf:RDF"
398
+ def qualified_name() element_name.qualified_name end
399
+
400
+ # +attributes+ returns attributes as a hash.
401
+ # The hash keys are HTree::Name objects.
402
+ # The hash values are HTree::Text or HTree::Location objects.
403
+ #
404
+ # p HTree('<a name="xx" href="uu">').root.attributes
405
+ # # =>
406
+ # {href=>{text "uu"}, name=>{text "xx"}}
407
+ #
408
+ # p HTree('<a name="xx" href="uu">').make_loc.root.attributes
409
+ # # =>
410
+ # {href=>#<HTree::Location: doc()/a/@href>, name=>#<HTree::Location: doc()/a/@name>}
411
+ #
412
+ def attributes
413
+ result = {}
414
+ each_attribute {|name, text|
415
+ result[name] = text
416
+ }
417
+ result
418
+ end
419
+
420
+ def each_attr
421
+ each_attribute {|name, text|
422
+ uname = name.universal_name
423
+ str = text.to_s
424
+ yield uname, str
425
+ }
426
+ end
427
+
428
+ # call-seq:
429
+ # elem.fetch_attribute(name) -> text or raise IndexError
430
+ # elem.fetch_attribute(name, default) -> text or default
431
+ # elem.fetch_attribute(name) {|uname| default } -> text or default
432
+ #
433
+ # +fetch_attribute+ returns an attribute value as a text.
434
+ #
435
+ # elem may be an instance of HTree::Elem or a location points to it.
436
+ def fetch_attribute(uname, *rest, &block)
437
+ if 1 < rest.length
438
+ raise ArgumentError, "wrong number of arguments (#{1+rest.length} for 2)"
439
+ end
440
+ if !rest.empty? && block_given?
441
+ raise ArgumentError, "block supersedes default value argument"
442
+ end
443
+ uname = uname.universal_name if uname.respond_to? :universal_name
444
+ return update_attribute_hash.fetch(uname) {
445
+ if block_given?
446
+ return yield(uname)
447
+ elsif !rest.empty?
448
+ return rest[0]
449
+ else
450
+ raise IndexError, "attribute not found: #{uname.inspect}"
451
+ end
452
+ }
453
+ end
454
+
455
+ # call-seq:
456
+ # elem.fetch_attr(name) -> string or raise IndexError
457
+ # elem.fetch_attr(name, default) -> string or default
458
+ # elem.fetch_attr(name) {|uname| default } -> string or default
459
+ #
460
+ # +fetch_attr+ returns an attribute value as a string.
461
+ #
462
+ # elem may be an instance of HTree::Elem or a location points to it.
463
+ def fetch_attr(uname, *rest, &block)
464
+ if 1 < rest.length
465
+ raise ArgumentError, "wrong number of arguments (#{1+rest.length} for 2)"
466
+ end
467
+ if !rest.empty? && block_given?
468
+ raise ArgumentError, "block supersedes default value argument"
469
+ end
470
+ uname = uname.universal_name if uname.respond_to? :universal_name
471
+ return update_attribute_hash.fetch(uname) {
472
+ if block_given?
473
+ return yield(uname)
474
+ elsif !rest.empty?
475
+ return rest[0]
476
+ else
477
+ raise IndexError, "attribute not found: #{uname.inspect}"
478
+ end
479
+ }.to_s
480
+ end
481
+
482
+ def get_attribute(uname)
483
+ uname = uname.universal_name if uname.respond_to? :universal_name
484
+ update_attribute_hash[uname]
485
+ end
486
+
487
+ def get_attr(uname)
488
+ if text = update_attribute_hash[uname]
489
+ text.to_s
490
+ else
491
+ nil
492
+ end
493
+ end
494
+
495
+ end
496
+
497
+ end