axlsx 1.0.18 → 1.1.0

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 (167) hide show
  1. data/CHANGELOG.md +11 -3
  2. data/README.md +93 -18
  3. data/examples/example.csv +1000 -0
  4. data/examples/example.rb +97 -5
  5. data/examples/example.xlsx +0 -0
  6. data/examples/example_streamed.xlsx +0 -0
  7. data/examples/no-use_autowidth.xlsx +0 -0
  8. data/examples/shared_strings_example.xlsx +0 -0
  9. data/lib/axlsx.rb +30 -9
  10. data/lib/axlsx/content_type/content_type.rb +9 -9
  11. data/lib/axlsx/content_type/default.rb +9 -6
  12. data/lib/axlsx/content_type/override.rb +12 -8
  13. data/lib/axlsx/doc_props/app.rb +37 -40
  14. data/lib/axlsx/doc_props/core.rb +12 -17
  15. data/lib/axlsx/drawing/axis.rb +38 -19
  16. data/lib/axlsx/drawing/bar_3D_chart.rb +33 -32
  17. data/lib/axlsx/drawing/bar_series.rb +13 -14
  18. data/lib/axlsx/drawing/cat_axis.rb +15 -14
  19. data/lib/axlsx/drawing/cat_axis_data.rb +16 -18
  20. data/lib/axlsx/drawing/chart.rb +37 -38
  21. data/lib/axlsx/drawing/drawing.rb +15 -12
  22. data/lib/axlsx/drawing/graphic_frame.rb +21 -21
  23. data/lib/axlsx/drawing/hyperlink.rb +12 -11
  24. data/lib/axlsx/drawing/line_3D_chart.rb +30 -28
  25. data/lib/axlsx/drawing/line_series.rb +11 -11
  26. data/lib/axlsx/drawing/marker.rb +10 -8
  27. data/lib/axlsx/drawing/named_axis_data.rb +36 -0
  28. data/lib/axlsx/drawing/one_cell_anchor.rb +17 -16
  29. data/lib/axlsx/drawing/pic.rb +24 -37
  30. data/lib/axlsx/drawing/picture_locking.rb +21 -18
  31. data/lib/axlsx/drawing/pie_3D_chart.rb +10 -8
  32. data/lib/axlsx/drawing/pie_series.rb +15 -12
  33. data/lib/axlsx/drawing/scaling.rb +10 -10
  34. data/lib/axlsx/drawing/scatter_chart.rb +69 -0
  35. data/lib/axlsx/drawing/scatter_series.rb +39 -0
  36. data/lib/axlsx/drawing/ser_axis.rb +10 -10
  37. data/lib/axlsx/drawing/series.rb +15 -15
  38. data/lib/axlsx/drawing/series_title.rb +14 -14
  39. data/lib/axlsx/drawing/title.rb +26 -26
  40. data/lib/axlsx/drawing/two_cell_anchor.rb +18 -20
  41. data/lib/axlsx/drawing/val_axis.rb +8 -7
  42. data/lib/axlsx/drawing/val_axis_data.rb +17 -17
  43. data/lib/axlsx/drawing/view_3D.rb +22 -20
  44. data/lib/axlsx/package.rb +32 -15
  45. data/lib/axlsx/rels/relationship.rb +9 -6
  46. data/lib/axlsx/rels/relationships.rb +7 -1
  47. data/lib/axlsx/stylesheet/#num_fmt.rb# +69 -0
  48. data/lib/axlsx/stylesheet/border.rb +27 -23
  49. data/lib/axlsx/stylesheet/border_pr.rb +16 -15
  50. data/lib/axlsx/stylesheet/cell_alignment.rb +23 -21
  51. data/lib/axlsx/stylesheet/cell_protection.rb +10 -7
  52. data/lib/axlsx/stylesheet/cell_style.rb +8 -5
  53. data/lib/axlsx/stylesheet/color.rb +20 -14
  54. data/lib/axlsx/stylesheet/fill.rb +7 -5
  55. data/lib/axlsx/stylesheet/font.rb +14 -14
  56. data/lib/axlsx/stylesheet/gradient_fill.rb +19 -16
  57. data/lib/axlsx/stylesheet/gradient_stop.rb +9 -5
  58. data/lib/axlsx/stylesheet/num_fmt.rb +12 -6
  59. data/lib/axlsx/stylesheet/pattern_fill.rb +25 -10
  60. data/lib/axlsx/stylesheet/styles.rb +41 -32
  61. data/lib/axlsx/stylesheet/table_style.rb +9 -4
  62. data/lib/axlsx/stylesheet/table_style_element.rb +10 -7
  63. data/lib/axlsx/stylesheet/table_styles.rb +11 -8
  64. data/lib/axlsx/stylesheet/xf.rb +29 -25
  65. data/lib/axlsx/util/constants.rb +4 -0
  66. data/lib/axlsx/util/simple_typed_list.rb +18 -9
  67. data/lib/axlsx/util/validators.rb +13 -6
  68. data/lib/axlsx/version.rb +1 -1
  69. data/lib/axlsx/workbook/shared_strings_table.rb +19 -21
  70. data/lib/axlsx/workbook/workbook.rb +43 -19
  71. data/lib/axlsx/workbook/worksheet/cell.rb +93 -91
  72. data/lib/axlsx/workbook/worksheet/col.rb +114 -0
  73. data/lib/axlsx/workbook/worksheet/col.rb~ +0 -0
  74. data/lib/axlsx/workbook/worksheet/page_margins.rb +16 -13
  75. data/lib/axlsx/workbook/worksheet/row.rb +13 -13
  76. data/lib/axlsx/workbook/worksheet/table.rb +96 -0
  77. data/lib/axlsx/workbook/worksheet/table.rb~ +97 -0
  78. data/lib/axlsx/workbook/worksheet/worksheet.rb +152 -118
  79. data/lib/schema/dc.xsd +5 -5
  80. data/lib/schema/dcmitype.xsd +5 -3
  81. data/lib/schema/dcterms.xsd +15 -15
  82. data/lib/schema/opc-coreProperties.xsd +6 -2
  83. data/lib/schema/xml.xsd +7 -8
  84. data/test/#benchmark.txt# +7 -0
  85. data/test/#tc_helper.rb# +3 -0
  86. data/test/benchmark.rb +81 -0
  87. data/test/benchmark.rb~ +0 -0
  88. data/test/benchmark.txt +6 -0
  89. data/test/benchmark.txt~ +6 -0
  90. data/test/content_type/tc_content_type.rb +30 -32
  91. data/test/content_type/tc_default.rb +8 -23
  92. data/test/content_type/tc_override.rb +7 -21
  93. data/test/doc_props/tc_app.rb +2 -8
  94. data/test/doc_props/tc_core.rb +6 -7
  95. data/test/drawing/tc_axis.rb +7 -3
  96. data/test/drawing/tc_bar_3D_chart.rb +6 -7
  97. data/test/drawing/tc_bar_series.rb +4 -5
  98. data/test/drawing/tc_cat_axis.rb +2 -3
  99. data/test/drawing/tc_cat_axis_data.rb +2 -3
  100. data/test/drawing/tc_chart.rb +11 -12
  101. data/test/drawing/tc_drawing.rb +7 -8
  102. data/test/drawing/tc_graphic_frame.rb +3 -4
  103. data/test/drawing/tc_hyperlink.rb +2 -3
  104. data/test/drawing/tc_line_3d_chart.rb +5 -6
  105. data/test/drawing/tc_line_series.rb +3 -4
  106. data/test/drawing/tc_marker.rb +3 -4
  107. data/test/drawing/tc_one_cell_anchor.rb +6 -7
  108. data/test/drawing/tc_pic.rb +8 -9
  109. data/test/drawing/tc_picture_locking.rb +2 -3
  110. data/test/drawing/tc_pie_3D_chart.rb +5 -6
  111. data/test/drawing/tc_pie_series.rb +4 -5
  112. data/test/drawing/tc_scaling.rb +3 -4
  113. data/test/drawing/tc_scatter_chart.rb +43 -0
  114. data/test/drawing/tc_scatter_series.rb +20 -0
  115. data/test/drawing/tc_ser_axis.rb +2 -3
  116. data/test/drawing/tc_series.rb +4 -5
  117. data/test/drawing/tc_series_title.rb +4 -5
  118. data/test/drawing/tc_title.rb +4 -5
  119. data/test/drawing/tc_two_cell_anchor.rb +4 -5
  120. data/test/drawing/tc_val_axis.rb +2 -3
  121. data/test/drawing/tc_val_axis_data.rb +2 -3
  122. data/test/drawing/tc_view_3D.rb +6 -7
  123. data/test/example.csv +1000 -0
  124. data/test/example.xlsx +0 -0
  125. data/test/example_streamed.xlsx +0 -0
  126. data/test/profile.rb +33 -0
  127. data/test/rels/tc_relationship.rb +5 -6
  128. data/test/rels/tc_relationships.rb +4 -5
  129. data/test/stylesheet/tc_border.rb +3 -4
  130. data/test/stylesheet/tc_border_pr.rb +3 -4
  131. data/test/stylesheet/tc_cell_alignment.rb +4 -5
  132. data/test/stylesheet/tc_cell_protection.rb +2 -3
  133. data/test/stylesheet/tc_cell_style.rb +2 -3
  134. data/test/stylesheet/tc_color.rb +2 -3
  135. data/test/stylesheet/tc_fill.rb +1 -2
  136. data/test/stylesheet/tc_font.rb +5 -6
  137. data/test/stylesheet/tc_gradient_fill.rb +1 -2
  138. data/test/stylesheet/tc_gradient_stop.rb +1 -2
  139. data/test/stylesheet/tc_num_fmt.rb +1 -2
  140. data/test/stylesheet/tc_pattern_fill.rb +3 -4
  141. data/test/stylesheet/tc_styles.rb +15 -9
  142. data/test/stylesheet/tc_table_style.rb +2 -3
  143. data/test/stylesheet/tc_table_style_element.rb +2 -3
  144. data/test/stylesheet/tc_table_styles.rb +3 -4
  145. data/test/stylesheet/tc_xf.rb +16 -17
  146. data/test/tc_axlsx.rb +39 -0
  147. data/test/tc_axlsx.rb~ +0 -0
  148. data/test/tc_helper.rb +3 -0
  149. data/test/tc_helper.rb~ +3 -0
  150. data/test/tc_package.rb +13 -10
  151. data/test/util/tc_simple_typed_list.rb +8 -9
  152. data/test/util/tc_validators.rb +7 -8
  153. data/test/workbook/tc_shared_strings_table.rb +5 -6
  154. data/test/workbook/tc_workbook.rb +24 -6
  155. data/test/workbook/worksheet/table/tc_table.rb +71 -0
  156. data/test/workbook/worksheet/table/tc_table.rb~ +72 -0
  157. data/test/workbook/worksheet/tc_cell.rb +24 -10
  158. data/test/workbook/worksheet/tc_col.rb +59 -0
  159. data/test/workbook/worksheet/tc_col.rb~ +10 -0
  160. data/test/workbook/worksheet/tc_date_time_converter.rb +1 -2
  161. data/test/workbook/worksheet/tc_page_margins.rb +6 -9
  162. data/test/workbook/worksheet/tc_row.rb +26 -12
  163. data/test/workbook/worksheet/tc_worksheet.rb +134 -68
  164. metadata +150 -90
  165. data/test/drawing/tc_hyperlink.rb~ +0 -71
  166. data/test/workbook/tc_shared_strings_table.rb~ +0 -8
  167. data/test/workbook/worksheet/tc_date_time_converter.rb~ +0 -69
