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, BooleanNode, BooleanValue, Border, BorderContainer, BorderEdge, Break, BreakList, CalculationChain, CalculationChainCell, CalculationProperties, Cell, CellStyle, CellStyleContainer, CellStyleXFContainer, CellValue, CellXFContainer, Color, ColorScale, ColorScheme, ColorSet, Colors, ColumnRange, ColumnRanges, ConditionalFormatValue, ConditionalFormatting, ConditionalFormattingRule, DXF, DXFs, DataBar, DataValidation, DataValidations, DefinedName, DefinedNames, DocumentProperties, ExtensionStorageArea, ExternalReference, ExternalReferences, FileSharing, FileVersion, Fill, FillContainer, FloatNode, FloatValue, Font, FontContainer, FontScheme, FormatScheme, Formula, GradientFill, HeaderFooterSettings, Hyperlink, HyperlinkContainer, IconSet, IgnoredError, IgnoredErrorContainer, InputCells, IntegerNode, IntegerValue, MergedCell, MergedCells, NumFmt, NumberFormat, NumberFormatContainer, OutlineProperties, PageMargins, PageSetup, PageSetupProperties, Pane, PatternFill, PhoneticProperties, PhoneticRun, PrintOptions, ProtectedRange, ProtectedRanges, Protection, RID, RawOOXML, Relationship, RichText, RichTextRun, Row, RunProperties, Scenario, ScenarioContainer, Selection, SharedStringsTable, Sheet, SheetCalculationProperties, SheetData, SheetProtection, SheetView, SheetViews, Sheets, SortCondition, SortState, Stop, StringNode, StringValue, Stylesheet, TableParts, TableStyle, TableStyles, Text, Theme, ThemeElements, Variant, Vector, VectorValue, Workbook, WorkbookProperties, WorkbookProtection, WorkbookRelationships, WorkbookView, WorkbookViews, Worksheet, WorksheetDimensions, WorksheetFormatProperties, WorksheetProperties, XF

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params = {}) ⇒ OOXMLObject

Returns a new instance of OOXMLObject.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/rubyXL/objects/ooxml_object.rb', line 156

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)

  • 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.

    • :values - List of acceptable values for this attribute (curently not used).

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”



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

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

  attrs[attr_name] = {
    :accessor   => accessor,
    :attr_type  => attr_type,
    :optional   => !extra_params[:required], 
    :default    => extra_params[:default],
    :valies     => extra_params[:values]
  }

  self.send(:attr_accessor, accessor)
end

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



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/rubyXL/objects/ooxml_object.rb', line 73

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(v) ⇒ Object



91
92
93
# File 'lib/rubyXL/objects/ooxml_object.rb', line 91

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

.filepathObject



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

def self.filepath
  raise 'Subclass responsebility'
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.



18
19
20
21
22
23
24
# File 'lib/rubyXL/objects/ooxml_object.rb', line 18

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



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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
# File 'lib/rubyXL/objects/ooxml_object.rb', line 189

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

.parse_file(dirpath) ⇒ Object



279
280
281
282
283
# File 'lib/rubyXL/objects/ooxml_object.rb', line 279

def self.parse_file(dirpath)
  full_path = File.join(dirpath, filepath)
  return nil unless File.exist?(full_path)
  parse(File.open(full_path, 'r'))
end

.set_countableObject



95
96
97
98
# File 'lib/rubyXL/objects/ooxml_object.rb', line 95

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

.set_namespaces(namespace_hash) ⇒ Object

Sets the list of namespaces on this object to be added when writing out XML. Valid only on top-level objects.

Parameters

  • namespace_hash - Hash of namespaces in the form of "prefix" => "url"

Examples

set_namespaces('xmlns'   => 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
               'xmlns:r' => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships')


106
107
108
# File 'lib/rubyXL/objects/ooxml_object.rb', line 106

def self.set_namespaces(namespace_hash)
  self.class_variable_set(:@@ooxml_namespaces, namespace_hash)
end

Instance Method Details

#add_to_zip(zipfile) ⇒ Object



269
270
271
272
273
# File 'lib/rubyXL/objects/ooxml_object.rb', line 269

def add_to_zip(zipfile)
  xml_string = write_xml
  return if xml_string.empty?
  zipfile.get_output_stream(self.class.filepath) { |f| f << xml_string }
end

#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).



261
262
263
264
265
266
267
# File 'lib/rubyXL/objects/ooxml_object.rb', line 261

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



248
249
250
251
252
# File 'lib/rubyXL/objects/ooxml_object.rb', line 248

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

#index_in_collectionObject



254
255
256
# File 'lib/rubyXL/objects/ooxml_object.rb', line 254

def index_in_collection
  nil
end

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



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rubyXL/objects/ooxml_object.rb', line 110

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