rubyXL 1.2.10 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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