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