scxml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. data/README +0 -0
  2. data/lib/scxml.rb +86 -0
  3. data/lib/scxml/document.rb +165 -0
  4. data/lib/scxml/element.rb +219 -0
  5. data/lib/scxml/xpath.rb +142 -0
  6. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate01.out +2 -0
  7. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate02.out +2 -0
  8. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate03.out +2 -0
  9. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate04.out +2 -0
  10. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate12.out +4 -0
  11. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate13.out +2 -0
  12. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate41.out +2 -0
  13. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate42.out +2 -0
  14. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate45.out +2 -0
  15. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate51.out +2 -0
  16. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate52.out +2 -0
  17. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate56.out +2 -0
  18. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate57.out +5 -0
  19. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/predicate/predicate58.out +11 -0
  20. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select01.out +2 -0
  21. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select02.out +4 -0
  22. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select03.out +2 -0
  23. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select04.out +14 -0
  24. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select12.out +2 -0
  25. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select13.out +13 -0
  26. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select41.out +5 -0
  27. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select42.out +5 -0
  28. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select45.out +5 -0
  29. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select51.out +5 -0
  30. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select52.out +3 -0
  31. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select56.out +7 -0
  32. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select57.out +7 -0
  33. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select58.out +5 -0
  34. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select60.out +5 -0
  35. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select62.out +2 -0
  36. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select63.out +2 -0
  37. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select76.out +2 -0
  38. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select81.out +10 -0
  39. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select82.out +2 -0
  40. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select83.out +7 -0
  41. data/test/scxml/Xalan_Conformance_Tests/REF_OUT/select/select84.out +8 -0
  42. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate01.txt +8 -0
  43. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate01.xml +7 -0
  44. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate01.xsl +18 -0
  45. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate02.txt +8 -0
  46. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate02.xml +7 -0
  47. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate02.xsl +18 -0
  48. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate04.txt +8 -0
  49. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate04.xml +7 -0
  50. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate04.xsl +18 -0
  51. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate07.txt +9 -0
  52. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate07.xml +7 -0
  53. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate07.xsl +20 -0
  54. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate08.txt +9 -0
  55. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate08.xml +7 -0
  56. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate08.xsl +20 -0
  57. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate10.txt +8 -0
  58. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate10.xml +7 -0
  59. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate10.xsl +20 -0
  60. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate14.txt +9 -0
  61. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate14.xml +9 -0
  62. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate14.xsl +20 -0
  63. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate15.txt +9 -0
  64. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate15.xml +9 -0
  65. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate15.xsl +20 -0
  66. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate16.txt +9 -0
  67. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate16.xml +9 -0
  68. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate16.xsl +20 -0
  69. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate17.txt +9 -0
  70. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate17.xml +11 -0
  71. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate17.xsl +20 -0
  72. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate36.txt +8 -0
  73. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate36.xml +7 -0
  74. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate36.xsl +20 -0
  75. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate48.txt +8 -0
  76. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate48.xml +21 -0
  77. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate48.xsl +22 -0
  78. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate49.txt +8 -0
  79. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate49.xml +21 -0
  80. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate49.xsl +22 -0
  81. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate50.txt +8 -0
  82. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate50.xml +21 -0
  83. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate50.xsl +22 -0
  84. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate51.txt +8 -0
  85. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate51.xml +21 -0
  86. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate51.xsl +22 -0
  87. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate52.txt +8 -0
  88. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate52.xml +21 -0
  89. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate52.xsl +22 -0
  90. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate53.txt +8 -0
  91. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate53.xml +21 -0
  92. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate53.xsl +22 -0
  93. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate54.txt +7 -0
  94. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate54.xml +21 -0
  95. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate54.xsl +21 -0
  96. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate55.txt +8 -0
  97. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate55.xml +21 -0
  98. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate55.xsl +22 -0
  99. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate56.txt +8 -0
  100. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate56.xml +21 -0
  101. data/test/scxml/Xalan_Conformance_Tests/predicate/predicate56.xsl +22 -0
  102. data/test/scxml/Xalan_Conformance_Tests/predicate/prepare.rb +34 -0
  103. data/test/scxml/Xalan_Conformance_Tests/select/prepare.rb +34 -0
  104. data/test/scxml/Xalan_Conformance_Tests/select/select01.txt +7 -0
  105. data/test/scxml/Xalan_Conformance_Tests/select/select01.xml +9 -0
  106. data/test/scxml/Xalan_Conformance_Tests/select/select01.xsl +19 -0
  107. data/test/scxml/Xalan_Conformance_Tests/select/select02.txt +7 -0
  108. data/test/scxml/Xalan_Conformance_Tests/select/select02.xml +4 -0
  109. data/test/scxml/Xalan_Conformance_Tests/select/select02.xsl +17 -0
  110. data/test/scxml/Xalan_Conformance_Tests/select/select03.txt +8 -0
  111. data/test/scxml/Xalan_Conformance_Tests/select/select03.xml +13 -0
  112. data/test/scxml/Xalan_Conformance_Tests/select/select03.xsl +19 -0
  113. data/test/scxml/Xalan_Conformance_Tests/select/select04.txt +12 -0
  114. data/test/scxml/Xalan_Conformance_Tests/select/select04.xml +12 -0
  115. data/test/scxml/Xalan_Conformance_Tests/select/select04.xsl +35 -0
  116. data/test/scxml/Xalan_Conformance_Tests/select/select12.txt +7 -0
  117. data/test/scxml/Xalan_Conformance_Tests/select/select12.xml +9 -0
  118. data/test/scxml/Xalan_Conformance_Tests/select/select12.xsl +19 -0
  119. data/test/scxml/Xalan_Conformance_Tests/select/select13.txt +13 -0
  120. data/test/scxml/Xalan_Conformance_Tests/select/select13.xml +18 -0
  121. data/test/scxml/Xalan_Conformance_Tests/select/select13.xsl +33 -0
  122. data/test/scxml/Xalan_Conformance_Tests/select/select41.txt +7 -0
  123. data/test/scxml/Xalan_Conformance_Tests/select/select41.xml +9 -0
  124. data/test/scxml/Xalan_Conformance_Tests/select/select41.xsl +23 -0
  125. data/test/scxml/Xalan_Conformance_Tests/select/select42.txt +8 -0
  126. data/test/scxml/Xalan_Conformance_Tests/select/select42.xml +9 -0
  127. data/test/scxml/Xalan_Conformance_Tests/select/select42.xsl +26 -0
  128. data/test/scxml/Xalan_Conformance_Tests/select/select45.txt +7 -0
  129. data/test/scxml/Xalan_Conformance_Tests/select/select45.xml +24 -0
  130. data/test/scxml/Xalan_Conformance_Tests/select/select45.xsl +25 -0
  131. data/test/scxml/Xalan_Conformance_Tests/select/select51.txt +7 -0
  132. data/test/scxml/Xalan_Conformance_Tests/select/select51.xml +24 -0
  133. data/test/scxml/Xalan_Conformance_Tests/select/select51.xsl +25 -0
  134. data/test/scxml/Xalan_Conformance_Tests/select/select52.txt +6 -0
  135. data/test/scxml/Xalan_Conformance_Tests/select/select52.xml +6 -0
  136. data/test/scxml/Xalan_Conformance_Tests/select/select52.xsl +23 -0
  137. data/test/scxml/Xalan_Conformance_Tests/select/select56.txt +7 -0
  138. data/test/scxml/Xalan_Conformance_Tests/select/select56.xml +29 -0
  139. data/test/scxml/Xalan_Conformance_Tests/select/select56.xsl +25 -0
  140. data/test/scxml/Xalan_Conformance_Tests/select/select57.txt +7 -0
  141. data/test/scxml/Xalan_Conformance_Tests/select/select57.xml +29 -0
  142. data/test/scxml/Xalan_Conformance_Tests/select/select57.xsl +25 -0
  143. data/test/scxml/Xalan_Conformance_Tests/select/select58.txt +7 -0
  144. data/test/scxml/Xalan_Conformance_Tests/select/select58.xml +17 -0
  145. data/test/scxml/Xalan_Conformance_Tests/select/select58.xsl +25 -0
  146. data/test/scxml/Xalan_Conformance_Tests/select/select60.txt +6 -0
  147. data/test/scxml/Xalan_Conformance_Tests/select/select60.xml +9 -0
  148. data/test/scxml/Xalan_Conformance_Tests/select/select60.xsl +25 -0
  149. data/test/scxml/Xalan_Conformance_Tests/select/select62.txt +6 -0
  150. data/test/scxml/Xalan_Conformance_Tests/select/select62.xml +5 -0
  151. data/test/scxml/Xalan_Conformance_Tests/select/select62.xsl +22 -0
  152. data/test/scxml/Xalan_Conformance_Tests/select/select63.txt +7 -0
  153. data/test/scxml/Xalan_Conformance_Tests/select/select63.xml +10 -0
  154. data/test/scxml/Xalan_Conformance_Tests/select/select63.xsl +19 -0
  155. data/test/scxml/Xalan_Conformance_Tests/select/select76.txt +7 -0
  156. data/test/scxml/Xalan_Conformance_Tests/select/select76.xml +6 -0
  157. data/test/scxml/Xalan_Conformance_Tests/select/select76.xsl +17 -0
  158. data/test/scxml/Xalan_Conformance_Tests/select/select81.txt +9 -0
  159. data/test/scxml/Xalan_Conformance_Tests/select/select81.xml +6 -0
  160. data/test/scxml/Xalan_Conformance_Tests/select/select81.xsl +24 -0
  161. data/test/scxml/Xalan_Conformance_Tests/select/select82.txt +9 -0
  162. data/test/scxml/Xalan_Conformance_Tests/select/select82.xml +5 -0
  163. data/test/scxml/Xalan_Conformance_Tests/select/select82.xsl +28 -0
  164. data/test/scxml/Xalan_Conformance_Tests/select/select83.txt +10 -0
  165. data/test/scxml/Xalan_Conformance_Tests/select/select83.xml +10 -0
  166. data/test/scxml/Xalan_Conformance_Tests/select/select83.xsl +32 -0
  167. data/test/scxml/Xalan_Conformance_Tests/select/select84.txt +11 -0
  168. data/test/scxml/Xalan_Conformance_Tests/select/select84.xml +10 -0
  169. data/test/scxml/Xalan_Conformance_Tests/select/select84.xsl +33 -0
  170. data/test/scxml/document_test.rb +231 -0
  171. data/test/scxml/element_test.rb +552 -0
  172. data/test/scxml_test_helper.rb +73 -0
  173. data/test/scxml_test_suite.rb +2 -0
  174. metadata +241 -0