@@ -0,0 +1,114 @@
1
+ # encoding: UTF-8
2
+ module Axlsx
3
+
4
+ # The Col class defines column attributes for columns in sheets.
5
+ class Col
6
+
7
+ # First column affected by this 'column info' record.
8
+ # @return [Integer]
9
+ attr_reader :min
10
+
11
+ # Last column affected by this 'column info' record.
12
+ # @return [Integer]
13
+ attr_reader :max
14
+
15
+ # Flag indicating if the specified column(s) is set to 'best fit'. 'Best fit' is set to true under these conditions:
16
+ # The column width has never been manually set by the user, AND The column width is not the default width
17
+ # 'Best fit' means that when numbers are typed into a cell contained in a 'best fit' column, the column width should
18
+ # automatically resize to display the number. [Note: In best fit cases, column width must not be made smaller, only larger. end note]
19
+ # @return [Boolean]
20
+ attr_reader :bestFit
21
+
22
+ # Flag indicating if the outlining of the affected column(s) is in the collapsed state.
23
+ # @return [Boolean]
24
+ attr_reader :collapsed
25
+
26
+ # Flag indicating if the affected column(s) are hidden on this worksheet.
27
+ # @return [Boolean]
28
+ attr_reader :hidden
29
+
30
+ # Outline level of affected column(s). Range is 0 to 7.
31
+ # @return [Integer]
32
+ attr_reader :outlineLevel
33
+
34
+ # Flag indicating if the phonetic information should be displayed by default for the affected column(s) of the worksheet.
35
+ # @return [Boolean]
36
+ attr_reader :phonetic
37
+
38
+ # Default style for the affected column(s). Affects cells not yet allocated in the column(s). In other words, this style applies to new columns.
39
+ # @return [Integer]
40
+ attr_reader :style
41
+
42
+ # The width of the column
43
+ # @return [Numeric]
44
+ attr_reader :width
45
+
46
+ # @return [Boolean]
47
+ attr_reader :customWidth
48
+
49
+ # @see Col#collapsed
50
+ def collapsed=(v)
51
+ Axlsx.validate_boolean(v)
52
+ @collapsed = v
53
+ end
54
+
55
+ # @see Col#hidden
56
+ def hidden=(v)
57
+ Axlsx.validate_boolean(v)
58
+ @hidden = v
59
+ end
60
+
61
+ # @see Col#outline
62
+ def outlineLevel=(v)
63
+ Axlsx.validate_boolean(v)
64
+ @outlineLevel = v
65
+ end
66
+
67
+ # @see Col#phonetic
68
+ def phonetic=(v)
69
+ Axlsx.validate_boolean(v)
70
+ @phonetic = v
71
+ end
72
+
73
+ # @see Col#style
74
+ def style=(v)
75
+ Axlsx.validate_unsigned_int(v)
76
+ @style = v
77
+ end
78
+
79
+ # @see Col#width
80
+ def width=(v)
81
+ Axlsx.validate_unsigned_numeric(v) unless v == nil
82
+ @customWidth = @bestFit = v != nil
83
+ @width = v
84
+ end
85
+
86
+ # Create a new Col objects
87
+ # @param min First column affected by this 'column info' record.
88
+ # @param max Last column affected by this 'column info' record.
89
+ # @option options [Boolean] collapsed see Col#collapsed
90
+ # @option options [Boolean] hidden see Col#hidden
91
+ # @option options [Boolean] outlineLevel see Col#outlineLevel
92
+ # @option options [Boolean] phonetic see Col#phonetic
93
+ # @option options [Integer] style see Col#style
94
+ # @option options [Numeric] width see Col#width
95
+ def initialize(min, max, options={})
96
+ Axlsx.validate_unsigned_int(max)
97
+ Axlsx.validate_unsigned_int(min)
98
+ @min = min
99
+ @max = max
100
+ options.each do |o|
101
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
102
+ end
103
+ end
104
+
105
+ # Serialize this columns data to an xml string
106
+ # @param [String] str
107
+ # @return [String]
108
+ def to_xml_string(str = '')
109
+ attrs = self.instance_values.reject{ |key, value| value == nil }
110
+ str << '<col ' << attrs.map { |key, value| '' << key << '="' << value.to_s << '"' }.join(' ') << '/>'
111
+ end
112
+
113
+ end
114
+ end
File without changes
@@ -12,13 +12,13 @@ module Axlsx
12
12
 
