ayril 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +19 -0
- data/LICENSE +19 -0
- data/README.md +88 -0
- data/Rakefile +40 -0
- data/ayril.gemspec +55 -0
- data/lib/ayril.rb +48 -0
- data/lib/ayril/core_ext/core_ext.rb +96 -0
- data/lib/ayril/selector.rb +250 -0
- data/lib/ayril/version.rb +30 -0
- data/lib/ayril/xml_document.rb +54 -0
- data/lib/ayril/xml_element.rb +75 -0
- data/lib/ayril/xml_element/element_attribute_manipulation.rb +61 -0
- data/lib/ayril/xml_element/element_classname_manipulation.rb +34 -0
- data/lib/ayril/xml_element/element_manipulation.rb +85 -0
- data/lib/ayril/xml_element/element_style_manipulation.rb +11 -0
- data/lib/ayril/xml_element/xml_attribute_hash.rb +60 -0
- data/lib/ayril/xml_element/xml_css_hash.rb +47 -0
- data/lib/ayril/xml_node.rb +118 -0
- data/lib/ayril/xml_node/node_manipulation.rb +73 -0
- data/lib/ayril/xml_node/node_traversal.rb +158 -0
- data/test/invoke_ayril_selector.rb +3 -0
- data/test/invoke_prototype_selector.js +4 -0
- data/test/sanity.xml +124 -0
- data/test/selector.js +521 -0
- data/test/test_sanity.rb +27 -0
- data/test/test_selector.rb +78 -0
- metadata +129 -0
@@ -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
|
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>
|