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.
- data/Gemfile +14 -10
- data/Gemfile.lock +80 -21
- data/LICENSE.txt +1 -1
- data/README.rdoc +88 -82
- data/Rakefile +7 -2
- data/VERSION +1 -1
- data/lib/rubyXL.rb +13 -7
- data/lib/rubyXL/cell.rb +108 -268
- data/lib/rubyXL/generic_storage.rb +40 -0
- data/lib/rubyXL/objects/border.rb +66 -0
- data/lib/rubyXL/objects/calculation_chain.rb +28 -0
- data/lib/rubyXL/objects/cell_style.rb +75 -0
- data/lib/rubyXL/objects/color.rb +25 -0
- data/lib/rubyXL/objects/column_range.rb +74 -0
- data/lib/rubyXL/objects/container_nodes.rb +122 -0
- data/lib/rubyXL/objects/data_validation.rb +43 -0
- data/lib/rubyXL/objects/document_properties.rb +76 -0
- data/lib/rubyXL/objects/extensions.rb +36 -0
- data/lib/rubyXL/objects/fill.rb +57 -0
- data/lib/rubyXL/objects/font.rb +111 -0
- data/lib/rubyXL/objects/formula.rb +24 -0
- data/lib/rubyXL/objects/ooxml_object.rb +295 -0
- data/lib/rubyXL/objects/reference.rb +110 -0
- data/lib/rubyXL/objects/relationships.rb +59 -0
- data/lib/rubyXL/objects/shared_strings.rb +57 -0
- data/lib/rubyXL/objects/sheet_data.rb +149 -0
- data/lib/rubyXL/objects/sheet_view.rb +71 -0
- data/lib/rubyXL/objects/stylesheet.rb +200 -0
- data/lib/rubyXL/objects/text.rb +87 -0
- data/lib/rubyXL/objects/theme.rb +64 -0
- data/lib/rubyXL/objects/workbook.rb +233 -0
- data/lib/rubyXL/objects/worksheet.rb +485 -0
- data/lib/rubyXL/parser.rb +78 -442
- data/lib/rubyXL/workbook.rb +216 -385
- data/lib/rubyXL/worksheet.rb +509 -1062
- data/lib/rubyXL/writer/content_types_writer.rb +104 -68
- data/lib/rubyXL/writer/core_writer.rb +26 -43
- data/lib/rubyXL/writer/generic_writer.rb +43 -0
- data/lib/rubyXL/writer/root_rels_writer.rb +11 -19
- data/lib/rubyXL/writer/styles_writer.rb +6 -398
- data/lib/rubyXL/writer/theme_writer.rb +321 -327
- data/lib/rubyXL/writer/workbook_writer.rb +63 -67
- data/lib/rubyXL/writer/worksheet_writer.rb +29 -218
- data/rdoc/created.rid +39 -0
- data/rdoc/fonts.css +167 -0
- data/rdoc/fonts/Lato-Light.ttf +0 -0
- data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
- data/rdoc/fonts/Lato-Regular.ttf +0 -0
- data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/rdoc/images/add.png +0 -0
- data/rdoc/images/arrow_up.png +0 -0
- data/rdoc/images/brick.png +0 -0
- data/rdoc/images/brick_link.png +0 -0
- data/rdoc/images/bug.png +0 -0
- data/rdoc/images/bullet_black.png +0 -0
- data/rdoc/images/bullet_toggle_minus.png +0 -0
- data/rdoc/images/bullet_toggle_plus.png +0 -0
- data/rdoc/images/date.png +0 -0
- data/rdoc/images/delete.png +0 -0
- data/rdoc/images/find.png +0 -0
- data/rdoc/images/loadingAnimation.gif +0 -0
- data/rdoc/images/macFFBgHack.png +0 -0
- data/rdoc/images/package.png +0 -0
- data/rdoc/images/page_green.png +0 -0
- data/rdoc/images/page_white_text.png +0 -0
- data/rdoc/images/page_white_width.png +0 -0
- data/rdoc/images/plugin.png +0 -0
- data/rdoc/images/ruby.png +0 -0
- data/rdoc/images/tag_blue.png +0 -0
- data/rdoc/images/tag_green.png +0 -0
- data/rdoc/images/transparent.png +0 -0
- data/rdoc/images/wrench.png +0 -0
- data/rdoc/images/wrench_orange.png +0 -0
- data/rdoc/images/zoom.png +0 -0
- data/rdoc/js/darkfish.js +140 -0
- data/rdoc/js/jquery.js +18 -0
- data/rdoc/js/navigation.js +142 -0
- data/rdoc/js/search.js +109 -0
- data/rdoc/js/search_index.js +1 -0
- data/rdoc/js/searcher.js +228 -0
- data/rdoc/rdoc.css +580 -0
- data/rubyXL.gemspec +90 -34
- data/spec/lib/cell_spec.rb +29 -59
- data/spec/lib/parser_spec.rb +35 -19
- data/spec/lib/reference_spec.rb +29 -0
- data/spec/lib/stylesheet_spec.rb +29 -0
- data/spec/lib/workbook_spec.rb +22 -17
- data/spec/lib/worksheet_spec.rb +47 -202
- metadata +185 -148
- data/lib/.DS_Store +0 -0
- data/lib/rubyXL/Hash.rb +0 -60
- data/lib/rubyXL/color.rb +0 -14
- data/lib/rubyXL/private_class.rb +0 -265
- data/lib/rubyXL/writer/app_writer.rb +0 -62
- data/lib/rubyXL/writer/calc_chain_writer.rb +0 -33
- data/lib/rubyXL/writer/shared_strings_writer.rb +0 -30
- data/lib/rubyXL/writer/workbook_rels_writer.rb +0 -59
- data/lib/rubyXL/zip.rb +0 -20
- 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
|