data/README ADDED
File without changes
@@ -0,0 +1,86 @@
1
+ require 'scxml/xpath'
2
+ require 'scxml/document'
3
+ require 'scxml/element'
4
+ require 'strscan'
5
+
6
+ # = SCXML
7
+ # Fast, lightweight access to simple xml data.
8
+ #
9
+ # == Introduction
10
+ # REXML is a standard, pure-Ruby XML processing library. It comes in the standard library and
11
+ # as far as I can tell is quite complete. It also is painfully slow when loading large XML documents
12
+ # and produces enormous memory footprints.
13
+ #
14
+ # SCanningXML parses XML using a scanning approach that works very quickly and with a small memory
15
+ # footprint. SCXML can run in a lightweight mode that minimizes memory at the expense of access
16
+ # time.
17
+ #
18
+ # Currently SCXML does not support insertion/removal of elements, although it may in the future.
19
+ # SCXML does allow elements to be re-written with new attributes and content. For making XML
20
+ # documents, I generally use the Builder[http://builder.rubyforge.com] gem.
21
+ #
22
+ # Elements can be accessed using XPath. Currently XPath support is incomplete, as well as the type
23
+ # of elements and content that can be parsed. See the limitations section below.
24
+ #
25
+ # Copyright (c) 2007 Simon Chiang
26
+ # Version: 0.1
27
+ # Licence: MIT-Style
28
+ #
29
+ # == Usage
30
+ #
31
+ # require 'scxml'
32
+ #
33
+ # xml_string = %Q{
34
+ # <?xml version="1.0" encoding="UTF-8"?>
35
+ # <root>
36
+ # <elements>
37
+ # <element id="0">a</element>
38
+ # <element id="1">b</element>
39
+ # <element id="2">c</element>
40
+ # </elements>
41
+ # </root> }
42
+ #
43
+ # # create a new xml document
44
+ # doc = SCXML::Document.new(xml_string)
45
+ #
46
+ # # select all elements
47
+ # elements = doc.select('/root/elements/element')
48
+ #
49
+ # # select element id = 1
50
+ # e = doc.select('/root/elements/element[@id='1']')
51
+ # e.name # -> 'element'
52
+ # e.attribute('key') # -> '1'
53
+ # e.atttributes # -> {'id' => '1'}
54
+ # e.content # -> 'a'
55
+ # e.rewrite(:attributes => {'id' => '10', 'new' => 'attr'}, :content => 'content') # -> <element id='10' new='attr'>content</element>
56
+ #
57
+ # # element creation sets the element to the first parsed node
58
+ # r = SCXML::Element.new(xml_string)
59
+ # r.name # -> 'root'
60
+ #
61
+ # # elements select relative to themselves
62
+ # e = r.select('elements')
63
+ # e.name # -> 'elements'
64
+ # elements = e.select('element')
65
+ # elements.length # -> 3
66
+ # elements.first.attribute('key') # -> '1'
67
+ #
68
+ # == Limitations
69
+ # SCXML does not support all types of xml nodes. At the moment only simple xml documents will be
70
+ # correctly parsed by SCXML. The 'Usage' example is about SCXML can handle: elements with children
71
+ # or content, as well as attributes.
72
+ #
73
+ # Supported XPath expressions are likewise limited to simple paths and predicates:
74
+ #
75
+ # doc.select('/') # -> selects the document
76
+ # doc.select('/root') # -> selects the root element
77
+ # e = doc.select('/root/elements') # -> selects the elements node
78
+ # e.select('*') # -> selects all children of the elements node
79
+ # e.select('element') # -> selects all children of the elements node named element
80
+ # doc.select('//element') # -> selects all element nodes wherever they occur
81
+ # doc.select('//element[@id='1']') # -> selects all element nodes with attribute id='1' wherever they occur
82
+ #
83
+ # See the tests for specific expression support.
84
+ #
85
+ module SCXML
86
+ end
@@ -0,0 +1,165 @@
1
+
2
+ module SCXML
3
+
4
+ # SCXML Documents keep one central instance of an XML string. All elements
5
+ # in a document are tracked using ranges. For example:
6
+ #
7
+ # <?xml version="1.0"?>
8
+ # <doc>
9
+ # <a>1</a>
10
+ # </doc>
11
+ #
12
+ # Is tracked as:
13
+ # doc.string_range = 0...45
14
+ # doc.string = "<?xml version="1.0"?>\n<doc>\n <a>1</a>\n</doc>"
15
+ # doc.content_range = 24...45
16
+ # doc.content = "<doc>\n <a>1</a>\n</doc>"
17
+ #
18
+ # a.string_range = 30...-7
19
+ # a.string = "<a>1</a>"
20
+ # a.content_range = 3...-4
21
+ # a.content = "1"
22
+ #
23
+ # In lightweight mode, the string and content of elements is not stored internally but rather
24
+ # computed from the ranges and extracted from document. Access is therefore slower, but
25
+ # the memory footprint significantly less, especially in large documents. Content for the document
26
+ # is always computed, and never stored internally.
27
+ #
28
+ # Attributes are parsed into a hash on their first access. In lightweight mode, the hash is not
29
+ # stored internally. These attribute hashes account for most of the access/footprint difference.
30
+ #
31
+ # Note that ranges are exclusive of the last indexed character (ie '...' is used in the range rather than '..'),
32
+ # and that range end will ONLY be positive if the range extends to the end of the document.
33
+ #
34
+ # String ranges encompass the entire tag and are relative to the full document. Content ranges are relative
35
+ # to the string range, and indicate all content within the tag. Ergo:
36
+ # doc.string == doc.string
37
+ # doc.content == doc.string[doc.content_range]
38
+ # a.string == doc.string[a.string_range]
39
+ # a.content == a.string[a.content_range]
40
+ class Document
41
+ include XPath
42
+
43
+ attr_reader :root, :scanner
44
+
45
+ # Creates a new Document from the input string. Options:
46
+ #
47
+ # +lightweight+:: In lightweight mode the string, content, and attributes of elements is recalculated on
48
+ # every access. This results in slower access, but a much smaller memory footprint. default => true
49
+ def initialize(string, options={})
50
+ @options = {:lightweight => true, :remove_whitespace => false}.merge(options)
51
+
52
+ @string = options[:remove_whitespace] ? string.gsub(/\s*\r?\n\s*/, '') : string
53
+
54
+ @scanner = StringScanner.new(@string)
55
+ s, range = scan_node(@scanner)
56
+ range = range.nil? ? 0...@string.length : range.begin...(range.end == 0 ? @string.length : range.end)
57
+
58
+ @content_range = range
59
+ @root = Element.new(self, range, s)
60
+ end
61
+
62
+ # Returns true if the document is set to lightweight mode.
63
+ def lightweight?
64
+ @options[:lightweight]
65
+ end
66
+
67
+ # The full range of the document (ie 0...length)
68
+ def string_range
69
+ 0...string.length
70
+ end
71
+
72
+ # The range from the beginning of the first element tag to the end of the corresponding end tag.
73
+ def content_range
74
+ @content_range #||= string_range
75
+ end
76
+
77
+ # Select elements using XPath statements. Not all statements are supported. See the introduction
78
+ # or tests for allowed statements.
79
+ def select(xpath)
80
+ return [] if xpath.nil?
81
+ return [self] if xpath == '/'
82
+
83
+ paths = xpath.scan(/\/*[^\/]+/)
84
+ select_by_paths(paths)
85
+ end
86
+
87
+ # Returns an array of all node names present in the document.
88
+ def node_names
89
+ return @nodes if @nodes
90
+
91
+ nodes = string.scan(/<(\w+)/m).flatten.uniq
92
+ @nodes = nodes unless lightweight?
93
+ nodes
94
+ end
95
+
96
+ # Returns a table of element contents as configured. Options:
97
+ #
98
+ # +target+:: Specify the output target of the tableize operation. By default a string, but any object
99
+ # responding to '<<' can be provided. The target is returned by +tableize+
100
+ # +row+:: The xpath expression used to select rows of the table. default => '*'
101
+ # +col+:: The xpath expression used to select columns relative to the row elements. default => '*'
102
+ # +header_row+:: These should currently select the header row and cols, but should be replaced in favor
103
+ # of a more intutive interface
104
+ # +header_col+::
105
+ # +row_delimit+:: The row delimiter. default => '\n'
106
+ # +col_delimit+:: The column delimiter. default => '\t'
107
+ # +index+:: If true, the output rows will be prefixed by an index corresponding to the row.
108
+ # +col_width+:: Specifies the width of the columns. Content will be trimmed if it exceeds this width,
109
+ # and will be justified left if width > 0 and justified right if width < 0.
110
+ #
111
+ # Selected elements are passed to the block. The content for each table cell will be the return
112
+ # value of the block, or the element contents if no block is given.
113
+ def tableize(options={}, &block)
114
+ options = {
115
+ :target => "",
116
+ :row_delimit => "\n",
117
+ :col_delimit => "\t",
118
+ :row => "*",
119
+ :col => "*",
120
+ :header_row => nil,
121
+ :header_col => "*",
122
+ :index => false,
123
+ :col_width => nil
124
+ }.merge(options)
125
+
126
+ target = options[:target]
127
+ col_delimit = options[:col_delimit]
128
+ row_delimit = options[:row_delimit]
129
+ index = options[:index]
130
+ col_width = options[:col_width]
131
+
132
+ ['header_', ''].each do |prefix|
133
+ row_xpath = options[ "#{prefix}row".to_sym ]
134
+ col_xpath = options[ "#{prefix}col".to_sym ]
135
+
136
+ rows = select(row_xpath)
137
+ rows.each_index do |i|
138
+ row = rows[i]
139
+ cols = row.select(col_xpath)
140
+ cols = block_given? ?
141
+ yield(row, cols) :
142
+ cols.collect {|col| col.content}
143
+
144
+ cols.unshift i if index
145
+ unless col_width.nil?
146
+ cols = cols.collect do |c|
147
+ col_width < 0 ? c.to_s.rjust(-col_width) : c.to_s.ljust(col_width)
148
+ end
149
+ end
150
+
151
+ target << cols.join(col_delimit)
152
+ target << row_delimit
153
+ end
154
+ end
155
+
156
+ target
157
+ end
158
+
159
+ protected
160
+
161
+ def children
162
+ [root]
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,219 @@
1
+
2
+ module SCXML
3
+ class Element
4
+ class << self
5
+ def new(*args)
6
+ obj = super
7
+ (obj.string_range == obj.doc.content_range && !obj.doc.root.nil?) ? obj.doc.root : obj
8
+ end
9
+ end
10
+
11
+ include XPath
12
+
13
+ attr_reader :doc, :name, :parent
14
+
15
+ def initialize(arg, range=nil, string=nil)
16
+ case arg
17
+ when String
18
+ @doc = Document.new(arg)
19
+ @string_range = @doc.content_range
20
+ when Document
21
+ @doc = arg
22
+ @string_range = arg.content_range
23
+ when Element
24
+ @parent = arg
25
+ @doc = arg.doc
26
+ @string_range = join_range(arg.string_range, range)
27
+ else
28
+ raise ArgumentError, "Cannot initialize using: #{arg.class}"
29
+ end
30
+
31
+ @string = string unless doc.lightweight?
32
+ @name, @content_range = parse(self.string)
33
+ end
34
+
35
+ def string
36
+ if doc.lightweight?
37
+ doc.string[string_range]
38
+ else
39
+ @string ||= doc.string[string_range]
40
+ end
41
+ end
42
+
43
+ def string_range
44
+ @string_range ||= doc.content_range
45
+ end
46
+
47
+ def content(objectify=false)
48
+ content_str = doc.lightweight? ? super() : (@content_str ||= super())
49
+ return content_str unless objectify
50
+
51
+ stripped = content_str.strip
52
+
53
+ case stripped
54
+ when /^\d+$/ then stripped.to_i
55
+ when /^\d*\.?\d+$/ then stripped.to_f
56
+ when /^true$/i then true
57
+ when /^false$/i then false
58
+ else
59
+ content_str
60
+ end
61
+ end
62
+
63
+ def attribute(key)
64
+ return @attributes[key] unless @attributes.nil?
65
+
66
+ attr_range_begin = string_range.begin + name.length + 1
67
+ attr_range_end = string_range.begin + content_range.begin - 1
68
+
69
+ scanner = doc.scanner
70
+ scanner.pos = attr_range_begin
71
+ scanner.skip_until(Regexp.new("#{key}="))
72
+
73
+ return nil if scanner.pos > attr_range_end
74
+
75
+ result = scanner.scan(/'[^']*'|\"[^\"]*\"/)
76
+ result ? result[1...-1] : nil
77
+
78
+ #attr_range = (string_range.begin + name.length + 1)...(string_range.begin + content_range.begin - 1)
79
+ #doc.string[attr_range] =~ Regexp.new("#{key}=('[^']*'|\"[^\"]*\")", Regexp::MULTILINE)
80
+
81
+ #$1 ? $1[1...-1] : nil
82
+ end
83
+
84
+ def attributes
85
+ return @attributes unless @attributes.nil?
86
+
87
+ attrs = {}
88
+
89
+ # indexing and selection is vs the doc.string rather than element.string
90
+ # because in lightweight mode this will require an additional string selection
91
+ # to get element.string. Better to go straight to the source
92
+ attr_range = (string_range.begin + name.length + 1)...(string_range.begin + content_range.begin - 1)
93
+ doc.string[attr_range].scan(/(\w+)=('[^']*'|\"[^\"]*\")/m).each do |attr|
94
+ key, value = attr
95
+ attrs[key] = value[1...-1]
96
+ end
97
+
98
+ @attributes = attrs unless doc.lightweight?
99
+ attrs
100
+ end
101
+
102
+ def children
103
+ unless @children
104
+ @children = []
105
+
106
+ scanner = StringScanner.new(content)
107
+ while node = content_node(scanner)
108
+ @children << node
109
+ end
110
+ end
111
+
112
+ @children
113
+ end
114
+
115
+ def select(xpath)
116
+ return [] if xpath.nil?
117
+
118
+ if xpath =~ /^\//
119
+ doc.select(xpath)
120
+ else
121
+ paths = xpath.scan(/\/*[^\/]+/)
122
+ select_by_paths(paths)
123
+ end
124
+ end
125
+
126
+ def hashify(xpath='*', &block)
127
+ hash = {}
128
+ select(xpath).each do |element|
129
+ key = block_given? ? yield(element) : element.name
130
+ hash[key] = element.content
131
+ end
132
+ hash
133
+ end
134
+
135
+ def to_s
136
+ string
137
+ end
138
+
139
+ def rewrite(options={})
140
+ n = options[:indent] || 0
141
+
142
+ attributes = options[:attributes] || self.attributes
143
+ attributes.merge!(options[:merge_attributes]) if options[:merge_attributes]
144
+
145
+ content = options[:content] || self.content
146
+ content += options[:add_content] if options[:add_content]
147
+
148
+ attr_string = attributes_to_s(attributes)
149
+ attr_string.insert(0, ' ') unless attr_string.strip.empty?
150
+
151
+ if content.empty?
152
+ closed = (string[-2..-1] == '/>')
153
+ closed ?
154
+ "#{indentation(n)}<#{name}#{attr_string}/>" :
155
+ "#{indentation(n)}<#{name}#{attr_string}></#{name}>"
156
+ else
157
+ content_str = reindent(content, n)
158
+ content_str = "#{content_str}#{indentation(n, true)}" unless content_str.strip.empty? || content_str == content
159
+
160
+ "#{indentation(n)}<#{name}#{attr_string}>#{content_str}</#{name}>"
161
+ end
162
+ end
163
+
164
+ protected
165
+
166
+ def attributes_to_s(attributes)
167
+ array = []
168
+ attributes.each_pair do |key, value|
169
+ array << "#{key}='#{value}'"
170
+ end
171
+ array.join(' ')
172
+ end
173
+
174
+ def indentation(n, newline=false)
175
+ %Q{#{newline ? "\n" : '' }#{' ' * n}}
176
+ end
177
+
178
+ def reindent(string, n)
179
+ scanner = StringScanner.new(string.strip)
180
+ output = []
181
+ while tag = scanner.scan_until(/>/)
182
+ closed_tag = (tag[-2..-1] == '/>')
183
+ end_tag = (closed_tag || tag.index('/'))
184
+
185
+ n += 2 if closed_tag || !end_tag
186
+ output << "#{indentation(n, true)}#{tag.strip}"
187
+ n -= 2 if closed_tag || end_tag
188
+
189
+ # checks to see if a tag end occurs before the next newline
190
+ # ie '<a>\n <b>some content</b>\n<a>
191
+ # and assures that this content is NOT reindented
192
+ if scanner.check(/[^\n<]*<\//)
193
+ output << scanner.scan_until(/>/)
194
+ n -= 2
195
+ end
196
+ end
197
+
198
+ # if no tags are present, simply return the input
199
+ output << string if output.empty?
200
+
201
+ output.join('')
202
+ end
203
+
204
+ def parse(str)
205
+ scanner = StringScanner.new(str)
206
+
207
+ scanner.scan_until(/<(\w+)[^>]*>/m)
208
+ start = scanner.pos
209
+ name = scanner[1]
210
+
211
+ end_tag = "</#{name}>"
212
+ scanner.skip_until(Regexp.new(end_tag, Regexp::MULTILINE))
213
+ finish = -scanner.restsize - end_tag.length
214
+
215
+ content_range = start...(finish)
216
+ [name, content_range]
217
+ end
218
+ end
219
+ end