rspreadsheet 0.2.15 → 0.3
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.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.travis.yml +5 -4
- data/DEVEL_BLOG.md +18 -2
- data/GUIDE.md +5 -0
- data/Guardfile +3 -3
- data/README.md +9 -9
- data/lib/helpers/class_extensions.rb +101 -38
- data/lib/rspreadsheet.rb +2 -1
- data/lib/rspreadsheet/cell.rb +110 -19
- data/lib/rspreadsheet/image.rb +118 -0
- data/lib/rspreadsheet/row.rb +14 -369
- data/lib/rspreadsheet/tools.rb +20 -1
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +81 -20
- data/lib/rspreadsheet/worksheet.rb +31 -10
- data/lib/rspreadsheet/xml_tied_array.rb +179 -0
- data/lib/rspreadsheet/xml_tied_item.rb +111 -0
- data/lib/rspreadsheet/xml_tied_repeatable.rb +151 -0
- data/reinstall_local_gem.sh +2 -2
- data/rspreadsheet.gemspec +15 -3
- data/spec/cell_spec.rb +82 -1
- data/spec/class_extensions_spec.rb +49 -0
- data/spec/image_spec.rb +104 -0
- data/spec/included_image_spec.rb +8 -0
- data/spec/io_spec.rb +39 -27
- data/spec/row_spec.rb +16 -1
- data/spec/rspreadsheet_spec.rb +94 -35
- data/spec/spec_helper.rb +0 -1
- data/spec/test-image-blue.png +0 -0
- data/spec/test-image.png +0 -0
- data/spec/testfile1.ods +0 -0
- data/spec/testfile2-images.ods +0 -0
- data/spec/workbook_spec.rb +27 -0
- data/spec/worksheet_spec.rb +9 -2
- metadata +20 -47
- data/investigate.rb +0 -19
- data/lib/rspreadsheet/xml_tied.rb +0 -253
data/lib/rspreadsheet/tools.rb
CHANGED
@@ -117,7 +117,8 @@ module Tools
|
|
117
117
|
'loext'=>"urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
|
118
118
|
'field'=>"urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
|
119
119
|
'formx'=>"urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0",
|
120
|
-
'css3t'=>"http://www.w3.org/TR/css3-text/"
|
120
|
+
'css3t'=>"http://www.w3.org/TR/css3-text/",
|
121
|
+
'manifest'=>"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
|
121
122
|
}
|
122
123
|
if @pomnode.nil?
|
123
124
|
@pomnode = LibXML::XML::Node.new('xxx')
|
@@ -148,6 +149,7 @@ module Tools
|
|
148
149
|
end
|
149
150
|
def self.get_ns_attribute(node,ns_prefix,key,default=:undefined_default)
|
150
151
|
if default==:undefined_default
|
152
|
+
raise 'Nil does not have any attributes' if node.nil?
|
151
153
|
node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
|
152
154
|
else
|
153
155
|
node.nil? ? default : node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key) || default
|
@@ -167,6 +169,23 @@ module Tools
|
|
167
169
|
def self.prepare_ns_node(ns_prefix,nodename,value=nil)
|
168
170
|
LibXML::XML::Node.new(nodename,value, Tools.get_namespace(ns_prefix))
|
169
171
|
end
|
172
|
+
def self.insert_as_first_node_child(node,subnode)
|
173
|
+
if node.first?
|
174
|
+
node.first.prev = subnode
|
175
|
+
else
|
176
|
+
node << subnode
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
def self.get_unused_filename(zip,prefix, extension)
|
182
|
+
(1000..9999).each do |ndx|
|
183
|
+
filename = prefix + ndx.to_s + ((Time.now.to_r*1000000000).to_i.to_s(16)) + extension
|
184
|
+
return filename if zip.find_entry(filename).nil?
|
185
|
+
end
|
186
|
+
raise 'Could not get unused filename within sane times of iterations'
|
187
|
+
end
|
188
|
+
|
170
189
|
end
|
171
190
|
|
172
191
|
end
|
data/lib/rspreadsheet/version.rb
CHANGED
@@ -9,15 +9,16 @@ class Workbook
|
|
9
9
|
|
10
10
|
#@!group Worskheets methods
|
11
11
|
def create_worksheet_from_node(source_node)
|
12
|
-
sheet = Worksheet.new(source_node)
|
12
|
+
sheet = Worksheet.new(source_node,self)
|
13
13
|
register_worksheet(sheet)
|
14
14
|
return sheet
|
15
15
|
end
|
16
16
|
def create_worksheet(name = "Sheet#{worksheets_count+1}")
|
17
|
-
sheet = Worksheet.new(name)
|
17
|
+
sheet = Worksheet.new(name,self)
|
18
18
|
register_worksheet(sheet)
|
19
19
|
return sheet
|
20
20
|
end
|
21
|
+
alias :add_worksheet :create_worksheet
|
21
22
|
# @return [Integer] number of sheets in the workbook
|
22
23
|
def worksheets_count; @worksheets.length end
|
23
24
|
# @return [String] names of sheets in the workbook
|
@@ -28,10 +29,10 @@ class Workbook
|
|
28
29
|
case index_or_name
|
29
30
|
when Integer then begin
|
30
31
|
case index_or_name
|
31
|
-
when 0 then nil
|
32
|
+
when 0 then nil
|
32
33
|
when 1..Float::INFINITY then @worksheets[index_or_name-1]
|
33
34
|
when -Float::INFINITY..-1 then @worksheets[index_or_name] # zaporne indexy znamenaji pocitani zezadu
|
34
|
-
end
|
35
|
+
end
|
35
36
|
end
|
36
37
|
when String then @worksheets.select{|ws| ws.name == index_or_name}.first
|
37
38
|
when NilClass then nil
|
@@ -42,47 +43,107 @@ class Workbook
|
|
42
43
|
alias :sheet :worksheets
|
43
44
|
alias :sheets :worksheets
|
44
45
|
def [](index_or_name); self.worksheets(index_or_name) end
|
45
|
-
#@!group Loading and saving related methods
|
46
|
+
#@!group Loading and saving related methods
|
47
|
+
|
48
|
+
# @return Mime of the file
|
49
|
+
def mime; 'application/vnd.oasis.opendocument.spreadsheet'.freeze end
|
50
|
+
# @return [String] Prefered file extension
|
51
|
+
def mime_preferred_extension; 'ods'.freeze end
|
52
|
+
alias :mime_default_extension :mime_preferred_extension
|
53
|
+
|
46
54
|
def initialize(afilename=nil)
|
47
55
|
@worksheets=[]
|
48
56
|
@filename = afilename
|
49
|
-
@content_xml = Zip::File.open(@filename ||
|
50
|
-
LibXML::XML::Document.io zip.get_input_stream(
|
57
|
+
@content_xml = Zip::File.open(@filename || TEMPLATE_FILE) do |zip|
|
58
|
+
LibXML::XML::Document.io zip.get_input_stream(CONTENT_FILE_NAME)
|
51
59
|
end
|
52
60
|
@xmlnode = @content_xml.find_first('//office:spreadsheet')
|
53
61
|
@xmlnode.find('./table:table').each do |node|
|
54
62
|
create_worksheet_from_node(node)
|
55
63
|
end
|
56
64
|
end
|
65
|
+
|
57
66
|
# @param [String] Optional new filename
|
58
67
|
# Saves the worksheet. Optionally you can provide new filename.
|
68
|
+
|
59
69
|
def save(new_filename_or_io_object=nil)
|
60
|
-
|
70
|
+
@par = new_filename_or_io_object
|
71
|
+
if @filename.nil? and @par.nil? then raise 'New file should be named on first save.' end
|
61
72
|
|
62
|
-
if
|
63
|
-
|
64
|
-
elsif
|
73
|
+
if @par.kind_of? StringIO
|
74
|
+
@par.write(@content_xml.to_s(indent: false))
|
75
|
+
elsif @par.nil? or @par.kind_of? String
|
65
76
|
|
66
|
-
if
|
77
|
+
if @par.kind_of? String # the filename has changed
|
67
78
|
# first copy the original file to new location (or template if it is a new file)
|
68
|
-
FileUtils.cp(@filename || File.dirname(__FILE__)+'/empty_file_template.ods',
|
69
|
-
@filename =
|
79
|
+
FileUtils.cp(@filename || File.dirname(__FILE__)+'/empty_file_template.ods', @par)
|
80
|
+
@filename = @par
|
70
81
|
end
|
82
|
+
|
83
|
+
|
71
84
|
Zip::File.open(@filename) do |zip|
|
72
|
-
#
|
85
|
+
# open manifest
|
86
|
+
@manifest_xml = LibXML::XML::Document.io zip.get_input_stream('META-INF/manifest.xml')
|
87
|
+
|
88
|
+
# save all pictures - iterate through sheets and pictures and check if they are saved and if not, save them
|
89
|
+
@worksheets.each do |sheet|
|
90
|
+
sheet.images.each do |image|
|
91
|
+
# check if it is saved
|
92
|
+
@ifname = image.internal_filename
|
93
|
+
if @ifname.nil? or zip.find_entry(@ifname).nil?
|
94
|
+
# if it does not have name -> make up unused name
|
95
|
+
if @ifname.nil?
|
96
|
+
@ifname = image.internal_filename = Rspreadsheet::Tools.get_unused_filename(zip,'Pictures/',File.extname(image.original_filename))
|
97
|
+
end
|
98
|
+
raise 'Could not set up internal_filename correctly.' if @ifname.nil?
|
99
|
+
|
100
|
+
# save it to zip file
|
101
|
+
zip.add(@ifname, image.original_filename)
|
102
|
+
|
103
|
+
# make sure it is in manifest
|
104
|
+
if @manifest_xml.find("//manifest:file-entry[@manifest:full-path='#{@ifname}']").empty?
|
105
|
+
node = Tools.prepare_ns_node('manifest','file-entry')
|
106
|
+
Tools.set_ns_attribute(node,'manifest','full-path',@ifname)
|
107
|
+
Tools.set_ns_attribute(node,'manifest','media-type',image.mime)
|
108
|
+
@manifest_xml.find_first("//manifest:manifest") << node
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
73
114
|
zip.get_output_stream('content.xml') do |f|
|
74
115
|
f.write @content_xml.to_s(:indent => false)
|
75
116
|
end
|
117
|
+
|
118
|
+
zip.get_output_stream('META-INF/manifest.xml') do |f|
|
119
|
+
f.write @manifest_xml.to_s
|
120
|
+
end
|
76
121
|
end
|
77
122
|
end
|
78
123
|
end
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
124
|
+
|
125
|
+
# Saves the worksheet to IO stream.
|
126
|
+
def save_to_io(io = ::StringIO.new)
|
127
|
+
::Zip::OutputStream.write_buffer(io) do |output|
|
128
|
+
::Zip::File.open(TEMPLATE_FILE) do |input|
|
129
|
+
input.
|
130
|
+
select { |entry| entry.file? }.
|
131
|
+
select { |entry| entry.name != CONTENT_FILE_NAME }.
|
132
|
+
each do |entry|
|
133
|
+
output.put_next_entry(entry.name)
|
134
|
+
output.write(entry.get_input_stream.read)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
output.put_next_entry(CONTENT_FILE_NAME)
|
139
|
+
output.write(@content_xml.to_s(indent: false))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
alias :to_io :save_to_io
|
84
143
|
|
85
144
|
private
|
145
|
+
CONTENT_FILE_NAME = 'content.xml'
|
146
|
+
TEMPLATE_FILE = (File.dirname(__FILE__)+'/empty_file_template.ods').freeze
|
86
147
|
def register_worksheet(worksheet)
|
87
148
|
index = worksheets_count+1
|
88
149
|
@worksheets[index-1]=worksheet
|
@@ -1,17 +1,19 @@
|
|
1
1
|
require 'rspreadsheet/row'
|
2
2
|
require 'rspreadsheet/column'
|
3
|
+
require 'rspreadsheet/image'
|
3
4
|
require 'rspreadsheet/tools'
|
5
|
+
require 'helpers/class_extensions'
|
4
6
|
# require 'forwardable'
|
5
7
|
|
6
8
|
module Rspreadsheet
|
7
9
|
|
8
10
|
class Worksheet
|
9
|
-
include
|
11
|
+
include XMLTiedArray_WithRepeatableItems
|
10
12
|
attr_accessor :xmlnode
|
11
13
|
def subitem_xml_options; {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'} end
|
12
14
|
|
13
|
-
def initialize(xmlnode_or_sheet_name)
|
14
|
-
|
15
|
+
def initialize(xmlnode_or_sheet_name,workbook) # workbook is here ONLY because of inserting images - to find unique name - it would be much better if it should bot be there
|
16
|
+
initialize_xml_tied_array
|
15
17
|
# set up the @xmlnode according to parameter
|
16
18
|
case xmlnode_or_sheet_name
|
17
19
|
when LibXML::XML::Node
|
@@ -29,31 +31,50 @@ class Worksheet
|
|
29
31
|
def name=(value); Tools.set_ns_attribute(@xmlnode,'table','name', value) end
|
30
32
|
|
31
33
|
def rowxmlnode(rowi)
|
32
|
-
|
34
|
+
my_subnode(rowi)
|
33
35
|
end
|
34
36
|
|
35
37
|
def first_unused_row_index
|
36
|
-
|
38
|
+
first_unused_subitem_index
|
37
39
|
end
|
38
40
|
|
39
41
|
def add_row_above(arowi)
|
40
|
-
|
42
|
+
insert_new_empty_subitem_before(arowi)
|
41
43
|
end
|
42
44
|
|
43
|
-
def insert_cell_before(arowi,acoli)
|
45
|
+
def insert_cell_before(arowi,acoli) # TODO: maybe move this to row level
|
44
46
|
detach_row_in_xml(arowi)
|
45
|
-
rows(arowi).
|
47
|
+
rows(arowi).insert_new_item(acoli)
|
46
48
|
end
|
47
49
|
|
48
50
|
def detach_row_in_xml(rowi)
|
49
|
-
return detach_my_subnode_respect_repeated(rowi
|
51
|
+
return detach_my_subnode_respect_repeated(rowi)
|
50
52
|
end
|
51
53
|
|
52
54
|
def nonemptycells
|
53
55
|
used_rows_range.collect{ |rowi| rows(rowi).nonemptycells }.flatten
|
54
56
|
end
|
55
57
|
|
56
|
-
#@!group
|
58
|
+
#@!group images
|
59
|
+
def worksheet_images
|
60
|
+
@worksheet_images ||= WorksheetImages.new(self)
|
61
|
+
end
|
62
|
+
def images_count
|
63
|
+
worksheet_images.size
|
64
|
+
end
|
65
|
+
def images(*params)
|
66
|
+
worksheet_images.subitems(*params)
|
67
|
+
end
|
68
|
+
def insert_image(filename,mime='image/png')
|
69
|
+
worksheet_images.insert_image(filename,mime)
|
70
|
+
end
|
71
|
+
def insert_image_to(x,y,filename,mime='image/png')
|
72
|
+
img = insert_image(filename,mime)
|
73
|
+
img.move_to(x,y)
|
74
|
+
img
|
75
|
+
end
|
76
|
+
|
77
|
+
#@!group XMLTiedArray_WithRepeatableItems connected methods
|
57
78
|
def rows(*params); subitems(*params) end
|
58
79
|
alias :row :rows
|
59
80
|
def prepare_subitem(rowi); Row.new(self,rowi) end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'helpers/class_extensions'
|
2
|
+
|
3
|
+
module Rspreadsheet
|
4
|
+
|
5
|
+
using ClassExtensions if RUBY_VERSION > '2.1'
|
6
|
+
|
7
|
+
# @private
|
8
|
+
class XMLTied
|
9
|
+
def xml
|
10
|
+
xmlnode.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Abstract class representing and array which is tied to a particular element of XML file.
|
15
|
+
# It uses cashing to make access to array more effective. Implements the following methods:
|
16
|
+
#
|
17
|
+
# * subitems(index) - returns subitem object on index
|
18
|
+
# * subitems - returns array of all subitems. Please note that first item is always nil so
|
19
|
+
# the array can be accessed using 1-based indexes.
|
20
|
+
#
|
21
|
+
# Importer must provide:
|
22
|
+
#
|
23
|
+
# * prepare_subitem(aindex) - must return newly created object representing item on aindex
|
24
|
+
# * delete - ???
|
25
|
+
# * xmlnode - must return xmlnode to which the array is tied. If speed is not a concern,
|
26
|
+
# consider not cashing it into variable, but finding it through document or parent.
|
27
|
+
# This prevents "broken" links. Sometimes when array is empty, the node does note
|
28
|
+
# necessarily exists. That is fine, XMLTiedArray behaves correctly even with nil xmlnode,
|
29
|
+
# of course util you want to insert something. If this may happens, importer must
|
30
|
+
# provide method prepare_empty_xmlnode which prepares (and returns) empty xml node.
|
31
|
+
# It is lazy called, as late as possible.
|
32
|
+
# * subitem_xml_options - returns hash of options used to locate subitems in xml (TODO: rewrite in clear way)
|
33
|
+
# * intilize must call initialize_xml_tied_array
|
34
|
+
#
|
35
|
+
# Terminology
|
36
|
+
# * item, subitem is object from @itemcache (quite often subclass of XMLTiedItem)
|
37
|
+
# * node, subnode is LibXML::XML::Node object
|
38
|
+
#
|
39
|
+
# this class is made to be included.
|
40
|
+
#
|
41
|
+
# Note for developers:
|
42
|
+
# Beware that the implementation of methods needs to be done in a way that it continues to
|
43
|
+
# work when items are "repeatable" - see XMLTiedArray_WithRepeatableItems. When impractical or impossible
|
44
|
+
# please implement the corresponding method in XMLTiedArray_WithRepeatableItems or at least override it there
|
45
|
+
# and make it raise exception.
|
46
|
+
#
|
47
|
+
# @private
|
48
|
+
module XMLTiedArray
|
49
|
+
attr_reader :itemcache
|
50
|
+
|
51
|
+
def initialize_xml_tied_array
|
52
|
+
@itemcache = Hash.new
|
53
|
+
end
|
54
|
+
|
55
|
+
# @!group accessing items
|
56
|
+
|
57
|
+
# Returns item with index aindex
|
58
|
+
def subitem(aindex)
|
59
|
+
aindex = aindex.to_i
|
60
|
+
if aindex.to_i<=0
|
61
|
+
raise 'Item index should be greater then 0' if Rspreadsheet.raise_on_negative_coordinates
|
62
|
+
nil
|
63
|
+
else
|
64
|
+
@itemcache[aindex] ||= prepare_subitem(aindex)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def last
|
69
|
+
subitem(size)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns an array of subitems (when called without parameter) or an item on paricular index (when called with parameter).
|
73
|
+
def subitems(*params)
|
74
|
+
case params.length
|
75
|
+
when 0 then subitems_array
|
76
|
+
when 1 then subitem(params[0])
|
77
|
+
else raise Exception.new('Wrong number of arguments.')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns array of subitems (repeated friendly)
|
82
|
+
def subitems_array
|
83
|
+
(1..self.size).collect do |i|
|
84
|
+
subitem(i)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Number of subitems
|
89
|
+
def size; first_unused_subitem_index-1 end
|
90
|
+
alias :lenght :size
|
91
|
+
|
92
|
+
# Finds first unused subitem index
|
93
|
+
def first_unused_subitem_index
|
94
|
+
(1 + xmlsubnodes.sum { |node| how_many_times_node_is_repeated(node) }).to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
# @!group inserting new items
|
98
|
+
# Inserts empty subitem at the index position. Item currently on this position and all items
|
99
|
+
# after are shifter by index one.
|
100
|
+
def insert_new_item(aindex)
|
101
|
+
@itemcache.keys.sort.reverse.select{|i| i>=aindex }.each do |i|
|
102
|
+
@itemcache[i+1]=@itemcache.delete(i)
|
103
|
+
@itemcache[i+1]._shift_by(1)
|
104
|
+
end
|
105
|
+
insert_new_empty_subnode_before(aindex) # nyní vlož node do xml
|
106
|
+
@itemcache[aindex] = subitem(aindex)
|
107
|
+
end
|
108
|
+
alias :insert_new_empty_subitem_before :insert_new_item
|
109
|
+
|
110
|
+
def push_new
|
111
|
+
insert_new_item(first_unused_subitem_index)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @!group other subitems methods
|
115
|
+
# This is used (i.e. in first_unused_subitem_index) so it is flexible and can be reused in XMLTiedArray_WithRepeatableItems
|
116
|
+
# @private
|
117
|
+
def how_many_times_node_is_repeated(node); 1 end
|
118
|
+
|
119
|
+
# # @!supergroup XML STRUCTURE internal handling methods #######################################
|
120
|
+
|
121
|
+
# # @!group accessing subnodes
|
122
|
+
# returns xmlnode with index
|
123
|
+
# DOES not respect repeated_attribute
|
124
|
+
def my_subnode(aindex)
|
125
|
+
raise 'Using method which does not respect repeated_attribute with options that are using it. You probably donot want to do that.' unless subitem_xml_options[:xml_repeated_attribute].nil?
|
126
|
+
return xmlsubnodes[aindex-1]
|
127
|
+
end
|
128
|
+
|
129
|
+
# @!group inserting new subnodes TODO: refactor out repeatable connected code
|
130
|
+
def insert_new_empty_subnode_before(aindex)
|
131
|
+
node_after = my_subnode(aindex)
|
132
|
+
|
133
|
+
if !node_after.nil?
|
134
|
+
node_after.prev = prepare_empty_subnode
|
135
|
+
return node_after.prev
|
136
|
+
elsif aindex==size+1
|
137
|
+
# check whether xmlnode is ready for insetion
|
138
|
+
if xmlnode.nil?
|
139
|
+
prepare_empty_xmlnode
|
140
|
+
if xmlnode.nil?
|
141
|
+
raise 'Attempted call prepare_empty_xmlnode, but it did not created xmlnode correctly (it is still nil).'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
# do the insertion
|
145
|
+
xmlnode << prepare_empty_subnode
|
146
|
+
return xmlnode.last
|
147
|
+
else
|
148
|
+
raise IndexError.new("Index #{aindex} out of bounds (1..#{self.size})")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def prepare_empty_subnode
|
153
|
+
Tools.prepare_ns_node(
|
154
|
+
subitem_xml_options[:xml_items_node_namespace] || 'table',
|
155
|
+
subitem_xml_options[:xml_items_node_name]
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
# @!supergroup internal procedures dealing solely with xml structure ==========
|
160
|
+
|
161
|
+
# importer must provide this only if it may happen that xmlnode is empty AND we will want to insert subitems
|
162
|
+
def prepare_empty_xmlnode
|
163
|
+
raise 'xmlnode is empty and I do not know how to create empty xmlnode. Please provide prepare_empty_xmlnode method in your object.'
|
164
|
+
end
|
165
|
+
|
166
|
+
# @!group finding and accessing subnodes
|
167
|
+
# array containing subnodes of xmlnode which represent subitems
|
168
|
+
def xmlsubnodes
|
169
|
+
return [] if xmlnode.nil?
|
170
|
+
so = subitem_xml_options[:xml_items_node_name]
|
171
|
+
|
172
|
+
xmlnode.elements.select do |node|
|
173
|
+
node.andand.name == so
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|