From 564db909bffa62f17276cf0e887e93446b2ded2e Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Sun, 1 Mar 2015 18:27:54 -0800 Subject: [PATCH 1/2] Add optional support for hidden sheets in LibreOffice files --- lib/roo/open_office.rb | 33 +++++++++++++++++++++++++------ spec/lib/roo/libreoffice_spec.rb | 16 +++++++++++++++ test/files/hidden_sheets.ods | Bin 0 -> 10560 bytes 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 test/files/hidden_sheets.ods diff --git a/lib/roo/open_office.rb b/lib/roo/open_office.rb index b06ebdac..425987b8 100644 --- a/lib/roo/open_office.rb +++ b/lib/roo/open_office.rb @@ -11,6 +11,7 @@ def initialize(filename, options={}) packed = options[:packed] file_warning = options[:file_warning] || :error + @only_visible_sheets = options[:only_visible_sheets] file_type_check(filename,'.ods','an Roo::OpenOffice', file_warning, packed) @tmpdir = make_tmpdir(filename.split('/').last, options[:tmpdir_root]) @filename = local_filename(filename, @tmpdir, packed) @@ -33,7 +34,8 @@ def initialize(filename, options={}) @formula = Hash.new @style = Hash.new @style_defaults = Hash.new { |h,k| h[k] = [] } - @style_definitions = Hash.new + @table_display = Hash.new { |h,k| h[k] = true } + @font_style_definitions = Hash.new @comment = Hash.new @comments_read = Hash.new end @@ -309,7 +311,7 @@ def font(row, col, sheet=nil) read_cells(sheet) row,col = normalize(row,col) style_name = @style[sheet][[row,col]] || @style_defaults[sheet][col - 1] || 'Default' - @style_definitions[style_name] + @font_style_definitions[style_name] end # returns the type of a cell: @@ -332,9 +334,16 @@ def celltype(row,col,sheet=nil) end def sheets - doc.xpath("//*[local-name()='table']").map do |sheet| - sheet.attributes["name"].value + unless @table_display.any? + doc.xpath("//*[local-name()='automatic-styles']").each do |style| + read_table_styles(style) + end end + doc.xpath("//*[local-name()='table']").map do |sheet| + if !@only_visible_sheets || @table_display[attr(sheet,'style-name')] + sheet.attributes["name"].value + end + end.compact end # version of the Roo::OpenOffice document @@ -595,7 +604,7 @@ def read_labels end def read_styles(style_elements) - @style_definitions['Default'] = Roo::Font.new + @font_style_definitions['Default'] = Roo::Font.new style_elements.each do |style| next unless style.name == 'style' style_name = attr(style,'name') @@ -604,7 +613,19 @@ def read_styles(style_elements) font.bold = attr(properties,'font-weight') font.italic = attr(properties,'font-style') font.underline = attr(properties,'text-underline-style') - @style_definitions[style_name] = font + @font_style_definitions[style_name] = font + end + end + end + + def read_table_styles(styles) + styles.children.each do |style| + next unless style.name == 'style' + style_name = attr(style,'name') + style.children.each do |properties| + display = attr(properties,'display') + next unless display + @table_display[style_name] = (display == 'true') end end end diff --git a/spec/lib/roo/libreoffice_spec.rb b/spec/lib/roo/libreoffice_spec.rb index d480c84a..3a64744f 100644 --- a/spec/lib/roo/libreoffice_spec.rb +++ b/spec/lib/roo/libreoffice_spec.rb @@ -10,4 +10,20 @@ expect(subject).to be_a(Roo::LibreOffice) end end + + describe '#sheets' do + let(:path) { 'test/files/hidden_sheets.ods' } + + describe 'showing all sheets' do + it 'returns the expected result' do + expect(Roo::LibreOffice.new(path).sheets).to eq ["HiddenSheet1", "VisibleSheet1", "HiddenSheet2"] + end + end + + describe 'only showing visible sheets' do + it 'returns the expected result' do + expect(Roo::LibreOffice.new(path, only_visible_sheets: true).sheets).to eq ["VisibleSheet1"] + end + end + end end diff --git a/test/files/hidden_sheets.ods b/test/files/hidden_sheets.ods new file mode 100644 index 0000000000000000000000000000000000000000..51e0f3985a4f7ad5b28967f30d58bd520885780e GIT binary patch literal 10560 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 Date: Sun, 1 Mar 2015 18:39:12 -0800 Subject: [PATCH 2/2] Add optional support for hidden sheets in Excelx files --- lib/roo/excelx.rb | 6 +++++- spec/lib/roo/excelx_spec.rb | 8 ++++++++ test/files/hidden_sheets.xlsx | Bin 0 -> 6152 bytes 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/files/hidden_sheets.xlsx diff --git a/lib/roo/excelx.rb b/lib/roo/excelx.rb index 82325e94..a97a83a6 100644 --- a/lib/roo/excelx.rb +++ b/lib/roo/excelx.rb @@ -261,7 +261,11 @@ def initialize(filename, options = {}) @rels_files = [] process_zipfile(@tmpdir, @filename) - @sheet_names = workbook.sheets.map { |sheet| sheet['name'] } + @sheet_names = workbook.sheets.map do |sheet| + unless options[:only_visible_sheets] && sheet['state'] == 'hidden' + sheet['name'] + end + end.compact @sheets = [] @sheets_by_name = Hash[@sheet_names.map.with_index do |sheet_name, n| @sheets[n] = Sheet.new(sheet_name, @rels_files[n], @sheet_files[n], @comments_files[n], styles, shared_strings, workbook) diff --git a/spec/lib/roo/excelx_spec.rb b/spec/lib/roo/excelx_spec.rb index 800d181f..8b3d796f 100644 --- a/spec/lib/roo/excelx_spec.rb +++ b/spec/lib/roo/excelx_spec.rb @@ -77,6 +77,14 @@ it 'returns the expected result' do expect(subject.sheets).to eq ["Tabelle1", "Name of Sheet 2", "Sheet3", "Sheet4", "Sheet5"] end + + describe 'only showing visible sheets' do + let(:path) { 'test/files/hidden_sheets.xlsx' } + + it 'returns the expected result' do + expect(Roo::Excelx.new(path, only_visible_sheets: true).sheets).to eq ["VisibleSheet1"] + end + end end describe '#sheet_for' do diff --git a/test/files/hidden_sheets.xlsx b/test/files/hidden_sheets.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..78eccaa601cdc1f8029f1132a639c5d33b7972b9 GIT binary patch literal 6152 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 Kn%j~fR{sIvMS$V} literal 0 HcmV?d00001