summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRandy Morgan (@morgan_randy) <[email protected]>2012-11-22 22:54:42 -0800
committerRandy Morgan (@morgan_randy) <[email protected]>2012-11-22 22:54:42 -0800
commit79bd112ce6dc7a944ae1b1d2f7a8ad14c258a969 (patch)
tree135da3aa4a710864d6daa817afd6cdc36eef49bd
parent55f4e147bf2bf687ba97aee2cb8a3c717f878fe2 (diff)
parentbeb479ee45e263ea8cd4f48673ee9e70c791dbd7 (diff)
downloadcaxlsx-79bd112ce6dc7a944ae1b1d2f7a8ad14c258a969.tar.gz
caxlsx-79bd112ce6dc7a944ae1b1d2f7a8ad14c258a969.zip
Merge pull request #145 from freerunningtechnologies/master
Basic support for headers and footers.
-rw-r--r--lib/axlsx/workbook/workbook.rb1
-rw-r--r--lib/axlsx/workbook/worksheet/header_footer.rb56
-rw-r--r--lib/axlsx/workbook/worksheet/worksheet.rb37
-rw-r--r--test/workbook/worksheet/tc_header_footer.rb151
-rw-r--r--test/workbook/worksheet/tc_worksheet.rb37
5 files changed, 266 insertions, 16 deletions
diff --git a/lib/axlsx/workbook/workbook.rb b/lib/axlsx/workbook/workbook.rb
index c9556c4d..6c99d6de 100644
--- a/lib/axlsx/workbook/workbook.rb
+++ b/lib/axlsx/workbook/workbook.rb
@@ -9,6 +9,7 @@ require 'axlsx/workbook/worksheet/cell.rb'
require 'axlsx/workbook/worksheet/page_margins.rb'
require 'axlsx/workbook/worksheet/page_set_up_pr.rb'
require 'axlsx/workbook/worksheet/page_setup.rb'
+require 'axlsx/workbook/worksheet/header_footer.rb'
require 'axlsx/workbook/worksheet/print_options.rb'
require 'axlsx/workbook/worksheet/cfvo.rb'
require 'axlsx/workbook/worksheet/cfvos.rb'
diff --git a/lib/axlsx/workbook/worksheet/header_footer.rb b/lib/axlsx/workbook/worksheet/header_footer.rb
new file mode 100644
index 00000000..58e89d9e
--- /dev/null
+++ b/lib/axlsx/workbook/worksheet/header_footer.rb
@@ -0,0 +1,56 @@
+module Axlsx
+ # Header/Footer options for printing a worksheet. All settings are optional.
+ #
+ # @note The recommended way of managing header/footers is via Worksheet#header_footer
+ # @see Worksheet#initialize
+ class HeaderFooter
+
+ include Axlsx::OptionsParser
+ include Axlsx::SerializedAttributes
+ include Axlsx::Accessors
+
+ # Creates a new HeaderFooter object
+ # @option options [String] odd_header The content for headers on odd numbered pages.
+ # @option options [String] odd_footer The content for footers on odd numbered pages.
+ # @option options [String] even_header The content for headers on even numbered pages.
+ # @option options [String] even_footer The content for footers on even numbered pages.
+ # @option options [String] first_header The content for headers on even numbered pages.
+ # @option options [String] first_footer The content for footers on even numbered pages.
+ # @option options [Boolean] different_odd_even Setting this to true will show different headers/footers on odd and even pages. When false, the odd headers/footers are used on each page. (Default: false)
+ # @option options [Boolean] different_first If true, will use the first header/footer on page 1. Otherwise, the odd header/footer is used.
+ def initialize(options = {})
+ parse_options options
+ end
+
+ serializable_attributes :different_odd_even, :different_first
+
+ string_attr_accessor :odd_header, :odd_footer, :even_header, :even_footer, :first_header, :first_footer
+ boolean_attr_accessor :different_odd_even, :different_first
+
+ # Set some or all header/footers at once.
+ # @param [Hash] options The header/footer options to set (possible keys are :odd_header, :odd_footer, :even_header, :even_footer, :first_header, :first_footer, :different_odd_even, and :different_first).
+ def set(options)
+ parse_options options
+ end
+
+ # Serializes the header/footer object.
+ # @param [String] str
+ # @return [String]
+ def to_xml_string(str = '')
+ str << "<headerFooter "
+ serialized_attributes str
+ str << ">"
+
+ str << "<oddHeader>#{odd_header}</oddHeader>" unless odd_header.nil?
+ str << "<oddFooter>#{odd_footer}</oddFooter>" unless odd_footer.nil?
+
+ str << "<evenHeader>#{even_header}</evenHeader>" unless even_header.nil?
+ str << "<evenFooter>#{even_footer}</evenFooter>" unless even_footer.nil?
+
+ str << "<firstHeader>#{first_header}</firstHeader>" unless first_header.nil?
+ str << "<firstFooter>#{first_footer}</firstFooter>" unless first_footer.nil?
+
+ str << "</headerFooter>"
+ end
+ end
+end
diff --git a/lib/axlsx/workbook/worksheet/worksheet.rb b/lib/axlsx/workbook/worksheet/worksheet.rb
index 681987d7..8a595b2d 100644
--- a/lib/axlsx/workbook/worksheet/worksheet.rb
+++ b/lib/axlsx/workbook/worksheet/worksheet.rb
@@ -19,6 +19,7 @@ module Axlsx
# @option options [String] name The name of this worksheet.
# @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
# @option options [Hash] print_options A hash containing print options for this worksheet. @see PrintOptions
+ # @option options [Hash] header_footer A hash containing header/footer options for this worksheet. @see HeaderFooter
# @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
def initialize(wb, options={})
self.workbook = wb
@@ -35,6 +36,7 @@ module Axlsx
@page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
@page_setup = PageSetup.new options[:page_setup] if options[:page_setup]
@print_options = PrintOptions.new options[:print_options] if options[:print_options]
+ @header_footer = HeaderFooter.new options[:header_footer] if options[:header_footer]
end
# The name of the worksheet
@@ -42,7 +44,7 @@ module Axlsx
def name
@name ||= "Sheet" + (index+1).to_s
end
-
+
# The sheet calculation properties
# @return [SheetCalcPr]
def sheet_calc_pr
@@ -105,7 +107,7 @@ module Axlsx
# An range that excel will apply an autfilter to "A1:B3"
# This will turn filtering on for the cells in the range.
# The first row is considered the header, while subsequent rows are considerd to be data.
- # @return String
+ # @return String
def auto_filter
@auto_filter ||= AutoFilter.new self
end
@@ -194,6 +196,21 @@ module Axlsx
@print_options
end
+ # Options for headers and footers.
+ # @example
+ # wb = Axlsx::Package.new.workbook
+ # # would generate something like: "file.xlsx : sheet_name 2 of 7 date with timestamp"
+ # header = {:different_odd_ => false, :odd_header => "&L&F : &A&C&Pof%N%R%D %T"}
+ # ws = wb.add_worksheet :header_footer => header
+ #
+ # @see HeaderFooter#initialize
+ # @return [HeaderFooter]
+ def header_footer
+ @header_footer ||= HeaderFooter.new
+ yield @header_footer if block_given?
+ @header_footer
+ end
+
# convinience method to access all cells in this worksheet
# @return [Array] cells
def cells
@@ -281,7 +298,7 @@ module Axlsx
# The name of the worksheet
# The name of a worksheet must be unique in the workbook, and must not exceed 31 characters
- # @param [String] name
+ # @param [String] name
def name=(name)
validate_sheet_name name
@name=Axlsx::coder.encode(name)
@@ -389,7 +406,7 @@ module Axlsx
cf = ConditionalFormatting.new( :sqref => cells )
cf.add_rules rules
conditional_formattings << cf
- conditional_formattings
+ conditional_formattings
end
# Add data validation to this worksheet.
@@ -514,7 +531,7 @@ module Axlsx
def sanitize(str)
str.gsub(CONTROL_CHAR_REGEX, '')
end
-
+
# The worksheet relationships. This is managed automatically by the worksheet
# @return [Relationships]
def relationships
@@ -571,12 +588,12 @@ module Axlsx
private
-
+
def validate_sheet_name(name)
DataTypeValidator.validate "Worksheet.name", String, name
raise ArgumentError, (ERR_SHEET_NAME_TOO_LONG % name) if name.size > 31
raise ArgumentError, (ERR_SHEET_NAME_COLON_FORBIDDEN % name) if name.include? ':'
- name = Axlsx::coder.encode(name)
+ name = Axlsx::coder.encode(name)
sheet_names = @workbook.worksheets.map { |s| s.name }
raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % name) if sheet_names.include?(name)
end
@@ -587,7 +604,7 @@ module Axlsx
sheet_data, sheet_calc_pr, @sheet_protection, protected_ranges,
auto_filter, merged_cells, conditional_formattings,
data_validations, hyperlinks, print_options, page_margins,
- page_setup, worksheet_drawing, worksheet_comments,
+ page_setup, header_footer, worksheet_drawing, worksheet_comments,
tables]
end
@@ -607,7 +624,7 @@ module Axlsx
# @see Worksheet#protect_range
# @return [SimpleTypedList] The protected ranges for this worksheet
def protected_ranges
- @protected_ranges ||= ProtectedRanges.new self
+ @protected_ranges ||= ProtectedRanges.new self
# SimpleTypedList.new ProtectedRange
end
@@ -620,7 +637,7 @@ module Axlsx
# data validations array
# @return [Array]
def data_validations
- @data_validations ||= DataValidations.new self
+ @data_validations ||= DataValidations.new self
end
# merged cells array
diff --git a/test/workbook/worksheet/tc_header_footer.rb b/test/workbook/worksheet/tc_header_footer.rb
new file mode 100644
index 00000000..4d78aa9d
--- /dev/null
+++ b/test/workbook/worksheet/tc_header_footer.rb
@@ -0,0 +1,151 @@
+require 'tc_helper'
+
+class TestHeaderFooter < Test::Unit::TestCase
+
+ def setup
+ @p = Axlsx::Package.new
+ ws = @p.workbook.add_worksheet :name => 'test'
+ @hf = ws.header_footer
+ end
+
+ def test_initialize
+ assert_equal(nil, @hf.odd_header)
+ assert_equal(nil, @hf.odd_footer)
+
+ assert_equal(nil, @hf.even_header)
+ assert_equal(nil, @hf.even_footer)
+
+ assert_equal(nil, @hf.first_header)
+ assert_equal(nil, @hf.first_footer)
+
+ assert_equal(nil, @hf.different_first)
+ assert_equal(nil, @hf.different_odd_even)
+ end
+
+ def test_initialize_with_options
+ header_footer = {
+ :odd_header => 'oh',
+ :odd_footer => 'of',
+
+ :even_header => 'eh',
+ :even_footer => 'ef',
+
+ :first_header => 'fh',
+ :first_footer => 'ff',
+
+ :different_first => true,
+ :different_odd_even => true
+ }
+ optioned = @p.workbook.add_worksheet(:name => 'optioned', :header_footer => header_footer).header_footer
+
+ assert_equal('oh', optioned.odd_header)
+ assert_equal('of', optioned.odd_footer)
+
+ assert_equal('eh', optioned.even_header)
+ assert_equal('ef', optioned.even_footer)
+
+ assert_equal('fh', optioned.first_header)
+ assert_equal('ff', optioned.first_footer)
+
+ assert_equal(true, optioned.different_first)
+ assert_equal(true, optioned.different_odd_even)
+ end
+
+ def test_string_attributes
+ %w(odd_header odd_footer even_header even_footer first_header first_footer).each do |attr|
+ assert_raise(ArgumentError, 'only strings allowed in string attributes') { @hf.send("#{attr}=", 1) }
+ assert_nothing_raised { @hf.send("#{attr}=", 'test_string') }
+ end
+ end
+
+ def test_boolean_attributes
+ %w(different_first different_odd_even).each do |attr|
+ assert_raise(ArgumentError, 'only booleanish allowed in string attributes') { @hf.send("#{attr}=", 'foo') }
+ assert_nothing_raised { @hf.send("#{attr}=", 1) }
+ end
+ end
+
+ def test_set_all_values
+ @hf.set(
+ :odd_header => 'oh',
+ :odd_footer => 'of',
+
+ :even_header => 'eh',
+ :even_footer => 'ef',
+
+ :first_header => 'fh',
+ :first_footer => 'ff',
+
+ :different_first => true,
+ :different_odd_even => true
+ )
+
+ assert_equal('oh', @hf.odd_header)
+ assert_equal('of', @hf.odd_footer)
+
+ assert_equal('eh', @hf.even_header)
+ assert_equal('ef', @hf.even_footer)
+
+ assert_equal('fh', @hf.first_header)
+ assert_equal('ff', @hf.first_footer)
+
+ assert_equal(true, @hf.different_first)
+ assert_equal(true, @hf.different_odd_even)
+ end
+
+ def test_to_xml_all_values
+ @hf.set(
+ :odd_header => 'oh',
+ :odd_footer => 'of',
+
+ :even_header => 'eh',
+ :even_footer => 'ef',
+
+ :first_header => 'fh',
+ :first_footer => 'ff',
+
+ :different_first => true,
+ :different_odd_even => true
+ )
+
+ doc = Nokogiri::XML.parse(@hf.to_xml_string)
+ assert_equal(1, doc.xpath(".//headerFooter[@differentFirst='true'][@differentOddEven='true']").size)
+
+ assert_equal(1, doc.xpath(".//headerFooter/oddHeader").size)
+ assert_equal('oh', doc.xpath(".//headerFooter/oddHeader").text)
+ assert_equal(1, doc.xpath(".//headerFooter/oddFooter").size)
+ assert_equal('of', doc.xpath(".//headerFooter/oddFooter").text)
+
+ assert_equal(1, doc.xpath(".//headerFooter/evenHeader").size)
+ assert_equal('eh', doc.xpath(".//headerFooter/evenHeader").text)
+ assert_equal(1, doc.xpath(".//headerFooter/evenFooter").size)
+ assert_equal('ef', doc.xpath(".//headerFooter/evenFooter").text)
+
+ assert_equal(1, doc.xpath(".//headerFooter/firstHeader").size)
+ assert_equal('fh', doc.xpath(".//headerFooter/firstHeader").text)
+ assert_equal(1, doc.xpath(".//headerFooter/firstFooter").size)
+ assert_equal('ff', doc.xpath(".//headerFooter/firstFooter").text)
+ end
+
+ def test_to_xml_some_values
+ @hf.set(
+ :odd_header => 'oh',
+ :different_odd_even => false
+ )
+
+ doc = Nokogiri::XML.parse(@hf.to_xml_string)
+ assert_equal(1, doc.xpath(".//headerFooter[@differentOddEven='false']").size)
+ assert_equal(0, doc.xpath(".//headerFooter[@differentFirst]").size)
+
+ assert_equal(1, doc.xpath(".//headerFooter/oddHeader").size)
+ assert_equal('oh', doc.xpath(".//headerFooter/oddHeader").text)
+ assert_equal(0, doc.xpath(".//headerFooter/oddFooter").size)
+
+ assert_equal(0, doc.xpath(".//headerFooter/evenHeader").size)
+ assert_equal(0, doc.xpath(".//headerFooter/evenFooter").size)
+
+ assert_equal(0, doc.xpath(".//headerFooter/firstHeader").size)
+ assert_equal(0, doc.xpath(".//headerFooter/firstFooter").size)
+ end
+end
+
diff --git a/test/workbook/worksheet/tc_worksheet.rb b/test/workbook/worksheet/tc_worksheet.rb
index 4c8f2822..da90ffa0 100644
--- a/test/workbook/worksheet/tc_worksheet.rb
+++ b/test/workbook/worksheet/tc_worksheet.rb
@@ -55,6 +55,17 @@ class TestWorksheet < Test::Unit::TestCase
end
end
+ def test_header_footer
+ assert(@ws.header_footer.is_a? Axlsx::HeaderFooter)
+ end
+
+ def test_header_footer_yield
+ @ws.header_footer do |hf|
+ assert(hf.is_a? Axlsx::HeaderFooter)
+ assert(@ws.header_footer == hf)
+ end
+ end
+
def test_no_autowidth
@ws.workbook.use_autowidth = false
@ws.add_row [1,2,3,4]
@@ -65,7 +76,8 @@ class TestWorksheet < Test::Unit::TestCase
page_margins = {:left => 2, :right => 2, :bottom => 2, :top => 2, :header => 2, :footer => 2}
page_setup = {:fit_to_height => 1, :fit_to_width => 1, :orientation => :landscape, :paper_width => "210mm", :paper_height => "297mm", :scale => 80}
print_options = {:grid_lines => true, :headings => true, :horizontal_centered => true, :vertical_centered => true}
- optioned = @ws.workbook.add_worksheet(:name => 'bob', :page_margins => page_margins, :page_setup => page_setup, :print_options => print_options, :selected => true, :show_gridlines => false)
+ header_footer = {:different_first => false, :different_odd_even => false, :odd_header => 'Header'}
+ optioned = @ws.workbook.add_worksheet(:name => 'bob', :page_margins => page_margins, :page_setup => page_setup, :print_options => print_options, :header_footer => header_footer, :selected => true, :show_gridlines => false)
page_margins.keys.each do |key|
assert_equal(page_margins[key], optioned.page_margins.send(key))
end
@@ -75,6 +87,9 @@ class TestWorksheet < Test::Unit::TestCase
print_options.keys.each do |key|
assert_equal(print_options[key], optioned.print_options.send(key))
end
+ header_footer.keys.each do |key|
+ assert_equal(header_footer[key], optioned.header_footer.send(key))
+ end
assert_equal(optioned.name, 'bob')
assert_equal(optioned.selected, true)
assert_equal(optioned.show_gridlines, false)
@@ -293,6 +308,16 @@ class TestWorksheet < Test::Unit::TestCase
assert_equal(doc.xpath('//xmlns:worksheet/xmlns:printOptions[@gridLines="true"][@horizontalCentered="true"]').size, 1)
end
+ def test_to_xml_string_header_footer
+ @ws.header_footer do |hf|
+ hf.different_first = false
+ hf.different_odd_even = false
+ hf.odd_header = 'Test Header'
+ end
+ doc = Nokogiri::XML(@ws.to_xml_string)
+ assert_equal(doc.xpath('//xmlns:worksheet/xmlns:headerFooter[@differentFirst="false"][@differentOddEven="false"]').size, 1)
+ end
+
def test_to_xml_string_drawing
@ws.add_chart Axlsx::Pie3DChart
doc = Nokogiri::XML(@ws.to_xml_string)
@@ -317,7 +342,7 @@ class TestWorksheet < Test::Unit::TestCase
def test_styles
assert(@ws.styles.is_a?(Axlsx::Styles), 'worksheet provides access to styles')
end
-
+
def test_to_xml_string_with_illegal_chars
nasties = "\v\u2028\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u001f"
@ws.add_row [nasties]
@@ -400,7 +425,7 @@ class TestWorksheet < Test::Unit::TestCase
@ws.add_row [1, 2, 3]
assert_nothing_raised {@ws.protect_range(@ws.rows.first.cells) }
assert_equal('A1:C1', @ws.send(:protected_ranges).last.sqref)
-
+
end
def test_merge_cells
@ws.add_row [1,2,3]
@@ -412,7 +437,7 @@ class TestWorksheet < Test::Unit::TestCase
assert_equal(@ws.send(:merged_cells).size, 3)
assert_equal(@ws.send(:merged_cells).last, "A3:B3")
end
-
+
def test_merge_cells_sorts_correctly_by_row_when_given_array
10.times do |i|
@ws.add_row [i]
@@ -420,7 +445,7 @@ class TestWorksheet < Test::Unit::TestCase
@ws.merge_cells [@ws.rows[8].cells.first, @ws.rows[9].cells.first]
assert_equal "A9:A10", @ws.send(:merged_cells).first
end
-
+
def test_auto_filter
assert(@ws.auto_filter.range.nil?)
assert_raise(ArgumentError) { @ws.auto_filter = 123 }
@@ -432,6 +457,6 @@ class TestWorksheet < Test::Unit::TestCase
@ws.auto_filter.range = 'A1:D9'
@ws.auto_filter.add_column 0, :filters, :filter_items => [1]
doc = Nokogiri::XML(@ws.to_xml_string)
- assert(doc.xpath('//sheetPr[@filterMode="true"]'))
+ assert(doc.xpath('//sheetPr[@filterMode="true"]'))
end
end