rubyXL 1.2.10 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. data/Gemfile +14 -10
  2. data/Gemfile.lock +80 -21
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +88 -82
  5. data/Rakefile +7 -2
  6. data/VERSION +1 -1
  7. data/lib/rubyXL.rb +13 -7
  8. data/lib/rubyXL/cell.rb +108 -268
  9. data/lib/rubyXL/generic_storage.rb +40 -0
  10. data/lib/rubyXL/objects/border.rb +66 -0
  11. data/lib/rubyXL/objects/calculation_chain.rb +28 -0
  12. data/lib/rubyXL/objects/cell_style.rb +75 -0
  13. data/lib/rubyXL/objects/color.rb +25 -0
  14. data/lib/rubyXL/objects/column_range.rb +74 -0
  15. data/lib/rubyXL/objects/container_nodes.rb +122 -0
  16. data/lib/rubyXL/objects/data_validation.rb +43 -0
  17. data/lib/rubyXL/objects/document_properties.rb +76 -0
  18. data/lib/rubyXL/objects/extensions.rb +36 -0
  19. data/lib/rubyXL/objects/fill.rb +57 -0
  20. data/lib/rubyXL/objects/font.rb +111 -0
  21. data/lib/rubyXL/objects/formula.rb +24 -0
  22. data/lib/rubyXL/objects/ooxml_object.rb +295 -0
  23. data/lib/rubyXL/objects/reference.rb +110 -0
  24. data/lib/rubyXL/objects/relationships.rb +59 -0
  25. data/lib/rubyXL/objects/shared_strings.rb +57 -0
  26. data/lib/rubyXL/objects/sheet_data.rb +149 -0
  27. data/lib/rubyXL/objects/sheet_view.rb +71 -0
  28. data/lib/rubyXL/objects/stylesheet.rb +200 -0
  29. data/lib/rubyXL/objects/text.rb +87 -0
  30. data/lib/rubyXL/objects/theme.rb +64 -0
  31. data/lib/rubyXL/objects/workbook.rb +233 -0
  32. data/lib/rubyXL/objects/worksheet.rb +485 -0
  33. data/lib/rubyXL/parser.rb +78 -442
  34. data/lib/rubyXL/workbook.rb +216 -385
  35. data/lib/rubyXL/worksheet.rb +509 -1062
  36. data/lib/rubyXL/writer/content_types_writer.rb +104 -68
  37. data/lib/rubyXL/writer/core_writer.rb +26 -43
  38. data/lib/rubyXL/writer/generic_writer.rb +43 -0
  39. data/lib/rubyXL/writer/root_rels_writer.rb +11 -19
  40. data/lib/rubyXL/writer/styles_writer.rb +6 -398
  41. data/lib/rubyXL/writer/theme_writer.rb +321 -327
  42. data/lib/rubyXL/writer/workbook_writer.rb +63 -67
  43. data/lib/rubyXL/writer/worksheet_writer.rb +29 -218
  44. data/rdoc/created.rid +39 -0
  45. data/rdoc/fonts.css +167 -0
  46. data/rdoc/fonts/Lato-Light.ttf +0 -0
  47. data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
  48. data/rdoc/fonts/Lato-Regular.ttf +0 -0
  49. data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
  50. data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
  51. data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
  52. data/rdoc/images/add.png +0 -0
  53. data/rdoc/images/arrow_up.png +0 -0
  54. data/rdoc/images/brick.png +0 -0
  55. data/rdoc/images/brick_link.png +0 -0
  56. data/rdoc/images/bug.png +0 -0
  57. data/rdoc/images/bullet_black.png +0 -0
  58. data/rdoc/images/bullet_toggle_minus.png +0 -0
  59. data/rdoc/images/bullet_toggle_plus.png +0 -0
  60. data/rdoc/images/date.png +0 -0
  61. data/rdoc/images/delete.png +0 -0
  62. data/rdoc/images/find.png +0 -0
  63. data/rdoc/images/loadingAnimation.gif +0 -0
  64. data/rdoc/images/macFFBgHack.png +0 -0
  65. data/rdoc/images/package.png +0 -0
  66. data/rdoc/images/page_green.png +0 -0
  67. data/rdoc/images/page_white_text.png +0 -0
  68. data/rdoc/images/page_white_width.png +0 -0
  69. data/rdoc/images/plugin.png +0 -0
  70. data/rdoc/images/ruby.png +0 -0
  71. data/rdoc/images/tag_blue.png +0 -0
  72. data/rdoc/images/tag_green.png +0 -0
  73. data/rdoc/images/transparent.png +0 -0
  74. data/rdoc/images/wrench.png +0 -0
  75. data/rdoc/images/wrench_orange.png +0 -0
  76. data/rdoc/images/zoom.png +0 -0
  77. data/rdoc/js/darkfish.js +140 -0
  78. data/rdoc/js/jquery.js +18 -0
  79. data/rdoc/js/navigation.js +142 -0
  80. data/rdoc/js/search.js +109 -0
  81. data/rdoc/js/search_index.js +1 -0
  82. data/rdoc/js/searcher.js +228 -0
  83. data/rdoc/rdoc.css +580 -0
  84. data/rubyXL.gemspec +90 -34
  85. data/spec/lib/cell_spec.rb +29 -59
  86. data/spec/lib/parser_spec.rb +35 -19
  87. data/spec/lib/reference_spec.rb +29 -0
  88. data/spec/lib/stylesheet_spec.rb +29 -0
  89. data/spec/lib/workbook_spec.rb +22 -17
  90. data/spec/lib/worksheet_spec.rb +47 -202
  91. metadata +185 -148
  92. data/lib/.DS_Store +0 -0
  93. data/lib/rubyXL/Hash.rb +0 -60
  94. data/lib/rubyXL/color.rb +0 -14
  95. data/lib/rubyXL/private_class.rb +0 -265
  96. data/lib/rubyXL/writer/app_writer.rb +0 -62
  97. data/lib/rubyXL/writer/calc_chain_writer.rb +0 -33
  98. data/lib/rubyXL/writer/shared_strings_writer.rb +0 -30
  99. data/lib/rubyXL/writer/workbook_rels_writer.rb +0 -59
  100. data/lib/rubyXL/zip.rb +0 -20
  101. data/spec/lib/hash_spec.rb +0 -28