13
13
  # Default left and right margin (in inches)
14
14
  DEFAULT_LEFT_RIGHT = 0.75
15
-
15
+
16
16
  # Default top and bottom margins (in inches)
17
17
  DEFAULT_TOP_BOTTOM = 1.00
18
-
18
+
19
19
  # Default header and footer margins (in inches)
20
20
  DEFAULT_HEADER_FOOTER = 0.50
21
-
21
+
22
22
  # Left margin (in inches)
23
23
  # @return [Float]
24
24
  attr_reader :left
@@ -26,23 +26,23 @@ module Axlsx
26
26
  # Right margin (in inches)
27
27
  # @return [Float]
28
28
  attr_reader :right
29
-
29
+
30
30
  # Top margin (in inches)
31
31
  # @return [Float]
32
32
  attr_reader :top
33
-
33
+
34
34
  # Bottom margin (in inches)
35
35
  # @return [Float]
36
36
  attr_reader :bottom
37
-
37
+
38
38
  # Header margin (in inches)
39
39
  # @return [Float]
40
40
  attr_reader :header
41
-
41
+
42
42
  # Footer margin (in inches)
43
43
  # @return [Float]
44
44
  attr_reader :footer
45
-
45
+
46
46
  # Creates a new PageMargins object
47
47
  # @option options [Numeric] left The left margin in inches
