Class: RubyXL::OOXMLObject

Inherits:
Object
  • Object
show all
Defined in:
lib/rubyXL/objects/ooxml_object.rb

Overview

Parent class for defining OOXML based objects (not unlike Rails’ ActiveRecord!) Most importantly, provides functionality of parsing such objects from XML, and marshalling them to XML.

Direct Known Subclasses

AExtensionStorageArea, Alignment, AutoFilter, AutoFilterColumn, BooleanNode, BooleanValue, Border, BorderContainer, BorderEdge, Break, BreakList, CalculationChainCell, CalculationProperties, Cell, CellSmartTag, CellSmartTagContainer, CellSmartTagProperty, CellStyle, CellStyleContainer, CellStyleXFContainer, CellValue, CellWatch, CellWatchContainer, CellXFContainer, ChartsheetPageSetup, ChartsheetProperties, ChartsheetProtection, ChartsheetView, ChartsheetViewContainer, Color, ColorFilter, ColorScale, ColorScheme, ColorSet, Colors, ColumnRange, ColumnRanges, ConditionalFormatValue, ConditionalFormatting, ConditionalFormattingRule, CustomFilter, CustomFilterContainer, CustomProperty, CustomPropertyContainer, CustomSheetView, CustomSheetViews, CustomWorkbookView, CustomWorkbookViewContainer, DXF, DXFs, DataBar, DataConsolidate, DataConsolidationReference, DataConsolidationReferences, DataValidation, DataValidations, DateGroupItem, DefinedName, DefinedNames, DynamicFilter, EmbeddedControl, EmbeddedControlContainer, ExtensionStorageArea, ExternalReference, ExternalReferences, FieldItem, FileRecoveryProperties, FileSharing, FileVersion, Fill, FillContainer, FilterContainer, FloatNode, FloatValue, Font, FontContainer, FontScheme, FormatScheme, Formula, FunctionGroup, FunctionGroupContainer, GradientFill, HeaderFooterSettings, Hyperlink, HyperlinkContainer, IconFilter, IconSet, IgnoredError, IgnoredErrorContainer, IndexedColorContainer, InputCells, IntegerNode, IntegerValue, MRUColorContainer, MergedCell, MergedCells, NumFmt, NumberFormat, NumberFormatContainer, OLEObject, OLEObjects, OLESize, OOXMLTopLevelObject, OutlineProperties, PageMargins, PageSetup, PageSetupProperties, Pane, PatternFill, PhoneticProperties, PhoneticRun, PivotArea, PivotCache, PivotCaches, PivotReference, PivotReferenceContainer, PivotTableSelection, PrintOptions, ProtectedRange, ProtectedRanges, Protection, RID, RawOOXML, Relationship, RichText, RichTextRun, Row, RunProperties, Scenario, ScenarioContainer, Selection, Sheet, SheetCalculationProperties, SheetData, Sheets, SmartTagContainer, SmartTagProperties, SmartTagType, SmartTagTypeContainer, SortCondition, SortState, Stop, StringNode, StringValue, TableParts, TableStyle, TableStyles, Text, Theme, ThemeElements, Top10, Variant, Vector, VectorValue, WebPublishObject, WebPublishObjectContainer, WebPublishingItem, WebPublishingItemContainer, WebPublishingProperties, WorkbookProperties, WorkbookProtection, WorkbookView, WorkbookViews, WorksheetDimensions, WorksheetFormatProperties, WorksheetProperties, WorksheetProtection, WorksheetView, WorksheetViews, XF

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params = {}) ⇒ OOXMLObject

Returns a new instance of OOXMLObject.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rubyXL/objects/ooxml_object.rb', line 188

