write_xlsx 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitattributes +1 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +82 -0
- data/Rakefile +78 -0
- data/VERSION +1 -0
- data/examples/a_simple.rb +45 -0
- data/examples/array_formula.rb +33 -0
- data/examples/autofilter.rb +235 -0
- data/examples/chart_area.rb +59 -0
- data/examples/chart_bar.rb +59 -0
- data/examples/chart_column.rb +58 -0
- data/examples/chart_line.rb +59 -0
- data/examples/chart_pie.rb +49 -0
- data/examples/chart_scatter.rb +59 -0
- data/examples/chart_stock.rb +65 -0
- data/examples/colors.rb +130 -0
- data/examples/comments1.rb +12 -0
- data/examples/comments2.rb +335 -0
- data/examples/conditional_format.rb +67 -0
- data/examples/data_validate.rb +279 -0
- data/examples/defined_name.rb +28 -0
- data/examples/demo.rb +104 -0
- data/examples/diag_border.rb +26 -0
- data/examples/headers.rb +119 -0
- data/examples/hide_sheet.rb +30 -0
- data/examples/hyperlink1.rb +58 -0
- data/examples/indent.rb +28 -0
- data/examples/merge1.rb +38 -0
- data/examples/merge2.rb +48 -0
- data/examples/merge3.rb +43 -0
- data/examples/merge4.rb +82 -0
- data/examples/merge5.rb +70 -0
- data/examples/merge6.rb +48 -0
- data/examples/outline.rb +252 -0
- data/examples/properties.rb +33 -0
- data/examples/protection.rb +34 -0
- data/examples/rich_strings.rb +42 -0
- data/examples/right_to_left.rb +24 -0
- data/examples/tab_colors.rb +26 -0
- data/lib/write_xlsx.rb +77 -0
- data/lib/write_xlsx/chart.rb +3027 -0
- data/lib/write_xlsx/chart/area.rb +52 -0
- data/lib/write_xlsx/chart/bar.rb +126 -0
- data/lib/write_xlsx/chart/column.rb +132 -0
- data/lib/write_xlsx/chart/line.rb +51 -0
- data/lib/write_xlsx/chart/pie.rb +210 -0
- data/lib/write_xlsx/chart/scatter.rb +252 -0
- data/lib/write_xlsx/chart/stock.rb +134 -0
- data/lib/write_xlsx/chartsheet.rb +173 -0
- data/lib/write_xlsx/colors.rb +65 -0
- data/lib/write_xlsx/compatibility.rb +71 -0
- data/lib/write_xlsx/drawing.rb +547 -0
- data/lib/write_xlsx/format.rb +683 -0
- data/lib/write_xlsx/package/app.rb +218 -0
- data/lib/write_xlsx/package/comments.rb +221 -0
- data/lib/write_xlsx/package/content_types.rb +189 -0
- data/lib/write_xlsx/package/core.rb +196 -0
- data/lib/write_xlsx/package/packager.rb +510 -0
- data/lib/write_xlsx/package/relationships.rb +98 -0
- data/lib/write_xlsx/package/shared_strings.rb +96 -0
- data/lib/write_xlsx/package/styles.rb +705 -0
- data/lib/write_xlsx/package/theme.rb +45 -0
- data/lib/write_xlsx/package/vml.rb +386 -0
- data/lib/write_xlsx/package/xml_writer_simple.rb +90 -0
- data/lib/write_xlsx/utility.rb +113 -0
- data/lib/write_xlsx/workbook.rb +1488 -0
- data/lib/write_xlsx/worksheet.rb +6578 -0
- data/lib/write_xlsx/zip_file_utils.rb +98 -0
- data/test/chart/test_add_series.rb +113 -0
- data/test/chart/test_process_names.rb +27 -0
- data/test/chart/test_write_auto.rb +15 -0
- data/test/chart/test_write_ax_id.rb +15 -0
- data/test/chart/test_write_ax_pos.rb +15 -0
- data/test/chart/test_write_chart_space.rb +15 -0
- data/test/chart/test_write_cross_ax.rb +15 -0
- data/test/chart/test_write_crosses.rb +15 -0
- data/test/chart/test_write_format_code.rb +15 -0
- data/test/chart/test_write_idx.rb +15 -0
- data/test/chart/test_write_label_align.rb +15 -0
- data/test/chart/test_write_label_offset.rb +15 -0
- data/test/chart/test_write_lang.rb +15 -0
- data/test/chart/test_write_layout.rb +15 -0
- data/test/chart/test_write_legend.rb +16 -0
- data/test/chart/test_write_legend_pos.rb +15 -0
- data/test/chart/test_write_major_gridlines.rb +15 -0
- data/test/chart/test_write_marker.rb +17 -0
- data/test/chart/test_write_marker_size.rb +15 -0
- data/test/chart/test_write_marker_value.rb +16 -0
- data/test/chart/test_write_num_cache.rb +16 -0
- data/test/chart/test_write_num_fmt.rb +16 -0
- data/test/chart/test_write_number_format.rb +15 -0
- data/test/chart/test_write_order.rb +15 -0
- data/test/chart/test_write_orientation.rb +15 -0
- data/test/chart/test_write_page_margins.rb +15 -0
- data/test/chart/test_write_page_setup.rb +15 -0
- data/test/chart/test_write_plot_vis_only.rb +15 -0
- data/test/chart/test_write_pt.rb +16 -0
- data/test/chart/test_write_pt_count.rb +16 -0
- data/test/chart/test_write_series_formula.rb +16 -0
- data/test/chart/test_write_style.rb +41 -0
- data/test/chart/test_write_symbol.rb +16 -0
- data/test/chart/test_write_tick_lbl_pos.rb +16 -0
- data/test/chart/test_write_v.rb +16 -0
- data/test/drawing/test_drawing_chart_01.rb +50 -0
- data/test/drawing/test_drawing_image_01.rb +59 -0
- data/test/helper.rb +90 -0
- data/test/package/app/test_app01.rb +44 -0
- data/test/package/app/test_app02.rb +46 -0
- data/test/package/app/test_app03.rb +53 -0
- data/test/package/comments/test_comments01.rb +36 -0
- data/test/package/comments/test_write_text_t.rb +44 -0
- data/test/package/content_types/test_content_types.rb +35 -0
- data/test/package/content_types/test_write_default.rb +13 -0
- data/test/package/content_types/test_write_override.rb +13 -0
- data/test/package/core/test_core01.rb +28 -0
- data/test/package/core/test_core02.rb +42 -0
- data/test/package/relationships/test_relationships.rb +28 -0
- data/test/package/relationships/test_sheet_rels.rb +22 -0
- data/test/package/shared_strings/test_shared_strings01.rb +30 -0
- data/test/package/shared_strings/test_shared_strings02.rb +30 -0
- data/test/package/shared_strings/test_write_si.rb +13 -0
- data/test/package/shared_strings/test_write_sst.rb +15 -0
- data/test/package/styles/test_styles_01.rb +69 -0
- data/test/package/styles/test_styles_02.rb +104 -0
- data/test/package/styles/test_styles_03.rb +90 -0
- data/test/package/styles/test_styles_04.rb +216 -0
- data/test/package/styles/test_styles_05.rb +150 -0
- data/test/package/styles/test_styles_06.rb +104 -0
- data/test/package/styles/test_styles_07.rb +104 -0
- data/test/package/styles/test_styles_08.rb +109 -0
- data/test/package/styles/test_styles_09.rb +95 -0
- data/test/package/vml/test_vml_01.rb +42 -0
- data/test/package/vml/test_write_anchor.rb +14 -0
- data/test/package/vml/test_write_auto_fill.rb +14 -0
- data/test/package/vml/test_write_column.rb +14 -0
- data/test/package/vml/test_write_div.rb +14 -0
- data/test/package/vml/test_write_fill.rb +14 -0
- data/test/package/vml/test_write_idmap.rb +14 -0
- data/test/package/vml/test_write_move_with_cells.rb +14 -0
- data/test/package/vml/test_write_path.rb +22 -0
- data/test/package/vml/test_write_row.rb +14 -0
- data/test/package/vml/test_write_shadow.rb +14 -0
- data/test/package/vml/test_write_shapelayout.rb +14 -0
- data/test/package/vml/test_write_shapetype.rb +14 -0
- data/test/package/vml/test_write_size_with_cells.rb +14 -0
- data/test/package/vml/test_write_stroke.rb +14 -0
- data/test/package/vml/test_write_textbox.rb +14 -0
- data/test/perl_output/a_simple.xlsx +0 -0
- data/test/perl_output/array_formula.xlsx +0 -0
- data/test/perl_output/autofilter.xlsx +0 -0
- data/test/perl_output/chart_area.xlsx +0 -0
- data/test/perl_output/chart_bar.xlsx +0 -0
- data/test/perl_output/chart_column.xlsx +0 -0
- data/test/perl_output/chart_line.xlsx +0 -0
- data/test/perl_output/chart_pie.xlsx +0 -0
- data/test/perl_output/chart_scatter.xlsx +0 -0
- data/test/perl_output/chart_stock.xlsx +0 -0
- data/test/perl_output/comments1.xlsx +0 -0
- data/test/perl_output/comments2.xlsx +0 -0
- data/test/perl_output/conditional_format.xlsx +0 -0
- data/test/perl_output/data_validate.xlsx +0 -0
- data/test/perl_output/defined_name.xlsx +0 -0
- data/test/perl_output/demo.xlsx +0 -0
- data/test/perl_output/diag_border.xlsx +0 -0
- data/test/perl_output/fit_to_pages.xlsx +0 -0
- data/test/perl_output/headers.xlsx +0 -0
- data/test/perl_output/hide_sheet.xlsx +0 -0
- data/test/perl_output/hyperlink.xlsx +0 -0
- data/test/perl_output/indent.xlsx +0 -0
- data/test/perl_output/merge1.xlsx +0 -0
- data/test/perl_output/merge2.xlsx +0 -0
- data/test/perl_output/merge3.xlsx +0 -0
- data/test/perl_output/merge4.xlsx +0 -0
- data/test/perl_output/merge5.xlsx +0 -0
- data/test/perl_output/merge6.xlsx +0 -0
- data/test/perl_output/outline.xlsx +0 -0
- data/test/perl_output/print_scale.xlsx +0 -0
- data/test/perl_output/properties.xlsx +0 -0
- data/test/perl_output/protection.xlsx +0 -0
- data/test/perl_output/rich_strings.xlsx +0 -0
- data/test/perl_output/right_to_left.xlsx +0 -0
- data/test/perl_output/tab_colors.xlsx +0 -0
- data/test/test_delete_files.rb +37 -0
- data/test/test_example_match.rb +2281 -0
- data/test/test_xml_writer_simple.rb +63 -0
- data/test/workbook/test_get_chart_range.rb +59 -0
- data/test/workbook/test_sort_defined_names.rb +77 -0
- data/test/workbook/test_workbook_01.rb +29 -0
- data/test/workbook/test_workbook_02.rb +31 -0
- data/test/workbook/test_workbook_03.rb +31 -0
- data/test/workbook/test_workbook_new.rb +18 -0
- data/test/workbook/test_write_defined_name.rb +17 -0
- data/test/workbook/test_write_defined_names.rb +41 -0
- data/test/worksheet/test_calculate_spans.rb +58 -0
- data/test/worksheet/test_convert_date_time_01.rb +439 -0
- data/test/worksheet/test_convert_date_time_02.rb +478 -0
- data/test/worksheet/test_convert_date_time_03.rb +435 -0
- data/test/worksheet/test_extract_filter_tokens.rb +109 -0
- data/test/worksheet/test_parse_filter_expression.rb +143 -0
- data/test/worksheet/test_position_object.rb +50 -0
- data/test/worksheet/test_repeat_formula.rb +55 -0
- data/test/worksheet/test_worksheet_01.rb +32 -0
- data/test/worksheet/test_worksheet_02.rb +38 -0
- data/test/worksheet/test_worksheet_03.rb +44 -0
- data/test/worksheet/test_worksheet_04.rb +45 -0
- data/test/worksheet/test_write_array_formula_01.rb +99 -0
- data/test/worksheet/test_write_autofilter.rb +260 -0
- data/test/worksheet/test_write_brk.rb +18 -0
- data/test/worksheet/test_write_cell.rb +49 -0
- data/test/worksheet/test_write_cell_value.rb +33 -0
- data/test/worksheet/test_write_col_breaks.rb +27 -0
- data/test/worksheet/test_write_col_info.rb +95 -0
- data/test/worksheet/test_write_conditional_formatting.rb +72 -0
- data/test/worksheet/test_write_custom_filter.rb +18 -0
- data/test/worksheet/test_write_custom_filters.rb +25 -0
- data/test/worksheet/test_write_data_validation_01.rb +113 -0
- data/test/worksheet/test_write_data_validation_02.rb +528 -0
- data/test/worksheet/test_write_dimension.rb +94 -0
- data/test/worksheet/test_write_ext.rb +18 -0
- data/test/worksheet/test_write_ext_lst.rb +18 -0
- data/test/worksheet/test_write_filter.rb +18 -0
- data/test/worksheet/test_write_filter_column.rb +18 -0
- data/test/worksheet/test_write_filters.rb +32 -0
- data/test/worksheet/test_write_header_footer.rb +53 -0
- data/test/worksheet/test_write_hyperlink.rb +39 -0
- data/test/worksheet/test_write_hyperlinks.rb +27 -0
- data/test/worksheet/test_write_legacy_drawing.rb +19 -0
- data/test/worksheet/test_write_merge_cell.rb +18 -0
- data/test/worksheet/test_write_merge_cells.rb +192 -0
- data/test/worksheet/test_write_methods.rb +353 -0
- data/test/worksheet/test_write_mx_plv.rb +19 -0
- data/test/worksheet/test_write_page_margins.rb +98 -0
- data/test/worksheet/test_write_page_set_up_pr.rb +19 -0
- data/test/worksheet/test_write_page_setup.rb +54 -0
- data/test/worksheet/test_write_pane.rb +123 -0
- data/test/worksheet/test_write_phonetic_pr.rb +19 -0
- data/test/worksheet/test_write_print_options.rb +77 -0
- data/test/worksheet/test_write_row_breaks.rb +27 -0
- data/test/worksheet/test_write_row_element.rb +69 -0
- data/test/worksheet/test_write_selection.rb +18 -0
- data/test/worksheet/test_write_sheet_calc_pr.rb +18 -0
- data/test/worksheet/test_write_sheet_data.rb +18 -0
- data/test/worksheet/test_write_sheet_format_pr.rb +18 -0
- data/test/worksheet/test_write_sheet_pr.rb +36 -0
- data/test/worksheet/test_write_sheet_protection.rb +174 -0
- data/test/worksheet/test_write_sheet_view.rb +62 -0
- data/test/worksheet/test_write_sheet_view1.rb +64 -0
- data/test/worksheet/test_write_sheet_view2.rb +56 -0
- data/test/worksheet/test_write_sheet_view3.rb +83 -0
- data/test/worksheet/test_write_sheet_view4.rb +83 -0
- data/test/worksheet/test_write_sheet_view5.rb +74 -0
- data/test/worksheet/test_write_sheet_view6.rb +51 -0
- data/test/worksheet/test_write_sheet_view7.rb +71 -0
- data/test/worksheet/test_write_sheet_view8.rb +51 -0
- data/test/worksheet/test_write_sheet_view9.rb +51 -0
- data/test/worksheet/test_write_tab_color.rb +23 -0
- data/test/worksheet/test_write_worksheet.rb +19 -0
- data/write_xlsx.gemspec +308 -0
- metadata +363 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module Writexlsx
|
3
|
+
module Utility
|
4
|
+
#
|
5
|
+
# xl_rowcol_to_cell($row, $col, $row_absolute, $col_absolute)
|
6
|
+
#
|
7
|
+
def xl_rowcol_to_cell(row, col, row_absolute = false, col_absolute = false)
|
8
|
+
row += 1 # Change from 0-indexed to 1 indexed.
|
9
|
+
row_abs = row_absolute ? '$' : ''
|
10
|
+
col_abs = col_absolute ? '$' : ''
|
11
|
+
col_str = xl_col_to_name(col, col_absolute)
|
12
|
+
"#{col_str}#{absolute_char(row_absolute)}#{row}"
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Returns: ($row, $col, $row_absolute, $col_absolute)
|
17
|
+
#
|
18
|
+
# The $row_absolute and $col_absolute parameters aren't documented because they
|
19
|
+
# mainly used internally and aren't very useful to the user.
|
20
|
+
#
|
21
|
+
def xl_cell_to_rowcol(cell)
|
22
|
+
cell =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/
|
23
|
+
|
24
|
+
col_abs = $1 != ""
|
25
|
+
col = $2
|
26
|
+
row_abs = $3 != ""
|
27
|
+
row = $4.to_i
|
28
|
+
|
29
|
+
# Convert base26 column string to number
|
30
|
+
# All your Base are belong to us.
|
31
|
+
chars = col.split(//)
|
32
|
+
expn = 0
|
33
|
+
col = 0
|
34
|
+
|
35
|
+
chars.reverse.each do |char|
|
36
|
+
col += (char.ord - 'A'.ord + 1) * (26 ** expn)
|
37
|
+
expn += 1
|
38
|
+
end
|
39
|
+
|
40
|
+
# Convert 1-index to zero-index
|
41
|
+
row -= 1
|
42
|
+
col -= 1
|
43
|
+
|
44
|
+
return [row, col, row_abs, col_abs]
|
45
|
+
end
|
46
|
+
|
47
|
+
def xl_col_to_name(col, col_absolute)
|
48
|
+
# Change from 0-indexed to 1 indexed.
|
49
|
+
col += 1
|
50
|
+
col_str = ''
|
51
|
+
|
52
|
+
while col > 0
|
53
|
+
# Set remainder from 1 .. 26
|
54
|
+
remainder = col % 26
|
55
|
+
remainder = 26 if remainder == 0
|
56
|
+
|
57
|
+
# Convert the remainder to a character. C-ishly.
|
58
|
+
col_letter = ("A".ord + remainder - 1).chr
|
59
|
+
|
60
|
+
# Accumulate the column letters, right to left.
|
61
|
+
col_str = col_letter + col_str
|
62
|
+
|
63
|
+
# Get the next order of magnitude.
|
64
|
+
col = (col - 1) / 26
|
65
|
+
end
|
66
|
+
|
67
|
+
"#{absolute_char(col_absolute)}#{col_str}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def xl_range(row_1, row_2, col_1, col_2,
|
71
|
+
row_abs_1 = false, row_abs_2 = false, col_abs_1 = false, col_abs_2 = false)
|
72
|
+
range1 = xl_rowcol_to_cell(row_1, col_1, row_abs_1, col_abs_1)
|
73
|
+
range2 = xl_rowcol_to_cell(row_2, col_2, row_abs_2, col_abs_2)
|
74
|
+
|
75
|
+
"#{range1}:#{range2}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def xl_range_formula(sheetname, row_1, row_2, col_1, col_2)
|
79
|
+
# Use Excel's conventions and quote the sheet name if it contains any
|
80
|
+
# non-word character or if it isn't already quoted.
|
81
|
+
sheetname = "'#{sheetname}'" if sheetname =~ /\W/ && !(sheetname =~ /^'/)
|
82
|
+
|
83
|
+
range1 = xl_rowcol_to_cell( row_1, col_1, 1, 1 )
|
84
|
+
range2 = xl_rowcol_to_cell( row_2, col_2, 1, 1 )
|
85
|
+
|
86
|
+
"=#{sheetname}!#{range1}:#{range2}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def absolute_char(absolute)
|
90
|
+
absolute ? '$' : ''
|
91
|
+
end
|
92
|
+
|
93
|
+
def xml_str
|
94
|
+
@writer.string
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.delete_files(path)
|
98
|
+
if FileTest.file?(path)
|
99
|
+
File.delete(path)
|
100
|
+
elsif FileTest.directory?(path)
|
101
|
+
Dir.foreach(path) do |file|
|
102
|
+
next if file =~ /^\.\.?$/ # '.' or '..'
|
103
|
+
delete_files(path.sub(/\/+$/,"") + '/' + file)
|
104
|
+
end
|
105
|
+
Dir.rmdir(path)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def put_deprecate_message(method)
|
110
|
+
$stderr.puts("Warning: calling deprecated method #{method}. This method will be removed in a future release.")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,1488 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'write_xlsx/package/xml_writer_simple'
|
3
|
+
require 'write_xlsx/package/packager'
|
4
|
+
require 'write_xlsx/worksheet'
|
5
|
+
require 'write_xlsx/format'
|
6
|
+
require 'write_xlsx/utility'
|
7
|
+
require 'write_xlsx/chart'
|
8
|
+
require 'write_xlsx/zip_file_utils'
|
9
|
+
require 'tmpdir'
|
10
|
+
require 'tempfile'
|
11
|
+
require 'digest/md5'
|
12
|
+
|
13
|
+
module Writexlsx
|
14
|
+
class Workbook
|
15
|
+
|
16
|
+
include Writexlsx::Utility
|
17
|
+
|
18
|
+
attr_accessor :str_total, :str_unique
|
19
|
+
attr_writer :firstsheet
|
20
|
+
attr_reader :palette
|
21
|
+
attr_reader :font_count, :num_format_count, :border_count, :fill_count, :custom_colors
|
22
|
+
attr_reader :worksheets, :sheetnames, :charts, :drawings, :num_comment_files, :named_ranges
|
23
|
+
attr_reader :str_array, :doc_properties
|
24
|
+
attr_reader :image_types, :images
|
25
|
+
|
26
|
+
#
|
27
|
+
# A new Excel workbook is created using the new() constructor which accepts either a filename
|
28
|
+
# or a IO object as a parameter.
|
29
|
+
# The following example creates a new Excel file based on a filename:
|
30
|
+
#
|
31
|
+
# workbook = WriteXLSX.new('filename.xlsx')
|
32
|
+
# worksheet = workbook.add_worksheet
|
33
|
+
# worksheet.write(0, 0, 'Hi Excel!')
|
34
|
+
# workbook.close
|
35
|
+
#
|
36
|
+
# Here are some other examples of using new() with filenames:
|
37
|
+
#
|
38
|
+
# workbook1 = WriteXLSX.new(filename)
|
39
|
+
# workbook2 = WriteXLSX.new('/tmp/filename.xlsx')
|
40
|
+
# workbook3 = WriteXLSX.new("c:\\tmp\\filename.xlsx")
|
41
|
+
# workbook4 = WriteXLSX.new('c:\tmp\filename.xlsx')
|
42
|
+
#
|
43
|
+
# The last two examples demonstrates how to create a file on DOS or Windows where it is
|
44
|
+
# necessary to either escape the directory separator \ or to use single quotes to ensure
|
45
|
+
# that it isn't interpolated.
|
46
|
+
#
|
47
|
+
# It is recommended that the filename uses the extension .xlsx rather than .xls since
|
48
|
+
# the latter causes an Excel warning when used with the XLSX format.
|
49
|
+
#
|
50
|
+
# The new() constructor returns a WriteXLSX object that you can use to add worksheets and
|
51
|
+
# store data.
|
52
|
+
#
|
53
|
+
# You can also pass a valid IO object to the new() constructor.
|
54
|
+
#
|
55
|
+
# xlsx = StringIO.new
|
56
|
+
# workbook = WriteXLSX.new(xlsx)
|
57
|
+
# ....
|
58
|
+
# workbook.close
|
59
|
+
# # you can get XLSX binary data as xlsx.string
|
60
|
+
#
|
61
|
+
# And you can pass default_formats parameter like this:
|
62
|
+
#
|
63
|
+
# formats = {
|
64
|
+
# :font => 'Arial',
|
65
|
+
# :size => 10.5
|
66
|
+
# }
|
67
|
+
# workbook = WriteXLSX.new('file.xlsx', formats)
|
68
|
+
#
|
69
|
+
def initialize(file, default_formats = {})
|
70
|
+
@writer = Package::XMLWriterSimple.new
|
71
|
+
|
72
|
+
@tempdir = File.join(Dir.tmpdir, Digest::MD5.hexdigest(Time.now.to_s))
|
73
|
+
setup_filename(file)
|
74
|
+
@date_1904 = false
|
75
|
+
@activesheet = 0
|
76
|
+
@firstsheet = 0
|
77
|
+
@selected = 0
|
78
|
+
@fileclosed = false
|
79
|
+
@sheet_name = 'Sheet'
|
80
|
+
@chart_name = 'Chart'
|
81
|
+
@sheetname_count = 0
|
82
|
+
@chartname_count = 0
|
83
|
+
@worksheets = []
|
84
|
+
@charts = []
|
85
|
+
@drawings = []
|
86
|
+
@sheetnames = []
|
87
|
+
@formats = []
|
88
|
+
@xf_formats = []
|
89
|
+
@xf_format_indices = {}
|
90
|
+
@dxf_formats = []
|
91
|
+
@dxf_format_indices = {}
|
92
|
+
@font_count = 0
|
93
|
+
@num_format_count = 0
|
94
|
+
@defined_names = []
|
95
|
+
@named_ranges = []
|
96
|
+
@custom_colors = []
|
97
|
+
@doc_properties = {}
|
98
|
+
@local_time = Time.now
|
99
|
+
@num_comment_files = 0
|
100
|
+
@image_types = {}
|
101
|
+
@images = []
|
102
|
+
|
103
|
+
# Structures for the shared strings data.
|
104
|
+
@str_total = 0
|
105
|
+
@str_unique = 0
|
106
|
+
@str_table = {}
|
107
|
+
@str_array = []
|
108
|
+
|
109
|
+
add_format(default_formats.merge(:xf_index => 0))
|
110
|
+
set_color_palette
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# write XLSX data to file or IO object.
|
115
|
+
#
|
116
|
+
def close
|
117
|
+
# In case close() is called twice, by user and by DESTROY.
|
118
|
+
return if @fileclosed
|
119
|
+
|
120
|
+
@fileclosed = 1
|
121
|
+
store_workbook
|
122
|
+
end
|
123
|
+
|
124
|
+
# get array of Worksheet objects
|
125
|
+
#
|
126
|
+
# :call-seq:
|
127
|
+
# sheets -> array of all Wordsheet object
|
128
|
+
# sheets(1, 3, 4) -> array of spcified Worksheet object.
|
129
|
+
#
|
130
|
+
# The sheets() method returns a array, or a sliced array, of the worksheets
|
131
|
+
# in a workbook.
|
132
|
+
#
|
133
|
+
# If no arguments are passed the method returns a list of all the worksheets
|
134
|
+
# in the workbook. This is useful if you want to repeat an operation on each
|
135
|
+
# worksheet:
|
136
|
+
#
|
137
|
+
# workbook.sheets.each do |worksheet|
|
138
|
+
# print worksheet.get_name
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# You can also specify a slice list to return one or more worksheet objects:
|
142
|
+
#
|
143
|
+
# worksheet = workbook.sheets(0)
|
144
|
+
# worksheet.write('A1', 'Hello')
|
145
|
+
#
|
146
|
+
# you can write the above example as:
|
147
|
+
#
|
148
|
+
# workbook.sheets(0).write('A1', 'Hello')
|
149
|
+
#
|
150
|
+
# The following example returns the first and last worksheet in a workbook:
|
151
|
+
#
|
152
|
+
# workbook.sheets(0, -1).each do |sheet|
|
153
|
+
# # Do something
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
def sheets(*args)
|
157
|
+
if args.empty?
|
158
|
+
@worksheets
|
159
|
+
else
|
160
|
+
args.collect{|i| @worksheets[i] }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Set the date system: false = 1900 (the default), true = 1904
|
166
|
+
#
|
167
|
+
# Excel stores dates as real numbers where the integer part stores
|
168
|
+
# the number of days since the epoch and the fractional part stores
|
169
|
+
# the percentage of the day. The epoch can be either 1900 or 1904.
|
170
|
+
# Excel for Windows uses 1900 and Excel for Macintosh uses 1904.
|
171
|
+
# However, Excel on either platform will convert automatically between
|
172
|
+
# one system and the other.
|
173
|
+
#
|
174
|
+
# WriteXLSX stores dates in the 1900 format by default. If you wish to
|
175
|
+
# change this you can call the set_1904() workbook method.
|
176
|
+
# You can query the current value by calling the get_1904() workbook method.
|
177
|
+
# This returns 0 for 1900 and 1 for 1904.
|
178
|
+
#
|
179
|
+
# In general you probably won't need to use set_1904().
|
180
|
+
#
|
181
|
+
def set_1904(mode = true)
|
182
|
+
unless sheets.empty?
|
183
|
+
raise "set_1904() must be called before add_worksheet()"
|
184
|
+
end
|
185
|
+
@date_1904 = (!mode || mode == 0) ? false : true
|
186
|
+
end
|
187
|
+
|
188
|
+
def get_1904
|
189
|
+
@date_1904
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# user must not use. it is internal method.
|
194
|
+
#
|
195
|
+
def set_xml_writer(filename) #:nodoc:
|
196
|
+
@writer.set_xml_writer(filename)
|
197
|
+
end
|
198
|
+
|
199
|
+
#
|
200
|
+
# user must not use. it is internal method.
|
201
|
+
#
|
202
|
+
def xml_str #:nodoc:
|
203
|
+
@writer.string
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# user must not use. it is internal method.
|
208
|
+
#
|
209
|
+
def assemble_xml_file #:nodoc:
|
210
|
+
return unless @writer
|
211
|
+
|
212
|
+
# Prepare format object for passing to Style.pm.
|
213
|
+
prepare_format_properties
|
214
|
+
|
215
|
+
write_xml_declaration
|
216
|
+
|
217
|
+
# Write the root workbook element.
|
218
|
+
write_workbook
|
219
|
+
|
220
|
+
# Write the XLSX file version.
|
221
|
+
write_file_version
|
222
|
+
|
223
|
+
# Write the workbook properties.
|
224
|
+
write_workbook_pr
|
225
|
+
|
226
|
+
# Write the workbook view properties.
|
227
|
+
write_book_views
|
228
|
+
|
229
|
+
# Write the worksheet names and ids.
|
230
|
+
write_sheets
|
231
|
+
|
232
|
+
# Write the workbook defined names.
|
233
|
+
write_defined_names
|
234
|
+
|
235
|
+
# Write the workbook calculation properties.
|
236
|
+
write_calc_pr
|
237
|
+
|
238
|
+
# Write the workbook extension storage.
|
239
|
+
#write_ext_lst
|
240
|
+
|
241
|
+
# Close the workbook tag.
|
242
|
+
write_workbook_end
|
243
|
+
|
244
|
+
# Close the XML writer object and filehandle.
|
245
|
+
@writer.crlf
|
246
|
+
@writer.close
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# At least one worksheet should be added to a new workbook. A worksheet is used to write data into cells:
|
251
|
+
#
|
252
|
+
# worksheet1 = workbook.add_worksheet # Sheet1
|
253
|
+
# worksheet2 = workbook.add_worksheet('Foglio2') # Foglio2
|
254
|
+
# worksheet3 = workbook.add_worksheet('Data') # Data
|
255
|
+
# worksheet4 = workbook.add_worksheet # Sheet4
|
256
|
+
# If name is not specified the default Excel convention will be followed, i.e. Sheet1, Sheet2, etc.
|
257
|
+
#
|
258
|
+
# The worksheet name must be a valid Excel worksheet name,
|
259
|
+
# i.e. it cannot contain any of the following characters,
|
260
|
+
# [ ] : * ? / \
|
261
|
+
#
|
262
|
+
# and it must be less than 32 characters.
|
263
|
+
# In addition, you cannot use the same, case insensitive,
|
264
|
+
# sheetname for more than one worksheet.
|
265
|
+
#
|
266
|
+
def add_worksheet(name = '')
|
267
|
+
index = @worksheets.size
|
268
|
+
name = check_sheetname(name)
|
269
|
+
|
270
|
+
worksheet = Worksheet.new(self, index, name)
|
271
|
+
@worksheets[index] = worksheet
|
272
|
+
@sheetnames[index] = name
|
273
|
+
|
274
|
+
worksheet
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# This method is use to create a new chart either as a standalone worksheet
|
279
|
+
# (the default) or as an embeddable object that can be inserted into
|
280
|
+
# a worksheet via the insert_chart() Worksheet method.
|
281
|
+
#
|
282
|
+
# chart = workbook.add_chart(:type => 'column')
|
283
|
+
#
|
284
|
+
# The properties that can be set are:
|
285
|
+
#
|
286
|
+
# :type (required)
|
287
|
+
# :subtype (optional)
|
288
|
+
# :name (optional)
|
289
|
+
# :embedded (optional)
|
290
|
+
#
|
291
|
+
# :type
|
292
|
+
#
|
293
|
+
# This is a required parameter. It defines the type of chart that will be created.
|
294
|
+
#
|
295
|
+
# chart = workbook.add_chart(:type => 'line')
|
296
|
+
#
|
297
|
+
# The available types are:
|
298
|
+
#
|
299
|
+
# area
|
300
|
+
# bar
|
301
|
+
# column
|
302
|
+
# line
|
303
|
+
# pie
|
304
|
+
# scatter
|
305
|
+
# stock
|
306
|
+
#
|
307
|
+
# :subtype
|
308
|
+
#
|
309
|
+
# Used to define a chart subtype where available.
|
310
|
+
#
|
311
|
+
# chart = workbook.add_chart(:type => 'bar', :subtype => 'stacked')
|
312
|
+
#
|
313
|
+
# Currently only Bar and Column charts support subtypes
|
314
|
+
# (stacked and percent_stacked). See the documentation for those chart
|
315
|
+
# types.
|
316
|
+
#
|
317
|
+
# :name
|
318
|
+
#
|
319
|
+
# Set the name for the chart sheet. The name property is optional and
|
320
|
+
# if it isn't supplied will default to Chart1 .. n. The name must be
|
321
|
+
# a valid Excel worksheet name. See add_worksheet for more details on
|
322
|
+
# valid sheet names. The name property can be omitted for embedded charts.
|
323
|
+
#
|
324
|
+
# chart = workbook.add_chart(:type => 'line', :name => 'Results Chart')
|
325
|
+
#
|
326
|
+
# :embedded
|
327
|
+
#
|
328
|
+
# Specifies that the Chart object will be inserted in a worksheet
|
329
|
+
# via the insert_chart Worksheet method. It is an error to try insert
|
330
|
+
# a Chart that doesn't have this flag set.
|
331
|
+
#
|
332
|
+
# chart = workbook.add_chart(:type => 'line', :embedded => 1)
|
333
|
+
#
|
334
|
+
# # Configure the chart.
|
335
|
+
# ...
|
336
|
+
#
|
337
|
+
# # Insert the chart into the a worksheet.
|
338
|
+
# worksheet.insert_chart('E2', chart)
|
339
|
+
#
|
340
|
+
# See Chart for details on how to configure the chart object
|
341
|
+
# once it is created. See also the chart_*.pl programs in the examples
|
342
|
+
# directory of the distro.
|
343
|
+
#
|
344
|
+
def add_chart(params = {})
|
345
|
+
name = ''
|
346
|
+
index = @worksheets.size
|
347
|
+
|
348
|
+
# Type must be specified so we can create the required chart instance.
|
349
|
+
type = params[:type]
|
350
|
+
raise "Must define chart type in add_chart()" unless type
|
351
|
+
|
352
|
+
# Ensure that the chart defaults to non embedded.
|
353
|
+
embedded = params[:embedded] || 0
|
354
|
+
|
355
|
+
# Check the worksheet name for non-embedded charts.
|
356
|
+
name = check_sheetname(params[:name], 1) unless embedded
|
357
|
+
|
358
|
+
chart = Chart.factory(type)
|
359
|
+
|
360
|
+
# Get an incremental id to use for axes ids.
|
361
|
+
chart.id = @charts.size
|
362
|
+
|
363
|
+
# If the chart isn't embedded let the workbook control it.
|
364
|
+
if embedded
|
365
|
+
# Set index to 0 so that the activate() and set_first_sheet() methods
|
366
|
+
# point back to the first worksheet if used for embedded charts.
|
367
|
+
chart.index = 0
|
368
|
+
chart.palette = @palette
|
369
|
+
chart.set_embedded_config_data
|
370
|
+
@charts << chart
|
371
|
+
|
372
|
+
return chart
|
373
|
+
else
|
374
|
+
chartsheet = Chartsheet.new(self, name, index)
|
375
|
+
chart.palette = @palette
|
376
|
+
chartsheet.chart = chart
|
377
|
+
chartsheet.drawing = Drawing.new
|
378
|
+
@worksheets.index = chartsheet
|
379
|
+
@sheetnames.index = name
|
380
|
+
|
381
|
+
@charts << chart
|
382
|
+
|
383
|
+
return chartsheet
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
# The add_format method can be used to create new Format objects
|
389
|
+
# which are used to apply formatting to a cell. You can either define
|
390
|
+
# the properties at creation time via a hash of property values
|
391
|
+
# or later via method calls.
|
392
|
+
#
|
393
|
+
# format1 = workbook.add_format(property_hash) # Set properties at creation
|
394
|
+
# format2 = workbook.add_format # Set properties later
|
395
|
+
#
|
396
|
+
# See the Format Class's rdoc for more details about Format properties and how to set them.
|
397
|
+
#
|
398
|
+
def add_format(properties = {})
|
399
|
+
init_data = [
|
400
|
+
@xf_format_indices,
|
401
|
+
@dxf_format_indices,
|
402
|
+
properties
|
403
|
+
]
|
404
|
+
|
405
|
+
format = Format.new(*init_data)
|
406
|
+
|
407
|
+
@formats.push(format) # Store format reference
|
408
|
+
|
409
|
+
format
|
410
|
+
end
|
411
|
+
|
412
|
+
#
|
413
|
+
# Create a defined name in Excel. We handle global/workbook level names and
|
414
|
+
# local/worksheet names.
|
415
|
+
#
|
416
|
+
# This method is used to defined a name that can be used to represent a value,
|
417
|
+
# a single cell or a range of cells in a workbook.
|
418
|
+
#
|
419
|
+
# For example to set a global/workbook name:
|
420
|
+
#
|
421
|
+
# # Global/workbook names.
|
422
|
+
# workbook.define_name('Exchange_rate', '=0.96')
|
423
|
+
# workbook.define_name('Sales', '=Sheet1!$G$1:$H$10')
|
424
|
+
#
|
425
|
+
# It is also possible to define a local/worksheet name by prefixing the name
|
426
|
+
# with the sheet name using the syntax sheetname!definedname:
|
427
|
+
#
|
428
|
+
# # Local/worksheet name.
|
429
|
+
# workbook.define_name('Sheet2!Sales', '=Sheet2!$G$1:$G$10')
|
430
|
+
# If the sheet name contains spaces or special characters
|
431
|
+
# you must enclose it in single quotes like in Excel:
|
432
|
+
#
|
433
|
+
# workbook.define_name("'New Data'!Sales", '=Sheet2!$G$1:$G$10')
|
434
|
+
#
|
435
|
+
# See the defined_name.rb program in the examples dir of the distro.
|
436
|
+
#
|
437
|
+
def define_name(name, formula)
|
438
|
+
sheet_index = nil
|
439
|
+
sheetname = ''
|
440
|
+
full_name = name
|
441
|
+
|
442
|
+
# Remove the = sign from the formula if it exists.
|
443
|
+
formula.sub!(/^=/, '')
|
444
|
+
|
445
|
+
# Local defined names are formatted like "Sheet1!name".
|
446
|
+
if name =~ /^(.*)!(.*)$/
|
447
|
+
sheetname = $1
|
448
|
+
name = $2
|
449
|
+
sheet_index = get_sheet_index(sheetname)
|
450
|
+
else
|
451
|
+
sheet_index =-1 # Use -1 to indicate global names.
|
452
|
+
end
|
453
|
+
|
454
|
+
# Warn if the sheet index wasn't found.
|
455
|
+
if !sheet_index
|
456
|
+
raise "Unknown sheet name #{sheetname} in defined_name()\n"
|
457
|
+
return -1
|
458
|
+
end
|
459
|
+
|
460
|
+
# Warn if the sheet name contains invalid chars as defined by Excel help.
|
461
|
+
if name !~ %r!^[a-zA-Z_\\][a-zA-Z_.]+!
|
462
|
+
raise "Invalid characters in name '#{name}' used in defined_name()\n"
|
463
|
+
return -1
|
464
|
+
end
|
465
|
+
|
466
|
+
# Warn if the sheet name looks like a cell name.
|
467
|
+
if name =~ %r(^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$)
|
468
|
+
raise "Invalid name '#{name}' looks like a cell name in defined_name()\n"
|
469
|
+
return -1
|
470
|
+
end
|
471
|
+
|
472
|
+
@defined_names.push([ name, sheet_index, formula])
|
473
|
+
end
|
474
|
+
|
475
|
+
#
|
476
|
+
# The set_properties method can be used to set the document properties
|
477
|
+
# of the Excel file created by WriteXLSX. These properties are visible
|
478
|
+
# when you use the Office Button -> Prepare -> Properties option in Excel
|
479
|
+
# and are also available to external applications that read or index windows files.
|
480
|
+
#
|
481
|
+
# The properties should be passed in hash format as follows:
|
482
|
+
#
|
483
|
+
# workbook.set_properties(
|
484
|
+
# :title => 'This is an example spreadsheet',
|
485
|
+
# :author => 'Hideo NAKAMURA',
|
486
|
+
# :comments => 'Created with Ruby and WriteXLSX'
|
487
|
+
# )
|
488
|
+
#
|
489
|
+
# The properties that can be set are:
|
490
|
+
#
|
491
|
+
# :title
|
492
|
+
# :subject
|
493
|
+
# :author
|
494
|
+
# :manager
|
495
|
+
# :company
|
496
|
+
# :category
|
497
|
+
# :keywords
|
498
|
+
# :comments
|
499
|
+
# :status
|
500
|
+
#
|
501
|
+
# See also the properties.rb program in the examples directory of the distro.
|
502
|
+
#
|
503
|
+
def set_properties(params)
|
504
|
+
# Ignore if no args were passed.
|
505
|
+
return -1 if params.empty?
|
506
|
+
|
507
|
+
# List of valid input parameters.
|
508
|
+
valid = {
|
509
|
+
:title => 1,
|
510
|
+
:subject => 1,
|
511
|
+
:author => 1,
|
512
|
+
:keywords => 1,
|
513
|
+
:comments => 1,
|
514
|
+
:last_author => 1,
|
515
|
+
:created => 1,
|
516
|
+
:category => 1,
|
517
|
+
:manager => 1,
|
518
|
+
:company => 1,
|
519
|
+
:status => 1
|
520
|
+
}
|
521
|
+
|
522
|
+
# Check for valid input parameters.
|
523
|
+
params.each_key do |key|
|
524
|
+
return -1 unless valid.has_key?(key)
|
525
|
+
end
|
526
|
+
|
527
|
+
# Set the creation time unless specified by the user.
|
528
|
+
params[:created] = @local_time unless params.has_key?(:created)
|
529
|
+
|
530
|
+
@doc_properties = params.dup
|
531
|
+
end
|
532
|
+
|
533
|
+
#
|
534
|
+
# Change the RGB components of the elements in the colour palette.
|
535
|
+
#
|
536
|
+
# The set_custom_color() method can be used to override one of the built-in
|
537
|
+
# palette values with a more suitable colour.
|
538
|
+
#
|
539
|
+
# The value for _index_ should be in the range 8..63, see "COLOURS IN EXCEL".
|
540
|
+
#
|
541
|
+
# The default named colours use the following indices:
|
542
|
+
#
|
543
|
+
# 8 => black
|
544
|
+
# 9 => white
|
545
|
+
# 10 => red
|
546
|
+
# 11 => lime
|
547
|
+
# 12 => blue
|
548
|
+
# 13 => yellow
|
549
|
+
# 14 => magenta
|
550
|
+
# 15 => cyan
|
551
|
+
# 16 => brown
|
552
|
+
# 17 => green
|
553
|
+
# 18 => navy
|
554
|
+
# 20 => purple
|
555
|
+
# 22 => silver
|
556
|
+
# 23 => gray
|
557
|
+
# 33 => pink
|
558
|
+
# 53 => orange
|
559
|
+
#
|
560
|
+
# A new colour is set using its RGB (red green blue) components. The red,
|
561
|
+
# green and blue values must be in the range 0..255. You can determine the
|
562
|
+
# required values in Excel using the Tools->Options->Colors->Modify dialog.
|
563
|
+
#
|
564
|
+
# The set_custom_color() workbook method can also be used with a HTML style
|
565
|
+
# #rrggbb hex value:
|
566
|
+
#
|
567
|
+
# workbook.set_custom_color(40, 255, 102, 0 ) # Orange
|
568
|
+
# workbook.set_custom_color(40, 0xFF, 0x66, 0x00) # Same thing
|
569
|
+
# workbook.set_custom_color(40, '#FF6600' ) # Same thing
|
570
|
+
#
|
571
|
+
# font = workbook.add_format(:color => 40) # Use the modified colour
|
572
|
+
#
|
573
|
+
# The return value from set_custom_color() is the index of the colour that
|
574
|
+
# was changed:
|
575
|
+
#
|
576
|
+
# ferrari = workbook.set_custom_color(40, 216, 12, 12)
|
577
|
+
#
|
578
|
+
# format = workbook.add_format(
|
579
|
+
# :bg_color => ferrari,
|
580
|
+
# :pattern => 1,
|
581
|
+
# :border => 1
|
582
|
+
# )
|
583
|
+
#
|
584
|
+
# Note, In the XLSX format the color palette isn't actually confined to 53
|
585
|
+
# unique colors. The WriteXLSX gem will be extended at a later stage to
|
586
|
+
# support the newer, semi-infinite, palette.
|
587
|
+
#
|
588
|
+
def set_custom_color(index, red = 0, green = 0, blue = 0)
|
589
|
+
# Match a HTML #xxyyzz style parameter
|
590
|
+
if !red.nil? && red =~ /^#(\w\w)(\w\w)(\w\w)/
|
591
|
+
red = $1.hex
|
592
|
+
green = $2.hex
|
593
|
+
blue = $3.hex
|
594
|
+
end
|
595
|
+
|
596
|
+
# Check that the colour index is the right range
|
597
|
+
if index < 8 || index > 64
|
598
|
+
raise "Color index #{index} outside range: 8 <= index <= 64"
|
599
|
+
end
|
600
|
+
|
601
|
+
# Check that the colour components are in the right range
|
602
|
+
if (red < 0 || red > 255) ||
|
603
|
+
(green < 0 || green > 255) ||
|
604
|
+
(blue < 0 || blue > 255)
|
605
|
+
raise "Color component outside range: 0 <= color <= 255"
|
606
|
+
end
|
607
|
+
|
608
|
+
index -=8 # Adjust colour index (wingless dragonfly)
|
609
|
+
|
610
|
+
# Set the RGB value
|
611
|
+
@palette[index] = [red, green, blue]
|
612
|
+
|
613
|
+
# Store the custome colors for the style.xml file.
|
614
|
+
@custom_colors << sprintf("FF%02X%02X%02X", red, green, blue)
|
615
|
+
|
616
|
+
index + 8
|
617
|
+
end
|
618
|
+
|
619
|
+
def activesheet=(worksheet) #:nodoc:
|
620
|
+
@activesheet = worksheet
|
621
|
+
end
|
622
|
+
|
623
|
+
def writer #:nodoc:
|
624
|
+
@writer
|
625
|
+
end
|
626
|
+
|
627
|
+
def date_1904? #:nodoc:
|
628
|
+
@date_1904 ||= false
|
629
|
+
!!@date_1904
|
630
|
+
end
|
631
|
+
|
632
|
+
#
|
633
|
+
# Add a string to the shared string table, if it isn't already there, and
|
634
|
+
# return the string index.
|
635
|
+
#
|
636
|
+
def shared_string_index(str) #:nodoc:
|
637
|
+
# Add the string to the shared string table.
|
638
|
+
unless @str_table[str]
|
639
|
+
@str_table[str] = @str_unique
|
640
|
+
@str_unique += 1
|
641
|
+
end
|
642
|
+
|
643
|
+
@str_total += 1
|
644
|
+
@str_table[str]
|
645
|
+
end
|
646
|
+
|
647
|
+
def xf_formats # :nodoc:
|
648
|
+
@xf_formats.dup
|
649
|
+
end
|
650
|
+
|
651
|
+
def dxf_formats # :nodoc:
|
652
|
+
@dxf_formats.dup
|
653
|
+
end
|
654
|
+
|
655
|
+
private
|
656
|
+
|
657
|
+
def setup_filename(file) #:nodoc:
|
658
|
+
if file.respond_to?(:to_str) && file != ''
|
659
|
+
@filename = file
|
660
|
+
@fileobj = nil
|
661
|
+
elsif file.respond_to?(:write)
|
662
|
+
@filename = File.join(@tempdir, Digest::MD5.hexdigest(Time.now.to_s) + '.xlsx.tmp')
|
663
|
+
@fileobj = file
|
664
|
+
else
|
665
|
+
raise "'file' must be valid filename String of IO object."
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
#
|
670
|
+
# Sets the colour palette to the Excel defaults.
|
671
|
+
#
|
672
|
+
def set_color_palette #:nodoc:
|
673
|
+
@palette = [
|
674
|
+
[ 0x00, 0x00, 0x00, 0x00 ], # 8
|
675
|
+
[ 0xff, 0xff, 0xff, 0x00 ], # 9
|
676
|
+
[ 0xff, 0x00, 0x00, 0x00 ], # 10
|
677
|
+
[ 0x00, 0xff, 0x00, 0x00 ], # 11
|
678
|
+
[ 0x00, 0x00, 0xff, 0x00 ], # 12
|
679
|
+
[ 0xff, 0xff, 0x00, 0x00 ], # 13
|
680
|
+
[ 0xff, 0x00, 0xff, 0x00 ], # 14
|
681
|
+
[ 0x00, 0xff, 0xff, 0x00 ], # 15
|
682
|
+
[ 0x80, 0x00, 0x00, 0x00 ], # 16
|
683
|
+
[ 0x00, 0x80, 0x00, 0x00 ], # 17
|
684
|
+
[ 0x00, 0x00, 0x80, 0x00 ], # 18
|
685
|
+
[ 0x80, 0x80, 0x00, 0x00 ], # 19
|
686
|
+
[ 0x80, 0x00, 0x80, 0x00 ], # 20
|
687
|
+
[ 0x00, 0x80, 0x80, 0x00 ], # 21
|
688
|
+
[ 0xc0, 0xc0, 0xc0, 0x00 ], # 22
|
689
|
+
[ 0x80, 0x80, 0x80, 0x00 ], # 23
|
690
|
+
[ 0x99, 0x99, 0xff, 0x00 ], # 24
|
691
|
+
[ 0x99, 0x33, 0x66, 0x00 ], # 25
|
692
|
+
[ 0xff, 0xff, 0xcc, 0x00 ], # 26
|
693
|
+
[ 0xcc, 0xff, 0xff, 0x00 ], # 27
|
694
|
+
[ 0x66, 0x00, 0x66, 0x00 ], # 28
|
695
|
+
[ 0xff, 0x80, 0x80, 0x00 ], # 29
|
696
|
+
[ 0x00, 0x66, 0xcc, 0x00 ], # 30
|
697
|
+
[ 0xcc, 0xcc, 0xff, 0x00 ], # 31
|
698
|
+
[ 0x00, 0x00, 0x80, 0x00 ], # 32
|
699
|
+
[ 0xff, 0x00, 0xff, 0x00 ], # 33
|
700
|
+
[ 0xff, 0xff, 0x00, 0x00 ], # 34
|
701
|
+
[ 0x00, 0xff, 0xff, 0x00 ], # 35
|
702
|
+
[ 0x80, 0x00, 0x80, 0x00 ], # 36
|
703
|
+
[ 0x80, 0x00, 0x00, 0x00 ], # 37
|
704
|
+
[ 0x00, 0x80, 0x80, 0x00 ], # 38
|
705
|
+
[ 0x00, 0x00, 0xff, 0x00 ], # 39
|
706
|
+
[ 0x00, 0xcc, 0xff, 0x00 ], # 40
|
707
|
+
[ 0xcc, 0xff, 0xff, 0x00 ], # 41
|
708
|
+
[ 0xcc, 0xff, 0xcc, 0x00 ], # 42
|
709
|
+
[ 0xff, 0xff, 0x99, 0x00 ], # 43
|
710
|
+
[ 0x99, 0xcc, 0xff, 0x00 ], # 44
|
711
|
+
[ 0xff, 0x99, 0xcc, 0x00 ], # 45
|
712
|
+
[ 0xcc, 0x99, 0xff, 0x00 ], # 46
|
713
|
+
[ 0xff, 0xcc, 0x99, 0x00 ], # 47
|
714
|
+
[ 0x33, 0x66, 0xff, 0x00 ], # 48
|
715
|
+
[ 0x33, 0xcc, 0xcc, 0x00 ], # 49
|
716
|
+
[ 0x99, 0xcc, 0x00, 0x00 ], # 50
|
717
|
+
[ 0xff, 0xcc, 0x00, 0x00 ], # 51
|
718
|
+
[ 0xff, 0x99, 0x00, 0x00 ], # 52
|
719
|
+
[ 0xff, 0x66, 0x00, 0x00 ], # 53
|
720
|
+
[ 0x66, 0x66, 0x99, 0x00 ], # 54
|
721
|
+
[ 0x96, 0x96, 0x96, 0x00 ], # 55
|
722
|
+
[ 0x00, 0x33, 0x66, 0x00 ], # 56
|
723
|
+
[ 0x33, 0x99, 0x66, 0x00 ], # 57
|
724
|
+
[ 0x00, 0x33, 0x00, 0x00 ], # 58
|
725
|
+
[ 0x33, 0x33, 0x00, 0x00 ], # 59
|
726
|
+
[ 0x99, 0x33, 0x00, 0x00 ], # 60
|
727
|
+
[ 0x99, 0x33, 0x66, 0x00 ], # 61
|
728
|
+
[ 0x33, 0x33, 0x99, 0x00 ], # 62
|
729
|
+
[ 0x33, 0x33, 0x33, 0x00 ], # 63
|
730
|
+
]
|
731
|
+
end
|
732
|
+
|
733
|
+
#
|
734
|
+
# Check for valid worksheet names. We check the length, if it contains any
|
735
|
+
# invalid characters and if the name is unique in the workbook.
|
736
|
+
#
|
737
|
+
def check_sheetname(name, chart = nil) #:nodoc:
|
738
|
+
name ||= ''
|
739
|
+
invalid_char = /[\[\]:*?\/\\]/
|
740
|
+
|
741
|
+
# Increment the Sheet/Chart number used for default sheet names below.
|
742
|
+
if chart
|
743
|
+
@chartname_count += 1
|
744
|
+
else
|
745
|
+
@sheetname_count += 1
|
746
|
+
end
|
747
|
+
|
748
|
+
# Supply default Sheet/Chart name if none has been defined.
|
749
|
+
if name == ''
|
750
|
+
if chart
|
751
|
+
name = "#{@chart_name}#{@chartname_count}"
|
752
|
+
else
|
753
|
+
name = "#{@sheet_name}#{@sheetname_count}"
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
# Check that sheet name is <= 31. Excel limit.
|
758
|
+
raise "Sheetname #{name} must be <= 31 chars" if name.bytesize > 31
|
759
|
+
|
760
|
+
# Check that sheetname doesn't contain any invalid characters
|
761
|
+
if name =~ invalid_char
|
762
|
+
raise 'Invalid character []:*?/\\ in worksheet name: ' + name
|
763
|
+
end
|
764
|
+
|
765
|
+
# Check that the worksheet name doesn't already exist since this is a fatal
|
766
|
+
# error in Excel 97. The check must also exclude case insensitive matches.
|
767
|
+
@worksheets.each do |worksheet|
|
768
|
+
name_a = name
|
769
|
+
name_b = worksheet.name
|
770
|
+
|
771
|
+
if name_a.downcase == name_b.downcase
|
772
|
+
raise "Worksheet name '#{name}', with case ignored, is already used."
|
773
|
+
end
|
774
|
+
end
|
775
|
+
|
776
|
+
name
|
777
|
+
end
|
778
|
+
|
779
|
+
#
|
780
|
+
# Convert a range formula such as Sheet1!$B$1:$B$5 into a sheet name and cell
|
781
|
+
# range such as ( 'Sheet1', 0, 1, 4, 1 ).
|
782
|
+
#
|
783
|
+
def get_chart_range(range) #:nodoc:
|
784
|
+
# Split the range formula into sheetname and cells at the last '!'.
|
785
|
+
pos = range.rindex('!')
|
786
|
+
return nil unless pos
|
787
|
+
|
788
|
+
if pos > 0
|
789
|
+
sheetname = range[0, pos]
|
790
|
+
cells = range[pos + 1 .. -1]
|
791
|
+
end
|
792
|
+
|
793
|
+
# Split the cell range into 2 cells or else use single cell for both.
|
794
|
+
if cells =~ /:/
|
795
|
+
cell_1, cell_2 = cells.split(/:/)
|
796
|
+
else
|
797
|
+
cell_1, cell_2 = cells, cells
|
798
|
+
end
|
799
|
+
|
800
|
+
# Remove leading/trailing apostrophes and convert escaped quotes to single.
|
801
|
+
sheetname.sub!(/^'/, '')
|
802
|
+
sheetname.sub!(/'$/, '')
|
803
|
+
sheetname.gsub!(/''/, "'")
|
804
|
+
|
805
|
+
row_start, col_start = xl_cell_to_rowcol(cell_1)
|
806
|
+
row_end, col_end = xl_cell_to_rowcol(cell_2)
|
807
|
+
|
808
|
+
# Check that we have a 1D range only.
|
809
|
+
return nil if row_start != row_end && col_start != col_end
|
810
|
+
return [sheetname, row_start, col_start, row_end, col_end]
|
811
|
+
end
|
812
|
+
|
813
|
+
def write_xml_declaration #:nodoc:
|
814
|
+
@writer.xml_decl
|
815
|
+
end
|
816
|
+
|
817
|
+
def write_workbook #:nodoc:
|
818
|
+
schema = 'http://schemas.openxmlformats.org'
|
819
|
+
attributes = [
|
820
|
+
'xmlns',
|
821
|
+
schema + '/spreadsheetml/2006/main',
|
822
|
+
'xmlns:r',
|
823
|
+
schema + '/officeDocument/2006/relationships'
|
824
|
+
]
|
825
|
+
@writer.start_tag('workbook', attributes)
|
826
|
+
end
|
827
|
+
|
828
|
+
def write_workbook_end #:nodoc:
|
829
|
+
@writer.end_tag('workbook')
|
830
|
+
end
|
831
|
+
|
832
|
+
def write_file_version #:nodoc:
|
833
|
+
attributes = [
|
834
|
+
'appName', 'xl',
|
835
|
+
'lastEdited', 4,
|
836
|
+
'lowestEdited', 4,
|
837
|
+
'rupBuild', 4505
|
838
|
+
]
|
839
|
+
@writer.empty_tag('fileVersion', attributes)
|
840
|
+
end
|
841
|
+
|
842
|
+
def write_workbook_pr #:nodoc:
|
843
|
+
attributes = date_1904? ? ['date1904', 1] : []
|
844
|
+
attributes << 'defaultThemeVersion' << 124226
|
845
|
+
@writer.empty_tag('workbookPr', attributes)
|
846
|
+
end
|
847
|
+
|
848
|
+
def write_book_views #:nodoc:
|
849
|
+
@writer.start_tag('bookViews') << write_workbook_view << @writer.end_tag('bookViews')
|
850
|
+
end
|
851
|
+
|
852
|
+
def write_workbook_view #:nodoc:
|
853
|
+
attributes = [
|
854
|
+
'xWindow', 240,
|
855
|
+
'yWindow', 15,
|
856
|
+
'windowWidth', 16095,
|
857
|
+
'windowHeight', 9660
|
858
|
+
]
|
859
|
+
if @firstsheet > 0
|
860
|
+
attributes << 'firstSheet' << @firstsheet
|
861
|
+
end
|
862
|
+
if @activesheet > 0
|
863
|
+
attributes << 'activeTab' << @activesheet
|
864
|
+
end
|
865
|
+
@writer.empty_tag('workbookView', attributes)
|
866
|
+
end
|
867
|
+
|
868
|
+
def write_sheets #:nodoc:
|
869
|
+
str = @writer.start_tag('sheets')
|
870
|
+
id_num = 1
|
871
|
+
@worksheets.each do |sheet|
|
872
|
+
str << write_sheet(sheet.name, id_num, sheet.hidden)
|
873
|
+
id_num += 1
|
874
|
+
end
|
875
|
+
str << @writer.end_tag('sheets')
|
876
|
+
end
|
877
|
+
|
878
|
+
def write_sheet(name, sheet_id, hidden = false) #:nodoc:
|
879
|
+
attributes = [
|
880
|
+
'name', name,
|
881
|
+
'sheetId', sheet_id
|
882
|
+
]
|
883
|
+
|
884
|
+
if hidden
|
885
|
+
attributes << 'state' << 'hidden'
|
886
|
+
end
|
887
|
+
attributes << 'r:id' << "rId#{sheet_id}"
|
888
|
+
@writer.empty_tag('sheet', attributes)
|
889
|
+
end
|
890
|
+
|
891
|
+
def write_calc_pr #:nodoc:
|
892
|
+
attributes = ['calcId', 124519]
|
893
|
+
@writer.empty_tag('calcPr', attributes)
|
894
|
+
end
|
895
|
+
|
896
|
+
def write_ext_lst #:nodoc:
|
897
|
+
tag = 'extLst'
|
898
|
+
@writer.start_tag(tag) << write_ext << @writer.end_tag(tag)
|
899
|
+
end
|
900
|
+
|
901
|
+
def write_ext #:nodoc:
|
902
|
+
tag = 'ext'
|
903
|
+
attributes = [
|
904
|
+
'xmlns:mx', 'http://schemas.microsoft.com/office/mac/excel/2008/main',
|
905
|
+
'uri', 'http://schemas.microsoft.com/office/mac/excel/2008/main'
|
906
|
+
]
|
907
|
+
@writer.start_tag(tag, attributes) << write_mx_arch_id << @writer.end_tag(tag)
|
908
|
+
end
|
909
|
+
|
910
|
+
def write_mx_arch_id #:nodoc:
|
911
|
+
@writer.empty_tag('mx:ArchID', ['Flags', 2])
|
912
|
+
end
|
913
|
+
|
914
|
+
def write_defined_names #:nodoc:
|
915
|
+
return if @defined_names.nil? || @defined_names.empty?
|
916
|
+
tag = 'definedNames'
|
917
|
+
str = @writer.start_tag(tag)
|
918
|
+
@defined_names.each { |defined_name| str << write_defined_name(defined_name) }
|
919
|
+
str << @writer.end_tag(tag)
|
920
|
+
end
|
921
|
+
|
922
|
+
def write_defined_name(data) #:nodoc:
|
923
|
+
name, id, range, hidden = data
|
924
|
+
|
925
|
+
attributes = ['name', name]
|
926
|
+
attributes << 'localSheetId' << "#{id}" unless id == -1
|
927
|
+
attributes << 'hidden' << '1' if hidden
|
928
|
+
|
929
|
+
@writer.data_element('definedName', range, attributes)
|
930
|
+
end
|
931
|
+
|
932
|
+
def write_io(str) #:nodoc:
|
933
|
+
@writer << str
|
934
|
+
str
|
935
|
+
end
|
936
|
+
|
937
|
+
def firstsheet #:nodoc:
|
938
|
+
@firstsheet ||= 0
|
939
|
+
end
|
940
|
+
|
941
|
+
def activesheet #:nodoc:
|
942
|
+
@activesheet ||= 0
|
943
|
+
end
|
944
|
+
|
945
|
+
# for test
|
946
|
+
def defined_names #:nodoc:
|
947
|
+
@defined_names ||= []
|
948
|
+
end
|
949
|
+
|
950
|
+
#
|
951
|
+
# Assemble worksheets into a workbook.
|
952
|
+
#
|
953
|
+
def store_workbook #:nodoc:
|
954
|
+
packager = Package::Packager.new
|
955
|
+
|
956
|
+
# Add a default worksheet if non have been added.
|
957
|
+
add_worksheet if @worksheets.empty?
|
958
|
+
|
959
|
+
# Ensure that at least one worksheet has been selected.
|
960
|
+
@worksheets.first.select if @activesheet == 0
|
961
|
+
|
962
|
+
# Set the active sheet.
|
963
|
+
@worksheets.each { |sheet| sheet.activate if sheet.index == @activesheet }
|
964
|
+
|
965
|
+
# Convert the SST strings data structure.
|
966
|
+
prepare_sst_string_data
|
967
|
+
|
968
|
+
# Prepare the worksheet cell comments.
|
969
|
+
prepare_comments
|
970
|
+
|
971
|
+
# Set the defined names for the worksheets such as Print Titles.
|
972
|
+
prepare_defined_names
|
973
|
+
|
974
|
+
# Prepare the drawings, charts and images.
|
975
|
+
prepare_drawings
|
976
|
+
|
977
|
+
# Add cached data to charts.
|
978
|
+
add_chart_data
|
979
|
+
|
980
|
+
# Package the workbook.
|
981
|
+
packager.add_workbook(self)
|
982
|
+
packager.set_package_dir(@tempdir)
|
983
|
+
packager.create_package
|
984
|
+
|
985
|
+
# Free up the Packager object.
|
986
|
+
packager = nil
|
987
|
+
|
988
|
+
# Store the xlsx component files with the temp dir name removed.
|
989
|
+
ZipFileUtils.zip("#{@tempdir}", @filename)
|
990
|
+
IO.copy_stream(@filename, @fileobj) if @fileobj
|
991
|
+
Writexlsx::Utility.delete_files(@tempdir)
|
992
|
+
end
|
993
|
+
|
994
|
+
#
|
995
|
+
# Convert the SST string data from a hash to an array.
|
996
|
+
#
|
997
|
+
def prepare_sst_string_data #:nodoc:
|
998
|
+
strings = []
|
999
|
+
|
1000
|
+
@str_table.each_key { |key| strings[@str_table[key]] = key }
|
1001
|
+
|
1002
|
+
# The SST data could be very large, free some memory (maybe).
|
1003
|
+
@str_table = nil
|
1004
|
+
@str_array = strings
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
#
|
1008
|
+
# Prepare all of the format properties prior to passing them to Styles.pm.
|
1009
|
+
#
|
1010
|
+
def prepare_format_properties #:nodoc:
|
1011
|
+
# Separate format objects into XF and DXF formats.
|
1012
|
+
prepare_formats
|
1013
|
+
|
1014
|
+
# Set the font index for the format objects.
|
1015
|
+
prepare_fonts
|
1016
|
+
|
1017
|
+
# Set the number format index for the format objects.
|
1018
|
+
prepare_num_formats
|
1019
|
+
|
1020
|
+
# Set the border index for the format objects.
|
1021
|
+
prepare_borders
|
1022
|
+
|
1023
|
+
# Set the fill index for the format objects.
|
1024
|
+
prepare_fills
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
#
|
1028
|
+
# Iterate through the XF Format objects and separate them into XF and DXF
|
1029
|
+
# formats.
|
1030
|
+
#
|
1031
|
+
def prepare_formats #:nodoc:
|
1032
|
+
@formats.each do |format|
|
1033
|
+
xf_index = format.xf_index
|
1034
|
+
dxf_index = format.dxf_index
|
1035
|
+
|
1036
|
+
@xf_formats[xf_index] = format if xf_index
|
1037
|
+
@dxf_formats[dxf_index] = format if dxf_index
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
#
|
1042
|
+
# Set the default index for each format. This is mainly used for testing.
|
1043
|
+
#
|
1044
|
+
def set_default_xf_indices #:nodoc:
|
1045
|
+
@formats.each { |format| format.get_xf_index }
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
#
|
1049
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
1050
|
+
# font elements.
|
1051
|
+
#
|
1052
|
+
def prepare_fonts #:nodoc:
|
1053
|
+
fonts = {}
|
1054
|
+
index = 0
|
1055
|
+
|
1056
|
+
@xf_formats.each do |format|
|
1057
|
+
key = format.get_font_key
|
1058
|
+
|
1059
|
+
if fonts[key]
|
1060
|
+
# Font has already been used.
|
1061
|
+
format.font_index = fonts[key]
|
1062
|
+
format.has_font = 0
|
1063
|
+
else
|
1064
|
+
# This is a new font.
|
1065
|
+
fonts[key] = index
|
1066
|
+
format.font_index = index
|
1067
|
+
format.has_font = 1
|
1068
|
+
index += 1
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
@font_count = index
|
1073
|
+
|
1074
|
+
# For the DXF formats we only need to check if the properties have changed.
|
1075
|
+
@dxf_formats.each do |format|
|
1076
|
+
# The only font properties that can change for a DXF format are: color,
|
1077
|
+
# bold, italic, underline and strikethrough.
|
1078
|
+
if format.color || format.bold? || format.italic? || format.underline? || format.strikeout?
|
1079
|
+
format.has_dxf_font = 1
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
#
|
1085
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
1086
|
+
# number format elements.
|
1087
|
+
#
|
1088
|
+
# User defined records start from index 0xA4.
|
1089
|
+
#
|
1090
|
+
def prepare_num_formats #:nodoc:
|
1091
|
+
num_formats = {}
|
1092
|
+
index = 164
|
1093
|
+
num_format_count = 0
|
1094
|
+
|
1095
|
+
(@xf_formats + @dxf_formats).each do |format|
|
1096
|
+
num_format = format.num_format
|
1097
|
+
|
1098
|
+
# Check if num_format is an index to a built-in number format.
|
1099
|
+
# Also check for a string of zeros, which is a valid number format
|
1100
|
+
# string but would evaluate to zero.
|
1101
|
+
#
|
1102
|
+
if num_format.to_s =~ /^\d+$/ && num_format.to_s !~ /^0+\d/
|
1103
|
+
# Index to a built-in number format.
|
1104
|
+
format.num_format_index = num_format
|
1105
|
+
next
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
if num_formats[num_format]
|
1109
|
+
# Number format has already been used.
|
1110
|
+
format.num_format_index = num_formats[num_format]
|
1111
|
+
else
|
1112
|
+
# Add a new number format.
|
1113
|
+
num_formats[num_format] = index
|
1114
|
+
format.num_format_index = index
|
1115
|
+
index += 1
|
1116
|
+
|
1117
|
+
# Only increase font count for XF formats (not for DXF formats).
|
1118
|
+
num_format_count += 1 unless format.xf_index == 0
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
@num_format_count = num_format_count
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
#
|
1126
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
1127
|
+
# border elements.
|
1128
|
+
#
|
1129
|
+
def prepare_borders #:nodoc:
|
1130
|
+
borders = {}
|
1131
|
+
index = 0
|
1132
|
+
|
1133
|
+
@xf_formats.each do |format|
|
1134
|
+
key = format.get_border_key
|
1135
|
+
|
1136
|
+
if borders[key]
|
1137
|
+
# Border has already been used.
|
1138
|
+
format.border_index = borders[key]
|
1139
|
+
format.has_border = 0
|
1140
|
+
else
|
1141
|
+
# This is a new border.
|
1142
|
+
borders[key] = index
|
1143
|
+
format.border_index = index
|
1144
|
+
format.has_border = 1
|
1145
|
+
index += 1
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
@border_count = index
|
1150
|
+
|
1151
|
+
# For the DXF formats we only need to check if the properties have changed.
|
1152
|
+
@dxf_formats.each do |format|
|
1153
|
+
key = format.get_border_key
|
1154
|
+
format.has_dxf_border = 1 if key =~ /[^0:]/
|
1155
|
+
end
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
#
|
1159
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
1160
|
+
# fill elements.
|
1161
|
+
#
|
1162
|
+
# The user defined fill properties start from 2 since there are 2 default
|
1163
|
+
# fills: patternType="none" and patternType="gray125".
|
1164
|
+
#
|
1165
|
+
def prepare_fills #:nodoc:
|
1166
|
+
fills = {}
|
1167
|
+
index = 2 # Start from 2. See above.
|
1168
|
+
|
1169
|
+
# Add the default fills.
|
1170
|
+
fills['0:0:0'] = 0
|
1171
|
+
fills['17:0:0'] = 1
|
1172
|
+
|
1173
|
+
@xf_formats.each do |format|
|
1174
|
+
# The following logical statements jointly take care of special cases
|
1175
|
+
# in relation to cell colours and patterns:
|
1176
|
+
# 1. For a solid fill (_pattern == 1) Excel reverses the role of
|
1177
|
+
# foreground and background colours, and
|
1178
|
+
# 2. If the user specifies a foreground or background colour without
|
1179
|
+
# a pattern they probably wanted a solid fill, so we fill in the
|
1180
|
+
# defaults.
|
1181
|
+
#
|
1182
|
+
if format.pattern <= 1 && format.bg_color != 0 && format.fg_color == 0
|
1183
|
+
format.fg_color = format.bg_color
|
1184
|
+
format.bg_color = 0
|
1185
|
+
format.pattern = 1
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
if format.pattern <= 1 && format.bg_color == 0 && format.fg_color != 0
|
1189
|
+
format.bg_color = 0
|
1190
|
+
format.pattern = 1
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
key = format.get_fill_key
|
1194
|
+
|
1195
|
+
if fills[key]
|
1196
|
+
# Fill has already been used.
|
1197
|
+
format.fill_index = fills[key]
|
1198
|
+
format.has_fill = 0
|
1199
|
+
else
|
1200
|
+
# This is a new fill.
|
1201
|
+
fills[key] = index
|
1202
|
+
format.fill_index = index
|
1203
|
+
format.has_fill = 1
|
1204
|
+
index += 1
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
@fill_count = index
|
1209
|
+
|
1210
|
+
# For the DXF formats we only need to check if the properties have changed.
|
1211
|
+
@dxf_formats.each do |format|
|
1212
|
+
format.has_dxf_fill = 1 if format.pattern || format.bg_color || format.fg_color
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
#
|
1217
|
+
# Iterate through the worksheets and store any defined names in addition to
|
1218
|
+
# any user defined names. Stores the defined names for the Workbook.xml and
|
1219
|
+
# the named ranges for App.xml.
|
1220
|
+
#
|
1221
|
+
def prepare_defined_names #:nodoc:
|
1222
|
+
defined_names = @defined_names
|
1223
|
+
|
1224
|
+
@worksheets.each do |sheet|
|
1225
|
+
# Check for Print Area settings.
|
1226
|
+
if sheet.autofilter_area
|
1227
|
+
range = sheet.autofilter_area
|
1228
|
+
hidden = 1
|
1229
|
+
|
1230
|
+
# Store the defined names.
|
1231
|
+
defined_names << ['_xlnm._FilterDatabase', sheet.index, range, hidden]
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
# Check for Print Area settings.
|
1235
|
+
if !sheet.print_area.empty?
|
1236
|
+
range = sheet.print_area
|
1237
|
+
|
1238
|
+
# Store the defined names.
|
1239
|
+
defined_names << ['_xlnm.Print_Area', sheet.index, range]
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
# Check for repeat rows/cols. aka, Print Titles.
|
1243
|
+
if !sheet.print_repeat_cols.empty? || !sheet.print_repeat_rows.empty?
|
1244
|
+
range = ''
|
1245
|
+
|
1246
|
+
if !sheet.print_repeat_cols.empty? && !sheet.print_repeat_rows.empty?
|
1247
|
+
range = sheet.print_repeat_cols + ',' + sheet.print_repeat_rows
|
1248
|
+
else
|
1249
|
+
range = sheet.print_repeat_cols + sheet.print_repeat_rows
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
# Store the defined names.
|
1253
|
+
defined_names << ['_xlnm.Print_Titles', sheet.index, range]
|
1254
|
+
end
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
defined_names = sort_defined_names(defined_names)
|
1258
|
+
@defined_names = defined_names
|
1259
|
+
@named_ranges = extract_named_ranges(defined_names)
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
#
|
1263
|
+
# Iterate through the worksheets and set up the comment data.
|
1264
|
+
#
|
1265
|
+
def prepare_comments #:nodoc:
|
1266
|
+
comment_id = 0
|
1267
|
+
vml_data_id = 1
|
1268
|
+
vml_shape_id = 1024
|
1269
|
+
|
1270
|
+
@worksheets.each do |sheet|
|
1271
|
+
next unless sheet.has_comments?
|
1272
|
+
|
1273
|
+
comment_id += 1
|
1274
|
+
count = sheet.prepare_comments( vml_data_id, vml_shape_id, comment_id)
|
1275
|
+
|
1276
|
+
# Each VML file should start with a shape id incremented by 1024.
|
1277
|
+
vml_data_id += 1 * ( ( 1024 + count ) / 1024.0 ).to_i
|
1278
|
+
vml_shape_id += 1024 * ( ( 1024 + count ) / 1024.0 ).to_i
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
@num_comment_files = comment_id
|
1282
|
+
|
1283
|
+
# Add a font format for cell comments.
|
1284
|
+
if comment_id > 0
|
1285
|
+
format = Format.new(
|
1286
|
+
@xf_format_indices,
|
1287
|
+
@dxf_format_indices,
|
1288
|
+
:font => 'Tahoma',
|
1289
|
+
:size => 8,
|
1290
|
+
:color_indexed => 81,
|
1291
|
+
:font_only => 1
|
1292
|
+
)
|
1293
|
+
|
1294
|
+
format.get_xf_index
|
1295
|
+
|
1296
|
+
@formats << format
|
1297
|
+
end
|
1298
|
+
end
|
1299
|
+
|
1300
|
+
#
|
1301
|
+
# Add "cached" data to charts to provide the numCache and strCache data for
|
1302
|
+
# series and title/axis ranges.
|
1303
|
+
#
|
1304
|
+
def add_chart_data #:nodoc:
|
1305
|
+
worksheets = {}
|
1306
|
+
seen_ranges = {}
|
1307
|
+
|
1308
|
+
# Map worksheet names to worksheet objects.
|
1309
|
+
@worksheets.each { |worksheet| worksheets[worksheet.name] = worksheet }
|
1310
|
+
|
1311
|
+
@charts.each do |chart|
|
1312
|
+
chart.formula_ids.each do |range, id|
|
1313
|
+
# Skip if the series has user defined data.
|
1314
|
+
if chart.formula_data[id]
|
1315
|
+
if !seen_ranges.has_key?(range) || seen_ranges[range]
|
1316
|
+
data = chart.formula_data[id]
|
1317
|
+
seen_ranges[range] = data
|
1318
|
+
end
|
1319
|
+
next
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
# Check to see if the data is already cached locally.
|
1323
|
+
if seen_ranges.has_key?(range)
|
1324
|
+
chart.formula_data[id] = seen_ranges[range]
|
1325
|
+
next
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
# Convert the range formula to a sheet name and cell range.
|
1329
|
+
sheetname, *cells = get_chart_range(range)
|
1330
|
+
|
1331
|
+
# Skip if we couldn't parse the formula.
|
1332
|
+
next unless sheetname
|
1333
|
+
|
1334
|
+
# Skip if the name is unknown. Probably should throw exception.
|
1335
|
+
next unless worksheets[sheetname]
|
1336
|
+
|
1337
|
+
# Find the worksheet object based on the sheet name.
|
1338
|
+
worksheet = worksheets[sheetname]
|
1339
|
+
|
1340
|
+
# Get the data from the worksheet table.
|
1341
|
+
data = worksheet.get_range_data(*cells)
|
1342
|
+
|
1343
|
+
# Convert shared string indexes to strings.
|
1344
|
+
data.collect! do |token|
|
1345
|
+
if token.kind_of?(Hash)
|
1346
|
+
token = @str_array[token[:sst_id]]
|
1347
|
+
|
1348
|
+
# Ignore rich strings for now. Deparse later if necessary.
|
1349
|
+
token = '' if token =~ %r!^<r>! && token =~ %r!</r>$!
|
1350
|
+
end
|
1351
|
+
token
|
1352
|
+
end
|
1353
|
+
|
1354
|
+
# Add the data to the chart.
|
1355
|
+
chart.formula_data[id] = data
|
1356
|
+
|
1357
|
+
# Store range data locally to avoid lookup if seen again.
|
1358
|
+
seen_ranges[range] = data
|
1359
|
+
end
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
#
|
1364
|
+
# Sort internal and user defined names in the same order as used by Excel.
|
1365
|
+
# This may not be strictly necessary but unsorted elements caused a lot of
|
1366
|
+
# issues in the the Spreadsheet::WriteExcel binary version. Also makes
|
1367
|
+
# comparison testing easier.
|
1368
|
+
#
|
1369
|
+
def sort_defined_names(names) #:nodoc:
|
1370
|
+
names.sort do |a, b|
|
1371
|
+
name_a = normalise_defined_name(a[0])
|
1372
|
+
name_b = normalise_defined_name(b[0])
|
1373
|
+
sheet_a = normalise_sheet_name(a[2])
|
1374
|
+
sheet_b = normalise_sheet_name(b[2])
|
1375
|
+
# Primary sort based on the defined name.
|
1376
|
+
if name_a > name_b
|
1377
|
+
1
|
1378
|
+
elsif name_a < name_b
|
1379
|
+
-1
|
1380
|
+
else # name_a == name_b
|
1381
|
+
# Secondary sort based on the sheet name.
|
1382
|
+
if sheet_a >= sheet_b
|
1383
|
+
1
|
1384
|
+
else
|
1385
|
+
-1
|
1386
|
+
end
|
1387
|
+
end
|
1388
|
+
end
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
# Used in the above sort routine to normalise the defined names. Removes any
|
1392
|
+
# leading '_xmln.' from internal names and lowercases the strings.
|
1393
|
+
def normalise_defined_name(name) #:nodoc:
|
1394
|
+
name.sub(/^_xlnm./, '').downcase
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
# Used in the above sort routine to normalise the worksheet names for the
|
1398
|
+
# secondary sort. Removes leading quote and lowercases the strings.
|
1399
|
+
def normalise_sheet_name(name) #:nodoc:
|
1400
|
+
name.sub(/^'/, '').downcase
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
#
|
1404
|
+
# Extract the named ranges from the sorted list of defined names. These are
|
1405
|
+
# used in the App.xml file.
|
1406
|
+
#
|
1407
|
+
def extract_named_ranges(defined_names) #:nodoc:
|
1408
|
+
named_ranges = []
|
1409
|
+
|
1410
|
+
defined_names.each do |defined_name|
|
1411
|
+
name, index, range = defined_name
|
1412
|
+
|
1413
|
+
# Skip autoFilter ranges.
|
1414
|
+
next if name == '_xlnm._FilterDatabase'
|
1415
|
+
|
1416
|
+
# We are only interested in defined names with ranges.
|
1417
|
+
if range =~ /^([^!]+)!/
|
1418
|
+
sheet_name = $1
|
1419
|
+
|
1420
|
+
# Match Print_Area and Print_Titles xlnm types.
|
1421
|
+
if name =~ /^_xlnm\.(.*)$/
|
1422
|
+
xlnm_type = $1
|
1423
|
+
name = sheet_name + '!' + xlnm_type
|
1424
|
+
elsif index != -1
|
1425
|
+
name = sheet_name + '!' + name
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
named_ranges << name
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
named_ranges
|
1433
|
+
end
|
1434
|
+
|
1435
|
+
#
|
1436
|
+
# Iterate through the worksheets and set up any chart or image drawings.
|
1437
|
+
#
|
1438
|
+
def prepare_drawings #:nodoc:
|
1439
|
+
chart_ref_id = 0
|
1440
|
+
image_ref_id = 0
|
1441
|
+
drawing_id = 0
|
1442
|
+
@worksheets.each do |sheet|
|
1443
|
+
chart_count = sheet.charts.size
|
1444
|
+
image_count = sheet.images.size
|
1445
|
+
next if chart_count + image_count == 0
|
1446
|
+
|
1447
|
+
drawing_id += 1
|
1448
|
+
|
1449
|
+
(0 .. chart_count - 1).each do |index|
|
1450
|
+
chart_ref_id += 1
|
1451
|
+
sheet.prepare_chart(index, chart_ref_id, drawing_id)
|
1452
|
+
end
|
1453
|
+
|
1454
|
+
(0 .. image_count - 1).each do |index|
|
1455
|
+
filename = sheet.images[index][2]
|
1456
|
+
|
1457
|
+
image_id, type, width, height, name = get_image_properties(filename)
|
1458
|
+
|
1459
|
+
image_ref_id += 1
|
1460
|
+
|
1461
|
+
sheet.prepare_image(index, image_ref_id, drawing_id, width, height, name, type)
|
1462
|
+
end
|
1463
|
+
|
1464
|
+
drawing = sheet.drawing
|
1465
|
+
@drawings << drawing
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
@drawing_count = drawing_id
|
1469
|
+
end
|
1470
|
+
|
1471
|
+
#
|
1472
|
+
# Convert a sheet name to its index. Return undef otherwise.
|
1473
|
+
#
|
1474
|
+
def get_sheet_index(sheetname) #:nodoc:
|
1475
|
+
sheet_count = @sheetnames.size
|
1476
|
+
sheet_index = nil
|
1477
|
+
|
1478
|
+
sheetname.sub!(/^'/, '')
|
1479
|
+
sheetname.sub!(/'$/, '')
|
1480
|
+
|
1481
|
+
( 0 .. sheet_count - 1 ).each do |i|
|
1482
|
+
sheet_index = i if sheetname == @sheetnames[i]
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
sheet_index
|
1486
|
+
end
|
1487
|
+
end
|
1488
|
+
end
|