48
48
  # @option options [Numeric] right The right margin in inches
@@ -60,7 +60,7 @@ module Axlsx
60
60
  self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
61
61
  end
62
62
  end
63
-
63
+
64
64
  # Set some or all margins at once.
65
65
  # @param [Hash] margins the margins to set (possible keys are :left, :right, :top, :bottom, :header and :footer).
66
66
  def set(margins)
@@ -69,7 +69,7 @@ module Axlsx
69
69
  send("#{k}=", v)
70
70
  end
71
71
  end
72
-
72
+
73
73
  # @see left
74
74
  def left=(v); Axlsx::validate_unsigned_numeric(v); @left = v end
75
75
  # @see right
@@ -84,11 +84,14 @@ module Axlsx
84
84
  def footer=(v); Axlsx::validate_unsigned_numeric(v); @footer = v end
85
85
 
86
86
  # Serializes the page margins element
87
+ # @param [String] str
88
+ # @return [String]
87
89
  # @note For compatibility, this is a noop unless custom margins have been specified.
88
- # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
89
90
  # @see #custom_margins_specified?
90
- def to_xml(xml)
91
- xml.pageMargins :left => left, :right => right, :top => top, :bottom => bottom, :header => header, :footer => footer
91
+ def to_xml_string(str = '')
92
+ str << '<pageMargins '
93
+ str << instance_values.map { |key, value| '' << key << '="' << value.to_s << '"' }.join(' ')
94
+ str << '/>'
92
95
  end
93
96
  end
94
97
  end
@@ -60,19 +60,26 @@ module Axlsx
60
60
  end
61
61
 
62
62
  # Serializes the row
63
- # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
63
+ # @param [Integer] r_index The row index, 0 based.
64
+ # @param [String] str The string this rows xml will be appended to.
64
65
  # @return [String]
65
- def to_xml(xml)
66
- attrs = {:r => index+1}
67
- attrs.merge!(:customHeight => 1, :ht => height) if custom_height?
68
- xml.row(attrs) { |ixml| @cells.each { |cell| cell.to_xml(ixml) } }
66
+ def to_xml_string(r_index, str = '')
67
+ str << '<row r="' << (r_index + 1 ).to_s << '" '
68
+ if custom_height?
69
+ str << 'customHeight="1" ht="' << height.to_s << '">'
70
+ else
71
+ str << '>'
72
+ end
73
+ @cells.each_with_index { |cell, c_index| cell.to_xml_string(r_index, c_index, str) }
74
+ str << '</row>'
75
+ str
69
76
  end
70
77
 
71
78
  # Adds a singel sell to the row based on the data provided and updates the worksheet's autofit data.
72
79
  # @return [Cell]
73
80
  def add_cell(value="", options={})
74
81
  c = Cell.new(self, value, options)
75
- update_auto_fit_data
82
+ worksheet.send(:update_column_info, self.cells, self.cells.map(&:style))
76
83
  c
77
84
  end
78
85
 
@@ -106,13 +113,6 @@ module Axlsx
106
113
  # assigns the owning worksheet for this row
107
114
  def worksheet=(v) DataTypeValidator.validate "Row.worksheet", Worksheet, v; @worksheet=v; end
108
115
 
109
- # Tell the worksheet to update autofit data for the columns based on this row's cells.
110
- # @return [SimpleTypedList]
111
- def update_auto_fit_data
112
- worksheet.send(:update_auto_fit_data, self.cells)
113
- end
114
-
115
-
116
116
  # Converts values, types, and style options into cells and associates them with this row.
117
117
  # A new cell is created for each item in the values array.
118
118
  # If value option is defined and is a symbol it is applied to all the cells created.