def initialize(params = {})
  obtain_class_variable(:@@ooxml_attributes).each_value { |v|
    instance_variable_set("@#{v[:accessor]}", params[v[:accessor]])
  }

  obtain_class_variable(:@@ooxml_child_nodes).each_value { |v|

    initial_value =
      if params.has_key?(v[:accessor]) then params[v[:accessor]]
      elsif v[:is_array] then []
      else nil
      end

    instance_variable_set("@#{v[:accessor]}", initial_value)
  }

  instance_variable_set("@count", 0) if obtain_class_variable(:@@ooxml_countable, false)
end

Class Method Details

.define_attribute(attr_name, attr_type, extra_params = {}) ⇒ Object

Defines an attribute of OOXML object.

Parameters

  • attribute_name - Name of the element attribute as seen in the source XML. Can be either "String" or :Symbol

    • Special attibute name '_' (underscore) denotes the value of the element rather than attribute.

  • attribute_type - Specifies the conversion type for the attribute when parsing. Available options are:

    • :int - Integer

    • :float - Float

    • :string - String (no conversion)

    • :sqref - RubyXL::Sqref

    • :ref - RubyXL::Reference

    • :bool - Boolean (“1” and “true” convert to true, others to false)

    • one of simple_types - String, plus the list of acceptable values is saved for future validation (not used yet).

  • extra_parameters - Hash of optional parameters as follows:

    • :accessor - Name of the accessor for this attribute to be defined on the object. If not provided, defaults to classidied attribute_name.

    • :default - Value this attribute defaults to if not explicitly provided.

    • :required - Whether this attribute is required when writing XML. If the value of the attrinute is not explicitly provided, :default is written instead.

Examples

define_attribute(:outline, :bool, :default => true)

A Boolean attribute ‘outline’ with default value true will be accessible by calling obj.outline

define_attribute(:uniqueCount,  :int)

An Integer attribute ‘uniqueCount’ accessible as obj.unique_count

define_attribute(:_,  :string, :accessor => :expression)

The value of the element will be accessible as a String by calling obj.expression

define_attribute(:errorStyle, :string, :default => 'stop', :values => %w{ stop warning information })

A String attribute named ‘errorStyle’ will be accessible as obj.error_style, valid values are "stop", "warning", "information"



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/rubyXL/objects/ooxml_object.rb', line 53

def self.define_attribute(attr_name, attr_type, extra_params = {})
  attrs = obtain_class_variable(:@@ooxml_attributes)

  accessor = extra_params[:accessor] || accessorize(attr_name)
  attr_name = attr_name.to_s

  attr_hash = {
    :accessor   => accessor,
    :attr_type  => attr_type,
    :optional   => !extra_params[:required], 
    :default    => extra_params[:default],
  }

  if attr_type.is_a?(Array) then
    attr_hash[:values] = attr_type
    attr_hash[:attr_type] = :string
  end


  attrs[attr_name] = attr_hash

  self.send(:attr_accessor, accessor)
end

.define_child_node(klass, extra_params = {}) ⇒ Object

Defines a child node of OOXML object.

Parameters

  • klass - Class (descendant of RubyXL::OOXMLObject) of the child nodes. Child node objects will be produced by calling parse method of that class.

  • extra_parameters - Hash of optional parameters as follows:

    • :accessor - Name of the accessor for this attribute to be defined on the object. If not provided, defaults to classidied attribute_name.

    • :node_name - Node name for the child node, in case it does not match the one defined by the klass.

    • :collection - Whether the child node should be treated as a single node or a collection of nodes:

      • false (default) - child node is directly accessible through the respective accessor;

      • true - a collection of child nodes is accessed as Array through the respective accessor;

      • :with_count - same as true, but in addition, the attribute count is defined on the current object, that will be automatically set to the number of elements in the collection at the start of write_xml call.

Examples

define_child_node(RubyXL::Alignment)

Define a singular child node parsed by the RubyXL::BorderEdge.parse() and accessed by the default obj.alignment accessor

define_child_node(RubyXL::Hyperlink, :colection => true, :accessor => :hyperlinks)

