diff options
| author | Jurriaan Pruis <[email protected]> | 2012-03-27 13:55:42 +0200 |
|---|---|---|
| committer | Jurriaan Pruis <[email protected]> | 2012-03-27 13:55:42 +0200 |
| commit | 3867eb132f20c3258f007ca61a738e90a09087dc (patch) | |
| tree | e8d0b196badf01ceefe56aa02ac40ad82371afce /lib | |
| parent | 5afd30be1774fb5255dad2eb18c700d0d8f4a628 (diff) | |
| parent | be2d7bee9b9236fff4a378db8770c3ae793e8422 (diff) | |
| download | caxlsx-3867eb132f20c3258f007ca61a738e90a09087dc.tar.gz caxlsx-3867eb132f20c3258f007ca61a738e90a09087dc.zip | |
Merge github.com:randym/axlsx
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/axlsx.rb | 3 | ||||
| -rw-r--r-- | lib/axlsx/package.rb | 6 | ||||
| -rw-r--r-- | lib/axlsx/stylesheet/color.rb | 28 | ||||
| -rw-r--r-- | lib/axlsx/workbook/shared_strings_table.rb | 25 | ||||
| -rw-r--r-- | lib/axlsx/workbook/worksheet/cell.rb | 119 | ||||
| -rw-r--r-- | lib/axlsx/workbook/worksheet/page_margins.rb | 23 | ||||
| -rw-r--r-- | lib/axlsx/workbook/worksheet/row.rb | 7 | ||||
| -rw-r--r-- | lib/axlsx/workbook/worksheet/worksheet.rb | 76 |
8 files changed, 209 insertions, 78 deletions
diff --git a/lib/axlsx.rb b/lib/axlsx.rb index eb5068b5..cf3beb87 100644 --- a/lib/axlsx.rb +++ b/lib/axlsx.rb @@ -5,6 +5,9 @@ require 'axlsx/util/simple_typed_list.rb' require 'axlsx/util/constants.rb' require 'axlsx/util/validators.rb' require 'axlsx/util/storage.rb' + +#not even close to being ready but it does not break anything so it stays for now. +# needs a full re-write to use agile-encryption properly require 'axlsx/util/cbf.rb' require 'axlsx/util/ms_off_crypto.rb' diff --git a/lib/axlsx/package.rb b/lib/axlsx/package.rb index 73e72493..35ab181a 100644 --- a/lib/axlsx/package.rb +++ b/lib/axlsx/package.rb @@ -184,7 +184,7 @@ module Axlsx end workbook.tables.each do |table| - @parts << {:entry => "xl/#{table.pn}", :doc => table.to_xml, :schema => SML_XSD} + @parts << {:entry => "xl/#{table.pn}", :doc => table.to_xml, :schema => SML_XSD} end workbook.charts.each do |chart| @@ -196,12 +196,12 @@ module Axlsx end if use_shared_strings - @parts << {:entry => "xl/#{SHARED_STRINGS_PN}", :doc => workbook.shared_strings.to_xml, :schema => SML_XSD} + @parts << {:entry => "xl/#{SHARED_STRINGS_PN}", :doc => workbook.shared_strings.to_xml_string, :schema => SML_XSD} end workbook.worksheets.each do |sheet| @parts << {:entry => "xl/#{sheet.rels_pn}", :doc => sheet.relationships.to_xml, :schema => RELS_XSD} - @parts << {:entry => "xl/#{sheet.pn}", :doc => sheet.to_xml, :schema => SML_XSD} + @parts << {:entry => "xl/#{sheet.pn}", :doc => sheet.to_xml_string, :schema => SML_XSD} end @parts end diff --git a/lib/axlsx/stylesheet/color.rb b/lib/axlsx/stylesheet/color.rb index 4e9806ad..5d3e60ca 100644 --- a/lib/axlsx/stylesheet/color.rb +++ b/lib/axlsx/stylesheet/color.rb @@ -5,9 +5,9 @@ module Axlsx # Determines if the color is system color dependant # @return [Boolean] attr_reader :auto - - # The color as defined in rgb terms. - # @note + + # The color as defined in rgb terms. + # @note # rgb colors need to conform to ST_UnsignedIntHex. That basically means put 'FF' before you color # When assigning the rgb value the behavior is much like CSS selectors and can use shorthand versions as follows: # If you provide a two character value it will be repeated for each r, g, b assignment @@ -25,16 +25,16 @@ module Axlsx # no support for theme just yet # @return [Integer] #attr_reader :theme - + # The tint value. # @note valid values are between -1.0 and 1.0 # @return [Float] attr_reader :tint - + # Creates a new Color object # @option options [Boolean] auto # @option options [String] rgb - # @option options [Float] tint + # @option options [Float] tint def initialize(options={}) @rgb = "FF000000" options.each do |o| @@ -42,7 +42,7 @@ module Axlsx end end # @see auto - def auto=(v) Axlsx::validate_boolean v; @auto = v end + def auto=(v) Axlsx::validate_boolean v; @auto = v end # @see color def rgb=(v) Axlsx::validate_string(v) @@ -56,11 +56,19 @@ module Axlsx def tint=(v) Axlsx::validate_float v; @tint = v end # This version does not support themes - # def theme=(v) Axlsx::validate_unsigned_integer v; @theme = v end - + # def theme=(v) Axlsx::validate_unsigned_integer v; @theme = v end + # Indexed colors are for backward compatability which I am choosing not to support - # def indexed=(v) Axlsx::validate_unsigned_integer v; @indexed = v end + # def indexed=(v) Axlsx::validate_unsigned_integer v; @indexed = v end + def to_xml_string + str = ["<color "] + self.instance_values.each do |key, value| + str << "%s='%s' " % [key, value] + end + str << "/>" + str.join + end # Serializes the color # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to. diff --git a/lib/axlsx/workbook/shared_strings_table.rb b/lib/axlsx/workbook/shared_strings_table.rb index 91892c38..5ebfd87f 100644 --- a/lib/axlsx/workbook/shared_strings_table.rb +++ b/lib/axlsx/workbook/shared_strings_table.rb @@ -3,7 +3,7 @@ module Axlsx # The Shared String Table class is responsible for managing and serializing common strings in a workbook. # While the ECMA-376 spec allows for both inline and shared strings it seems that at least some applications like iWorks Numbers - # and Google Docs require that the shared string table is populated in order to interoperate properly. + # and Google Docs require that the shared string table is populated in order to interoperate properly. # As a developer, you should never need to directly work against this class. Simply set 'use_shared_strings' # on the package or workbook to generate a package that uses the shared strings table instead of inline strings. # @note Serialization performance is affected by using this serialization method so if you do not need interoperability @@ -15,14 +15,14 @@ module Axlsx # @return [Integer] attr_reader :count - # The total number of unique strings in the workbook. + # The total number of unique strings in the workbook. # @return [Integer] def unique_count @unique_cells.size end # An array of unique cells. Multiple attributes of the cell are used in comparison - # each of these unique cells is parsed into the shared string table. + # each of these unique cells is parsed into the shared string table. # @see Cell#sharable attr_reader :unique_cells @@ -35,10 +35,14 @@ module Axlsx resolve(cells) end + def to_xml_string + str = "<sst xmlns=\"%s\" count='%s' uniqueCount='%s'>%s</sst>" % [XML_NS, count, unique_count, @unique_cells.map {|cell| cell[:data]}.join] + end # Generate the xml document for the Shared Strings Table # @return [String] def to_xml + builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml| xml.sst(:xmlns => Axlsx::XML_NS, :count => count, :uniqueCount => unique_count) { @unique_cells.each do |cell| @@ -50,22 +54,25 @@ module Axlsx end private - - # Interate over all of the cells in the array. - # if our unique cells array does not contain a sharable cell, + + # Interate over all of the cells in the array. + # if our unique cells array does not contain a sharable cell, # add the cell to our unique cells array and set the ssti attribute on the index of this cell in the shared strings table # if a sharable cell already exists in our unique_cells array, set the ssti attribute of the cell and move on. # @return [Array] unique cells def resolve(cells) cells.each do |cell| - index = @unique_cells.index { |item| item.shareable(cell) } + cell_hash = cell.shareable_hash + index = @unique_cells.index do |item| + item[:hash] == cell_hash + end if index == nil cell.send :ssti=, @unique_cells.size - @unique_cells << cell + @unique_cells << {:hash => cell_hash, :data => '<si>'<< cell.run_xml_string << '</si>'} else cell.send :ssti=, index end end - end + end end end diff --git a/lib/axlsx/workbook/worksheet/cell.rb b/lib/axlsx/workbook/worksheet/cell.rb index ff759cdf..a3ec046a 100644 --- a/lib/axlsx/workbook/worksheet/cell.rb +++ b/lib/axlsx/workbook/worksheet/cell.rb @@ -25,11 +25,18 @@ module Axlsx # An array of available inline styes. + # TODO change this to a hash where each key defines attr name and validator (and any info the validator requires) + # then move it out to a module so we can re-use in in other classes. + # needs to define bla=(v) and bla methods on the class that hook into a + # set_attr method that kicks the suplied validator and updates the instance_variable + # for the key INLINE_STYLES = ['value', 'type', 'font_name', 'charset', 'family', 'b', 'i', 'strike','outline', 'shadow', 'condense', 'extend', 'u', 'vertAlign', 'sz', 'color', 'scheme'] + INLINE_ATTR = [:font_name => { :validator=>:validate_string}] + # The index of the cellXfs item to be applied to this cell. # @return [Integer] @@ -69,71 +76,79 @@ module Axlsx @value = cast_value(v) end + + # Indicates that the cell has one or more of the custom cell styles applied. + # @return [Boolean] + def is_text_run? + @is_text_run ||= false + end + + # The inline font_name property for the cell # @return [String] attr_reader :font_name # @see font_name - def font_name=(v) Axlsx::validate_string(v); @font_name = v; end + def font_name=(v) set_run_style :validate_string, :font_name, v; end # The inline charset property for the cell # @return [String] attr_reader :charset # @see charset - def charset=(v) Axlsx::validate_unsigned_int(v); @charset = v; end + def charset=(v) set_run_style :validate_unsigned_int, :charset, v; end # The inline family property for the cell # @return [String] attr_reader :family # @see family - def family=(v) Axlsx::validate_string(v); @family = v; end + def family=(v) set_run_style :validate_string, :family, v; end # The inline bold property for the cell # @return [Boolean] attr_reader :b # @see b - def b=(v) Axlsx::validate_boolean(v); @b = v; end + def b=(v) set_run_style :validate_boolean, :b, v; end # The inline italic property for the cell # @return [Boolean] attr_reader :i # @see i - def i=(v) Axlsx::validate_boolean(v); @i = v; end + def i=(v) set_run_style :validate_boolean, :i, v; end # The inline strike property for the cell # @return [Boolean] attr_reader :strike # @see strike - def strike=(v) Axlsx::validate_boolean(v); @strike = v; end + def strike=(v) set_run_style :validate_boolean, :strike, v; end # The inline outline property for the cell # @return [Boolean] attr_reader :outline # @see outline - def outline=(v) Axlsx::validate_boolean(v); @outline = v; end + def outline=(v) set_run_style :validate_boolean, :outline, v; end # The inline shadow property for the cell # @return [Boolean] attr_reader :shadow # @see shadow - def shadow=(v) Axlsx::validate_boolean(v); @shadow = v; end + def shadow=(v) set_run_style :validate_boolean, :shadow, v; end # The inline condense property for the cell # @return [Boolean] attr_reader :condense # @see condense - def condense=(v) Axlsx::validate_boolean(v); @condense = v; end + def condense=(v) set_run_style :validate_boolean, :condense, v; end # The inline extend property for the cell # @return [Boolean] attr_reader :extend # @see extend - def extend=(v) Axlsx::validate_boolean(v); @extend = v; end + def extend=(v) set_run_style :validate_boolean, :extend, v; end # The inline underline property for the cell # @return [Boolean] attr_reader :u # @see u - def u=(v) Axlsx::validate_boolean(v); @u = v; end + def u=(v) set_run_style :validate_boolean, :u, v; end # The inline color property for the cell # @return [Color] @@ -141,27 +156,34 @@ module Axlsx # @param [String] The 8 character representation for an rgb color #FFFFFFFF" def color=(v) @color = v.is_a?(Color) ? v : Color.new(:rgb=>v) + @has_run_style = true end # The inline sz property for the cell # @return [Boolean] attr_reader :sz # @see sz - def sz=(v) Axlsx::validate_unsigned_int(v); @sz = v; end + def sz=(v) set_run_style :validate_unsigned_int, :sz, v; end # The inline vertical alignment property for the cell # this must be one of [:baseline, :subscript, :superscript] # @return [Symbol] attr_reader :vertAlign # @see vertAlign - def vertAlign=(v) RestrictionValidator.validate "Cell.vertAlign", [:baseline, :subscript, :superscript], v; @vertAlign = v; end + def vertAlign=(v) + RestrictionValidator.validate "Cell.vertAlign", [:baseline, :subscript, :superscript], v + set_run_style nil, :vertAlign, v + end # The inline scheme property for the cell # this must be one of [:none, major, minor] # @return [Symbol] attr_reader :scheme # @see scheme - def scheme=(v) RestrictionValidator.validate "Cell.schema", [:none, :major, :minor], v; @scheme = v; end + def scheme=(v) + RestrictionValidator.validate "Cell.schema", [:none, :major, :minor], v + set_run_style nil, :scheme, v + end # @param [Row] row The row this cell belongs to. # @param [Any] value The value associated with this cell. @@ -202,16 +224,10 @@ module Axlsx # equality comparison to test value, type and inline style attributes # this is how we work out if the cell needs to be added or already exists in the shared strings table - def shareable(v) - - #using reject becase 1.8.7 select returns an array... - v_hash = v.instance_values.reject { |key, val| !INLINE_STYLES.include?(key) } + def shareable_hash self_hash = self.instance_values.reject { |key, val| !INLINE_STYLES.include?(key) } - # required as color is an object, and the comparison will fail even though both use the same color. - v_hash['color'] = v_hash['color'].instance_values if v_hash['color'] self_hash['color'] = self_hash['color'].instance_values if self_hash['color'] - - v_hash == self_hash + self_hash end # @return [Integer] The index of the cell in the containing row. @@ -259,6 +275,29 @@ module Axlsx self.row.worksheet.merge_cells "#{self.r}:#{range_end}" unless range_end.nil? end + def run_xml_string + str = "" + if is_text_run? + data = self.instance_values.reject{|key, value| value == nil } + keys = data.keys & INLINE_STYLES + keys.delete ['value', 'type'] + str << "<r><rPr>" + keys.each do |key| + case key + when 'font_name' + str << "<rFont val='"<< @font_name << "'/>" + when 'color' + str << data[key].to_xml_string + else + "<" << key.to_s << " val='" << data[key].to_s << "'/>" + end + end + str << "</rPr>" << "<t>" << value.to_s << "</t></r>" + else + str << "<t>" << value.to_s << "</t>" + end + str + end # builds an xml text run based on this cells attributes. This is extracted from to_xml so that shared strings can use it. # @param [Nokogiri::XML::Builder] xml The document builder instance this output will be added to. # @return [String] the xml for this cell's text run @@ -294,11 +333,37 @@ module Axlsx # Serializes the cell # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to. # @return [String] xml text for the cell + def to_xml_string + case @type + when :string + #parse formula + if @value.start_with?('=') + '<c r="' << r << '" t="str" s="' << @style.to_s << '"><f>' << value.to_s.gsub('=', '') << '</f></c>' + else + #parse shared + if @ssti + '<c r="' << r << '" t="s" s="' << @style.to_s << '"><v>' << ssti << '</v></c>' + else + '<c r="' << r << '" t="inlineStr" s="' << @style.to_s << '"><is>' << run_xml_string << '</is></c>' + end + end + when :date + # TODO: See if this is subject to the same restriction as Time below + '<c r="' << r << '" s="' << @style.to_s << '"><v>' << DateTimeConverter::date_to_serial(@value).to_s << '</v></c>' + when :time + '<c r="' << r << '" s="' << @style.to_s << '"><v>' << DateTimeConverter::time_to_serial(@value).to_s << '</v></c>' + when :boolean + '<c r="' << r << '" t="b" s="' << @style.to_s << '"><v>' << @value.to_s << '</v></c>' + else + '<c r="' << r << '" s="' << @style.to_s << '"><v>' << @value.to_s << '</v></c>' + end + end + def to_xml(xml) if @type == :string #parse formula if @value.start_with?('=') - xml.c(:r => r, :t=>:str, :s=>style) { + xml.c(:r => r, :s=>style, :t=>:str) { xml.f @value.to_s.gsub('=', '') } else @@ -330,6 +395,14 @@ module Axlsx private + # Utility method for setting inline style attributes + def set_run_style( validator, attr, value) + return unless INLINE_STYLES.include?(attr.to_s) + Axlsx.send(validator, value) unless validator == nil + self.instance_variable_set :"@#{attr.to_s}", value + @is_text_run = true + end + # @see ssti def ssti=(v) Axlsx::validate_unsigned_int(v) diff --git a/lib/axlsx/workbook/worksheet/page_margins.rb b/lib/axlsx/workbook/worksheet/page_margins.rb index f41e3426..1c456f2a 100644 --- a/lib/axlsx/workbook/worksheet/page_margins.rb +++ b/lib/axlsx/workbook/worksheet/page_margins.rb @@ -12,13 +12,13 @@ module Axlsx # Default left and right margin (in inches) DEFAULT_LEFT_RIGHT = 0.75 - + # Default top and bottom margins (in inches) DEFAULT_TOP_BOTTOM = 1.00 - + # Default header and footer margins (in inches) DEFAULT_HEADER_FOOTER = 0.50 - + # Left margin (in inches) # @return [Float] attr_reader :left @@ -26,23 +26,23 @@ module Axlsx # Right margin (in inches) # @return [Float] attr_reader :right - + # Top margin (in inches) # @return [Float] attr_reader :top - + # Bottom margin (in inches) # @return [Float] attr_reader :bottom - + # Header margin (in inches) # @return [Float] attr_reader :header - + # Footer margin (in inches) # @return [Float] attr_reader :footer - + # Creates a new PageMargins object # @option options [Numeric] left The left margin in inches # @option options [Numeric] right The right margin in inches @@ -60,7 +60,7 @@ module Axlsx self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}=" end end - + # Set some or all margins at once. # @param [Hash] margins the margins to set (possible keys are :left, :right, :top, :bottom, :header and :footer). def set(margins) @@ -69,7 +69,7 @@ module Axlsx send("#{k}=", v) end end - + # @see left def left=(v); Axlsx::validate_unsigned_numeric(v); @left = v end # @see right @@ -83,6 +83,9 @@ module Axlsx # @see footer def footer=(v); Axlsx::validate_unsigned_numeric(v); @footer = v end + def to_xml_string + "<pageMargins left='%s' right='%s' top='%s' bottom='%s' header='%s' footer='%s'/>" % [left, right, top, bottom, header, footer] + end # Serializes the page margins element # @note For compatibility, this is a noop unless custom margins have been specified. # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to. diff --git a/lib/axlsx/workbook/worksheet/row.rb b/lib/axlsx/workbook/worksheet/row.rb index bb6a92a8..30101a28 100644 --- a/lib/axlsx/workbook/worksheet/row.rb +++ b/lib/axlsx/workbook/worksheet/row.rb @@ -59,6 +59,13 @@ module Axlsx worksheet.rows.index(self) end + def to_xml_string + if custom_height? + '<row r="' << (index+1).to_s << '" customHeight="1" ht="' << height.to_s << '">' << @cells.map { |cell| cell.to_xml_string }.join << '</row>' + else + '<row r="' << (index+1).to_s << '">' << (@cells.map { |cell| cell.to_xml_string }.join) << '</row>' + end + end # Serializes the row # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to. # @return [String] diff --git a/lib/axlsx/workbook/worksheet/worksheet.rb b/lib/axlsx/workbook/worksheet/worksheet.rb index 3f651078..64fae531 100644 --- a/lib/axlsx/workbook/worksheet/worksheet.rb +++ b/lib/axlsx/workbook/worksheet/worksheet.rb @@ -168,27 +168,6 @@ module Axlsx @fit_to_page = v end - # Returns the cell or cells defined using excel style A1:B3 references. - # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber - # @return [Cell, Array] - def [](cell_def) - return rows[cell_def - 1] if cell_def.is_a? Integer - parts = cell_def.split(':') - first = name_to_cell parts[0] - - if parts.size == 1 - first - else - cells = [] - last = name_to_cell(parts[1]) - rows[(first.row.index..last.row.index)].each do |r| - r.cells[(first.index..last.index)].each do |c| - cells << c - end - end - cells - end - end # returns the column and row index for a named based cell # @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row. @@ -381,6 +360,7 @@ module Axlsx chart end + # needs documentation def add_table(ref, options={}) table = Table.new(ref, self, options) @tables << table @@ -397,14 +377,40 @@ module Axlsx image end + def to_xml_string + str = "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R] + str.concat "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % fit_to_page if fit_to_page + str.concat "<dimension ref=\"%s\"></dimension>" % dimension unless rows.size == 0 + str.concat "<sheetViews><sheetView tabSelected='%s' workbookViewId='0' showGridLines='%s'><selection activeCell=\"A1\" sqref=\"A1\"/></sheetView></sheetViews>" % [@selected, show_gridlines] + + if @auto_fit_data.size > 0 + str.concat "<cols>" + @auto_fit_data.each_with_index do |col, index| + min_max = index+1 + str.concat "<col min='%s' max='%s' width='%s' customWidth='1'></col>" % [min_max, min_max, auto_width(col)] + end + str.concat '</cols>' + end + + str.concat "<sheetData>%s</sheetData>" % @rows.map { |obj| obj.to_xml_string }.join + str.concat page_margins.to_xml_string if @page_margins + str.concat "<autoFilter ref='%s'></autoFilter>" % @auto_filter if @auto_filter + str.concat "<mergeCells count='%s'>%s</mergeCells>" % [@merged_cells.size, @merged_cells.reduce('') { |memo, obj| "<mergeCell ref='%s'></mergeCell>" % obj } ] unless @merged_cells.empty? + str.concat "<drawing r:id='rId1'></drawing>" if @drawing + unless @tables.empty? + str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }] + end + str + '</worksheet>' + end + # Serializes the worksheet document # @return [String] def to_xml builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml| xml.worksheet(:xmlns => XML_NS, :'xmlns:r' => XML_NS_R) { - xml.sheetPr { - xml.pageSetUpPr :fitToPage => fit_to_page if fit_to_page + xml.sheetPr { + xml.pageSetUpPr :fitToPage => fit_to_page if fit_to_page } # another patch for the folks at rubyXL as thier parser depends on this optional element. xml.dimension :ref=>dimension unless rows.size == 0 @@ -456,6 +462,29 @@ module Axlsx r end + # Returns the cell or cells defined using excel style A1:B3 references. + # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber + # @return [Cell, Array] + + def [] (cell_def) + return rows[cell_def] if cell_def.is_a?(Integer) + + parts = cell_def.split(':') + first = name_to_cell parts[0] + if parts.size == 1 + first + else + cells = [] + last = name_to_cell(parts[1]) + rows[(first.row.index..last.row.index)].each do |r| + r.cells[(first.index..last.index)].each do |c| + cells << c + end + end + cells + end + end + private # assigns the owner workbook for this worksheet @@ -484,6 +513,7 @@ module Axlsx next if width == :ignore || (item.value.is_a?(String) && item.value.start_with?('=')) # make sure we can turn that fixed with off! col[:fixed] = nil if width == :auto + next unless self.workbook.use_autowidth cell_xf = cellXfs[item.style] font = fonts[cell_xf.fontId || 0] |