@@ -0,0 +1,96 @@
1
+ # encoding: UTF-8
2
+ module Axlsx
3
+ # Table
4
+ # @note Worksheet#add_table is the recommended way to create tables for your worksheets.
5
+ # @see README for examples
6
+ class Table
7
+
8
+
9
+ # The reference to the table data
10
+ # @return [String]
11
+ attr_reader :ref
12
+
13
+ # The name of the table.
14
+ # @return [String]
15
+ attr_reader :name
16
+
17
+ # The style for the table.
18
+ # @return [TableStyle]
19
+ attr_reader :style
20
+
21
+ # Creates a new Table object
22
+ # @param [String] ref The reference to the table data.
23
+ # @param [Sheet] ref The sheet containing the table data.
24
+ # @option options [Cell, String] name
25
+ # @option options [TableStyle] style
26
+ def initialize(ref, sheet, options={})
27
+ @ref = ref
28
+ @sheet = sheet
29
+ @style = nil
30
+ @sheet.workbook.tables << self
31
+ @name = "Table#{index+1}"
32
+ options.each do |o|
33
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
34
+ end
35
+ yield self if block_given?
36
+ end
37
+
38
+ # The index of this chart in the workbooks charts collection
39
+ # @return [Integer]
40
+ def index
41
+ @sheet.workbook.tables.index(self)
42
+ end
43
+
44
+ # The part name for this table
45
+ # @return [String]
46
+ def pn
47
+ "#{TABLE_PN % (index+1)}"
48
+ end
49
+
50
+ # The relation reference id for this table
51
+ # @return [String]
52
+ def rId
53
+ "rId#{index+1}"
54
+ end
55
+
56
+ # The name of the Table.
57
+ # @param [String, Cell] v
58
+ # @return [Title]
59
+ def name=(v)
60
+ DataTypeValidator.validate "#{self.class}.name", [String], v
61
+ if v.is_a?(String)
62
+ @name = v
63
+ end
64
+ end
65
+
66
+ # Serializes the object
67
+ # @param [String] str
68
+ # @return [String]
69
+ def to_xml_string(str = '')
70
+ str << '<?xml version="1.0" encoding="UTF-8"?>'
71
+ str << '<table xmlns="' << XML_NS << '" id="' << (index+1).to_s << '" name="' << @name << '" displayName="' << @name.gsub(/\s/,'_') << '" '
72
+ str << 'ref="' << @ref << '" totalsRowShown="0">'
73
+ str << '<autoFilter ref="' << @ref << '"/>'
74
+ str << '<tableColumns count="' << header_cells.length.to_s << '">'
75
+ header_cells.each_with_index do |cell,index|
76
+ str << '<tableColumn id ="' << (index+1).to_s << '" name="' << cell.value << '"/>'
77
+ end
78
+ str << '</tableColumns>'
79
+ #TODO implement tableStyleInfo
80
+ str << '<tableStyleInfo showFirstColumn="0" showLastColumn="0" showRowStripes="1" showColumnStripes="0" name="TableStyleMedium9" />'
81
+ str << '</table>'
82
+ end
83
+
84
+ # The style for the table.
85
+ # TODO
86
+ # def style=(v) DataTypeValidator.validate "Table.style", Integer, v, lambda { |arg| arg >= 1 && arg <= 48 }; @style = v; end
87
+
88
+ private
89
+
90
+ # get the header cells (hackish)
91
+ def header_cells
92
+ header = @ref.gsub(/^(\w+)(\d+)\:(\w+)\d+$/, '\1\2:\3\2')
93
+ @sheet[header]
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: UTF-8
2
+ module Axlsx
3
+ # Table
4
+ # @note Worksheet#add_table is the recommended way to create charts for your worksheets.
5
+ # @see README for examples
6
+ class Table
7
+
8
+
9
+ # The reference to the table data
10
+ # @return [String]
11
+ attr_reader :ref
12
+
13
+ # The name of the table.
14
+ # @return [String]
15
+ attr_reader :name
16
+
17
+ # The style for the table.
18
+ # @return [TableStyle]
19
+ attr_reader :style
20
+
21
+ # Creates a new Table object
22
+ # @param [String] ref The reference to the table data.
23
+ # @param [Sheet] ref The sheet containing the table data.
24
+ # @option options [Cell, String] name
25
+ # @option options [TableStyle] style
26
+ def initialize(ref, sheet, options={})
27
+ @ref = ref
28
+ @sheet = sheet
29
+ @style = nil
30
+ @sheet.workbook.tables << self
31
+ @name = "Table#{index+1}"
32
+ options.each do |o|
33
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
34
+ end
35
+ yield self if block_given?
36
+ end
37
+
38
+ # The index of this chart in the workbooks charts collection
39
+ # @return [Integer]
40
+ def index
41
+ @sheet.workbook.tables.index(self)
42
+ end
43
+
44
+ # The part name for this table
45
+ # @return [String]
46
+ def pn
47
+ "#{TABLE_PN % (index+1)}"
48
+ end
49
+
50
+ # The relation reference id for this table
51
+ # @return [String]
52
+ def rId
53
+ "rId#{index+1}"
54
+ end
55
+
56
+ # The name of the Table.
57
+ # @param [String, Cell] v
58
+ # @return [Title]
59
+ def name=(v)
60
+ DataTypeValidator.validate "#{self.class}.name", [String], v
61
+ if v.is_a?(String)
62
+ @name = v
63
+ end
64
+ end
65
+
66
+
67
+ # The style for the table.
68
+ # TODO
69
+ # def style=(v) DataTypeValidator.validate "Chart.style", Integer, v, lambda { |arg| arg >= 1 && arg <= 48 }; @style = v; end
70
+
71
+ # Table Serialization
72
+ # serializes the table
73
+ def to_xml
74
+ builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml|
75
+ xml.table(:xmlns => XML_NS, :id => index+1, :name => @name, :displayName => @name.gsub(/\s/,'_'), :ref => @ref, :totalsRowShown => 0) {
76
+ xml.autoFilter :ref=>@ref
77
+ xml.tableColumns(:count => header_cells.length) {
78
+ header_cells.each_with_index do |cell,index|
79
+ xml.tableColumn :id => index+1, :name => cell.value
80
+ end
81
+ }
82
+ xml.tableStyleInfo :showFirstColumn=>"0", :showLastColumn=>"0", :showRowStripes=>"1", :showColumnStripes=>"0", :name=>"TableStyleMedium9"
83
+ #TODO implement tableStyleInfo
84
+ }
85
+ end
86
+ builder.to_xml(:save_with => 0)
87
+ end
88
+
89
+ private
90
+
91
+ # get the header cells (hackish)
92
+ def header_cells
93
+ header = @ref.gsub(/^(\w+)(\d+)\:(\w+)\d+$/, '\1\2:\3\2')
94
+ @sheet[header]
95
+ end
96
+ end
97
+ end
@@ -12,6 +12,9 @@ module Axlsx
12
12
  # @return [Workbook]
