module Rspreadsheet::XMLTiedArray
Abstract class representing and array which is tied to a particular element of XML file. It uses cashing to make access to array more effective. Implements the following methods: * subitems(index) - returns subitem object on index * subitems - returns array of all subitems. Please note that first item is always nil so the array can be accessed using 1-based indexes. Importer must provide: * prepare_subitem(aindex) - must return newly created object representing item on aindex * delete - ??? * xmlnode - must return xmlnode to which the array is tied. If speed is not a concern, consider not cashing it into variable, but finding it through document or parent. This prevents "broken" links. Sometimes when array is empty, the node does note necessarily exists. That is fine, XMLTiedArray behaves correctly even with nil xmlnode, of course util you want to insert something. If this may happens, importer must provide method prepare_empty_xmlnode which prepares (and returns) empty xml node. It is lazy called, as late as possible. * subnode_options - returns hash of options used to locate subitems in xml with these values * subnode_options[:node_name] - how the relevant subitems are named (string) * subnode_options[:alt_node_names] - array of strings of alternative names to :node_name these are recognized in searching, but never created when creating new node. * subnode_options[:node_namespace] - namespace of relevant subitems (defaults to table) * subnode_options[:repeated_attribute] - attribute of elements which tell how many times this is repeated (this is only used in XMLTiedArray_WithRepeatableItems) * subnode_options[:ignore_groupings] - some subnodes can rather be groups of subnodes, these groups need to be expanded and nodes put out of them * intilize must call initialize_xml_tied_array
Notes for developers¶ ↑
* This class is made to be included. * Terminology * item, subitem is object from @itemcache (quite often subclass of XMLTiedItem) * node, subnode is LibXML::XML::Node object * usual flow is that when user asks for an item the proxy item object is created (prepare_item) which only contains index etx, but no values. When the values are needed, it asks its parent to get the xmlnode, it first uses xmlsubnodes method to get all sumbodes and then by respecting the repeating finds apropriate node. * Beware that the implementation of methods needs to be done in a way that it continues to work when items are "repeatable" - see XMLTiedArray_WithRepeatableItems. When impractical or impossible please implement the corresponding method in XMLTiedArray_WithRepeatableItems or at least override it there and make it raise exception. @private
Attributes
Public Instance Methods
Finds first unused subitem index
# File lib/rspreadsheet/xml_tied_array.rb, line 104 def first_unused_subitem_index (1 + xmlsubnodes.sum { |node| how_many_times_node_is_repeated(node) }).to_i end
@!group other subitems methods This is used (i.e. in first_unused_subitem_index
) so it is flexible and can be reused in XMLTiedArray_WithRepeatableItems
@private
# File lib/rspreadsheet/xml_tied_array.rb, line 128 def how_many_times_node_is_repeated(node); 1 end
# File lib/rspreadsheet/xml_tied_array.rb, line 61 def initialize_xml_tied_array @itemcache = Hash.new end
@!group inserting new subnodes TODO: refactor out repeatable connected code
# File lib/rspreadsheet/xml_tied_array.rb, line 141 def insert_new_empty_subnode_before(aindex) node_after = my_subnode(aindex) if !node_after.nil? node_after.prev = prepare_empty_subnode return node_after.prev elsif aindex==size+1 # check whether xmlnode is ready for insetion if xmlnode.nil? prepare_empty_xmlnode if xmlnode.nil? raise 'Attempted call prepare_empty_xmlnode, but it did not created xmlnode correctly (it is still nil).' end end # do the insertion xmlnode << prepare_empty_subnode return xmlnode.last else raise IndexError.new("Index #{aindex} out of bounds (1..#{self.size})") end end
@!group inserting new items
Inserts empty subitem at the index position. Item currently on this position and all items after are shifter by index one.
# File lib/rspreadsheet/xml_tied_array.rb, line 111 def insert_new_item(aindex) @itemcache.keys.sort.reverse.select{|i| i>=aindex }.each do |i| @itemcache[i+1]=@itemcache.delete(i) @itemcache[i+1]._shift_by(1) end insert_new_empty_subnode_before(aindex) # nyní vlož node do xml @itemcache[aindex] = subitem(aindex) end
# File lib/rspreadsheet/xml_tied_array.rb, line 78 def last subitem(size) end
# @!group accessing subnodes
returns xmlnode with index does NOT respect repeated_attribute
# File lib/rspreadsheet/xml_tied_array.rb, line 135 def my_subnode(aindex) raise 'Using method which does not respect repeated_attribute with options that are using it. You probably donot want to do that.' unless subnode_options[:repeated_attribute].nil? return xmlsubnodes[aindex-1] end
# File lib/rspreadsheet/xml_tied_array.rb, line 162 def prepare_empty_subnode Tools.prepare_ns_node( subnode_options[:node_namespace] || 'table', subnode_options[:node_name] ) end
importer must provide this only if it may happen that xmlnode is empty AND we will want to insert subitems
# File lib/rspreadsheet/xml_tied_array.rb, line 172 def prepare_empty_xmlnode raise 'xmlnode is empty and I do not know how to create empty xmlnode. Please provide prepare_empty_xmlnode method in your object.' end
# File lib/rspreadsheet/xml_tied_array.rb, line 121 def push_new insert_new_item(first_unused_subitem_index) end
Number of subitems
# File lib/rspreadsheet/xml_tied_array.rb, line 99 def size; first_unused_subitem_index-1 end
Returns item with index aindex
# File lib/rspreadsheet/xml_tied_array.rb, line 68 def subitem(aindex) aindex = aindex.to_i if aindex.to_i<=0 raise 'Item index should be greater then 0' if Rspreadsheet.raise_on_negative_coordinates nil else @itemcache[aindex] ||= prepare_subitem(aindex) end end
Returns an array of subitems (when called without parameter) or an item on paricular index (when called with parameter).
# File lib/rspreadsheet/xml_tied_array.rb, line 83 def subitems(*params) case params.length when 0 then subitems_array when 1 then subitem(params[0]) else raise ArgumentError.new('Wrong number of arguments.') end end
Returns array of subitems (repeated friendly)
# File lib/rspreadsheet/xml_tied_array.rb, line 92 def subitems_array (1..self.size).collect do |i| subitem(i) end end
@!group finding and accessing subnodes array containing subnodes of xmlnode which represent subitems
# File lib/rspreadsheet/xml_tied_array.rb, line 178 def xmlsubnodes axmlnode = self.xmlnode return [] if axmlnode.nil? node_name = subnode_options[:node_name] alt_node_names = subnode_options[:alt_node_names] || [] ignore_groupings = subnode_options[:ignore_groupings] || [] result = [] axmlnode.children.each do |node| if ignore_groupings.include?(node.andand.name) node.children.each do |subnode| result << subnode end else result << node end end result.select do |node| node.element? && # nejde o textový node ( (node_name == node.andand.name) || # a jde o node s pořadovaným názvem alt_node_names.include?(node.andand.name) ) # nebo s alternativním přípustným názvem end end