axlsx 1.0.18 → 1.1.0

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