13
13
  attr_reader :workbook
14
14
 
15
+ # The tables in this worksheet
16
+ # @return [Array] of Table
17
+ attr_reader :tables
15
18
 
16
19
  # The rows in this worksheet
17
20
  # @note The recommended way to manage rows is Worksheet#add_row
@@ -35,6 +38,26 @@ module Axlsx
35
38
  # @return Array
36
39
  attr_reader :auto_filter
37
40
 
41
+ # Indicates if the worksheet should show gridlines or not
42
+ # @return Boolean
43
+ attr_reader :show_gridlines
44
+
45
+
46
+ # Indicates if the worksheet is selected in the workbook
47
+ # It is possible to have more than one worksheet selected, however it might cause issues
48
+ # in some older versions of excel when using copy and paste.
49
+ # @return Boolean
50
+ attr_reader :selected
51
+
52
+ # Indicates if the worksheet should print in a single page
53
+ # @return Boolean
54
+ attr_reader :fit_to_page
55
+
56
+
57
+ # Column info for the sheet
58
+ # @return [SimpleTypedList]
59
+ attr_reader :column_info
60
+
38
61
  # Page margins for printing the worksheet.
39
62
  # @example
40
63
  # wb = Axlsx::Package.new.workbook
@@ -55,6 +78,7 @@ module Axlsx
55
78
  @page_margins ||= PageMargins.new
56
79
  yield @page_margins if block_given?
57
80
  @page_margins
81
+
58
82
  end
59
83
 
60
84
  # Creates a new worksheet.
@@ -62,19 +86,36 @@ module Axlsx
62
86
  # @see Workbook#add_worksheet
63
87
  # @option options [String] name The name of this worksheet.
64
88
  # @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
89
+ # @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
65
90
  def initialize(wb, options={})
66
- @drawing = @page_margins = @auto_filter = nil
67
- @rows = SimpleTypedList.new Row
68
91
  self.workbook = wb
69
92
  @workbook.worksheets << self
70
- @auto_fit_data = []
71
- self.name = options[:name] || "Sheet" + (index+1).to_s
72
93
 
73
- @magick_draw = Magick::Draw.new
74
- @cols = SimpleTypedList.new Cell
94
+ @drawing = @page_margins = @auto_filter = nil
75
95
  @merged_cells = []
96
+ @auto_fit_data = []
76
97
 
98
+ @selected = false
99
+ @show_gridlines = true
100
+ self.name = "Sheet" + (index+1).to_s
77
101
  @page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
102
+
103
+ @rows = SimpleTypedList.new Row
104
+ @column_info = SimpleTypedList.new Col
105
+ # @cols = SimpleTypedList.new Cell
106
+ @tables = SimpleTypedList.new Table
107
+
108
+ if self.workbook.use_autowidth
109
+ require 'RMagick' unless defined?(Magick)
110
+ @magick_draw = Magick::Draw.new
111
+ else
112
+ @magick_draw = nil
113
+ end
114
+
115
+ options.each do |o|
116
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
117
+ end
118
+
78
119
  end
79
120
 
80
121
  # convinience method to access all cells in this worksheet
@@ -109,29 +150,31 @@ module Axlsx
109
150
  "#{rows.first.cells.first.r}:#{rows.last.cells.last.r}"
110
151
  end
111
152
 
153
+ # Indicates if gridlines should be shown in the sheet.
154
+ # This is true by default.
155
+ # @return [Boolean]
156
+ def show_gridlines=(v)
157
+ Axlsx::validate_boolean v
158
+ @show_gridlines = v
159
+ end
112
160
 
113
- # Returns the cell or cells defined using excel style A1:B3 references.
114
- # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber
115
- # @return [Cell, Array]
116
- def [](cell_def)
117
- return rows[cell_def - 1] if cell_def.is_a? Integer
118
- parts = cell_def.split(':')
119
- first = name_to_cell parts[0]
161
+ # @see selected
162
+ # @return [Boolean]
163
+ def selected=(v)
164
+ Axlsx::validate_boolean v
165
+ @selected = v
166
+ end
120
167
 
121
- if parts.size == 1
122
- first
123
- else
124
- cells = []
125
- last = name_to_cell(parts[1])
126
- rows[(first.row.index..last.row.index)].each do |r|
127
- r.cells[(first.index..last.index)].each do |c|
128
- cells << c
129
- end
130
- end
131
- cells
132
- end
168
+
169
+ # Indicates if the worksheet should print in a single page.
170
+ # This is true by default.
171
+ # @return [Boolean]
172
+ def fit_to_page=(v)
173
+ Axlsx::validate_boolean v
174
+ @fit_to_page = v
133
175
  end
134
176
 
177
+
135
178
  # returns the column and row index for a named based cell
136
179
  # @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
137
180
  # @return [Cell]
@@ -152,6 +195,12 @@ module Axlsx
152
195
  @name=v
