ayril 0.1.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.
@@ -0,0 +1,118 @@
1
+ # Copyright (C) 2011 by Wilson Lee <kourge[!]gmail.com>, Robert Lowe <rob[!]iblargz.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Ayril
22
+ class XMLNode < NSXMLNode
23
+
24
+ autoload :NodeManipulation, 'ayril/xml_node/node_manipulation'
25
+ autoload :NodeTraversal, 'ayril/xml_node/node_traversal'
26
+
27
+ include XMLNode::NodeManipulation
28
+ include XMLNode::NodeTraversal
29
+
30
+ # XXX: Why raise NotImplementedError?
31
+ # xmldocs with text nodes failed to parse and returned nil..?
32
+ #
33
+ # def initWithKind(kind) raise NotImplementedError end
34
+ # def initWithKind(kind, options: options) raise NotImplementedError end
35
+
36
+ def self.document
37
+ XMLDocument.alloc
38
+ end
39
+
40
+ def self.documentWithRootElement(element)
41
+ XMLDocument.alloc.initWithRootElement element
42
+ end
43
+
44
+ def self.elementWithName(name)
45
+ XMLElement.alloc.initWithName name
46
+ end
47
+
48
+ def self.elementWithName(name, stringValue: string)
49
+ XMLElement.alloc.initWithName name, stringValue: string
50
+ end
51
+
52
+ def self.elementWithName(name, children: children, attributes: attrs)
53
+ self.elementWithName(name).tap do |e|
54
+ attrs.kind_of?(Hash) ? e.setAttributesAsDictionary(attrs)
55
+ : e.setAttributes(attrs)
56
+ e.setChildren children
57
+ end
58
+ end
59
+
60
+ def self.elementWithName(name, URI: uri)
61
+ XMLElement.alloc.initWithName name, URI: uri
62
+ end
63
+
64
+ def self.attributeWithName(name, stringValue: string)
65
+ e = XMLElement.alloc.initWithName "r"
66
+ e.setAttributesAsDictionary name => string
67
+ (e.attributeForName name).tap { |a| a.detach }
68
+ end
69
+
70
+ def self.attributeWithName(name, URI: uri, stringValue: string)
71
+ self.attributeWithName(name, stringValue: string).tap do |n|
72
+ n.URI = uri
73
+ end
74
+ end
75
+
76
+ def self.textWithStringValue(string)
77
+ d = XMLDocument.initWithXMLString "<r>#{string}</r>", options: 0, error: nil
78
+ d.rootElement.childAtIndex(0).tap { |n| n.detach }
79
+ end
80
+
81
+ def self.commentWithStringValue(string)
82
+ d = XMLDocument.alloc.initWithXMLString "<r><!--#{string}--></r>", options: 0, error: nil
83
+ d.rootElement.childAtIndex(0).tap { |n| n.detach }
84
+ end
85
+
86
+ # def self.namespaceWithName(name, stringValue: string) raise NotImplementedError end
87
+ # def self.DTDNodeWithXMLString(string) raise NotImplementedError end
88
+ # def self.predefinedNamespaceForPrefix(prefix) raise NotImplementedError end
89
+ # def self.processingInstructionWithName(name, stringValue: string) raise NotImplementedError end
90
+
91
+ def kind?(kind)
92
+ return self.kind == kind if kind.kind_of? Fixnum
93
+ kind = kind.to_sym
94
+ kinds = %w(invalid document element attribute namespace processing_instruction comment text DTD).invoke(:to_sym)
95
+ if kinds.include? kind
96
+ camelcase = kind.to_s.capitalize.gsub(/_([a-z])/) { |m| m[1].upcase }
97
+ return self.kind == Object.const_get(:"NSXML#{camelcase}Kind")
98
+ end
99
+ false
100
+ end
101
+ alias :type? :kind?
102
+
103
+ def doc?; self.kind == NSXMLDocumentKind end
104
+
105
+ def elem?; self.kind == NSXMLElementKind end
106
+ alias :element? :elem?
107
+
108
+ def attr?; self.kind == NSXMLAttributeKind end
109
+ alias :attribute? :attr?
110
+
111
+ def namespace?; self.kind == NSXMLNamespaceKind end
112
+ def pi?; self.kind == NSXMLProcessingInstructionKind end
113
+ def comment?; self.kind == NSXMLCommentKind end
114
+ def text?; self.kind == NSXMLTextKind end
115
+ def dtd?; self.kind == NSXMLDTDKind end
116
+ end
117
+
118
+ end
@@ -0,0 +1,73 @@
1
+ # Copyright (C) 2011 by Wilson Lee <kourge[!]gmail.com>, Robert Lowe <rob[!]iblargz.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Ayril
22
+ class XMLNode
23
+ module NodeManipulation
24
+ # Removes node from parent and return the removed node itself.
25
+ def remove
26
+ self.tap { |e| e.parent.removeChildAtIndex e.index }
27
+ end
28
+
29
+ # Updates the content of node. If passed nothing or a blank string, the
30
+ # children are cleared. If passed a string, the string is parsed into
31
+ # element(s). Passing an element or an array of elements is good. All
32
+ # elements are first detached if possible.
33
+ def update(content='')
34
+ if content == ''; children = nil
35
+ elsif content.respond_to? :to_s
36
+ children = XMLElement.alloc.initWithXMLString("<r>#{content}</r>",
37
+ error: nil).children
38
+ elsif content.kind_of? XMLElement; children = [content]
39
+ end
40
+ children.each { |child| child.detach } if not children.nil?
41
+ self.setChildren children
42
+ end
43
+
44
+ # Replaces a node with another node and returns the old node.
45
+ def replace(content)
46
+ content = content.to_elem if content.respond_to? :to_elem
47
+ self.parent.replaceChildAtIndex self.index, withNode: content
48
+ self
49
+ end
50
+ alias :swap :replace
51
+
52
+ # Clean the whitespace of the node, i.e. remove all of its children text nodes
53
+ # that contain only whitespace.
54
+ def clean_whitespace
55
+ node = self.childAtIndex 0
56
+ while node
57
+ next_node = node.nextSibling
58
+ node.remove if node.kind == NSXMLTextKind and node.stringValue =~ /\S/
59
+ node = next_node
60
+ end
61
+ self
62
+ end
63
+
64
+ forward :xpath, :XPath
65
+
66
+ forward :text, :stringValue
67
+ forward :text=, :stringValue=
68
+
69
+ forward :to_html, :XMLString
70
+ def inner_html; self.children.invoke(:XMLString).join end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,158 @@
1
+ # Copyright (C) 2011 by Wilson Lee <kourge[!]gmail.com>, Robert Lowe <rob[!]iblargz.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Ayril
22
+ class XMLNode
23
+ module NodeTraversal
24
+ # Recursively collects the results of a method until nil is returned.
25
+ def recursively_collect(property)
26
+ elements = []; element = self
27
+ while element = element.send(property)
28
+ elements << element if element.kind == NSXMLElementKind
29
+ end
30
+ elements
31
+ end
32
+
33
+ # Returns all ancestors of a node.
34
+ def ancestors
35
+ self.recursively_collect :parent
36
+ end
37
+
38
+ # Returns all descendants of a node, direct or indirect.
39
+ def descendants
40
+ self.select "*"
41
+ end
42
+
43
+ # Returns the first descendant element of a node.
44
+ def first_descendant
45
+ element = self.childAtIndex 0
46
+ while element and element.kind != NSXMLElementKind
47
+ element = element.nextSibling
48
+ end
49
+ element
50
+ end
51
+
52
+ # Returns the direct children of a node.
53
+ def immediate_descendants
54
+ return [] unless (element = self.childAtIndex 0)
55
+ while element and element.kind != NSXMLElementKind
56
+ element = element.nextSibling
57
+ end
58
+ return [element] + element.next_siblings unless element.nil?
59
+ []
60
+ end
61
+ alias :child_elements :immediate_descendants
62
+
63
+ def previous_siblings
64
+ self.recursively_collect :previousSibling
65
+ end
66
+
67
+ def previous_element_sibling
68
+ element = self.previousSibling
69
+ while element and element.kind != NSXMLElementKind
70
+ element = element.previousSibling
71
+ end
72
+ element
73
+ end
74
+
75
+ def next_siblings
76
+ self.recursively_collect :nextSibling
77
+ end
78
+
79
+ def next_element_sibling
80
+ element = self.nextSibling
81
+ while element and element.kind != NSXMLElementKind
82
+ element = element.nextSibling
83
+ end
84
+ element
85
+ end
86
+
87
+ def siblings
88
+ self.previous_siblings.reverse + self.next_siblings
89
+ end
90
+
91
+ def up(*args)
92
+ expr, index = args[0..1]
93
+ return self.parent if args.length == 0
94
+ if expr.kind_of? Integer then self.ancestors[expr]
95
+ else Selector.find_element(self.ancestors, expr, index) end
96
+ end
97
+
98
+ def down(*args)
99
+ expr, index = args[0..1]
100
+ return self.first_descendant if args.length == 0
101
+ if expr.kind_of? Integer then self.descendants[expr]
102
+ else self.select(expr)[index || 0] end
103
+ end
104
+ alias :find :down
105
+ alias :search :down
106
+
107
+ def previous(*args)
108
+ expr, index = args[0..1]
109
+ return self.previous_element_sibling if args.length == 0
110
+ expr.kind_of?(Integer) ? self.previous_siblings[expr] :
111
+ Selector::find_element(self.previous_siblings, expr, index)
112
+ end
113
+
114
+ def next(*args)
115
+ expr, index = args[0..1]
116
+ return self.next_element_sibling if args.length == 0
117
+ expr.kind_of?(Integer) ? self.next_siblings[expr] :
118
+ Selector::find_element(self.next_siblings, expr, index)
119
+ end
120
+
121
+ def select(css)
122
+ self.select_by_xpath(Selector.new(css.to_s).xpath).to_a
123
+ end
124
+ alias :elements_by_selector :select
125
+ alias :[] :select # REXML inspired
126
+
127
+ def at(css)
128
+ self.select(css)[0]
129
+ end
130
+ alias :% :at
131
+
132
+ def select_by_xpath(xpath)
133
+ self.nodesForXPath(xpath.to_s, error: nil).to_a
134
+ end
135
+ alias :/ :select_by_xpath # Hpricot inspired
136
+
137
+ def adjacent(*args)
138
+ Selector.find_child_elements(self.parent, args) - [self]
139
+ end
140
+
141
+ def empty?
142
+ self.descendants.invoke(:XMLString).join('') == ''
143
+ end
144
+
145
+ def descendant_of?(ancestor)
146
+ element = self
147
+ while element = element.parent
148
+ return true if element == ancestor
149
+ end
150
+ false
151
+ end
152
+
153
+ def contains?(child)
154
+ child.descendant_of? self
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/../lib/ayril'
2
+
3
+ puts Ayril::Selector.new($*[0]).xpath
@@ -0,0 +1,4 @@
1
+
2
+ load("test/selector.js");
3
+ var s = new Selector(arguments[0]);
4
+ print(s.xpath);
data/test/sanity.xml ADDED
@@ -0,0 +1,124 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <response xmlns="http://www.example.com/api/" status="ok">
3
+ <invoices page="1" per_page="10" pages="4" total="47">
4
+ <invoice>
5
+ <invoice_id>344</invoice_id>
6
+ <client_id>3</client_id>
7
+ <number>FB00004</number>
8
+ <amount>45.6</amount>
9
+ <currency_code>CAD</currency_code>
10
+ <language>en</language>
11
+ <amount_outstanding>0</amount_outstanding>
12
+ <status>paid</status>
13
+ <date>2007-06-23</date>
14
+ <folder>active</folder>
15
+ <po_number></po_number>
16
+ <discount>0</discount>
17
+ <notes>Due upon receipt.</notes>
18
+ <terms>Payment due in 30 days.</terms>
19
+ <url deprecated="true">https://2ndsite.example.com/view/St2gThi6rA2t7RQ</url>
20
+ <auth_url deprecated="true">https://2ndsite.example.com/invoices/344</auth_url>
21
+ <links>
22
+ <client_view>https://2ndsite.example.com/view/St2gThi6rA2t7RQ</client_view>
23
+ <view>https://2ndsite.example.com/invoices/344</view>
24
+ <edit>https://2ndsite.example.com/invoices/344/edit</edit>
25
+ </links>
26
+ <return_uri>http://www.example.com/callback</return_uri>
27
+ <updated>2009-08-12 09:00:00</updated>
28
+ <recurring_id>15</recurring_id>
29
+ <organization>ABC Corp</organization>
30
+ <first_name>John</first_name>
31
+ <last_name>Doe</last_name>
32
+ <p_street1>123 Fake St.</p_street1>
33
+ <p_street2>Unit 555</p_street2>
34
+ <p_city>New York</p_city>
35
+ <p_state>New York</p_state>
36
+ <p_country>United States</p_country>
37
+ <p_code>553132</p_code>
38
+ <vat_name></vat_name>
39
+ <vat_number></vat_number>
40
+ <staff_id>1</staff_id>
41
+ <lines>
42
+ <line>
43
+ <line_id>1</line_id> <!-- (Read Only) line id -->
44
+ <amount>40</amount>
45
+ <name>Yard work</name>
46
+ <description>Mowed the lawn</description>
47
+ <unit_cost>10</unit_cost>
48
+ <quantity>4</quantity>
49
+ <tax1_name>GST</tax1_name>
50
+ <tax2_name>PST</tax2_name>
51
+ <tax1_percent>5</tax1_percent>
52
+ <tax2_percent>8</tax2_percent>
53
+ <type>Item</type>
54
+ </line>
55
+ </lines>
56
+ </invoice>
57
+ <invoice>
58
+ <invoice_id>345</invoice_id>
59
+ <client_id>3</client_id>
60
+ <number>FB000404</number>
61
+ <amount>44.6</amount>
62
+ <currency_code>USD</currency_code>
63
+ <language>en</language>
64
+ <amount_outstanding>0</amount_outstanding>
65
+ <status>paid</status>
66
+ <date>2007-06-23</date>
67
+ <folder>active</folder>
68
+ <po_number></po_number>
69
+ <discount>0</discount>
70
+ <notes>Due upon receipt.</notes>
71
+ <terms>Payment due in 30 days.</terms>
72
+ <url deprecated="true">https://2ndsite.example.com/view/St2gThi6rA2t7RQ</url>
73
+ <auth_url deprecated="true">https://2ndsite.example.com/invoices/344</auth_url>
74
+ <links>
75
+ <client_view>https://2ndsite.example.com/view/St2gThi6rA2t7RQ</client_view>
76
+ <view>https://2ndsite.example.com/invoices/344</view>
77
+ <edit>https://2ndsite.example.com/invoices/344/edit</edit>
78
+ </links>
79
+ <return_uri>http://www.example.com/callback</return_uri>
80
+ <updated>2009-08-12 09:00:00</updated>
81
+ <recurring_id>15</recurring_id>
82
+ <organization>ABC Corp</organization>
83
+ <first_name>John</first_name>
84
+ <last_name>Doe</last_name>
85
+ <p_street1>123 Fake St.</p_street1>
86
+ <p_street2>Unit 555</p_street2>
87
+ <p_city>New York</p_city>
88
+ <p_state>New York</p_state>
89
+ <p_country>United States</p_country>
90
+ <p_code>553132</p_code>
91
+ <vat_name></vat_name>
92
+ <vat_number></vat_number>
93
+ <staff_id>1</staff_id>
94
+ <lines>
95
+ <line>
96
+ <line_id class="can_i_has_selector">4</line_id> <!-- (Read Only) line id -->
97
+ <amount>40</amount>
98
+ <name>Yard work</name>
99
+ <description>Mowed the lawn</description>
100
+ <unit_cost>10</unit_cost>
101
+ <quantity>4</quantity>
102
+ <tax1_name>GST</tax1_name>
103
+ <tax2_name>PST</tax2_name>
104
+ <tax1_percent>5</tax1_percent>
105
+ <tax2_percent>8</tax2_percent>
106
+ <type>Item</type>
107
+ </line>
108
+ <line>
109
+ <line_id>1</line_id> <!-- (Read Only) line id -->
110
+ <amount>40</amount>
111
+ <name>Yard work</name>
112
+ <description>Mowed the lawn</description>
113
+ <unit_cost>10</unit_cost>
114
+ <quantity>4</quantity>
115
+ <tax1_name>GST</tax1_name>
116
+ <tax2_name>PST</tax2_name>
117
+ <tax1_percent>5</tax1_percent>
118
+ <tax2_percent id="can_i_has_id">8</tax2_percent>
119
+ <type>Item</type>
120
+ </line>
121
+ </lines>
122
+ </invoice>
123
+ </invoices>
124
+ </response>