ayril 0.1.0

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