class SharedStringTable SST_ID = 0x00FC def initialize @sst_record = nil @continues = [] @current_piece = [0,0].pack('V2') @str_indexes = {} # TODO replace with array? or is hash more efficient? @add_calls = 0 end def add_str(s) @add_calls += 1 index = @str_indexes[s] if index.nil? # This is a new string for the SST. position = @str_indexes.length @str_indexes[s] = position index = position add_to_sst(s) end index end def str_index(s) @str_indexes[s] end def to_biff new_piece # flush the 'current' piece result = [SST_ID, @sst_record.length, @add_calls, @str_indexes.length].pack('v2V2') result += @sst_record[8..-1] result += @continues.join result end def add_to_sst(s) u_str = mock_unicode_string(s) raise "very long string" if u_str.length > 0xFFFF save_atom(u_str[0...4]) save_splitted(u_str[4..-1], false) end # Store the @current_piece in @continues and initialize a new @current_piece def new_piece if @sst_record.nil? # We get here when we first run out of space, or if that never happens then we end # up here when everything is finished and we call to_biff for the first time. @sst_record = @current_piece else @continues << [BiffRecord::CONTINUE_RECORD_ID, @current_piece.length].pack('v2') + @current_piece end @current_piece = '' end def save_atom(atom) free_space = 0x2020 - @current_piece.length new_piece if free_space < atom.length @current_piece += atom end def save_splitted(s, is_unicode_str) i = 0 while i < s.length do free_space = 0x2020 - @current_piece.length tail_length = s.length - i need_more_space = free_space < tail_length if !need_more_space atom_length = tail_length else if is_unicode_str atom_length = free_space & 0xFFFE else atom_length = free_space end end @current_piece += s[i...(i+atom_length)] if need_more_space new_piece if is_unicode_str @current_piece += "\001" else @current_piece += "\000" end end i += atom_length end end end class BiffRecord attr_accessor :record_data BIFF_LIMIT = 0x2020 # limit for BIFF7/8 CONTINUE_RECORD_ID = 0x003C # By default, initialize to ''. # May be overridden in subclass. def initialize @record_data = '' end def record_header # TODO figure out if Ruby's or Python's length function is correct here. [self.class::RECORD_ID, @record_data.length].pack('v2') end def to_biff if @record_data.length > BIFF_LIMIT chunks = [] pos = 0 while pos < @record_data.length chunk_pos = pos + BIFF_LIMIT chunk = @record_data[pos...chunk_pos] chunks << chunk pos = chunk_pos end continues = [self.class::RECORD_ID, chunks[0].length].pack('v2') + chunks[0] chunks.each_with_index do |c, i| next if i == 0 continues += [CONTINUE_RECORD_ID, c.length].pack('v2') + c end continues else record_header + @record_data end end end # Offset Size Contents # 0 2 Version, contains 0600H for BIFF8 and BIFF8X # 2 2 Type of the following data: # 0005H = Workbook globals # 0006H = Visual Basic module # 0010H = Worksheet # 0020H = Chart # 0040H = Macro sheet # 0100H = Workspace file # 4 2 Build identifier # 6 2 Build year # 8 4 File history flags # 12 4 Lowest Excel version that can read all records in this file class Biff8BOFRecord < BiffRecord RECORD_ID = 0x0809 # Stream Types BOOK_GLOBAL = 0x0005 VB_MODULE = 0x0006 WORKSHEET = 0x0010 CHART = 0x0020 MACROSHEET = 0x0040 WORKSPACE = 0x0100 def initialize(rec_type) version = 0x0600 build = 0x0DBB year = 0x07CC file_hist_flags = 0x00 ver_can_read = 0x06 @record_data = [version, rec_type, build, year, file_hist_flags, ver_can_read].pack('v4V2') end end class InterfaceHeaderRecord < BiffRecord RECORD_ID = 0x00E1 def initialize @record_data = [0xB0, 0x04].pack('C2') end end class InterfaceEndRecord < BiffRecord RECORD_ID = 0x00E2 end class MMSRecord < BiffRecord RECORD_ID = 0x00C1 def initialize @record_data = [0x00].pack('v') end end # This record is part of the file protection. It contains the name of the # user that has saved the file. The user name is always stored as an # equal-sized string. All unused characters after the name are filled # with space characters. It is not required to write the mentioned string # length. Every other length will be accepted too. class WriteAccessRecord < BiffRecord RECORD_ID = 0x005C # TODO Can we extend this to 0x70? I think 0x30 is a holdover from Biff7 but 112 chars is Biff8. def initialize(owner) @record_data = [owner[0, 0x30]].pack('A112') end end # This record specifies if the file contains an additional BIFF5/BIFF7 # workbook stream. # Record DSF, BIFF8: # Offset Size Contents # 0 2 0 = Only the BIFF8 Workbook stream is present # 1 = Additional BIFF5/BIFF7 Book stream is in the file # A double stream file can be read by Excel 5.0 and Excel 95, and still # contains all new features added to BIFF8 (which are left out in the # BIFF5/BIFF7 Book stream). class DSFRecord < BiffRecord RECORD_ID = 0x0161 def initialize @record_data = [0x00].pack('v') end end class TabIDRecord < BiffRecord RECORD_ID = 0x013D def initialize(sheetcount) @record_data = '' for i in 1..sheetcount do @record_data += [i].pack('v') end end end class FnGroupCountRecord < BiffRecord RECORD_ID = 0x009C def initialize @record_data = [0x0E, 0X00].pack('C2') end end # This record is part of the worksheet/workbook protection. It determines # whether the window configuration of this document is protected. Window # protection is not active, if this record is omitted. class WindowProtectRecord < BiffRecord RECORD_ID = 0x0019 def initialize(protect) @record_data = [protect].pack('v') end end # This record is part of the worksheet/workbook protection. # It determines whether the objects of the current sheet are protected. # Object protection is not active, if this record is omitted. class ObjectProtectRecord < BiffRecord RECORD_ID = 0x0063 def initialize(protect) @record_data = [protect].pack('v') end end # This record is part of the worksheet/workbook protection. It # determines whether the scenarios of the current sheet are protected. # Scenario protection is not active, if this record is omitted. class ScenarioProtectRecord < BiffRecord RECORD_ID = 0x00DD def initialize(protect) @record_data = [protect].pack('v') end end # This record is part of the worksheet/workbook protection. It # determines whether the scenarios of the current sheet are protected. # Scenario protection is not active, if this record is omitted. class ProtectRecord < BiffRecord RECORD_ID = 0x0012 def initialize(protect) @record_data = [protect].pack('v') end end # This record is part of the worksheet/workbook protection. It # stores a 16-bit hash value, calculated from the worksheet or workbook # protection password. class PasswordRecord < BiffRecord RECORD_ID = 0x0013 def initialize(password = "") @record_data = [password_hash(password)].pack('v') end # Based on the algorithm provided by Daniel Rentz of OpenOffice. def password_hash(plaintext) return 0 if plaintext === "" hash = 0x0000 plaintext.unpack('C*').each_with_index do |t, i| c = t << (i + 1) low_15 = c & 0x7fff high_15 = c & 0x7fff << 15 high_15 = high_15 >> 15 c = low_15 | high_15 hash = (hash ^ c) end hash = (hash ^ plaintext.length) hash = (hash ^ 0xCE4B) hash end end class Prot4RevRecord < BiffRecord RECORD_ID = 0x01AF def initialize @record_data = [0x00].pack('v') end end class Prot4RevPassRecord < BiffRecord RECORD_ID = 0x01BC def initialize @record_data = [0x00].pack('v') end end # This record contains a Boolean value determining whether Excel makes # a backup of the file while saving. class BackupRecord < BiffRecord RECORD_ID = 0x0040 def initialize(backup) @record_data = [backup].pack('v') end end # This record specifies whether and how to show objects in the workbook. # # Record HIDEOBJ, BIFF3-BIFF8: # Offset Size Contents # 0 2 Viewing mode for objects: # 0 = Show all objects # 1 = Show placeholders # 2 = Do not show objects class HideObjRecord < BiffRecord RECORD_ID = 0x008D def initialize @record_data = [0x00].pack('v') end end class RefreshAllRecord < BiffRecord RECORD_ID = 0x01B7 def initialize @record_data = [0x00].pack('v') end end # This record contains a Boolean value determining whether to save values # linked from external workbooks (CRN records and XCT records). In BIFF3 # and BIFF4 this option is stored in the WSBOOL record. # # Record BOOKBOOL, BIFF5-BIFF8: # # Offset Size Contents # 0 2 0 = Save external linked values; # 1 = Do not save external linked values class BookBoolRecord < BiffRecord RECORD_ID = 0x00DA def initialize @record_data = [0x00].pack('v') end end # This record stores two Windows country identifiers. The first # represents the user interface language of the Excel version that has # saved the file, and the second represents the system regional settings # at the time the file was saved. # # Record COUNTRY, BIFF3-BIFF8: # # Offset Size Contents # 0 2 Windows country identifier of the user interface language of Excel # 2 2 Windows country identifier of the system regional settings # # The following table shows most of the used country identifiers. Most # of these identifiers are equal to the international country calling # codes. # # 1 USA # 2 Canada # 7 Russia class CountryRecord < BiffRecord RECORD_ID = 0x00DA def initialize(ui_id, sys_settings_id) @record_data = [ui_id, sys_settings_id].pack('v2') end end # This record specifies if the formulas in the workbook can use natural # language formulasî. This type of formula can refer to cells by its # content or the content of the column or row header cell. # # Record USESELFS, BIFF8: # # Offset Size Contents # 0 2 0 = Do not use natural language formulas # 1 = Use natural language formulas class UseSelfsRecord < BiffRecord RECORD_ID = 0x0160 def initialize @record_data = [0x01].pack('v') end end class EOFRecord < BiffRecord RECORD_ID = 0x000A end # This record specifies the base date for displaying date values. All # dates are stored as count of days past this base date. In BIFF2-BIFF4 # this record is part of the Calculation Settings Block. # In BIFF5-BIFF8 it is stored in the Workbook Globals Substream. # # Record DATEMODE, BIFF2-BIFF8: # # Offset Size Contents # 0 2 0 = Base is 1899-Dec-31 (the cell = 1 represents 1900-Jan-01) # 1 = Base is 1904-Jan-01 (the cell = 1 represents 1904-Jan-02) class DateModeRecord < BiffRecord RECORD_ID = 0x0022 def initialize(boolean) @record_data = boolean ? [1].pack('v') : [0].pack('v') end end # This record stores if formulas use the real cell values for calculation # or the values displayed on the screen. In BIFF2- BIFF4 this record # is part of the Calculation Settings Block. In BIFF5-BIFF8 it is stored # in the Workbook Globals Substream. # # Record PRECISION, BIFF2-BIFF8: # # Offset Size Contents # 0 2 0 = Use displayed values; # 1 = Use real cell values class PrecisionRecord < BiffRecord RECORD_ID = 0x000E def initialize(boolean) @record_data = boolean ? [1].pack('v') : [0].pack('v') end end # This record stores the text encoding used to write byte strings, stored # as MS Windows code page identifier. The CODEPAGE record in BIFF8 always # contains the code page 1200 (UTF-16). Therefore it is not # possible to obtain the encoding used for a protection password (it is # not UTF-16). # # Record CODEPAGE, BIFF2-BIFF8: # # Offset Size Contents # 0 2 Code page identifier used for byte string text encoding: # 016FH = 367 = ASCII # 01B5H = 437 = IBM PC CP-437 (US) # 02D0H = 720 = IBM PC CP-720 (OEM Arabic) # 02E1H = 737 = IBM PC CP-737 (Greek) # 0307H = 775 = IBM PC CP-775 (Baltic) # 0352H = 850 = IBM PC CP-850 (Latin I) # 0354H = 852 = IBM PC CP-852 (Latin II (Central European)) # 0357H = 855 = IBM PC CP-855 (Cyrillic) # 0359H = 857 = IBM PC CP-857 (Turkish) # 035AH = 858 = IBM PC CP-858 (Multilingual Latin I with Euro) # 035CH = 860 = IBM PC CP-860 (Portuguese) # 035DH = 861 = IBM PC CP-861 (Icelandic) # 035EH = 862 = IBM PC CP-862 (Hebrew) # 035FH = 863 = IBM PC CP-863 (Canadian (French)) # 0360H = 864 = IBM PC CP-864 (Arabic) # 0361H = 865 = IBM PC CP-865 (Nordic) # 0362H = 866 = IBM PC CP-866 (Cyrillic (Russian)) # 0365H = 869 = IBM PC CP-869 (Greek (Modern)) # 036AH = 874 = Windows CP-874 (Thai) # 03A4H = 932 = Windows CP-932 (Japanese Shift-JIS) # 03A8H = 936 = Windows CP-936 (Chinese Simplified GBK) # 03B5H = 949 = Windows CP-949 (Korean (Wansung)) # 03B6H = 950 = Windows CP-950 (Chinese Traditional BIG5) # 04B0H = 1200 = UTF-16 (BIFF8) # 04E2H = 1250 = Windows CP-1250 (Latin II) (Central European) # 04E3H = 1251 = Windows CP-1251 (Cyrillic) # 04E4H = 1252 = Windows CP-1252 (Latin I) (BIFF4-BIFF7) # 04E5H = 1253 = Windows CP-1253 (Greek) # 04E6H = 1254 = Windows CP-1254 (Turkish) # 04E7H = 1255 = Windows CP-1255 (Hebrew) # 04E8H = 1256 = Windows CP-1256 (Arabic) # 04E9H = 1257 = Windows CP-1257 (Baltic) # 04EAH = 1258 = Windows CP-1258 (Vietnamese) # 0551H = 1361 = Windows CP-1361 (Korean (Johab)) # 2710H = 10000 = Apple Roman # 8000H = 32768 = Apple Roman # 8001H = 32769 = Windows CP-1252 (Latin I) (BIFF2-BIFF3) class CodepageBiff8Record < BiffRecord RECORD_ID = 0x0042 UTF_16 = 0x04B0 def initialize @record_data = [UTF_16].pack('v') end end # Offset Size Contents # 0 2 Horizontal position of the document window (in twips = 1/20 of a point) # 2 2 Vertical position of the document window (in twips = 1/20 of a point) # 4 2 Width of the document window (in twips = 1/20 of a point) # 6 2 Height of the document window (in twips = 1/20 of a point) # 8 2 Option flags: # Bits Mask Contents # 0 0001H 0 = Window is visible 1 = Window is hidden # 1 0002H 0 = Window is open 1 = Window is minimised # 3 0008H 0 = Horizontal scroll bar hidden 1 = Horizontal scroll bar visible # 4 0010H 0 = Vertical scroll bar hidden 1 = Vertical scroll bar visible # 5 0020H 0 = Worksheet tab bar hidden 1 = Worksheet tab bar visible # 10 2 Index to active (displayed) worksheet # 12 2 Index of first visible tab in the worksheet tab bar # 14 2 Number of selected worksheets (highlighted in the worksheet tab bar) # 16 2 Width of worksheet tab bar (in 1/1000 of window width). The remaining space is used by the # horizontal scrollbar. class Window1Record < BiffRecord RECORD_ID = 0x003D def initialize(hpos_twips, vpos_twips, width_twips, height_twips, flags, active_sheet, first_tab_index, selected_tabs, tab_width) args = [hpos_twips, vpos_twips, width_twips, height_twips, flags, active_sheet, first_tab_index, selected_tabs, tab_width] @record_data = args.pack('v9') end end # WARNING # The font with index 4 is omitted in all BIFF versions. # This means the first four fonts have zero-based indexes, and # the fifth font and all following fonts are referenced with one-based # indexes. # # Offset Size Contents # 0 2 Height of the font (in twips = 1/20 of a point) # 2 2 Option flags: # Bit Mask Contents # 0 0001H 1 = Characters are bold (redundant, see below) # 1 0002H 1 = Characters are italic # 2 0004H 1 = Characters are underlined (redundant, see below) # 3 0008H 1 = Characters are struck out # 0010H 1 = Outline # 0020H 1 = Shadow # 4 2 Colour index # 6 2 Font weight (100-1000). # Standard values are 0190H (400) for normal text and 02BCH # (700) for bold text. # 8 2 Escapement type: # 0000H = None # 0001H = Superscript # 0002H = Subscript # 10 1 Underline type: # 00H = None # 01H = Single # 21H = Single accounting # 02H = Double # 22H = Double accounting # 11 1 Font family: # 00H = None (unknown or don't care) # 01H = Roman (variable width, serifed) # 02H = Swiss (variable width, sans-serifed) # 03H = Modern (fixed width, serifed or sans-serifed) # 04H = Script (cursive) # 05H = Decorative (specialised, i.e. Old English, Fraktur) # 12 1 Character set: # 00H = 0 = ANSI Latin # 01H = 1 = System default # 02H = 2 = Symbol # 4DH = 77 = Apple Roman # 80H = 128 = ANSI Japanese Shift-JIS # 81H = 129 = ANSI Korean (Hangul) # 82H = 130 = ANSI Korean (Johab) # 86H = 134 = ANSI Chinese Simplified GBK # 88H = 136 = ANSI Chinese Traditional BIG5 # A1H = 161 = ANSI Greek # A2H = 162 = ANSI Turkish # A3H = 163 = ANSI Vietnamese # B1H = 177 = ANSI Hebrew # B2H = 178 = ANSI Arabic # BAH = 186 = ANSI Baltic # CCH = 204 = ANSI Cyrillic # DEH = 222 = ANSI Thai # EEH = 238 = ANSI Latin II (Central European) # FFH = 255 = OEM Latin I # 13 1 Not used # 14 var. Font name: # BIFF5/BIFF7: Byte string, 8-bit string length # BIFF8: Unicode string, 8-bit string length # The boldness and underline flags are still set in the options field, # but not used on reading the font. Font weight and underline type # are specified in separate fields instead. class FontRecord < BiffRecord RECORD_ID = 0x0031 def initialize(height, options, colour_index, weight, escapement, underline, family, charset, name) #TODO implement upack1 fully args = [height, options, colour_index, weight, escapement,underline, family, charset, 0x00] @record_data = args.pack('v5C4') + [name.length, 0].pack('CC') + name end end # Record FORMAT, BIFF8: # Offset Size Contents # 0 2 Format index used in other records # 2 var. Number format string (Unicode string, 16-bit string length) # # From BIFF5 on, the built-in number formats will be omitted. The built-in # formats are dependent on the current regional settings of the operating # system. The following table shows which number formats are used by default # in a US-English environment. All indexes from 0 to 163 are reserved for # built-in formats. The first user-defined format starts at 164. # # The built-in number formats, BIFF5-BIFF8 # # Index Type Format string # 0 General General # 1 Decimal 0 # 2 Decimal 0.00 # 3 Decimal #,##0 # 4 Decimal #,##0.00 # 5 Currency "$"#,##0_);("$"#,## # 6 Currency "$"#,##0_);[Red]("$"#,## # 7 Currency "$"#,##0.00_);("$"#,## # 8 Currency "$"#,##0.00_);[Red]("$"#,## # 9 Percent 0% # 10 Percent 0.00% # 11 Scientific 0.00E+00 # 12 Fraction # ?/? # 13 Fraction # ??/?? # 14 Date M/D/YY # 15 Date D-MMM-YY # 16 Date D-MMM # 17 Date MMM-YY # 18 Time h:mm AM/PM # 19 Time h:mm:ss AM/PM # 20 Time h:mm # 21 Time h:mm:ss # 22 Date/Time M/D/YY h:mm # 37 Account _(#,##0_);(#,##0) # 38 Account _(#,##0_);[Red](#,##0) # 39 Account _(#,##0.00_);(#,##0.00) # 40 Account _(#,##0.00_);[Red](#,##0.00) # 41 Currency _("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_) # 42 Currency _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_) # 43 Currency _("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_) # 44 Currency _(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_) # 45 Time mm:ss # 46 Time [h]:mm:ss # 47 Time mm:ss.0 # 48 Scientific ##0.0E+0 # 49 Text @ class NumberFormatRecord < BiffRecord RECORD_ID = 0x041E def initialize(index, format_string) @record_data = [index].pack('v') + mock_unicode_string(format_string) end end # XF Substructures # ------------------------------------------------------------------------- # XF_TYPE_PROT XF Type and Cell Protection (3 Bits), BIFF3-BIFF8 # These 3 bits are part of a specific data byte. # Bit Mask Contents # 0 01H 1 = Cell is locked # 1 02H 1 = Formula is hidden # 2 04H 0 = Cell XF; 1 = Style XF # # XF_USED_ATTRIB Attributes Used from Parent Style XF (6 Bits), # BIFF3-BIFF8 Each bit describes the validity of a specific group # of attributes. In cell XFs a cleared bit means the attributes of the # parent style XF are used (but only if the attributes are valid there), # a set bit means the attributes of this XF are used. In style XFs # a cleared bit means the attribute setting is valid, a set bit means the # attribute should be ignored. # Bit Mask Contents # 0 01H Flag for number format # 1 02H Flag for font # 2 04H Flag for horizontal and vertical alignment, text wrap, indentation, orientation, rotation, and # text direction # 3 08H Flag for border lines # 4 10H Flag for background area style # 5 20H Flag for cell protection (cell locked and formula hidden) # # XF_HOR_ALIGN Horizontal Alignment (3 Bits), BIFF2-BIFF8 The horizontal # alignment consists of 3 bits and is part of a specific data byte. # Value Horizontal alignment # 00H General # 01H Left # 02H Centred # 03H Right # 04H Filled # 05H Justified (BIFF4-BIFF8X) # 06H Centred across selection (BIFF4-BIFF8X) # 07H Distributed (BIFF8X) # # XF_VERT_ALIGN Vertical Alignment (2 or 3 Bits), BIFF4-BIFF8 # The vertical alignment consists of 2 bits (BIFF4) or 3 bits (BIFF5-BIFF8) # and is part of a specific data byte. Vertical alignment is not available # in BIFF2 and BIFF3. # Value Vertical alignment # 00H Top # 01H Centred # 02H Bottom # 03H Justified (BIFF5-BIFF8X) # 04H Distributed (BIFF8X) # # XF_ORIENTATION Text Orientation (2 Bits), BIFF4-BIFF7 In the BIFF # versions BIFF4-BIFF7, text can be rotated in steps of 90 degrees # or stacked. The orientation mode consists of 2 bits and is part of # a specific data byte. In BIFF8 a rotation angle occurs instead of these # flags. # Value Text orientation # 00H Not rotated # 01H Letters are stacked top-to-bottom, but not rotated # 02H Text is rotated 90 degrees counterclockwise # 03H Text is rotated 90 degrees clockwise # # XF_ROTATION Text Rotation Angle (1 Byte), BIFF8 # Value Text rotation # 0 Not rotated # 1-90 1 to 90 degrees counterclockwise # 91-180 1 to 90 degrees clockwise # 255 Letters are stacked top-to-bottom, but not rotated # # XF_BORDER_34 Cell Border Style (4 Bytes), BIFF3-BIFF4 Cell borders # contain a line style and a line colour for each line of the border. # Bit Mask Contents # 2-0 00000007H Top line style # 7-3 000000F8H Colour index for top line colour # 10-8 00000700H Left line style # 15-11 0000F800H Colour index for left line colour # 18-16 00070000H Bottom line style # 23-19 00F80000H Colour index for bottom line colour # 26-24 07000000H Right line style # 31-27 F8000000H Colour index for right line colour # # XF_AREA_34 Cell Background Area Style (2 Bytes), BIFF3-BIFF4 A cell # background area style contains an area pattern and a foreground and # background colour. # Bit Mask Contents # 5-0 003FH Fill pattern # 10-6 07C0H Colour index for pattern colour # 15-11 F800H Colour index for pattern background # --------------------------------------------------------------------------------------------- # Record XF, BIFF8: # Offset Size Contents # 0 2 Index to FONT record # 2 2 Index to FORMAT record # 4 2 Bit Mask Contents # 2-0 0007H XF_TYPE_PROT . XF type, cell protection (see above) # 15-4 FFF0H Index to parent style XF (always FFFH in style XFs) # 6 1 Bit Mask Contents # 2-0 07H XF_HOR_ALIGN . Horizontal alignment (see above) # 3 08H 1 = Text is wrapped at right border # 6-4 70H XF_VERT_ALIGN . Vertical alignment (see above) # 7 1 XF_ROTATION: Text rotation angle (see above) # 8 1 Bit Mask Contents # 3-0 0FH Indent level # 4 10H 1 = Shrink content to fit into cell # 5 merge # 7-6 C0H Text direction (BIFF8X only) # 00b = According to context # 01b = Left-to-right # 10b = Right-to-left # 9 1 Bit Mask Contents # 7-2 FCH XF_USED_ATTRIB . Used attributes (see above) # 10 4 Cell border lines and background area: # Bit Mask Contents # 3-0 0000000FH Left line style # 7-4 000000F0H Right line style # 11-8 00000F00H Top line style # 15-12 0000F000H Bottom line style # 22-16 007F0000H Colour index for left line colour # 29-23 3F800000H Colour index for right line colour # 30 40000000H 1 = Diagonal line from top left to right bottom # 31 80000000H 1 = Diagonal line from bottom left to right top # 14 4 Bit Mask Contents # 6-0 0000007FH Colour index for top line colour # 13-7 00003F80H Colour index for bottom line colour # 20-14 001FC000H Colour index for diagonal line colour # 24-21 01E00000H Diagonal line style # 31-26 FC000000H Fill pattern # 18 2 Bit Mask Contents # 6-0 007FH Colour index for pattern colour # 13-7 3F80H Colour index for pattern background class XFRecord < BiffRecord RECORD_ID = 0x00E0 def initialize(xf, xf_type = 'cell') font_xf_idx, fmt_str_xf_idx, alignment, borders, pattern, protection = xf fnt = [font_xf_idx].pack('v') fmt = [fmt_str_xf_idx].pack('v') if xf_type === 'cell' protection = ((protection.cell_locked & 0x01) << 0) | ((protection.formula_hidden & 0x01) << 1) prt = [protection].pack('v') else prt = [0xFFF5].pack('v') end aln_value = ((alignment.horz & 0x07) << 0) | ((alignment.wrap & 0x01) << 3) | ((alignment.vert & 0x07) << 4) aln = [aln_value].pack('C') rot = [alignment.rota].pack('C') txt_value = ((alignment.inde & 0x0F) << 0) | ((alignment.shri & 0x01) << 4) | ((alignment.merg & 0x01) << 5) | ((alignment.dire & 0x03) << 6) txt = [txt_value].pack('C') used_attr = (xf_type === 'cell') ? [0xF8].pack('C') : [0xF4].pack('C') borders.left_colour = 0x00 if borders.left == Borders::NO_LINE borders.right_colour = 0x00 if borders.right == Borders::NO_LINE borders.top_colour = 0x00 if borders.top == Borders::NO_LINE borders.bottom_colour = 0x00 if borders.bottom == Borders::NO_LINE borders.diag_colour = 0x00 if borders.diag == Borders::NO_LINE brd1_value = ((borders.left & 0x0F) << 0 ) | ((borders.right & 0x0F) << 4 ) | ((borders.top & 0x0F) << 8 ) | ((borders.bottom & 0x0F) << 12) | ((borders.left_colour & 0x7F) << 16) | ((borders.right_colour & 0x7F) << 23) | ((borders.need_diag1 & 0x01) << 30) | ((borders.need_diag2 & 0x01) << 31) brd1 = [brd1_value].pack('V') brd2_value = ((borders.top_colour & 0x7F) << 0 ) | ((borders.bottom_colour & 0x7F) << 7 ) | ((borders.diag_colour & 0x7F) << 14) | ((borders.diag & 0x0F) << 21) | ((pattern.pattern & 0x3F) << 26) brd2 = [brd2_value].pack('V') pat_value = ((pattern.pattern_fore_colour & 0x7F) << 0 ) | ((pattern.pattern_back_colour & 0x7F) << 7 ) pat = [pat_value].pack('v') @record_data = fnt + fmt + prt + aln + rot + txt + used_attr + brd1 + brd2 + pat end end # STYLE record for user-defined cell styles, BIFF3-BIFF8: # Offset Size Contents # 0 2 Bit Mask Contents # 11-0 0FFFH Index to style XF record # 15 8000H Always 0 for user-defined styles # 2 var. BIFF2-BIFF7: Non-empty byte string, 8-bit string length # BIFF8: Non-empty Unicode string, 16-bit string length # STYLE record for built-in cell styles, BIFF3-BIFF8: # Offset Size Contents # 0 2 Bit Mask Contents # 11-0 0FFFH Index to style XF record # 15 8000H Always 1 for built-in styles # 2 1 Identifier of the built-in cell style: # 00H = Normal # 01H = RowLevel_lv (see next field) # 02H = ColLevel_lv (see next field) # 03H = Comma # 04H = Currency # 05H = Percent # 06H = Comma [0] (BIFF4-BIFF8) # 07H = Currency [0] (BIFF4-BIFF8) # 08H = Hyperlink (BIFF8) # 09H = Followed Hyperlink (BIFF8) # 3 1 Level for RowLevel or ColLevel style # (zero-based, lv), FFH otherwise # The RowLevel and ColLevel styles specify the formatting of subtotal # cells in a specific outline level. The level is specified by the last # field in the STYLE record. Valid values are 0-6 for the outline levels # 1-7. class StyleRecord < BiffRecord RECORD_ID = 0x0293 def initialize @record_data = [0x8000, 0x00, 0xFF].pack('vCC') # TODO: implement user-defined styles??? end end # This record contains the definition of all user-defined colours # available for cell and object formatting. # # Record PALETTE, BIFF3-BIFF8: # # Offset Size Contents # 0 2 Number of following colours (nm). Contains 16 in BIFF3-BIFF4 and 56 in BIFF5-BIFF8. # 2 4*nm List of nm RGB colours # # The following table shows how colour indexes are used in other records: # # Colour index Resulting colour or internal list index # 00H Built-in Black (R = 00H, G = 00H, B = 00H) # 01H Built-in White (R = FFH, G = FFH, B = FFH) # 02H Built-in Red (R = FFH, G = 00H, B = 00H) # 03H Built-in Green (R = 00H, G = FFH, B = 00H) # 04H Built-in Blue (R = 00H, G = 00H, B = FFH) # 05H Built-in Yellow (R = FFH, G = FFH, B = 00H) # 06H Built-in Magenta (R = FFH, G = 00H, B = FFH) # 07H Built-in Cyan (R = 00H, G = FFH, B = FFH) # 08H First user-defined colour from the PALETTE record (entry 0 from record colour list) # ......................... # # 17H (BIFF3-BIFF4) Last user-defined colour from the PALETTE record (entry 15 or 55 from record colour list) # 3FH (BIFF5-BIFF8) # # 18H (BIFF3-BIFF4) System window text colour for border lines (used in records XF, CF, and # 40H (BIFF5-BIFF8) WINDOW2 (BIFF8 only)) # # 19H (BIFF3-BIFF4) System window background colour for pattern background (used in records XF, and CF) # 41H (BIFF5-BIFF8) # # 43H System face colour (dialogue background colour) # 4DH System window text colour for chart border lines # 4EH System window background colour for chart areas # 4FH Automatic colour for chart border lines (seems to be always Black) # 50H System ToolTip background colour (used in note objects) # 51H System ToolTip text colour (used in note objects) # 7FFFH System window text colour for fonts (used in records FONT, EFONT, and CF) class PaletteRecord < BiffRecord RECORD_ID = 0x0092 end # This record is located in the workbook globals area and represents # a sheet inside of the workbook. For each sheet a BOUNDSHEET record # is written. It stores the sheet name and a stream offset to the BOF # record within the workbook stream. The record is also known # as BUNDLESHEET. # # Record BOUNDSHEET, BIFF5-BIFF8: # Offset Size Contents # 0 4 Absolute stream position of the BOF record of the sheet represented by this record. This # field is never encrypted in protected files. # 4 1 Visibility: # 00H = Visible # 01H = Hidden # 02H = Strong hidden # 5 1 Sheet type: # 00H = Worksheet # 02H = Chart # 06H = Visual Basic module # 6 var. Sheet name: # BIFF5/BIFF7: Byte string, 8-bit string length # BIFF8: Unicode string, 8-bit string length class BoundSheetRecord < BiffRecord RECORD_ID = 0x0085 def initialize(stream_pos, visibility, name) @record_data = [stream_pos, visibility, 0x00].pack('VCC') + [name.length, 0].pack('CC') + name end end # Whenever the content of a record exceeds the given limits (see table), # the record must be split. Several CONTINUE records containing the # additional data are added after the parent record. # # BIFF version Maximum data size of a record # BIFF2-BIFF7 2080 bytes (2084 bytes including record header) # BIFF8 8224 bytes (8228 bytes including record header) (0x2020) # # Record CONTINUE, BIFF2-BIFF8: # Offset Size Contents # 0 var. Data continuation of the previous record # # Unicode strings are split in a special way. At the beginning of each # CONTINUE record the option flags byte is repeated. Only the character # size flag will be set in this flags byte, the Rich-Text flag and the # Far-East flag are set to zero. In each CONTINUE record it is possible # that the character size changes from 8-bit characters to 16-bit # characters and vice versa. # # Never a Unicode string is split until and including the first # character. That means, all header fields (string length, option flags, # optional Rich-Text size, and optional Far-East data size) and the first # character of the string have to occur together in the leading record, # or have to be moved completely into the CONTINUE record. Formatting # runs cannot be split between their components (character index and FONT # record index). If a string is split between two formatting runs, the # option flags field will not be repeated in the CONTINUE record. class ContinueRecord < BiffRecord RECORD_ID = 0x003C end # This record contains a list of all strings used anywhere in the # workbook. Each string occurs only once. The workbook uses indexes into # the list to reference the strings. # # Record SST, BIFF8: # Offset Size Contents # 0 4 Total number of strings in the workbook (see below) # 4 4 Number of following strings (nm) # 8 var. List of nm Unicode strings, 16-bit string length # # The first field of the SST record counts the total occurrence # of strings in the workbook. For instance, the string AAA is used # 3 times and the string BBB is used 2 times. The first field contains # 5 and the second field contains 2, followed by the two strings. class SSTRecord < BiffRecord RECORD_ID = 0x00FC end # This record occurs in conjunction with the SST record. It is used # by Excel to create a hash table with stream offsets to the SST record # to optimise string search operations. Excel may not shorten this record # if strings are deleted from the shared string table, so the last part # might contain invalid data. The stream indexes in this record divide # the SST into portions containing a constant number of strings. # # Record EXTSST, BIFF8: # # Offset Size Contents # 0 2 Number of strings in a portion, this number is >=8 # 2 var. List of OFFSET structures for all portions. Each OFFSET contains the following data: # Offset Size Contents # 0 4 Absolute stream position of first string of the portion # 4 2 Position of first string of the portion inside of current record, # including record header. This counter restarts at zero, if the SST # record is continued with a CONTINUE record. # 6 2 Not used class ExtSSTRecord < BiffRecord RECORD_ID = 0x00FF def initialize(sst_stream_pos, str_placement, portions_len) extsst = {} abs_stream_pos = sst_stream_pos str_counter = 0 portion_counter = 0 while (str_counter < str_placement.length) do str_chunk_num, pos_in_chunk = str_placement[str_counter] if str_chunk_num != portion_counter portion_counter = str_chunk_num abs_stream_pos += portions_len[portion_counter-1] end str_stream_pos = abs_stream_pos + pos_in_chunk + 4 # header extsst[str_counter] = [pos_in_chunk, str_stream_pos] str_counter += 1 end exsst_str_count_delta = [8, str_placement.length*8/0x2000].max @record_data = [exsst_str_count_delta].pack('v') str_counter = 0 while (str_counter < str_placement.length) do @record_data += [extsst[str_counter][1], extsst[str_counter][0], 0].pack('Vvv') str_counter += exsst_str_count_delta end end end # Record DIMENSIONS, BIFF8: # # Offset Size Contents # 0 4 Index to first used row # 4 4 Index to last used row, increased by 1 # 8 2 Index to first used column # 10 2 Index to last used column, increased by 1 # 12 2 Not used class DimensionsRecord < BiffRecord RECORD_ID = 0x0200 def initialize(first_used_row, last_used_row, first_used_col, last_used_col) @record_data = [first_used_row, last_used_row + 1, first_used_col, last_used_col + 1, 0x00].pack('V2v3') end end # Record WINDOW2, BIFF8: # # Offset Size Contents # 0 2 Option flags (see below) # 2 2 Index to first visible row # 4 2 Index to first visible column # 6 2 Colour index of grid line colour. Note that in BIFF2-BIFF7 an RGB colour is # written instead. # 8 2 Not used # 10 2 Cached magnification factor in page break preview (in percent); 0 = Default (60%) # 12 2 Cached magnification factor in normal view (in percent); 0 = Default (100%) # 14 4 Not used # # In BIFF8 this record stores used magnification factors for page break # preview and normal view. These values are used to restore the # magnification, when the view is changed. The real magnification of the # currently active view is stored in the SCL record. The type of the # active view is stored in the option flags field (see below). # # 0 0001H 0 = Show formula results 1 = Show formulas # 1 0002H 0 = Do not show grid lines 1 = Show grid lines # 2 0004H 0 = Do not show sheet headers 1 = Show sheet headers # 3 0008H 0 = Panes are not frozen 1 = Panes are frozen (freeze) # 4 0010H 0 = Show zero values as empty cells 1 = Show zero values # 5 0020H 0 = Manual grid line colour 1 = Automatic grid line colour # 6 0040H 0 = Columns from left to right 1 = Columns from right to left # 7 0080H 0 = Do not show outline symbols 1 = Show outline symbols # 8 0100H 0 = Keep splits if pane freeze is removed 1 = Remove splits if pane freeze is removed # 9 0200H 0 = Sheet not selected 1 = Sheet selected (BIFF5-BIFF8) # 10 0400H 0 = Sheet not visible 1 = Sheet visible (BIFF5-BIFF8) # 11 0800H 0 = Show in normal view 1 = Show in page break preview (BIFF8) # # The freeze flag specifies, if a following PANE record describes unfrozen or frozen panes. # # *** This class appends the optional SCL record *** # # Record SCL, BIFF4-BIFF8: # # This record stores the magnification of the active view of the current worksheet. # In BIFF8 this can be either the normal view or the page break preview. # This is determined in the WINDOW2 record. The SCL record is part of the # Sheet View Settings Block. # # Offset Size Contents # 0 2 Numerator of the view magnification fraction (num) # 2 2 Denumerator [denominator] of the view magnification fraction (den) # The magnification is stored as reduced fraction. The magnification results from num/den. # # SJM note: Excel expresses (e.g.) 25% in reduced form i.e. 1/4. Reason unknown. This code # writes 25/100, and Excel is happy with that. class Window2Record < BiffRecord RECORD_ID = 0x023E def initialize(options, first_visible_row, first_visible_col, grid_colour, preview_magn, normal_magn, scl_magn) scl_rec = '' scl_rec = [0x00A0, 4, scl_magn, 100].pack('v4') unless scl_magn == 0 args = [options, first_visible_row, first_visible_col, grid_colour, 0x00, preview_magn, normal_magn, 0x00] @record_data = args.pack('v7V') + scl_rec end end # This record stores the position of window panes. It is part of the Sheet # View Settings Block. If the sheet does not contain any splits, this # record will not occur. # A sheet can be split in two different ways, with unfrozen panes or with # frozen panes. A flag in the WINDOW2 record specifies, if the panes are # frozen, which affects the contents of this record. # # Record PANE, BIFF2-BIFF8: # Offset Size Contents # 0 2 Position of the vertical split # (px, 0 = No vertical split): # Unfrozen pane: Width of the left pane(s) # (in twips = 1/20 of a point) # Frozen pane: Number of visible # columns in left pane(s) # 2 2 Position of the horizontal split # (py, 0 = No horizontal split): # Unfrozen pane: Height of the top pane(s) # (in twips = 1/20 of a point) # Frozen pane: Number of visible # rows in top pane(s) # 4 2 Index to first visible row # in bottom pane(s) # 6 2 Index to first visible column # in right pane(s) # 8 1 Identifier of pane with active # cell cursor # [9] 1 Not used (BIFF5-BIFF8 only, not written # in BIFF2-BIFF4) # # If the panes are frozen, pane†0 is always active, regardless # of the cursor position. The correct identifiers for all possible # combinations of visible panes are shown in the following pictures. # # px = 0, py = 0 px = 0, py > 0 # -------------------------- ------------|------------- # | | | | # | | | 3 | # | | | | # - 3 - -------------------------- # | | | | # | | | 2 | # | | | | # -------------------------- ------------|------------- # # px > 0, py = 0 px > 0, py > 0 # ------------|------------- ------------|------------- # | | | | | | # | | | | 3 | 2 | # | | | | | | # - 3 | 1 - -------------------------- # | | | | | | # | | | | 1 | 0 | # | | | | | | # ------------|------------- ------------|------------- class PanesRecord < BiffRecord RECORD_ID = 0x0041 def initialize(px, py, first_row_bottom, first_col_right, active_pane) @record_data = [px, py, first_row_bottom, first_col_right, active_pane].pack('v5') end end # This record contains the properties of a single row in a sheet. Rows # and cells in a sheet are divided into blocks of 32 rows. # # Record ROW, BIFF3-BIFF8: # # Offset Size Contents # 0 2 Index of this row # 2 2 Index to column of the first cell which is described by a cell record # 4 2 Index to column of the last cell which is described by a cell record, # increased by 1 # 6 2 Bit Mask Contents # 14-0 7FFFH Height of the row, in twips = 1/20 of a point # 15 8000H 0 = Row has custom height; 1 = Row has default height # 8 2 Not used # 10 2 In BIFF3-BIFF4 this field contains a relative offset # to calculate stream position of the first cell record # for this row. In BIFF5-BIFF8 this field is not used # anymore, but the DBCELL record instead. # 12 4 Option flags and default row formatting: # Bit Mask Contents # 2-0 00000007H Outline level of the row # 4 00000010H 1 = Outline group starts or ends here (depending # on where the outline buttons are located, # see WSBOOL record), and is collapsed # 5 00000020H 1 = Row is hidden (manually, or by a filter or outline group) # 6 00000040H 1 = Row height and default font height do not match # 7 00000080H 1 = Row has explicit default format (fl) # 8 00000100H Always 1 # 27-16 0FFF0000H If fl=1: Index to default XF record # 28 10000000H 1 = Additional space above the row. This flag is set, # if the upper border of at least one cell in this row # or if the lower border of at least one cell in the row # above is formatted with a thick line style. # Thin and medium line styles are not taken into account. # 29 20000000H 1 = Additional space below the row. This flag is set, # if the lower border of at least one cell in this row # or if the upper border of at least one cell in the row # below is formatted with a medium or thick line style. # Thin line styles are not taken into account. class RowRecord < BiffRecord RECORD_ID = 0x0208 def initialize(index, first_col, last_col, height_options, options) @record_data = [index, first_col, last_col + 1, height_options, 0x00, 0x00, options].pack('v6V') end end # This record represents a cell that contains a string. It replaces the # LABEL record and RSTRING record used in BIFF2-BIFF7. class LabelSSTRecord < BiffRecord RECORD_ID = 0x00FD def initialize(row, col, xf_idx, sst_idx) @record_data = [row, col, xf_idx, sst_idx].pack('v3V') end end # This record contains all merged cell ranges of the current sheet. # # Record MERGEDCELLS, BIFF8: # # Offset Size Contents # 0 var. Cell range address list with all merged ranges # # ------------------------------------------------------------------ # # A cell range address list consists of a field with the number of ranges # and the list of the range addresses. # # Cell range address list, BIFF8: # # Offset Size Contents # 0 2 Number of following cell range addresses (nm) # 2 8*nm List of nm cell range addresses # # --------------------------------------------------------------------- # Cell range address, BIFF8: # # Offset Size Contents # 0 2 Index to first row # 2 2 Index to last row # 4 2 Index to first column # 6 2 Index to last column class MergedCellsRecord < BiffRecord RECORD_ID = 0x00E5 def initialize(merged_list) @record_data = '' i = merged_list.length - 1 while i >= 0 do j = 0 merged = '' while (i >= 0) && (j < 0x403) do r1, r2, c1, c2 = merged_list[i] merged += [r1, r2, c1, c2].pack('v4') i -= 1 j += 1 end @record_data += [RECORD_ID, merged.length + 2, j].pack('v3') + merged end end # for some reason Excel doesn't use CONTINUE def to_biff @record_data end end # This record represents a cell range of empty cells. All cells are # located in the same row. # # Record MULBLANK, BIFF5-BIFF8: # # Offset Size Contents # 0 2 Index to row # 2 2 Index to first column (fc) # 4 2*nc List of nc=lc-fc+1 16-bit indexes to XF records # 4+2*nc 2 Index to last column (lc) class MulBlankRecord < BiffRecord RECORD_ID = 0x00BE def initialize(row, first_col, last_col, xf_index) blanks_count = last_col-first_col+1 blanks = ([xf_index]*blanks_count).pack('v*') @record_data = [row, first_col].pack('v2') + blanks + [last_col].pack('v') end end # This record represents an empty cell. # # Record BLANK, BIFF5-BIFF8: # # Offset Size Contents # 0 2 Index to row # 2 2 Index to first column (fc) # 4 2 indexes to XF record class BlankRecord < BiffRecord RECORD_ID = 0x0201 def initialize(row, col, xf_index) @record_data = [row, col, xf_index].pack('v3') end end # This record represents a cell that contains an RK value (encoded integer or # floating-point value). If a floating-point value cannot be encoded to an RK value, # a NUMBER record will be written. class RKRecord < BiffRecord RECORD_ID = 0x027E def initialize(row, col, xf_index, rk_encoded) @record_data = [row, col, xf_index, rk_encoded].pack('v3V') end end # This record represents a cell that contains an IEEE-754 floating-point value. class NumberRecord < BiffRecord RECORD_ID = 0x0203 def initialize(row, col, xf_index, number) @record_data = [row, col, xf_index, number].pack('v3E') end end # This record represents a cell that contains a boolean or error value. '<3HBB' class BoolErrRecord < BiffRecord RECORD_ID = 0x0205 def initialize(row, col, xf_index, number, is_error) @record_data = [row, col, xf_index, number, is_error].pack('v3C2') end end # Offset Size Contents # 0 2 Index to row # 2 2 Index to column # 4 2 Index to XF record # 6 8 Result of the formula # 14 2 Option flags: # Bit Mask Contents # 0 0001H 1 = Recalculate always # 1 0002H 1 = Calculate on open # 3 0008H 1 = Part of a shared formula # 16 4 Not used # 20 var. Formula data (RPN token array) class FormulaRecord < BiffRecord RECORD_ID = 0x0006 def initialize(row, col, xf_index, rpn, calc_flags = 0) @record_data = [row, col, xf_index, 0xFFFF000000000003, calc_flags & 0x03, 0].pack('v3wvV') + rpn end end # This record contains information about the layout of outline symbols. # # Record GUTS, BIFF3-BIFF8: # # Offset Size Contents # 0 2 Width of the area to display row outlines (left of the sheet), in pixel # 2 2 Height of the area to display column outlines (above the sheet), in pixel # 4 2 Number of visible row outline levels (used row levels + 1; or 0, if not used) # 6 2 Number of visible column outline levels (used column levels + 1; or 0, if not used) class GutsRecord < BiffRecord RECORD_ID = 0x0080 def initialize(row_gut_width, col_gut_height, row_visible_levels, col_visible_levels) @record_data = [row_gut_width, col_gut_height, row_visible_levels, col_visible_levels].pack('v4') end end # This record stores a 16 bit value with Boolean options for the current # sheet. From BIFF5 on the "Save external linked values" option is moved # to the record BOOKBOOL. # # Option flags of record WSBOOL, BIFF3-BIFF8: # # Bit Mask Contents # 0 0001H 0 = Do not show automatic page breaks # 1 = Show automatic page breaks # 4 0010H 0 = Standard sheet # 1 = Dialogue sheet (BIFF5-BIFF8) # 5 0020H 0 = No automatic styles in outlines # 1 = Apply automatic styles to outlines # 6 0040H 0 = Outline buttons above outline group # 1 = Outline buttons below outline group # 7 0080H 0 = Outline buttons left of outline group # 1 = Outline buttons right of outline group # 8 0100H 0 = Scale printout in percent # 1 = Fit printout to number of pages # 9 0200H 0 = Save external linked values (BIFF3?BIFF4 only) # 1 = Do not save external linked values (BIFF3?BIFF4 only) # 10 0400H 0 = Do not show row outline symbols # 1 = Show row outline symbols # 11 0800H 0 = Do not show column outline symbols # 1 = Show column outline symbols # 13-12 3000H These flags specify the arrangement of windows. # They are stored in BIFF4 only. # 00 = Arrange windows tiled # 01 = Arrange windows horizontal # 10 = Arrange windows vertical112 = Arrange windows cascaded # The following flags are valid for BIFF4-BIFF8 only: # 14 4000H 0 = Standard expression evaluation # 1 = Alternative expression evaluation # 15 8000H 0 = Standard formula entries # 1 = Alternative formula entries class WSBoolRecord < BiffRecord RECORD_ID = 0x0081 def initialize(options) @record_data = [options].pack('v') end end # This record specifies the width for a given range of columns. # If a column does not have a corresponding COLINFO record, # the width specified in the record STANDARDWIDTH is used. If # this record is also not present, the contents of the record # DEFCOLWIDTH is used instead. # This record also specifies a default XF record to use for # cells in the columns that are not described by any cell record # (which contain the XF index for that cell). Additionally, # the option flags field contains hidden, outline, and collapsed # options applied at the columns. # # Record COLINFO, BIFF3-BIFF8: # # Offset Size Contents # 0 2 Index to first column in the range # 2 2 Index to last column in the range # 4 2 Width of the columns in 1/256 of the width of the zero character, using default font # (first FONT record in the file) # 6 2 Index to XF record for default column formatting # 8 2 Option flags: # Bits Mask Contents # 0 0001H 1 = Columns are hidden # 10-8 0700H Outline level of the columns (0 = no outline) # 12 1000H 1 = Columns are collapsed # 10 2 Not used class ColInfoRecord < BiffRecord RECORD_ID = 0x007D def initialize(first_col, last_col, width, xf_index, options) @record_data = [first_col, last_col, width, xf_index, options, 0].pack('v6') end end # This record is part of the Calculation Settings Block. # It specifies whether to calculate formulas manually, # automatically or automatically except for multiple table operations. # # Record CALCMODE, BIFF2-BIFF8: # # Offset Size Contents # 0 2 FFFFH = automatic except for multiple table operations # 0000H = manually # 0001H = automatically (default) class CalcModeRecord < BiffRecord RECORD_ID = 0x000D def initialize(calc_mode) @record_data = [calc_mode].pack('v') end end # This record is part of the Calculation Settings Block. It specifies the maximum # number of times the formulas should be iteratively calculated. This is a fail-safe # against mutually recursive formulas locking up a spreadsheet application. # # Record CALCCOUNT, BIFF2-BIFF8: # # Offset Size Contents # 0 2 Maximum number of iterations allowed in circular references class CalcCountRecord < BiffRecord RECORD_ID = 0x000C def initialize(calc_count) @record_data = [calc_count].pack('v') end end # This record is part of the Calculation Settings Block. # It stores which method is used to show cell addresses in formulas. # The ìRCî mode uses numeric indexes for rows and columns, # i.e. ìR(1)C(-1)î, or ìR1C1:R2C2î. # The ìA1î mode uses characters for columns and numbers for rows, # i.e. ìB1î, or ì$A$1:$B$2î. # # Record REFMODE, BIFF2-BIFF8: # # Offset Size Contents # 0 2 0 = RC mode; 1 = A1 mode class RefModeRecord < BiffRecord RECORD_ID = 0x00F def initialize(ref_mode) @record_data = [ref_mode].pack('v') end end # This record is part of the Calculation Settings Block. # It stores if iterations are allowed while calculating recursive formulas. # # Record ITERATION, BIFF2-BIFF8: # # Offset Size Contents # 0 2 0 = Iterations off; 1 = Iterations on class IterationRecord < BiffRecord RECORD_ID = 0x011 def initialize(iterations_on) @record_data = [iterations_on].pack('v') end end # This record is part of the Calculation Settings Block. # It stores the maximum change of the result to exit an iteration. # # Record DELTA, BIFF2-BIFF8: # # Offset Size Contents # 0 8 Maximum change in iteration # (IEEE 754 floating-point value, # 64bit double precision) class DeltaRecord < BiffRecord RECORD_ID = 0x010 def initialize(delta) @record_data = [delta].pack('E') end end # This record is part of the Calculation Settings Block. # It contains the ìRecalculate before saveî option in # Excel's calculation settings dialogue. # # Record SAVERECALC, BIFF3-BIFF8: # # Offset Size Contents # 0 2 0 = Do not recalculate; # 1 = Recalculate before saving the document class SaveRecalcRecord < BiffRecord RECORD_ID = 0x05F def initialize(recalc) @record_data = [recalc].pack('v') end end # This record stores if the row and column headers # (the areas with row numbers and column letters) will be printed. # # Record PRINTHEADERS, BIFF2-BIFF8: # # Offset Size Contents # 0 2 0 = Do not print row/column headers; # 1 = Print row/column headers class PrintHeadersRecord < BiffRecord RECORD_ID = 0x02A def initialize(print_headers) @record_data = [print_headers].pack('v') end end # This record stores if sheet grid lines will be printed. # # Record PRINTGRIDLINES, BIFF2-BIFF8: # # Offset Size Contents # 0 2 0 = Do not print sheet grid lines; # 1 = Print sheet grid lines class PrintGridLinesRecord < BiffRecord RECORD_ID = 0x02B def initialize(print_grid) @record_data = [print_grid].pack('v') end end # This record specifies if the option to print sheet grid lines # (record PRINTGRIDLINES) has ever been changed. # # Record GRIDSET, BIFF3-BIFF8: # # Offset Size Contents # 0 2 0 = Print grid lines option never changed # 1 = Print grid lines option changed class GridSetRecord < BiffRecord RECORD_ID = 0x082 def initialize(print_grid_changed) @record_data = [print_grid_changed].pack('v') end end # This record specifies the default height and default flags # for rows that do not have a corresponding ROW record. # # Record DEFAULTROWHEIGHT, BIFF3-BIFF8: # # Offset Size Contents # 0 2 Option flags: # Bit Mask Contents # 0 0001H 1 = Row height and default font height do not match # 1 0002H 1 = Row is hidden # 2 0004H 1 = Additional space above the row # 3 0008H 1 = Additional space below the row # 2 2 Default height for unused rows, in twips = 1/20 of a point class DefaultRowHeight < BiffRecord RECORD_ID = 0x0225 def initialize(options, def_height) @record_data = [options, def_height].pack('v2') end end # This record specifies the default column width for columns that # do not have a specific width set using the record COLINFO or COLWIDTH. # This record has no effect, if a STANDARDWIDTH record is present in the file. # # Record DEFCOLWIDTH, BIFF2-BIFF8: # # Offset Size Contents # 0 2 Column width in characters, using the width of the zero # character from default font (first FONT record in the file) class DefColWidthRecord < BiffRecord RECORD_ID = 0x0055 def initialize(def_width) @record_data = [def_width].pack('v') end end # This record is part of the Page Settings Block. It contains all # horizontal manual page breaks. # # Record HORIZONTALPAGEBREAKS, BIFF8: # Offset Size Contents # 0 2 Number of following row index structures (nm) # 2 6nm List of nm row index structures. Each row index # structure contains: # Offset Size Contents # 0 2 Index to first row below the page break # 2 2 Index to first column of this page break # 4 2 Index to last column of this page break # # The row indexes in the lists must be ordered ascending. # If in BIFF8 a row contains several page breaks, they must be ordered # ascending by start column index. class HorizontalPageBreaksRecord < BiffRecord RECORD_ID = 0x001B def initialize(breaks_list) @record_data = [breaks_list.length].pack('v') breaks_list.each do |r, c1, c2| @record_data += [r, c1, c2].pack('v3') end end end # This record is part of the Page Settings Block. It contains all # vertical manual page breaks. # # Record VERTICALPAGEBREAKS, BIFF8: # Offset Size Contents # 0 2 Number of following column index structures (nm) # 2 6nm List of nm column index structures. Each column index # structure contains: # Offset Size Contents # 0 2 Index to first column following the page # break # 2 2 Index to first row of this page break # 4 2 Index to last row of this page break # # The column indexes in the lists must be ordered ascending. # If in BIFF8 a column contains several page breaks, they must be ordered # ascending by start row index. class VerticalPageBreaksRecord < BiffRecord RECORD_ID = 0x001A def initialize(breaks_list) @record_data = [breaks_list.length].pack('v') breaks_list.each do |c, r1, r2| @record_data += [c, r1, r2].pack('v3') end end end # This record is part of the Page Settings Block. It specifies the # page header string for the current worksheet. If this record is not # present or completely empty (record size is 0), the sheet does not # contain a page header. # # Record HEADER for non-empty page header, BIFF2-BIFF8: # Offset Size Contents # 0 var. Page header string # BIFF2-BIFF7: Non-empty byte string, 8bit string # length # BIFF8: Non-empty Unicode string, 16bit string length # The header string may contain special commands, i.e. placeholders for # the page number, current date, or text formatting attributes. These # fields are represented by single letters (exception: font name and # size, see below) with a leading ampersand ("&"). If the ampersand # is part of the regular header text, it will be duplicated ("&&"). The # page header is divided into 3 sections: the left, the centred, and the # right section. Each section is introduced by a special command. All # text and all commands following are part of the selected section. Each # section starts with the text formatting specified in the default font # (first FONT record in the file). Active formatting attributes from # a previous section do not go into the next section. # # The following table shows all available commands: # # Command Contents # && The "&" character itself # &L Start of the left section # &C Start of the centred section # &R Start of the right section # &P Current page number # &N Page count # &D Current date # &T Current time # &A Sheet name (BIFF5-BIFF8) # &F File name without path # &Z File path without file name (BIFF8X) # &G Picture (BIFF8X) # &B Bold on/off (BIFF2-BIFF4) # &I Italic on/off (BIFF2-BIFF4) # &U Underlining on/off # &E Double underlining on/off (BIFF5-BIFF8) # &S Strikeout on/off # &X Superscript on/off (BIFF5-BIFF8) # &Y Subscript on/off (BIFF5-BIFF8) # &"" Set new font # &"," # Set new font with specified style . # The style is in most cases one of # "Regular", "Bold", "Italic", or "Bold Italic". # But this setting is dependent on the used font, # it may differ (localised style names, or "Standard", # "Oblique", ...). (BIFF5-BIFF8) # & Set font height in points ( is a decimal value). # If this command is followed by a plain number to be printed # in the header, it will be separated from the font height # with a space character. class HeaderRecord < BiffRecord RECORD_ID = 0x0014 def initialize(str) @record_data = [str.length, 0].pack('vC') + str end end # Semantic is equal to HEADER record class FooterRecord < BiffRecord RECORD_ID = 0x0015 def initialize(str) @record_data = [str.length, 0].pack('vC') + str end end # This record is part of the Page Settings Block. It specifies if the # sheet is centred horizontally when printed. # # Record HCENTER, BIFF3-BIFF8: # # Offset Size Contents # 0 2 0 = Print sheet left aligned # 1 = Print sheet centred horizontally class HCenterRecord < BiffRecord RECORD_ID = 0x0083 def initialize(is_horz_center) @record_data = [is_horz_center].pack('v') end end # This record is part of the Page Settings Block. It specifies if the # sheet is centred vertically when printed. # # Record VCENTER, BIFF3-BIFF8: # # Offset Size Contents # 0 2 0 = Print sheet aligned at top page border # 1 = Print sheet vertically centred class VCenterRecord < BiffRecord RECORD_ID = 0x0084 def initialize(is_vert_center) @record_data = [is_vert_center].pack('v') end end # This record is part of the Page Settings Block. It contains the left # page margin of the current worksheet. # # Record LEFTMARGIN, BIFF2-BIFF8: # # Offset Size Contents # 0 8 Left page margin in inches # (IEEE 754 floating-point value, 64bit double precision) class LeftMarginRecord < BiffRecord RECORD_ID = 0x0026 def initialize(margin) @record_data = [margin].pack('E') end end # This record is part of the Page Settings Block. It contains the right # page margin of the current worksheet. # # Offset Size Contents # 0 8 Right page margin in inches # (IEEE 754 floating-point value, 64?bit double precision) class RightMarginRecord < BiffRecord RECORD_ID = 0x0027 def initialize(margin) @record_data = [margin].pack('E') end end # This record is part of the Page Settings Block. It contains the top # page margin of the current worksheet. # # Offset Size Contents # 0 8 Top page margin in inches # (IEEE 754 floating-point value, 64?bit double precision) class TopMarginRecord < BiffRecord RECORD_ID = 0x0028 def initialize(margin) @record_data = [margin].pack('E') end end # This record is part of the Page Settings Block. It contains the bottom # page margin of the current worksheet. # # Offset Size Contents # 0 8 Bottom page margin in inches # (IEEE 754 floating-point value, 64?bit double precision) class BottomMarginRecord < BiffRecord RECORD_ID = 0x0029 def initialize(margin) @record_data = [margin].pack('E') end end # This record is part of the Page Settings Block. It stores the page # format settings of the current sheet. The pages may be scaled in # percent or by using an absolute number of pages. This setting is # located in the WSBOOL record. If pages are scaled in percent, # the scaling factor in this record is used, otherwise the "Fit to # pages" values. One of the "Fit to pages" values may be 0. In this case # the sheet is scaled to fit only to the other value. # # Record SETUP, BIFF5-BIFF8: # # Offset Size Contents # 0 2 Paper size (see below) # 2 2 Scaling factor in percent # 4 2 Start page number # 6 2 Fit worksheet width to this number of pages # (0 = use as many as needed) # 8 2 Fit worksheet height to this number of pages # (0 = use as many as needed) # 10 2 Option flags: # Bit Mask Contents # 0 0001H 0 = Print pages in columns # 1 = Print pages in rows # 1 0002H 0 = Landscape # 1 = Portrait # 2 0004H 1 = Paper size, scaling factor, # paper orientation (portrait/landscape), # print resolution and number of copies # are not initialised # 3 0008H 0 = Print coloured # 1 = Print black and white # 4 0010H 0 = Default print quality # 1 = Draft quality # 5 0020H 0 = Do not print cell notes # 1 = Print cell notes # 6 0040H 0 = Paper orientation setting is valid # 1 = Paper orientation setting not # initialised # 7 0080H 0 = Automatic page numbers # 1 = Use start page number # The following flags are valid for BIFF8 only: # 9 0200H 0 = Print notes as displayed # 1 = Print notes at end of sheet # 11-10 0C00H 00 = Print errors as displayed # 01 = Do not print errors # 10 = Print errors as "--" # 11 = Print errors as "#N/A!" # 12 2 Print resolution in dpi # 14 2 Vertical print resolution in dpi # 16 8 Header margin (IEEE 754 floating-point value, # 64bit double precision) # 24 8 Footer margin (IEEE 754 floating-point value, # 64bit double precision) # 32 2 Number of copies to print # # # PAPER TYPES: # # Index Paper type Paper size # 0 Undefined # 1 Letter 8 1/2" x 11" # 2 Letter small 8 1/2" x 11" # 3 Tabloid 11" x 17" # 4 Ledger 17" x 11" # 5 Legal 8 1/2" x 14" # 6 Statement 5 1/2" x 8 1/2" # 7 Executive 7 1/4" x 10 1/2" # 8 A3 297mm x 420mm # 9 A4 210mm x 297mm # 10 A4 small 210mm x 297mm # 11 A5 148mm x 210mm # 12 B4 (JIS) 257mm x 364mm # 13 B5 (JIS) 182mm x 257mm # 14 Folio 8 1/2" x 13" # 15 Quarto 215mm x 275mm # 16 10x14 10" x 14" # 17 11x17 11" x 17" # 18 Note 8 1/2" x 11" # 19 Envelope #9 3 7/8" x 8 7/8" # 20 Envelope #10 4 1/8" x 9 1/2" # 21 Envelope #11 4 1/2" x 10 3/8" # 22 Envelope #12 4 3/4" x 11" # 23 Envelope #14 5" x 11 1/2" # 24 C 17" x 22" # 25 D 22" x 34" # 26 E 34" x 44" # 27 Envelope DL 110mm x 220mm # 28 Envelope C5 162mm x 229mm # 29 Envelope C3 324mm x 458mm # 30 Envelope C4 229mm x 324mm # 31 Envelope C6 114mm x 162mm # 32 Envelope C6/C5 114mm x 229mm # 33 B4 (ISO) 250mm x 353mm # 34 B5 (ISO) 176mm x 250mm # 35 B6 (ISO) 125mm x 176mm # 36 Envelope Italy 110mm x 230mm # 37 Envelope Monarch 3 7/8" x 7 1/2" # 38 63/4 Envelope 3 5/8" x 6 1/2" # 39 US Standard Fanfold 14 7/8" x 11" # 40 German Std. Fanfold 8 1/2" x 12" # 41 German Legal Fanfold 8 1/2" x 13" # 42 B4 (ISO) 250mm x 353mm # 43 Japanese Postcard 100mm x 148mm # 44 9x11 9" x 11" # 45 10x11 10" x 11" # 46 15x11 15" x 11" # 47 Envelope Invite 220mm x 220mm # 48 Undefined # 49 Undefined # 50 Letter Extra 9 1/2" x 12" # 51 Legal Extra 9 1/2" x 15" # 52 Tabloid Extra 11 11/16" x 18" # 53 A4 Extra 235mm x 322mm # 54 Letter Transverse 8 1/2" x 11" # 55 A4 Transverse 210mm x 297mm # 56 Letter Extra Transv. 9 1/2" x 12" # 57 Super A/A4 227mm x 356mm # 58 Super B/A3 305mm x 487mm # 59 Letter Plus 8 1/2" x 12 11/16" # 60 A4 Plus 210mm x 330mm # 61 A5 Transverse 148mm x 210mm # 62 B5 (JIS) Transverse 182mm x 257mm # 63 A3 Extra 322mm x 445mm # 64 A5 Extra 174mm x 235mm # 65 B5 (ISO) Extra 201mm x 276mm # 66 A2 420mm x 594mm # 67 A3 Transverse 297mm x 420mm # 68 A3 Extra Transverse 322mm x 445mm # 69 Dbl. Japanese Postcard 200mm x 148mm # 70 A6 105mm x 148mm # 71 # 72 # 73 # 74 # 75 Letter Rotated 11" x 8 1/2" # 76 A3 Rotated 420mm x 297mm # 77 A4 Rotated 297mm x 210mm # 78 A5 Rotated 210mm x 148mm # 79 B4 (JIS) Rotated 364mm x 257mm # 80 B5 (JIS) Rotated 257mm x 182mm # 81 Japanese Postcard Rot. 148mm x 100mm # 82 Dbl. Jap. Postcard Rot. 148mm x 200mm # 83 A6 Rotated 148mm x 105mm # 84 # 85 # 86 # 87 # 88 B6 (JIS) 128mm x 182mm # 89 B6 (JIS) Rotated 182mm x 128mm # 90 12x11 12" x 11" class SetupPageRecord < BiffRecord RECORD_ID = 0x00A1 def initialize(paper, scaling, start_num, fit_width_to, fit_height_to, options, hres, vres, header_margin, footer_margin, num_copies) args = [paper, scaling, start_num, fit_width_to, fit_height_to, options, hres, vres, header_margin, footer_margin, num_copies] @record_data = args.pack('v8E2v') end end # This record is part of a Link Table. It contains the name and the token # array of an internal defined name. Token arrays of defined names # contain tokens with aberrant token classes. # # Record NAME, BIFF5/BIFF7: # Offset Size Contents # 0 2 Option flags, see below # 2 1 Keyboard shortcut (only for command macro names, see below) # 3 1 Length of the name (character count, ln) # 4 2 Size of the formula data (sz) # 6 2 0 = Global name, otherwise index to EXTERNSHEET record (one-based) # 8 2 0 = Global name, otherwise index to sheet (one-based) # 10 1 Length of menu text (character count, lm) # 11 1 Length of description text (character count, ld) # 12 1 Length of help topic text (character count, lh) # 13 1 Length of status bar text (character count, ls) # 14 ln Character array of the name # 14+ln sz Formula data (RPN token array without size field, 4) # 14+ln+sz lm Character array of menu text # var. ld Character array of description text # var. lh Character array of help topic text # var. ls Character array of status bar text # # Record NAME, BIFF8: # Offset Size Contents # 0 2 Option flags, see below # 2 1 Keyboard shortcut (only for command macro names, see below) # 3 1 Length of the name (character count, ln) # 4 2 Size of the formula data (sz) # 6 2 Not used # 8 2 0 = Global name, otherwise index to sheet (one-based) # 10 1 Length of menu text (character count, lm) # 11 1 Length of description text (character count, ld) # 12 1 Length of help topic text (character count, lh) # 13 1 Length of status bar text (character count, ls) # 14 var. Name (Unicode string without length field, 3.4) # var. sz Formula data (RPN token array without size field, 4) # [var.] var. (optional, only if lm > 0) Menu text (Unicode string without length field, 3.4) # [var.] var. (optional, only if ld > 0) Description text (Unicode string without length field, 3.4) # [var.] var. (optional, only if lh > 0) Help topic text (Unicode string without length field, 3.4) # [var.] var. (optional, only if ls > 0) Status bar text (Unicode string without length field, 3.4) class NameRecord < BiffRecord RECORD_ID = 0x0018 def initialize(options, keyboard_shortcut, name, sheet_index, rpn, menu_text='', desc_text='', help_text='', status_text='') if name.is_a?(Integer) unicode_name = [name].pack('c') else unicode_name = name end args = [options, keyboard_shortcut, unicode_name.length, rpn.length] args += [0x0000, sheet_index, 0x00] args += [menu_text.length, desc_text.length, help_text.length, status_text.length] args += [unicode_name, rpn] @record_data = args.pack('vCCv vvC vvvv b*') + menu_text + desc_text + help_text + status_text end end # In BIFF8 the record stores a list with indexes to SUPBOOK # records (list of REF structures, 6.100). See 5.10.3 for # details about external references in BIFF8. # # Record EXTERNSHEET, BIFF8: # Offset Size Contents # 0 2 Number of following REF structures (nm) # 2 6nm List of nm REF structures. Each REF contains the following data: # Offset Size Contents # 0 2 Index to SUPBOOK record # 2 2 Index to first SUPBOOK sheet # 4 2 Index to last SUPBOOK sheet class ExternSheetRecord < BiffRecord RECORD_ID = 0x0017 def initialize(refs = []) # do we always need this ref? or only if there are no refs? # AN: I am assuming only needed if no other refs TODO test refs = [[0,0,0]] if refs.empty? ref_data = refs.collect {|r| r.pack('v3')}.join @record_data = [refs.length].pack('v') + ref_data end end # This record mainly stores the URL of an external document # and a list of sheet names inside this document. Furthermore # it is used to store DDE and OLE object links, or to indicate # an internal 3D reference or an add-in function. See 5.10.3 # for details about external references in BIFF8. class SupBookRecord < BiffRecord RECORD_ID = 0x01AE end # In each file occurs a SUPBOOK that is used for internal 3D # references. It stores the number of sheets of the own document. # # Record SUPBOOK for 3D references, BIFF8: # Offset Size Contents # 0 2 Number of sheets in this document # 2 2 01H 04H (relict of BIFF5/BIFF7, the byte string "<04H>", see 3.9.1) class InternalReferenceSupBookRecord < SupBookRecord def initialize(num_sheets) @record_data = [num_sheets, 0x01, 0x04].pack('vCC') end end