Define an array of nodes accessed by obj.hyperlinks accessor, each of which will be parsed by the RubyXL::Hyperlink.parse()

define_child_node(RubyXL::BorderEdge, :node_name => :left)
define_child_node(RubyXL::BorderEdge, :node_name => :right)

Use class RubyXL::BorderEdge when parsing both the elements <left ...> and <right ...> elements.

define_child_node(RubyXL::Font, :collection => :with_count, :accessor => :fonts)

Upon writing of the object this was defined on, its count attribute will be set to the count of nodes in fonts array



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/rubyXL/objects/ooxml_object.rb', line 97

def self.define_child_node(klass, extra_params = {})
  child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
  child_node_name = (extra_params[:node_name] || klass.class_variable_get(:@@ooxml_tag_name)).to_s
  accessor = (extra_params[:accessor] || accessorize(child_node_name)).to_sym

  child_nodes[child_node_name] = { 
    :class => klass,
    :is_array => extra_params[:collection],
    :accessor => accessor
  }

  if extra_params[:collection] == :with_count then
    define_attribute(:count, :int, :required => true)
  end

  self.send(:attr_accessor, accessor)
end

.define_element_name(element_name) ⇒ Object

Defines the name of the element that represents the current OOXML object. Should only be used once per object. In case of different objects represented by the same class in different parts of OOXML tree, :node_name extra parameter can be used to override the default element name.

Parameters

  • element_name

Examples

define_element_name 'externalReference'


122
123
124
# File 'lib/rubyXL/objects/ooxml_object.rb', line 122

def self.define_element_name(element_name)
  self.class_variable_set(:@@ooxml_tag_name, element_name)
end

.obtain_class_variable(var_name, default = {}) ⇒ Object

Get the value of a [sub]class variable if it exists, or create the respective variable with the passed-in default (or {}, if not specified)

Throughout this class, we are setting class variables through explicit method calls rather than by directly addressing the name of the variable because of context issues: addressing variable by name creates it in the context of defining class, while calling the setter/getter method addresses it in the context of descendant class, which is what we need.



15
16
17
18
19
20
21
# File 'lib/rubyXL/objects/ooxml_object.rb', line 15

def self.obtain_class_variable(var_name, default = {})
  if class_variable_defined?(var_name) then 
    self.class_variable_get(var_name)
  else
    self.class_variable_set(var_name, default)
  end
end

.parse(node) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/rubyXL/objects/ooxml_object.rb', line 207

def self.parse(node)
  node = Nokogiri::XML.parse(node) if node.is_a?(IO) || node.is_a?(String)

  if node.is_a?(Nokogiri::XML::Document) then
#        @namespaces = node.namespaces

    node = node.root
#        ignorable_attr = node.attributes['Ignorable']

#        @ignorables << ignorable_attr.value if ignorable_attr

  end

  obj = self.new

  known_attributes = obtain_class_variable(:@@ooxml_attributes)

  content_params = known_attributes['_']
  process_attribute(obj, node.text, content_params) if content_params

  node.attributes.each_pair { |attr_name, attr|
    attr_name = if attr.namespace then "#{attr.namespace.prefix}:#{attr.name}"
                else attr.name
                end

    attr_params = known_attributes[attr_name]

    next if attr_params.nil?
    # raise "Unknown attribute: #{attr_name}" if attr_params.nil?

    process_attribute(obj, attr.value, attr_params)
  }

  known_child_nodes = obtain_class_variable(:@@ooxml_child_nodes)

  unless known_child_nodes.empty?
    node.element_children.each { |child_node|

      child_node_name = if child_node.namespace.prefix then
                          "#{child_node.namespace.prefix}:#{child_node.name}"
                        else child_node.name 
                        end

      child_node_params = known_child_nodes[child_node_name]
      raise "Unknown child node: #{child_node_name}" if child_node_params.nil?
      parsed_object = child_node_params[:class].parse(child_node)
      if child_node_params[:is_array] then
        index = parsed_object.index_in_collection
        collection = obj.send(child_node_params[:accessor])
        if index.nil? then
          collection << parsed_object
        else
          collection[index] = parsed_object
        end
      else
        obj.send("#{child_node_params[:accessor]}=", parsed_object)
      end
    }
  end

  obj
