Module: RubyXL::OOXMLObjectClassMethods

Defined in:
lib/rubyXL/objects/ooxml_object.rb

Instance Method Summary collapse

Instance 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

    • :uint - Unsigned Integer

    • :double - Float</u>

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

    • :computed - Do not store this attribute on parse, but do call the object-provided read accessor on write_xml.

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, %w{ stop warning information }, :default => 'stop',)

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


48
49
50
51
52
53
54
# File 'lib/rubyXL/objects/ooxml_object.rb', line 48

def define_attribute(attr_name, attr_type, extra_params = {})
  attrs = obtain_class_variable(:@@ooxml_attributes)
  attr_hash = extra_params.merge({ :attr_type => attr_type })
  attr_hash[:accessor] ||= accessorize(attr_name)
  attrs[attr_name.to_s] = attr_hash
  self.send(:attr_accessor, attr_hash[:accessor]) unless attr_hash[:computed]
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, :collection => 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


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

def 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
  }

  define_count_attribute if extra_params[:collection] == :with_count

  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'

104
105
106
# File 'lib/rubyXL/objects/ooxml_object.rb', line 104

def 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
# File 'lib/rubyXL/objects/ooxml_object.rb', line 15

def obtain_class_variable(var_name, default = {})
  self.class_variable_get(var_name)
rescue NameError
  self.class_variable_set(var_name, default)
end

#parse(node, known_namespaces = nil) ⇒ Object


108
109
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/rubyXL/objects/ooxml_object.rb', line 108

def parse(node, known_namespaces = nil)
  case node
  when String, IO, Zip::InputStream then node = Nokogiri::XML.parse(node)
  end

  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}] for element [#{node.name}]" if attr_params.nil?
    process_attribute(obj, attr.value, attr_params) unless attr_params[:computed]
  }

  known_child_nodes = obtain_class_variable(:@@ooxml_child_nodes)

  unless known_child_nodes.empty?
    known_namespaces ||= obtain_class_variable(:@@ooxml_namespaces)

    node.element_children.each { |child_node|

      ns = child_node.namespace
      prefix = known_namespaces[ns.href] || ns.prefix

      child_node_name = case prefix
                        when '', nil then child_node.name
                        else "#{prefix}:#{child_node.name}"
                        end

      child_node_params = known_child_nodes[child_node_name]
      raise "Unknown child node [#{child_node_name}] for element [#{node.name}]" if child_node_params.nil?
      parsed_object = child_node_params[:class].parse(child_node, known_namespaces)
      if child_node_params[:is_array] then
        index = parsed_object.index_in_collection

        collection = if (self < RubyXL::OOXMLContainerObject) then obj
                     else obj.send(child_node_params[:accessor])
                     end

        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