webtranslateit-hpricot 0.9.0

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/CHANGELOG +122 -0
  4. data/COPYING +18 -0
  5. data/README.md +295 -0
  6. data/Rakefile +237 -0
  7. data/ext/fast_xs/FastXsService.java +1123 -0
  8. data/ext/fast_xs/extconf.rb +4 -0
  9. data/ext/fast_xs/fast_xs.c +210 -0
  10. data/ext/hpricot_scan/HpricotCss.java +850 -0
  11. data/ext/hpricot_scan/HpricotScanService.java +2085 -0
  12. data/ext/hpricot_scan/MANIFEST +0 -0
  13. data/ext/hpricot_scan/extconf.rb +9 -0
  14. data/ext/hpricot_scan/hpricot_common.rl +76 -0
  15. data/ext/hpricot_scan/hpricot_css.c +3511 -0
  16. data/ext/hpricot_scan/hpricot_css.java.rl +155 -0
  17. data/ext/hpricot_scan/hpricot_css.rl +120 -0
  18. data/ext/hpricot_scan/hpricot_scan.c +6848 -0
  19. data/ext/hpricot_scan/hpricot_scan.h +79 -0
  20. data/ext/hpricot_scan/hpricot_scan.java.rl +1173 -0
  21. data/ext/hpricot_scan/hpricot_scan.rl +911 -0
  22. data/extras/hpricot.png +0 -0
  23. data/hpricot.gemspec +18 -0
  24. data/lib/hpricot/blankslate.rb +63 -0
  25. data/lib/hpricot/builder.rb +217 -0
  26. data/lib/hpricot/elements.rb +514 -0
  27. data/lib/hpricot/htmlinfo.rb +691 -0
  28. data/lib/hpricot/inspect.rb +103 -0
  29. data/lib/hpricot/modules.rb +40 -0
  30. data/lib/hpricot/parse.rb +38 -0
  31. data/lib/hpricot/tag.rb +219 -0
  32. data/lib/hpricot/tags.rb +164 -0
  33. data/lib/hpricot/traverse.rb +839 -0
  34. data/lib/hpricot/xchar.rb +95 -0
  35. data/lib/hpricot.rb +26 -0
  36. data/setup.rb +1585 -0
  37. data/test/files/basic.xhtml +17 -0
  38. data/test/files/boingboing.html +2266 -0
  39. data/test/files/cy0.html +3653 -0
  40. data/test/files/immob.html +400 -0
  41. data/test/files/pace_application.html +1320 -0
  42. data/test/files/tenderlove.html +16 -0
  43. data/test/files/uswebgen.html +220 -0
  44. data/test/files/utf8.html +1054 -0
  45. data/test/files/week9.html +1723 -0
  46. data/test/files/why.xml +19 -0
  47. data/test/load_files.rb +7 -0
  48. data/test/nokogiri-bench.rb +64 -0
  49. data/test/test_alter.rb +96 -0
  50. data/test/test_builder.rb +37 -0
  51. data/test/test_parser.rb +496 -0
  52. data/test/test_paths.rb +25 -0
  53. data/test/test_preserved.rb +88 -0
  54. data/test/test_xml.rb +28 -0
  55. metadata +106 -0