@@ -0,0 +1,76 @@
1
+ require 'rubyXL/objects/ooxml_object'
2
+ require 'rubyXL/objects/container_nodes'
3
+
4
+ module RubyXL
5
+
6
+ # http://www.schemacentral.com/sc/ooxml/e-extended-properties_Properties.html
7
+ class DocumentProperties < OOXMLObject
8
+ attr_accessor :workbook
9
+
10
+ define_child_node(RubyXL::StringNode, :node_name => :Template)
11
+ define_child_node(RubyXL::StringNode, :node_name => :Manager)
12
+ define_child_node(RubyXL::StringNode, :node_name => :Company)
13
+ define_child_node(RubyXL::IntegerNode, :node_name => :Pages)
14
+ define_child_node(RubyXL::IntegerNode, :node_name => :Words)
15
+ define_child_node(RubyXL::IntegerNode, :node_name => :Characters)
16
+ define_child_node(RubyXL::StringNode, :node_name => :PresentationFormat)
17
+ define_child_node(RubyXL::IntegerNode, :node_name => :Lines)
18
+ define_child_node(RubyXL::IntegerNode, :node_name => :Paragraphs)
19
+ define_child_node(RubyXL::IntegerNode, :node_name => :Slides)
20
+ define_child_node(RubyXL::IntegerNode, :node_name => :Notes)
21
+ define_child_node(RubyXL::IntegerNode, :node_name => :TotalTime)
22
+ define_child_node(RubyXL::IntegerNode, :node_name => :HiddenSlides)
23
+ define_child_node(RubyXL::IntegerNode, :node_name => :MMClips)
24
+ define_child_node(RubyXL::BooleanNode, :node_name => :ScaleCrop)
25
+ define_child_node(RubyXL::VectorValue, :node_name => :HeadingPairs)
26
+ define_child_node(RubyXL::VectorValue, :node_name => :TitlesOfParts)
27
+ define_child_node(RubyXL::BooleanNode, :node_name => :LinksUpToDate)
28
+ define_child_node(RubyXL::IntegerNode, :node_name => :CharactersWithSpaces)
29
+ define_child_node(RubyXL::BooleanNode, :node_name => :SharedDoc)
30
+ define_child_node(RubyXL::StringNode, :node_name => :HyperlinkBase)
31
+ define_child_node(RubyXL::VectorValue, :node_name => :HLinks)
32
+ define_child_node(RubyXL::BooleanNode, :node_name => :HyperlinksChanged)
33
+ define_child_node(RubyXL::StringNode, :node_name => :DigSig)
34
+ define_child_node(RubyXL::StringNode, :node_name => :Application)
35
+ define_child_node(RubyXL::StringNode, :node_name => :AppVersion)
36
+ define_child_node(RubyXL::IntegerNode, :node_name => :DocSecurity)
37
+ set_namespaces('xmlns' => 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
38
+ 'xmlns:vt' => 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes')
39
+ define_element_name 'Properties'
40
+
41
+ def add_parts_count(name, count)
42
+ return unless count > 0
43
+ heading_pairs.vt_vector.vt_variant << RubyXL::Variant.new(:vt_lpstr => RubyXL::StringNode.new(:value => name))
44
+ heading_pairs.vt_vector.vt_variant << RubyXL::Variant.new(:vt_i4 => RubyXL::IntegerNode.new(:value => count))
45
+ end
46
+ private :add_parts_count
47
+
48
+ def add_part_title(name)
49
+ titles_of_parts.vt_vector.vt_lpstr << RubyXL::StringNode.new(:value => name)
50
+ end
51
+ private :add_parts_count
52
+
53
+ def before_write_xml
54
+ if @workbook then
55
+ self.heading_pairs = RubyXL::VectorValue.new(:vt_vector => RubyXL::Vector.new(:base_type => 'variant'))
56
+ self.titles_of_parts = RubyXL::VectorValue.new(:vt_vector => RubyXL::Vector.new(:base_type => 'lpstr'))
57
+
58
+ add_parts_count('Worksheets', @workbook.worksheets.size)
59
+ @workbook.worksheets.each { |sheet| add_part_title(sheet.sheet_name) }
60
+
61
+ if @workbook.defined_name_container then
62
+ add_parts_count('Named Ranges', @workbook.defined_name_container.defined_names.size)
63
+ @workbook.defined_name_container.defined_names.each { |defined_name| add_part_title(defined_name.name) }
64
+ end
65
+ end
66
+
67
+ true
68
+ end
69
+
70
+ def self.filepath
71
+ File.join('docProps', 'app.xml')
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,36 @@
1
+ require 'rubyXL/objects/ooxml_object'
2
+
3
+ module RubyXL
4
+
5
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_ext-1.html
6
+ class RawOOXML < OOXMLObject
7
+ attr_accessor :raw_xml
8
+
9
+ def self.parse(node)
10
+ obj = new
11
+ obj.raw_xml = node.to_xml
12
+ obj
13
+ end
14
+
15
+ def write_xml(xml, node_name_override = nil)
16
+ self.raw_xml
17
+ end
18
+ end
19
+
20
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_ext-1.html
21
+ class Extension < RawOOXML
22
+ define_attribute(:uri, :string)
23
+ define_element_name 'ext'
24
+ end
25
+
26
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_extLst-1.html
27
+ class ExtensionStorageArea < OOXMLObject
28
+ define_child_node(RubyXL::Extension, :collection => true)
29
+ define_element_name 'extLst'
30
+ end
31
+
32
+ class AlternateContent < RawOOXML
33
+ define_element_name 'mc:AlternateContent'
34
+ end
35
+
36
+ end
@@ -0,0 +1,57 @@
1
+ require 'rubyXL/objects/ooxml_object'
2
+
3
+ module RubyXL
4
+
5
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_gradientFill-1.html
6
+ class Stop < OOXMLObject
7
+ define_attribute(:position, :float)
8
+ define_child_node(RubyXL::Color)
9
+ define_element_name 'stop'
10
+ end
11
+
12
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_patternFill-1.html
13
+ class PatternFill < OOXMLObject
14
+ define_attribute(:patternType, :string, :values =>
15
+ %w{ none solid mediumGray darkGray lightGray
16
+ darkHorizontal darkVertical darkDown darkUp darkGrid darkTrellis
17
+ lightHorizontal lightVertical lightDown lightUp lightGrid lightTrellis
18
+ gray125 gray0625 })
19
+ define_child_node(RubyXL::Color, :node_name => :fgColor )
20
+ define_child_node(RubyXL::Color, :node_name => :bgColor )
21
+ define_element_name 'patternFill'
22
+ end
23
+
24
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_gradientFill-1.html
25
+ class GradientFill < OOXMLObject
26
+ define_attribute(:type, :string, :values => %w{ linear path }, :default => 'linear')
27
+ define_attribute(:degree, :float, :default => 0)
28
+ define_attribute(:left, :float, :default => 0)
29
+ define_attribute(:right, :float, :default => 0)
30
+ define_attribute(:top, :float, :default => 0)
31
+ define_attribute(:bottom, :float, :default => 0)
32
+ define_child_node(RubyXL::Stop, :collection => true)
33
+ define_element_name 'gradientFill'
34
+ end
35
+
36
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_fill-1.html
37
+ class Fill < OOXMLObject
38
+ define_child_node(RubyXL::PatternFill)
39
+ define_child_node(RubyXL::GradientFill)
40
+ define_element_name 'fill'
41
+ set_countable
42
+ end
43
+
44
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_fills-1.html
45
+ class FillContainer < OOXMLObject
46
+ define_child_node(RubyXL::Fill, :collection => :with_count, :accessor => :fills)
47
+ define_element_name 'fills'
48
+
49
+ def self.defaults
50
+ self.new(:fills => [
51
+ RubyXL::Fill.new(:pattern_fill => RubyXL::PatternFill.new(:pattern_type => 'none')),
52
+ RubyXL::Fill.new(:pattern_fill => RubyXL::PatternFill.new(:pattern_type => 'gray125'))
53
+ ])
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,111 @@
1
+ require 'rubyXL/objects/ooxml_object'
2
+ require 'rubyXL/objects/container_nodes'
3
+ require 'rubyXL/objects/color'
4
+
5
+ module RubyXL
6
+
7
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_font-1.html
8
+ class Font < OOXMLObject
9
+ define_child_node(RubyXL::StringValue, :node_name => :name)
10
+ define_child_node(RubyXL::IntegerValue, :node_name => :charset)
11
+ define_child_node(RubyXL::IntegerValue, :node_name => :family)
12
+ define_child_node(RubyXL::BooleanValue, :node_name => :b)
13
+ define_child_node(RubyXL::BooleanValue, :node_name => :i)
14
+ define_child_node(RubyXL::BooleanValue, :node_name => :strike)
15
+ define_child_node(RubyXL::BooleanValue, :node_name => :outline)
16
+ define_child_node(RubyXL::BooleanValue, :node_name => :shadow)
17
+ define_child_node(RubyXL::BooleanValue, :node_name => :condense)
18
+ define_child_node(RubyXL::BooleanValue, :node_name => :extend)
19
+ define_child_node(RubyXL::Color)
20
+ define_child_node(RubyXL::FloatValue, :node_name => :sz)
21
+ define_child_node(RubyXL::BooleanValue, :node_name => :u)
22
+ define_child_node(RubyXL::StringValue, :node_name => :vertAlign)
23
+ define_child_node(RubyXL::StringValue, :node_name => :scheme)
24
+ define_element_name 'font'
25
+ set_countable # TODO: phase put, eventually.
26
+
27
+ def ==(other)
28
+ (!(self.i && self.i.val) == !(other.i && other.i.val)) &&
29
+ (!(self.b && self.b.val) == !(other.b && other.b.val)) &&
30
+ (!(self.u && self.u.val) == !(other.u && other.u.val)) &&
31
+ (!(self.strike && self.strike.val) == !(other.strike && other.strike.val)) &&
32
+ ((self.name && self.name.val) == (other.name && other.name.val)) &&
33
+ ((self.sz && self.sz.val) == (other.sz && other.sz.val)) &&
34
+ (self.color == other.color) # Need to write proper comparison for color
35
+ end
36
+
37
+ def is_italic
38
+ i && i.val
39
+ end
40
+
41
+ def set_italic(val)
42
+ self.i = RubyXL::BooleanValue.new(:val => val)
43
+ end
44
+
45
+ def is_bold
46
+ b && b.val
47
+ end
48
+
49
+ def set_bold(val)
50
+ self.b = RubyXL::BooleanValue.new(:val => val)
51
+ end
52
+
53
+ def is_underlined
54
+ u && u.val
55
+ end
56
+
57
+ def set_underline(val)
58
+ self.u = RubyXL::BooleanValue.new(:val => val)
59
+ end
60
+
61
+ def is_strikethrough
62
+ strike && strike.val
63
+ end
64
+
65
+ def set_strikethrough(val)
66
+ self.strike = RubyXL::BooleanValue.new(:val => val)
67
+ end
68
+
69
+ def get_name
70
+ name && name.val
71
+ end
72
+
73
+ def set_name(val)
74
+ self.name = RubyXL::StringValue.new(:val => val)
75
+ end
76
+
77
+ def get_size
78
+ sz && sz.val
79
+ end
80
+
81
+ def set_size(val)
82
+ self.sz = RubyXL::FloatValue.new(:val => val)
83
+ end
84
+
85
+ def get_rgb_color
86
+ color && color.rgb
87
+ end
88
+
89
+ # Helper method to modify the font color
90
+ def set_rgb_color(font_color)
91
+ self.color = RubyXL::Color.new(:rgb => font_color.to_s)
92
+ end
93
+
94
+ end
95
+
96
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_fonts-1.html
97
+ class FontContainer < OOXMLObject
98
+ define_child_node(RubyXL::Font, :collection => :with_count, :accessor => :fonts)
99
+ define_element_name 'fonts'
100
+
101
+ def self.defaults
102
+ self.new(:fonts => [
103
+ RubyXL::Font.new(:name => RubyXL::StringValue.new(:val => 'Verdana'),
104
+ :sz => RubyXL::FloatValue.new(:val => 10) ),
105
+ RubyXL::Font.new(:name => RubyXL::StringValue.new(:val => 'Verdana'),
106
+ :sz => RubyXL::FloatValue.new(:val => 8) )
107
+ ])
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,24 @@
1
+ require 'rubyXL/objects/ooxml_object'
2
+
3
+ module RubyXL
4
+
5
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_f-1.html
6
+ class Formula < OOXMLObject
7
+ define_attribute(:_, :string, :accessor => :expression)
8
+ define_attribute(:t, :string, :default => 'normal', :values =>
9
+ %w{ normal array dataTable shared })
10
+ define_attribute(:aca, :bool, :default => false)
11
+ define_attribute(:ref, :ref)
12
+ define_attribute(:dt2D, :bool, :default => false)
13
+ define_attribute(:dtr, :bool, :default => false)
14
+ define_attribute(:del1, :bool, :default => false)
15
+ define_attribute(:del2, :bool, :default => false)
16
+ define_attribute(:r1, :ref)
17
+ define_attribute(:r2, :ref)
18
+ define_attribute(:ca, :bool, :default => false)
19
+ define_attribute(:si, :int)
20
+ define_attribute(:bx, :bool, :default => false)
21
+ define_element_name 'f'
22
+ end
23
+
24
+ end
@@ -0,0 +1,295 @@
1
+ require 'pp'
2
+
3
+ module RubyXL
4
+
5
+ # Parent class for defining OOXML based objects (not unlike Rails' +ActiveRecord+!)
6
+ # Most importantly, provides functionality of parsing such objects from XML,
7
+ # and marshalling them to XML.
8
+ class OOXMLObject
9
+
10
+ # Get the value of a [sub]class variable if it exists, or create the respective variable
11
+ # with the passed-in +default+ (or +{}+, if not specified)
12
+ #
13
+ # Throughout this class, we are setting class variables through explicit method calls
14
+ # rather than by directly addressing the name of the variable because of context issues:
15
+ # addressing variable by name creates it in the context of defining class, while calling
16
+ # the setter/getter method addresses it in the context of descendant class,
17
+ # which is what we need.
18
+ def self.obtain_class_variable(var_name, default = {})
19
+ if class_variable_defined?(var_name) then
20
+ self.class_variable_get(var_name)
21
+ else
22
+ self.class_variable_set(var_name, default)
23
+ end
24
+ end
25
+
26
+ def obtain_class_variable(var_name, default = {})
27
+ self.class.obtain_class_variable(var_name, default)
28
+ end
29
+ private :obtain_class_variable
30
+
31
+ # Defines an attribute of OOXML object.
32
+ # === Parameters
33
+ # * +attribute_name+ - Name of the element attribute as seen in the source XML. Can be either "String" or :Symbol
34
+ # * Special attibute name '_' (underscore) denotes the value of the element rather than attribute.
35
+ # * +attribute_type+ - Specifies the conversion type for the attribute when parsing. Available options are:
36
+ # * :int - Integer
37
+ # * :float - Float
38
+ # * :string - String (no conversion)
39
+ # * :sqref - RubyXL::Sqref
40
+ # * :ref - RubyXL::Reference
41
+ # * :bool - Boolean ("1" and "true" convert to +true+, others to +false+)
42
+ # * +extra_parameters+ - Hash of optional parameters as follows:
43
+ # * :accessor - Name of the accessor for this attribute to be defined on the object. If not provided, defaults to classidied +attribute_name+.
44
+ # * :default - Value this attribute defaults to if not explicitly provided.
45
+ # * :required - Whether this attribute is required when writing XML. If the value of the attrinute is not explicitly provided, +:default+ is written instead.
46
+ # * :values - List of acceptable values for this attribute (curently not used).
47
+ # ==== Examples
48
+ # define_attribute(:outline, :bool, :default => true)
49
+ # A Boolean attribute 'outline' with default value +true+ will be accessible by calling +obj.outline+
50
+ # define_attribute(:uniqueCount, :int)
51
+ # An Integer attribute 'uniqueCount' accessible as +obj.unique_count+
52
+ # define_attribute(:_, :string, :accessor => :expression)
53
+ # The value of the element will be accessible as a String by calling +obj.expression+
54
+ # define_attribute(:errorStyle, :string, :default => 'stop', :values => %w{ stop warning information })
55
+ # A String attribute named 'errorStyle' will be accessible as +obj.error_style+, valid values are "stop", "warning", "information"
56
+ def self.define_attribute(attr_name, attr_type, extra_params = {})
57
+ attrs = obtain_class_variable(:@@ooxml_attributes)
58
+
59
+ accessor = extra_params[:accessor] || accessorize(attr_name)
60
+ attr_name = attr_name.to_s
61
+
62
+ attrs[attr_name] = {
63
+ :accessor => accessor,
64
+ :attr_type => attr_type,
65
+ :optional => !extra_params[:required],
66
+ :default => extra_params[:default],
67
+ :valies => extra_params[:values]
68
+ }
69
+
70
+ self.send(:attr_accessor, accessor)
71
+ end
72
+
73
+ def self.define_child_node(klass, extra_params = {})
74
+ child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
75
+ child_node_name = (extra_params[:node_name] || klass.class_variable_get(:@@ooxml_tag_name)).to_s
76
+ accessor = (extra_params[:accessor] || accessorize(child_node_name)).to_sym
77
+
78
+ child_nodes[child_node_name] = {
79
+ :class => klass,
80
+ :is_array => extra_params[:collection],
81
+ :accessor => accessor
82
+ }
83
+
84
+ if extra_params[:collection] == :with_count then
85
+ define_attribute(:count, :int, :required => true)
86
+ end
87
+
88
+ self.send(:attr_accessor, accessor)
89
+ end
90
+
91
+ def self.define_element_name(v)
92
+ self.class_variable_set(:@@ooxml_tag_name, v)
93
+ end
94
+
95
+ def self.set_countable
96
+ self.class_variable_set(:@@ooxml_countable, true)
97
+ self.send(:attr_accessor, :count)
98
+ end
99
+
100
+ # Sets the list of namespaces on this object to be added when writing out XML. Valid only on top-level objects.
101
+ # === Parameters
102
+ # * +namespace_hash+ - Hash of namespaces in the form of <tt>"prefix" => "url"</tt>
103
+ # ==== Examples
104
+ # set_namespaces('xmlns' => 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
105
+ # 'xmlns:r' => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships')
106
+ def self.set_namespaces(namespace_hash)
107
+ self.class_variable_set(:@@ooxml_namespaces, namespace_hash)
108
+ end
109
+
110
+ def write_xml(xml = nil, node_name_override = nil)
111
+ if xml.nil? then
112
+ seed_xml = Nokogiri::XML('<?xml version = "1.0" standalone ="yes"?>')
113
+ seed_xml.encoding = 'UTF-8'
114
+ result = self.write_xml(seed_xml)
115
+ return result if result == ''
116
+ seed_xml << result
117
+ return seed_xml.to_xml({ :indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML })
118
+ end
119
+
120
+ return '' unless before_write_xml
121
+
122
+ attrs = obtain_class_variable(:@@ooxml_namespaces).dup
123
+
124
+ obtain_class_variable(:@@ooxml_attributes).each_pair { |k, v|
125
+ val = self.send(v[:accessor])
126
+
127
+ if val.nil? then
128
+ next if v[:optional]
129
+ val = v[:default]
130
+ end
131
+
132
+ val = val &&
133
+ case v[:attr_type]
134
+ when :bool then val ? '1' : '0'
135
+ when :float then val.to_s.gsub(/\.0*$/, '') # Trim trailing zeroes
136
+ else val
137
+ end
138
+
139
+ attrs[k] = val
140
+ }
141
+
142
+ element_text = attrs.delete('_')
143
+ elem = xml.create_element(node_name_override || obtain_class_variable(:@@ooxml_tag_name), attrs, element_text)
144
+ child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
145
+ child_nodes.each_pair { |child_node_name, child_node_params|
146
+ obj = self.send(child_node_params[:accessor])
147
+ unless obj.nil?
148
+ if child_node_params[:is_array] then obj.each { |item| elem << item.write_xml(xml, child_node_name) unless item.nil? }
149
+ else elem << obj.write_xml(xml, child_node_name)
150
+ end
151
+ end
152
+ }
153
+ elem
154
+ end
155
+
156
+ def initialize(params = {})
157
+ obtain_class_variable(:@@ooxml_attributes).each_value { |v|
158
+ instance_variable_set("@#{v[:accessor]}", params[v[:accessor]])
159
+ }
160
+
161
+ obtain_class_variable(:@@ooxml_child_nodes).each_value { |v|
162
+
163
+ initial_value =
164
+ if params.has_key?(v[:accessor]) then params[v[:accessor]]
165
+ elsif v[:is_array] then []
166
+ else nil
167
+ end
168
+
169
+ instance_variable_set("@#{v[:accessor]}", initial_value)
170
+ }
171
+
172
+ instance_variable_set("@count", 0) if obtain_class_variable(:@@ooxml_countable, false)
173
+ end
174
+
175
+ def self.process_attribute(obj, raw_value, params)
176
+ val = raw_value &&
177
+ case params[:attr_type]
178
+ when :int then Integer(raw_value)
179
+ when :float then Float(raw_value)
180
+ when :string then raw_value
181
+ when :sqref then RubyXL::Sqref.new(raw_value)
182
+ when :ref then RubyXL::Reference.new(raw_value)
183
+ when :bool then ['1', 'true'].include?(raw_value)
184
+ end
185
+ obj.send("#{params[:accessor]}=", val)
186
+ end
187
+ private_class_method :process_attribute
188
+
189
+ def self.parse(node)
190
+ node = Nokogiri::XML.parse(node) if node.is_a?(IO) || node.is_a?(String)
191
+
192
+ if node.is_a?(Nokogiri::XML::Document) then
193
+ # @namespaces = node.namespaces
194
+ node = node.root
195
+ # ignorable_attr = node.attributes['Ignorable']
196
+ # @ignorables << ignorable_attr.value if ignorable_attr
197
+ end
198
+
199
+ obj = self.new
200
+
201
+ known_attributes = obtain_class_variable(:@@ooxml_attributes)
202
+
203
+ content_params = known_attributes['_']
204
+ process_attribute(obj, node.text, content_params) if content_params
205
+
206
+ node.attributes.each_pair { |attr_name, attr|
207
+ attr_name = if attr.namespace then "#{attr.namespace.prefix}:#{attr.name}"
208
+ else attr.name
209
+ end
210
+
211
+ attr_params = known_attributes[attr_name]
212
+
213
+ next if attr_params.nil?
214
+ # raise "Unknown attribute: #{attr_name}" if attr_params.nil?
215
+ process_attribute(obj, attr.value, attr_params)
216
+ }
217
+
218
+ known_child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
219
+
220
+ unless known_child_nodes.empty?
221
+ node.element_children.each { |child_node|
222
+
223
+ child_node_name = if child_node.namespace.prefix then
224
+ "#{child_node.namespace.prefix}:#{child_node.name}"
225
+ else child_node.name
226
+ end
227
+
228
+ child_node_params = known_child_nodes[child_node_name]
229
+ raise "Unknown child node: #{child_node_name}" if child_node_params.nil?
230
+ parsed_object = child_node_params[:class].parse(child_node)
231
+ if child_node_params[:is_array] then
232
+ index = parsed_object.index_in_collection
233
+ collection = obj.send(child_node_params[:accessor])
234
+ if index.nil? then
235
+ collection << parsed_object
236
+ else
237
+ collection[index] = parsed_object
238
+ end
239
+ else
240
+ obj.send("#{child_node_params[:accessor]}=", parsed_object)
241
+ end
242
+ }
243
+ end
244
+
245
+ obj
246
+ end
247
+
248
+ def dup
249
+ new_copy = super
250
+ new_copy.count = 0 if obtain_class_variable(:@@ooxml_countable, false)
251
+ new_copy
252
+ end
253
+
254
+ def index_in_collection
255
+ nil
256
+ end
257
+
258
+ # Subclass provided filter to perform last-minute operations (cleanup, count, etc.) immediately prior to write,
259
+ # along with option to terminate the actual write if +false+ is returned (for example, to avoid writing
260
+ # the collection's root node if the collection is empty).
261
+ def before_write_xml
262
+ child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
263
+ child_nodes.each_pair { |child_node_name, child_node_params|
264
+ self.count = self.send(child_node_params[:accessor]).size if child_node_params[:is_array] == :with_count
265
+ }
266
+ true
267
+ end
268
+
269
+ def add_to_zip(zipfile)
270
+ xml_string = write_xml
271
+ return if xml_string.empty?
272
+ zipfile.get_output_stream(self.class.filepath) { |f| f << xml_string }
273
+ end
274
+
275
+ def self.filepath
276
+ raise 'Subclass responsebility'
277
+ end
278
+
279
+ def self.parse_file(dirpath)
280
+ full_path = File.join(dirpath, filepath)
281
+ return nil unless File.exist?(full_path)
282
+ parse(File.open(full_path, 'r'))
283
+ end
284
+
285
+ private
286
+ def self.accessorize(str)
287
+ acc = str.to_s.dup
288
+ acc.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
289
+ acc.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
290
+ acc.gsub!(':','_')
291
+ acc.downcase.to_sym
292
+ end
293
+
294
+ end
295
+ end