end

.set_countableObject

#TODO# This method will eventually be obsoleted.



127
128
129
130
# File 'lib/rubyXL/objects/ooxml_object.rb', line 127

def self.set_countable
  self.class_variable_set(:@@ooxml_countable, true)
  self.send(:attr_accessor, :count)
end

Instance Method Details

#before_write_xmlObject

Subclass provided filter to perform last-minute operations (cleanup, count, etc.) immediately prior to write, along with option to terminate the actual write if false is returned (for example, to avoid writing the collection’s root node if the collection is empty).



282
283
284
285
286
287
288
# File 'lib/rubyXL/objects/ooxml_object.rb', line 282

def before_write_xml
  child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
  child_nodes.each_pair { |child_node_name, child_node_params|
    self.count = self.send(child_node_params[:accessor]).size if child_node_params[:is_array] == :with_count
  }
  true 
end

#dupObject



266
267
268
269
270
# File 'lib/rubyXL/objects/ooxml_object.rb', line 266

def dup
  new_copy = super
  new_copy.count = 0 if obtain_class_variable(:@@ooxml_countable, false)
  new_copy
end

#index_in_collectionObject

Prototype method. For sparse collections (Rows, Cells, etc.) must return index at which this object is expected to reside in the collection. If nil is returned, then object is simply added to the end of the collection.



275
276
277
# File 'lib/rubyXL/objects/ooxml_object.rb', line 275

def index_in_collection
  nil
end

#write_xml(xml = nil, node_name_override = nil) ⇒ Object

Recursively write the OOXML object and all its children out as Nokogiri::XML. Immediately before the actual generation, before_write_xml() is called to perform last-minute cleanup and validation operations; if it returns false, an empty string is returned (rather than nil, so Nokogiri::XML’s &lt;&lt; operator can be used without additional nil checking)

Parameters

  • xml - Base Nokogiri::XML object used for building. If omitted, a blank document will be generated.

  • node_name_override - if present, is used instead of the default element name for this object provided by define_element_name

Examples

obj.write_xml

Creates a new Nokogiti::XML and



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/rubyXL/objects/ooxml_object.rb', line 142

def write_xml(xml = nil, node_name_override = nil)
  if xml.nil? then
    seed_xml = Nokogiri::XML('<?xml version = "1.0" standalone ="yes"?>')
    seed_xml.encoding = 'UTF-8'
    result = self.write_xml(seed_xml)
    return result if result == ''
    seed_xml << result
    return seed_xml.to_xml({ :indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML })
  end

  return '' unless before_write_xml

  attrs = obtain_class_variable(:@@ooxml_namespaces).dup

  obtain_class_variable(:@@ooxml_attributes).each_pair { |k, v|
    val = self.send(v[:accessor])

    if val.nil? then
      next if v[:optional]
      val = v[:default]
    end

    val = val &&
            case v[:attr_type]
            when :bool  then val ? '1' : '0'
            when :float then val.to_s.gsub(/\.0*$/, '') # Trim trailing zeroes

            else val
            end

    attrs[k] = val
  }

  element_text = attrs.delete('_')
  elem = xml.create_element(node_name_override || obtain_class_variable(:@@ooxml_tag_name), attrs, element_text)
  child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
  child_nodes.each_pair { |child_node_name, child_node_params|
    obj = self.send(child_node_params[:accessor])
    unless obj.nil?
      if child_node_params[:is_array] then obj.each { |item| elem << item.write_xml(xml, child_node_name) unless item.nil? }
      else elem << obj.write_xml(xml, child_node_name)
      end
    end
  }
  elem
end