@@ -0,0 +1,514 @@
1
+ module Hpricot
2
+ # Once you've matched a list of elements, you will often need to handle them as
3
+ # a group. Or you may want to perform the same action on each of them.
4
+ # Hpricot::Elements is an extension of Ruby's array class, with some methods
5
+ # added for altering elements contained in the array.
6
+ #
7
+ # If you need to create an element array from regular elements:
8
+ #
9
+ # Hpricot::Elements[ele1, ele2, ele3]
10
+ #
11
+ # Assuming that ele1, ele2 and ele3 contain element objects (Hpricot::Elem,
12
+ # Hpricot::Doc, etc.)
13
+ #
14
+ # == Continuing Searches
15
+ #
16
+ # Usually the Hpricot::Elements you're working on comes from a search you've
17
+ # done. Well, you can continue searching the list by using the same <tt>at</tt>
18
+ # and <tt>search</tt> methods you can use on plain elements.
19
+ #
20
+ # elements = doc.search("/div/p")
21
+ # elements = elements.search("/a[@href='http://hoodwink.d/']")
22
+ # elements = elements.at("img")
23
+ #
24
+ # == Altering Elements
25
+ #
26
+ # When you're altering elements in the list, your changes will be reflected in
27
+ # the document you started searching from.
28
+ #
29
+ # doc = Hpricot("That's my <b>spoon</b>, Tyler.")
30
+ # doc.at("b").swap("<i>fork</i>")
31
+ # doc.to_html
32
+ # #=> "That's my <i>fork</i>, Tyler."
33
+ #
34
+ # == Getting More Detailed
35
+ #
36
+ # If you can't find a method here that does what you need, you may need to
37
+ # loop through the elements and find a method in Hpricot::Container::Trav
38
+ # which can do what you need.
39
+ #
40
+ # For example, you may want to search for all the H3 header tags in a document
41
+ # and grab all the tags underneath the header, but not inside the header.
42
+ # A good method for this is <tt>next_sibling</tt>:
43
+ #
44
+ # doc.search("h3").each do |h3|
45
+ # while ele = h3.next_sibling
46
+ # ary << ele # stuff away all the elements under the h3
47
+ # end
48
+ # end
49
+ #
50
+ # Most of the useful element methods are in the mixins Hpricot::Traverse
51
+ # and Hpricot::Container::Trav.
52
+ class Elements < Array
53
+
54
+ # Searches this list for any elements (or children of these elements) matching
55
+ # the CSS or XPath expression +expr+. Root is assumed to be the element scanned.
56
+ #
57
+ # See Hpricot::Container::Trav.search for more.
58
+ def search(*expr,&blk)
59
+ Elements[*map { |x| x.search(*expr,&blk) }.flatten.uniq]
60
+ end
61
+ alias_method :/, :search
62
+
63
+ # Searches this list for the first element (or child of these elements) matching
64
+ # the CSS or XPath expression +expr+. Root is assumed to be the element scanned.
65
+ #
66
+ # See Hpricot::Container::Trav.at for more.
67
+ def at(expr, &blk)
68
+ if expr.kind_of? Fixnum
69
+ super
70
+ else
71
+ search(expr, &blk)[0]
72
+ end
73
+ end
74
+ alias_method :%, :at
75
+
76
+ # Convert this group of elements into a complete HTML fragment, returned as a
77
+ # string.
78
+ def to_html
79
+ map { |x| x.output("") }.join
80
+ end
81
+ alias_method :to_s, :to_html
82
+
83
+ # Returns an HTML fragment built of the contents of each element in this list.
84
+ #
85
+ # If a HTML +string+ is supplied, this method acts like inner_html=.
86
+ def inner_html(*string)
87
+ if string.empty?
88
+ map { |x| x.inner_html }.join
89
+ else
90
+ x = self.inner_html = string.pop || x
91
+ end
92
+ end
93
+ alias_method :html, :inner_html
94
+ alias_method :innerHTML, :inner_html
95
+
96
+ # Replaces the contents of each element in this list. Supply an HTML +string+,
97
+ # which is loaded into Hpricot objects and inserted into every element in this
98
+ # list.
99
+ def inner_html=(string)
100
+ each { |x| x.inner_html = string }
101
+ end
102
+ alias_method :html=, :inner_html=
103
+ alias_method :innerHTML=, :inner_html=
104
+
105
+ # Returns an string containing the text contents of each element in this list.
106
+ # All HTML tags are removed.
107
+ def inner_text
108
+ map { |x| x.inner_text }.join
109
+ end
110
+ alias_method :text, :inner_text
111
+
112
+ # Remove all elements in this list from the document which contains them.
113
+ #
114
+ # doc = Hpricot("<html>Remove this: <b>here</b></html>")
115
+ # doc.search("b").remove
116
+ # doc.to_html
117
+ # => "<html>Remove this: </html>"
118
+ #
119
+ def remove
120
+ each { |x| x.parent.children.delete(x) }
121
+ end
122
+
123
+ # Empty the elements in this list, by removing their insides.
124
+ #
125
+ # doc = Hpricot("<p> We have <i>so much</i> to say.</p>")
126
+ # doc.search("i").empty
127
+ # doc.to_html
128
+ # => "<p> We have <i></i> to say.</p>"
129
+ #
130
+ def empty
131
+ each { |x| x.inner_html = nil }
132
+ end
133
+
134
+ # Add to the end of the contents inside each element in this list.
135
+ # Pass in an HTML +str+, which is turned into Hpricot elements.
136
+ def append(str = nil, &blk)
137
+ each { |x| x.html(x.children + x.make(str, &blk)) }
138
+ end
139
+
140
+ # Add to the start of the contents inside each element in this list.
141
+ # Pass in an HTML +str+, which is turned into Hpricot elements.
142
+ def prepend(str = nil, &blk)
143
+ each { |x| x.html(x.make(str, &blk) + x.children) }
144
+ end
145
+
146
+ # Add some HTML just previous to each element in this list.
147
+ # Pass in an HTML +str+, which is turned into Hpricot elements.
148
+ def before(str = nil, &blk)
149
+ each { |x| x.parent.insert_before x.make(str, &blk), x }
150
+ end
151
+
152
+ # Just after each element in this list, add some HTML.
153
+ # Pass in an HTML +str+, which is turned into Hpricot elements.
154
+ def after(str = nil, &blk)
155
+ each { |x| x.parent.insert_after x.make(str, &blk), x }
156
+ end
157
+
158
+ # Wraps each element in the list inside the element created by HTML +str+.
159
+ # If more than one element is found in the string, Hpricot locates the
160
+ # deepest spot inside the first element.
161
+ #
162
+ # doc.search("a[@href]").
163
+ # wrap(%{<div class="link"><div class="link_inner"></div></div>})
164
+ #
165
+ # This code wraps every link on the page inside a +div.link+ and a +div.link_inner+ nest.
166
+ def wrap(str = nil, &blk)
167
+ each do |x|
168
+ wrap = x.make(str, &blk)
169
+ nest = wrap.detect { |w| w.respond_to? :children }
170
+ unless nest
171
+ raise "No wrapping element found."
172
+ end
173
+ x.parent.replace_child(x, wrap)
174
+ nest = nest.children.first until nest.empty?
175
+ nest.html([x])
176
+ end
177
+ end
178
+
179
+ # Gets and sets attributes on all matched elements.
180
+ #
181
+ # Pass in a +key+ on its own and this method will return the string value
182
+ # assigned to that attribute for the first elements. Or +nil+ if the
183
+ # attribute isn't found.
184
+ #
185
+ # doc.search("a").attr("href")
186
+ # #=> "http://hacketyhack.net/"
187
+ #
188
+ # Or, pass in a +key+ and +value+. This will set an attribute for all
189
+ # matched elements.
190
+ #
191
+ # doc.search("p").attr("class", "basic")
192
+ #
193
+ # You may also use a Hash to set a series of attributes:
194
+ #
195
+ # (doc/"a").attr(:class => "basic", :href => "http://hackety.org/")
196
+ #
197
+ # Lastly, a block can be used to rewrite an attribute based on the element
198
+ # it belongs to. The block will pass in an element. Return from the block
199
+ # the new value of the attribute.
200
+ #
201
+ # records.attr("href") { |e| e['href'] + "#top" }
202
+ #
203
+ # This example adds a <tt>#top</tt> anchor to each link.
204
+ #
205
+ def attr key, value = nil, &blk
206
+ if value or blk
207
+ each do |el|
208
+ el.set_attribute(key, value || blk[el])
209
+ end
210
+ return self
211
+ end
212
+ if key.is_a? Hash
213
+ key.each { |k,v| self.attr(k,v) }
214
+ return self
215
+ else
216
+ return self[0].get_attribute(key)
217
+ end
218
+ end
219
+ alias_method :set, :attr
220
+
221
+ # Adds the class to all matched elements.
222
+ #
223
+ # (doc/"p").add_class("bacon")
224
+ #
225
+ # Now all paragraphs will have class="bacon".
226
+ def add_class class_name
227
+ each do |el|
228
+ next unless el.respond_to? :get_attribute
229
+ classes = el.get_attribute('class').to_s.split(" ")
230
+ el.set_attribute('class', classes.push(class_name).uniq.join(" "))
231
+ end
232
+ self
233
+ end
234
+
235
+ # Remove an attribute from each of the matched elements.
236
+ #
237
+ # (doc/"input").remove_attr("disabled")
238
+ #
239
+ def remove_attr name
240
+ each do |el|
241
+ next unless el.respond_to? :remove_attribute
242
+ el.remove_attribute(name)
243
+ end
244
+ self
245
+ end
246
+
247
+ # Removes a class from all matched elements.
248
+ #
249
+ # (doc/"span").remove_class("lightgrey")
250
+ #
251
+ # Or, to remove all classes:
252
+ #
253
+ # (doc/"span").remove_class
254
+ #
255
+ def remove_class name = nil
256
+ each do |el|
257
+ next unless el.respond_to? :get_attribute
258
+ if name
259
+ classes = el.get_attribute('class').to_s.split(" ")
260
+ el.set_attribute('class', (classes - [name]).uniq.join(" "))
261
+ else
262
+ el.remove_attribute("class")
263
+ end
264
+ end
265
+ self
266
+ end
267
+
268
+ ATTR_RE = %r!\[ *(?:(@)([\w\(\)-]+)|([\w\(\)-]+\(\))) *([~\!\|\*$\^=]*) *'?"?([^'"]*)'?"? *\]!i # " (for emacs)
269
+ BRACK_RE = %r!(\[) *([^\]]*) *\]+!i
270
+ FUNC_RE = %r!(:)?([a-zA-Z0-9\*_-]*)\( *[\"']?([^ \)]*?)['\"]? *\)!
271
+ CUST_RE = %r!(:)([a-zA-Z0-9\*_-]*)()!
272
+ CATCH_RE = %r!([:\.#]*)([a-zA-Z0-9\*_-]+)!
273
+
274
+ def self.filter(nodes, expr, truth = true)
275
+ until expr.empty?
276
+ _, *m = *expr.match(/^(?:#{ATTR_RE}|#{BRACK_RE}|#{FUNC_RE}|#{CUST_RE}|#{CATCH_RE})/)
277
+ break unless _
278
+
279
+ expr = $'
280
+ m.compact!
281
+ if m[0] == '@'
282
+ m[0] = "@#{m.slice!(2,1).join}"
283
+ end
284
+
285
+ if m[0] == '[' && m[1] =~ /^\d+$/
286
+ m = [":", "nth", m[1].to_i-1]
287
+ end
288
+
289
+ if m[0] == ":" && m[1] == "not"
290
+ nodes, = Elements.filter(nodes, m[2], false)
291
+ elsif "#{m[0]}#{m[1]}" =~ /^(:even|:odd)$/
292
+ new_nodes = []
293
+ nodes.each_with_index {|n,i| new_nodes.push(n) if (i % 2 == (m[1] == "even" ? 0 : 1)) }
294
+ nodes = new_nodes
295
+ elsif "#{m[0]}#{m[1]}" =~ /^(:first|:last)$/
296
+ nodes = [nodes.send(m[1])]
297
+ else
298
+ meth = "filter[#{m[0]}#{m[1]}]" unless m[0].empty?
299
+ if meth and Traverse.method_defined? meth
300
+ args = m[2..-1]
301
+ else
302
+ meth = "filter[#{m[0]}]"
303
+ if Traverse.method_defined? meth
304
+ args = m[1..-1]
305
+ end
306
+ end
307
+ args << -1
308
+ nodes = Elements[*nodes.find_all do |x|
309
+ args[-1] += 1
310
+ x.send(meth, *args) ? truth : !truth
311
+ end]
312
+ end
313
+ end
314
+ [nodes, expr]
315
+ end
316
+
317
+ # Given two elements, attempt to gather an Elements array of everything between
318
+ # (and including) those two elements.
319
+ def self.expand(ele1, ele2, excl=false)
320
+ ary = []
321
+ offset = excl ? -1 : 0
322
+
323
+ if ele1 and ele2
324
+ # let's quickly take care of siblings
325
+ if ele1.parent == ele2.parent
326
+ ary = ele1.parent.children[ele1.node_position..(ele2.node_position+offset)]
327
+ else
328
+ # find common parent
329
+ p, ele1_p = ele1, [ele1]
330
+ ele1_p.unshift p while p.respond_to?(:parent) and p = p.parent
331
+ p, ele2_p = ele2, [ele2]
332
+ ele2_p.unshift p while p.respond_to?(:parent) and p = p.parent
333
+ common_parent = ele1_p.zip(ele2_p).select { |p1, p2| p1 == p2 }.flatten.last
334
+
335
+ child = nil
336
+ if ele1 == common_parent
337
+ child = ele2
338
+ elsif ele2 == common_parent
339
+ child = ele1
340
+ end
341
+
342
+ if child
343
+ ary = common_parent.children[0..(child.node_position+offset)]
344
+ end
345
+ end
346
+ end
347
+
348
+ return Elements[*ary]
349
+ end
350
+
351
+ def filter(expr)
352
+ nodes, = Elements.filter(self, expr)
353
+ nodes
354
+ end
355
+
356
+ def not(expr)
357
+ if expr.is_a? Traverse
358
+ nodes = self - [expr]
359
+ else
360
+ nodes, = Elements.filter(self, expr, false)
361
+ end
362
+ nodes
363
+ end
364
+
365
+ private
366
+ def copy_node(node, l)
367
+ l.instance_variables.each do |iv|
368
+ node.instance_variable_set(iv, l.instance_variable_get(iv))
369
+ end
370
+ end
371
+
372
+ end
373
+
374
+ module Traverse
375
+ def self.filter(tok, &blk)
376
+ define_method("filter[#{tok.is_a?(String) ? tok : tok.inspect}]", &blk)
377
+ end
378
+
379
+ filter '' do |name,i|
380
+ name == '*' || (self.respond_to?(:name) && self.name.downcase == name.downcase)
381
+ end
382
+
383
+ filter '#' do |id,i|
384
+ self.elem? and get_attribute('id').to_s == id
385
+ end
386
+
387
+ filter '.' do |name,i|
388
+ self.elem? and classes.include? name
389
+ end
390
+
391
+ filter :lt do |num,i|
392
+ self.position < num.to_i
393
+ end
394
+
395
+ filter :gt do |num,i|
396
+ self.position > num.to_i
397
+ end
398
+
399
+ nth = proc { |num,i| self.position == num.to_i }
400
+ nth_first = proc { |*a| self.position == 0 }
401
+ nth_last = proc { |*a| self == parent.children_of_type(self.name).last }
402
+
403
+ filter :nth, &nth
404
+ filter :eq, &nth
405
+ filter ":nth-of-type", &nth
406
+
407
+ filter :first, &nth_first
408
+ filter ":first-of-type", &nth_first
409
+
410
+ filter :last, &nth_last
411
+ filter ":last-of-type", &nth_last
412
+
413
+ filter :even do |num,i|
414
+ self.position % 2 == 0
415
+ end
416
+
417
+ filter :odd do |num,i|
418
+ self.position % 2 == 1
419
+ end
420
+
421
+ filter ':first-child' do |i|
422
+ self == parent.containers.first
423
+ end
424
+
425
+ filter ':nth-child' do |arg,i|
426
+ case arg
427
+ when 'even'; (parent.containers.index(self) + 1) % 2 == 0
428
+ when 'odd'; (parent.containers.index(self) + 1) % 2 == 1
429
+ else self == (parent.containers[arg.to_i - 1])
430
+ end
431
+ end
432
+
433
+ filter ":last-child" do |i|
434
+ self == parent.containers.last
435
+ end
436
+
437
+ filter ":nth-last-child" do |arg,i|
438
+ self == parent.containers[-1-arg.to_i]
439
+ end
440
+
441
+ filter ":nth-last-of-type" do |arg,i|
442
+ self == parent.children_of_type(self.name)[-1-arg.to_i]
443
+ end
444
+
445
+ filter ":only-of-type" do |arg,i|
446
+ parent.children_of_type(self.name).length == 1
447
+ end
448
+
449
+ filter ":only-child" do |arg,i|
450
+ parent.containers.length == 1
451
+ end
452
+
453
+ filter :parent do |*a|
454
+ containers.length > 0
455
+ end
456
+
457
+ filter :empty do |*a|
458
+ elem? && inner_html.length == 0
459
+ end
460
+
461
+ filter :root do |*a|
462
+ self.is_a? Hpricot::Doc
463
+ end
464
+
465
+ filter 'text' do |*a|
466
+ self.text?
467
+ end
468
+
469
+ filter 'comment' do |*a|
470
+ self.comment?
471
+ end
472
+
473
+ filter :contains do |arg, ignore|
474
+ html.include? arg
475
+ end
476
+
477
+
478
+
479
+ pred_procs =
480
+ {'text()' => proc { |ele, *_| ele.inner_text.strip },
481
+ '@' => proc { |ele, attr, *_| ele.get_attribute(attr).to_s if ele.elem? }}
482
+
483
+ oper_procs =
484
+ {'=' => proc { |a,b| a == b },
485
+ '!=' => proc { |a,b| a != b },
486
+ '~=' => proc { |a,b| a.split(/\s+/).include?(b) },
487
+ '|=' => proc { |a,b| a =~ /^#{Regexp::quote b}(-|$)/ },
488
+ '^=' => proc { |a,b| a.index(b) == 0 },
489
+ '$=' => proc { |a,b| a =~ /#{Regexp::quote b}$/ },
490
+ '*=' => proc { |a,b| idx = a.index(b) }}
491
+
492
+ pred_procs.each do |pred_n, pred_f|
493
+ oper_procs.each do |oper_n, oper_f|
494
+ filter "#{pred_n}#{oper_n}" do |*a|
495
+ qual = pred_f[self, *a]
496
+ oper_f[qual, a[-2]] if qual
497
+ end
498
+ end
499
+ end
500
+
501
+ filter 'text()' do |val,i|
502
+ self.children.grep(Hpricot::Text).detect { |x| x.content =~ /\S/ } if self.children
503
+ end
504
+
505
+ filter '@' do |attr,val,i|
506
+ self.elem? and has_attribute? attr
507
+ end
508
+
509
+ filter '[' do |val,i|
510
+ self.elem? and search(val).length > 0
511
+ end
512
+
513
+ end
514
+ end