153
196
  end
154
197
 
198
+ # The absolute auto filter range
199
+ # @see auto_filter
200
+ def abs_auto_filter
201
+ Axlsx.cell_range(@auto_filter.split(':').collect { |name| name_to_cell(name)}) if @auto_filter
202
+ end
203
+
155
204
  # The auto filter range for the worksheet
156
205
  # @param [String] v
157
206
  # @see auto_filter
@@ -233,7 +282,8 @@ module Axlsx
233
282
  # @option options [Float] height the row's height (in points)
234
283
  def add_row(values=[], options={})
235
284
  Row.new(self, values, options)
236
- update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
285
+ update_column_info @rows.last.cells, options.delete(:widths) ||[], options.delete(:style) || []
286
+ # update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
237
287
  yield @rows.last if block_given?
238
288
  @rows.last
239
289
  end
@@ -292,9 +342,9 @@ module Axlsx
292
342
  # @param [Integer|Float|Fixnum|nil] values
293
343
  def column_widths(*args)
294
344
  args.each_with_index do |value, index|
295
- raise ArgumentError, "Invalid column specification" unless index < @auto_fit_data.size
345
+ raise ArgumentError, "Invalid column specification" unless index < @column_info.size
296
346
  Axlsx::validate_unsigned_numeric(value) unless value == nil
297
- @auto_fit_data[index][:fixed] = value
347
+ @column_info[index].width = value
298
348
  end
299
349
  end
300
350
 
@@ -317,6 +367,14 @@ module Axlsx
317
367
  chart
318
368
  end
319
369
 
370
+ # needs documentation
371
+ def add_table(ref, options={})
372
+ table = Table.new(ref, self, options)
373
+ @tables << table
374
+ yield table if block_given?
375
+ table
376
+ end
377
+
320
378
  # Adds a media item to the worksheets drawing
321
379
  # @param [Class] media_type
322
380
  # @option options [] unknown
@@ -326,125 +384,101 @@ module Axlsx
326
384
  image
327
385
  end
328
386
 
329
- # Serializes the worksheet document
387
+ # Serializes the object
388
+ # @param [String] str
330
389
  # @return [String]
331
- def to_xml
332
- builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml|
333
- xml.worksheet(:xmlns => XML_NS,
334
- :'xmlns:r' => XML_NS_R) {
335
- # another patch for the folks at rubyXL as thier parser depends on this optional element.
336
- xml.dimension :ref=>dimension unless rows.size == 0
337
- # this is required by rubyXL, spec says who cares - but it seems they didnt notice
338
- # however, it also seems to be causing some odd [Grouped] stuff in excel 2011 - so
339
- # removing until I understand it better.
340
- # xml.sheetViews {
341
- # xml.sheetView(:tabSelected => 1, :workbookViewId => 0) {
342
- # xml.selection :activeCell=>"A1", :sqref => "A1"
343
- # }
344
- # }
345
-
346
- if @auto_fit_data.size > 0
347
- xml.cols {
348
- @auto_fit_data.each_with_index do |col, index|
349
- min_max = index+1
350
- xml.col(:min=>min_max, :max=>min_max, :width => auto_width(col), :customWidth=>1)
351
- end
352
- }
353
- end
354
- xml.sheetData {
355
- @rows.each do |row|
356
- row.to_xml(xml)
357
- end
358
- }
359
- xml.autoFilter :ref=>@auto_filter if @auto_filter
360
- xml.mergeCells(:count=>@merged_cells.size) { @merged_cells.each { | mc | xml.mergeCell(:ref=>mc) } } unless @merged_cells.empty?
361
- page_margins.to_xml(xml) if @page_margins
362
- xml.drawing :"r:id"=>"rId1" if @drawing
363
- }
390
+ def to_xml_string
391
+ str = '<?xml version="1.0" encoding="UTF-8"?>'
392
+ str.concat "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
393
+ str.concat "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % fit_to_page if fit_to_page
394
+ str.concat "<dimension ref=\"%s\"></dimension>" % dimension unless rows.size == 0
395
+ str.concat "<sheetViews><sheetView tabSelected='%s' workbookViewId='0' showGridLines='%s'><selection activeCell=\"A1\" sqref=\"A1\"/></sheetView></sheetViews>" % [@selected, show_gridlines]
396
+
397
+ if @column_info.size > 0
398
+ str << "<cols>"
399
+ @column_info.each { |col| col.to_xml_string(str) }
400
+ str.concat '</cols>'
401
+ end
402
+ str.concat '<sheetData>'
403
+ @rows.each_with_index { |row, index| row.to_xml_string(index, str) }
404
+ str.concat '</sheetData>'
405
+ page_margins.to_xml_string(str) if @page_margins
406
+ str.concat "<autoFilter ref='%s'></autoFilter>" % @auto_filter if @auto_filter
407
+ str.concat "<mergeCells count='%s'>%s</mergeCells>" % [@merged_cells.size, @merged_cells.reduce('') { |memo, obj| "<mergeCell ref='%s'></mergeCell>" % obj } ] unless @merged_cells.empty?
408
+ str.concat "<drawing r:id='rId1'></drawing>" if @drawing
409
+ unless @tables.empty?
410
+ str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }]
364
411
  end
365
- builder.to_xml(:save_with => 0)
412
+ str + '</worksheet>'
366
413
  end
367
414
 
368
415
  # The worksheet relationships. This is managed automatically by the worksheet
369
416
  # @return [Relationships]
370
417
  def relationships
371
418
  r = Relationships.new
419
+ @tables.each do |table|
420
+ r << Relationship.new(TABLE_R, "../#{table.pn}")
421
+ end
372
422
  r << Relationship.new(DRAWING_R, "../#{@drawing.pn}") if @drawing
373
423
  r
374
424
  end
375
425
 
426
+ # Returns the cell or cells defined using excel style A1:B3 references.
427
+ # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber
428
+ # @return [Cell, Array]
429
+
430
+ def [] (cell_def)
431
+ return rows[cell_def] if cell_def.is_a?(Integer)
432
+
433
+ parts = cell_def.split(':')
434
+ first = name_to_cell parts[0]
435
+ if parts.size == 1
436
+ first
437
+ else
438
+ cells = []
439
+ last = name_to_cell(parts[1])
440
+ rows[(first.row.index..last.row.index)].each do |r|
441
+ r.cells[(first.index..last.index)].each do |c|
442
+ cells << c
443
+ end
444
+ end
445
+ cells
446
+ end
447
+ end
448
+
376
449
  private
377
450
 
378
451
  # assigns the owner workbook for this worksheet
379
452
  def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
380
453
 
381
- # Updates auto fit data.
382
- # We store an auto_fit_data item for each column. when a row is added we multiple the font size by the length of the text to
383
- # attempt to identify the longest cell in the column. This is not 100% accurate as it needs to take into account
384
- # any formatting that will be applied to the data, as well as the actual rendering size when the length and size is equal
385
- # for two cells.
386
-
387
- # @return [Array] of Cell objects
388
- # @param [Array] cells an array of cells
389
- # @param [Array] widths an array of cell widths @see Worksheet#add_row
390
- def update_auto_fit_data(cells, widths=[])
391
- # TODO delay this until rendering. too much work when we dont know what they are going to do to the sheet.
454
+
455
+ def update_column_info(cells, widths=[], style=[])
392
456
  styles = self.workbook.styles
393
457
  cellXfs, fonts = styles.cellXfs, styles.fonts
394
458
  sz = 11
395
- cells.each_with_index do |item, index|
396
- col = @auto_fit_data[index] ||= {:longest=>"", :sz=>sz, :fixed=>nil}
459
+ cells.each_with_index do |cell, index|
460
+ @column_info[index] ||= Col.new index+1, index+1
461
+ col = @column_info[index]
397
462
  width = widths[index]
398
- # set fixed width and skip if numeric width is given
399
- col[:fixed] = width if [Integer, Float, Fixnum].include?(width.class)
400
- # ignore default column widths and formula
401
- next if width == :ignore || (item.value.is_a?(String) && item.value.start_with?('='))
402
- # make sure we can turn that fixed with off!
403
- col[:fixed] = nil if width == :auto
404
-
405
- cell_xf = cellXfs[item.style]
406
- font = fonts[cell_xf.fontId || 0]
407
- sz = item.sz || font.sz || fonts[0].sz
408
- if (col[:longest].scan(/./mu).size * col[:sz]) < (item.value.to_s.scan(/./mu).size * sz)
409
- col[:sz] = sz
410
- col[:longest] = item.value.to_s
463
+ col.width = width if [Integer, Float, Fixnum].include?(width.class)
464
+ c_style = style[index] if [Integer, Fixnum].include?(style[index].class)
465
+ next if width == :ignore || col.width || (cell.value.is_a?(String) && cell.value.start_with?('='))
466
+ if self.workbook.use_autowidth
467
+ cell_xf = cellXfs[(c_style || 0)]
468
+ font = fonts[(cell_xf.fontId || 0)]
469
+ sz = cell.sz || font.sz || sz
470
+ col.width = [(col.width || 0), calculate_width(cell.value.to_s, sz)].max
411
471
  end
412
472
  end
413
- cells
414
473
  end
415
474
 
416
- # Determines the proper width for a column based on content.
417
- # @note
418
- # width = Truncate([!{Number of Characters} * !{Maximum Digit Width} + !{5 pixel padding}]/!{Maximum Digit Width}*256)/256
419
- # @return [Float]
420
- # @param [Hash] A hash of auto_fit_data
421
- def auto_width(col)
422
- return col[:fixed] unless col[:fixed] == nil
423
-
424
- mdw_count, font_scale, mdw = 0, col[:sz]/11.0, 6.0
425
- mdw_count = col[:longest].scan(/./mu).reduce(0) do | count, char |
475
+ def calculate_width(text, sz)
476
+ mdw_count, font_scale, mdw = 0, sz/11.0, 6.0
477
+ mdw_count = text.scan(/./mu).reduce(0) do | count, char |
426
478
  count +=1 if @magick_draw.get_type_metrics(char).max_advance >= mdw
427
479
  count
428
480
  end
429
481
  ((mdw_count * mdw + 5) / mdw * 256) / 256.0 * font_scale
430
482
  end
431
-
432
- # Something to look into:
433
- # width calculation actually needs to be done agains the formatted value for items that apply a
434
- # format
435
- # def excel_format(cell)
436
- # # The most common case.
437
- # return time.value.to_s if cell.style == 0
438
- #
439
- # # The second most common case
440
- # num_fmt = workbook.styles.cellXfs[items.style].numFmtId
441
- # return value.to_s if num_fmt == 0
442
- #
443
- # format_code = workbook.styles.numFmts[num_fmt]
444
- # # need to find some exceptionally fast way of parsing value according to
445
- # # an excel format_code
446
- # item.value.to_s
447
- # end
448
-
449
483
  end
450
484
  end