public function handleName($data) { $bv = $this->biff_version; if ($bv < 50) { return; } $this->deriveEncoding(); # <HBBHHH4B list($option_flags, $kb_shortcut, $name_len, $fmla_len, $extsht_index, $sheet_index, $menu_text_len, $description_text_len, $help_topic_text_len, $status_bar_text_len) = array_values(unpack('va/C2b/v3c/C4d', substr($data, 0, 14))); $nobj = new Name(); $nobj->book = $this; ### CIRCUAL ### $name_index = count($this->name_obj_list); $this->name_obj_list[] = $nobj; # Not used anywhere in code. Commenting out. # $nobj->option_flags = $option_flags; $attrs = [['hidden', 1, 0], ['func', 2, 1], ['vbasic', 4, 2], ['macro', 8, 3], ['complex', 0x10, 4], ['builtin', 0x20, 5], ['funcgroup', 0xfc0, 6], ['binary', 0x1000, 12]]; foreach ($attrs as $row) { list($attr, $mask, $nshift) = $row; $nobj->{$attr} = ($option_flags & $mask) >> $nshift; } $macro_flag = $nobj->macro ? 'M' : ' '; if ($bv < 80) { list($internal_name, $pos) = Helper::unpack_string_update_pos($data, 14, $this->encoding, $name_len); } else { list($internal_name, $pos) = Helper::unpack_unicode_update_pos($data, 14, 2, $name_len); } $nobj->extn_sheet_num = $extsht_index; $nobj->excel_sheet_index = $sheet_index; $nobj->scope = Null; # patched up in the names_epilogue() method Helper::debug("NAME[%d]:%s oflags=%d, name_len=%d, fmla_len=%d, extsht_index=%d, sheet_index=%d, name=%s, %d", $name_index, $macro_flag, $option_flags, $name_len, $fmla_len, $extsht_index, $sheet_index, $internal_name, strlen($internal_name)); $name = $internal_name; if ($nobj->builtin) { $name = isset(Defs::$builtin_name_from_code[$name]) ? Defs::$builtin_name_from_code[$name] : "??Unknown??"; Helper::debug(" builtin: %s", $name); } $nobj->name = $name; $nobj->raw_formula = substr($data, $pos); $nobj->basic_formula_len = $fmla_len; $nobj->evaluated = 0; Helper::debug($nobj->dump("--- handle_name: name[{$name_index}] ---", '-------------------')); }
/** * @param Book $bk * @return int * @throws XLSParserException */ function read(Book $bk) { # Unused in code # $r1c1 = 0; $oldpos = $bk->position; $saved_obj_id = Null; $bk->position = $this->position; $XL_SHRFMLA_ETC_ETC = [XL_SHRFMLA, XL_ARRAY, XL_TABLEOP, XL_TABLEOP2, XL_ARRAY2, XL_TABLEOP_B2]; $bv = $this->biff_version; $fmt_info = $this->formatting_info; $do_sst_rich_text = $fmt_info && $bk->rich_text_runlist_map; $rowinfo_sharing_dict = []; /** @var $txos MSTxo[] */ $txos = []; $eof_found = 0; while (true) { list($rc, $data_len, $data) = $bk->readRecordParts(); switch ($rc) { case XL_NUMBER: # <HHHd list($rowx, $colx, $xf_index, $d) = array_values(unpack('v3a/db', substr($data, 0, 14))); $this->put_cell($rowx, $colx, Null, $d, $xf_index); break; case XL_LABELSST: # <HHHi list($rowx, $colx, $xf_index, $sstindex) = array_values(unpack('v3a/ib', $data)); $this->put_cell($rowx, $colx, XL_CELL_TEXT, $bk->sharedstrings[$sstindex], $xf_index); if ($do_sst_rich_text) { if (isset($bk->rich_text_runlist_map[$sstindex])) { $this->rich_text_runlist_map[$rowx][$colx] = $bk->rich_text_runlist_map[$sstindex]; } } break; case XL_LABEL: # <HHH list($rowx, $colx, $xf_index) = array_values(unpack('v3a', substr($data, 0, 6))); if ($bv < BIFF_FIRST_UNICODE) { $strg = Helper::unpack_string($data, 6, $bk->encoding ?: $bk->deriveEncoding(), 2); } else { $strg = Helper::unpack_unicode($data, 6, 2); } $this->put_cell($rowx, $colx, XL_CELL_TEXT, $strg, $xf_index); break; case XL_STRING: # <HHH list($rowx, $colx, $xf_index) = array_values(unpack('v3a', substr($data, 0, 6))); if ($bv < BIFF_FIRST_UNICODE) { list($strg, $pos) = Helper::unpack_string_update_pos($data, 6, $bk->encoding ?: $bk->deriveEncoding(), 2); $nrt = ord($data[$pos]); $pos++; $runlist = []; for ($tmp = 0; $tmp < $nrt; $tmp++) { $runlist[] = array_values(unpack('C2a', substr($data, $pos, 2))); $pos += 2; } assert($pos == strlen($data)); } else { list($strg, $pos) = Helper::unpack_unicode_update_pos($data, 6, 2); list($nrt) = array_values(unpack('v', substr($data, $pos, 2))); $pos += 2; $runlist = []; for ($tmp = 0; $tmp < $nrt; $tmp++) { $runlist[] = array_values(unpack('v2a', substr($data, $pos, 4))); $pos += 4; } assert($pos == strlen($data)); } $this->put_cell($rowx, $colx, XL_CELL_TEXT, $strg, $xf_index); $this->rich_text_runlist_map[$rowx][$colx] = $runlist; break; case XL_RK: # <HHH list($rowx, $colx, $xf_index) = array_values(unpack('v3a', substr($data, 0, 6))); $d = Helper::unpack_RK(substr($data, 6, 4)); $this->put_cell($rowx, $colx, Null, $d, $xf_index); break; case XL_MULRK: # <HH list($mulrk_row, $mulrk_first) = array_values(unpack('v2', substr($data, 0, 4))); # <H list($mulrk_last) = array_values(unpack('v', substr($data, -2))); $pos = 4; for ($colx = $mulrk_first; $colx <= $mulrk_last; $colx++) { # <H list($xf_index) = array_values(unpack('v', substr($data, $pos, 2))); $d = Helper::unpack_RK(substr($data, $pos + 2, 4)); $pos += 6; $this->put_cell($mulrk_row, $colx, Null, $d, $xf_index); } break; case XL_ROW: # Version 0.6.0a3: ROW records are just not worth using (for memory allocation). # Version 0.6.1: now used for formatting info. if (!$fmt_info) { break; } # <H4xH4xi list($rowx, , , , , $bits1, , , , , $bits2) = array_values(unpack('va/c4b/vc/c4d/i', substr($data, 0, 16))); if (!(0 <= $rowx && $rowx < $this->utter_max_rows)) { break; } $r = isset($rowinfo_sharing_dict[$bits1][$bits2]) ? $rowinfo_sharing_dict[$bits1][$bits2] : Null; if ($r == Null) { $r = new Rowinfo(); # Using upkbits() is far too slow on a file # with 30 sheets each with 10K rows :-( # upkbits(r, bits1, ( # ( 0, 0x7FFF, 'height'), # (15, 0x8000, 'has_default_height'), # )) # upkbits(r, bits2, ( # ( 0, 0x00000007, 'outline_level'), # ( 4, 0x00000010, 'outline_group_starts_ends'), # ( 5, 0x00000020, 'hidden'), # ( 6, 0x00000040, 'height_mismatch'), # ( 7, 0x00000080, 'has_default_xf_index'), # (16, 0x0FFF0000, 'xf_index'), # (28, 0x10000000, 'additional_space_above'), # (29, 0x20000000, 'additional_space_below'), # )) # So: $r->height = $bits1 & 0x7fff; $r->has_default_height = $bits1 >> 15 & 1; $r->outline_level = $bits2 & 7; $r->outline_group_starts_ends = $bits2 >> 4 & 1; $r->hidden = $bits2 >> 5 & 1; $r->height_mismatch = $bits2 >> 6 & 1; $r->has_default_xf_index = $bits2 >> 7 & 1; $r->xf_index = $bits2 >> 16 & 0xfff; $r->additional_space_above = $bits2 >> 28 & 1; $r->additional_space_below = $bits2 >> 29 & 1; if (!$r->has_default_xf_index) { $r->xf_index = -1; } $rowinfo_sharing_dict[$bits1][$bits2] = $r; } $this->rowinfo_map[$rowx] = $r; break; case 0x6: # XL_FORMULA_OPCODES: # XL_FORMULA_OPCODES: case 0x406: # XL_FORMULA_OPCODES: # XL_FORMULA_OPCODES: case 0x206: # XL_FORMULA_OPCODES: if ($bv >= 50) { # <HHH8sH list($rowx, $colx, $xf_index, $flags) = array_values(unpack('va/vb/vc/x8/ve', substr($data, 0, 16))); $result_str = substr($data, 6, 8); $lenlen = 2; $tkarr_offset = 20; } elseif ($bv >= 30) { # <HHH8sH list($rowx, $colx, $xf_index, $flags) = array_values(unpack('va/vb/vc/x8/ve', substr($data, 0, 16))); $result_str = substr($data, 6, 8); $lenlen = 2; $tkarr_offset = 16; } else { # BIFF2 # <HH3s8sB list($rowx, $colx, $flags) = array_values(unpack('va/vb/x11/cc', substr($data, 0, 16))); $cell_attr = substr($data, 4, 3); $result_str = substr($data, 7, 8); $xf_index = $this->fixed_BIFF2_xfindex($cell_attr, $rowx, $colx); $lenlen = 1; $tkarr_offset = 16; } if (substr($result_str, 6, 2) == "ÿÿ") { $first_byte = ord($result_str[0]); switch ($first_byte) { case 0: # need to read next record (STRING) $gotstring = 0; # actually there's an optional SHRFMLA or ARRAY etc record to skip over list($rc2, $data2_len, $data2) = $bk->readRecordParts(); switch ($rc2) { case XL_STRING: case XL_STRING_B2: $gotstring = 1; break; case XL_ARRAY: # <HHBBBxxxxxH // list($row1x, $rownx, $col1x, $colnx, $array_flags, $tokslen) = array_values(unpack("v2a/c3b/x5/vc", substr($data2, 0, 14))); break; case XL_SHRFMLA: # <HHBBxBH // list($row1x, $rownx, $col1x, $colnx, $nfmlas, $tokslen) = array_values(unpack("v2a/C2b/x/Cc/vd", substr($data, 0, 10))); break; default: if (!in_array($rc2, $XL_SHRFMLA_ETC_ETC)) { throw new XLSParserException(sprintf("Expected SHRFMLA, ARRAY, TABLEOP* or STRING record; found 0x%04x", $rc2)); } } if (!$gotstring) { list($rc2, $tmp, $data2) = $bk->readRecordParts(); if ($rc2 !== XL_STRING && $rc2 !== XL_STRING_B2) { throw new XLSParserException(sprintf("Expected STRING record; found 0x%04x", $rc2)); } } $strg = $this->string_record_contents($data2); $this->put_cell($rowx, $colx, XL_CELL_TEXT, $strg, $xf_index); break; case 1: # boolean formula result $value = ord($result_str[2]); $this->put_cell($rowx, $colx, XL_CELL_BOOLEAN, $value, $xf_index); break; case 2: # Error in cell $value = ord($result_str[2]); $this->put_cell($rowx, $colx, XL_CELL_ERROR, $value, $xf_index); break; case 3: # empty ... i.e. empty (zero-length) string, NOT an empty cell. $this->put_cell($rowx, $colx, XL_CELL_TEXT, "", $xf_index); break; default: throw new XLSParserException(sprintf("unexpected special case (0x%02x) in FORMULA", $first_byte)); } } else { # <d list($d) = array_values(unpack("d", $result_str)); $this->put_cell($rowx, $colx, Null, $d, $xf_index); } break; case XL_BOOLERR: # <HHHBB list($rowx, $colx, $xf_index, $value, $is_err) = array_values(unpack('v3a/c2', substr($data, 0, 8))); # Note OOo Calc 2.0 writes 9-byte BOOLERR records. # OOo docs say 8. Excel writes 8. $cellty = $is_err ? XL_CELL_ERROR : XL_CELL_BOOLEAN; $this->put_cell($rowx, $colx, $cellty, $value, $xf_index); break; case XL_COLINFO: if (!$fmt_info) { break; } $c = new Colinfo(); # <HHHHH list($first_colx, $last_colx, $c->width, $c->xf_index, $flags) = array_values(unpack('v5a', substr($data, 0, 10))); #### Colinfo.width is denominated in 256ths of a character, #### *not* in characters. if (!(0 <= $first_colx && $first_colx <= $last_colx && $last_colx <= 256)) { # Note: 256 instead of 255 is a common mistake. # We silently ignore the non-existing 257th column in that case. break; } Helper::upkbits($c, $flags, [[0, 0x1, 'hidden'], [1, 0x2, 'bit1_flag'], [8, 0x700, 'outline_level'], [12, 0x1000, 'collapsed']]); for ($colx = $first_colx; $colx <= $last_colx; $colx++) { if ($colx > 255) { break; } # Excel does 0 to 256 inclusive $this->colinfo_map[$colx] = $c; } break; case XL_DEFCOLWIDTH: # <HHHHH list($this->defcolwidth) = array_values(unpack('v', substr($data, 0, 2))); break; case XL_STANDARDWIDTH: if ($data_len == 2) { list($this->standardwidth) = array_values(unpack('v', substr($data, 0, 2))); } break; case XL_GCW: if (!$fmt_info) { break; } assert($data_len == 34); assert(substr($data, 0, 2) == " "); # <8i $iguff = array_values(unpack("i8", substr($data, 2, 32))); $gcw = []; // print_r($iguff); foreach ($iguff as $bits) { for ($j = 0; $j < 32; $j++) { $gow[] = $bits & 1; $bits >>= 1; } } $this->gcw = $gcw; break; case XL_BLANK: if (!$fmt_info) { break; } # <HHH list($rowx, $colx, $xf_index) = array_values(unpack('v3a', substr($data, 0, 6))); $this->put_cell($rowx, $colx, XL_CELL_BLANK, '', $xf_index); break; case XL_MULBLANK: if (!$fmt_info) { break; } $nitems = $data_len >> 1; # "<%dH" % nitems $result = array_values(unpack('v' . $nitems . 'a', $data)); $rowx = $result[0]; $mul_first = $result[1]; $mul_last = $result[count($result) - 1]; assert($nitems == $mul_last + 4 - $mul_first); $pos = 2; for ($colx = $mul_first; $colx <= $mul_last; $colx++) { $this->put_cell($rowx, $colx, XL_CELL_BLANK, '', $result[$pos]); $pos++; } break; case XL_DIMENSION: case XL_DIMENSION2: # Four zero bytes after some other record. See xlrd github issue 64. if ($data_len == 0) { break; } # if data_len == 10: # Was crashing on BIFF 4.0 file w/o the two trailing unused bytes. # Reported by Ralph Heimburger. if ($bv < 80) { # <HxxH list($this->dimnrows, , , $this->dimncols) = array_values(unpack('va/c2b/vc', substr($data, 2, 6))); } else { # <ixxH list($this->dimnrows, , , $this->dimncols) = array_values(unpack('ia/c2b/vc', substr($data, 4, 8))); } $this->nrows = $this->ncols = 0; if (in_array($bv, [21, 30, 40]) && $this->book->xf_list && !$this->book->xf_epilogue_done) { $this->book->XFEpilogue(); } break; case XL_HLINK: $this->handle_hlink($data); break; case XL_QUICKTIP: $this->handle_quicktip($data); break; case XL_EOF: $eof_found = true; break; case XL_OBJ: # handle SHEET-level objects; note there's a separate Book.handle_obj $saved_obj = $this->handle_obj($data); // $saved_obj_id = Null; if ($saved_obj) { $saved_obj_id = $saved_obj->id; } break; case XL_MSO_DRAWING: $this->handle_msodrawingetc($rc, $data_len, $data); break; case XL_TXO: $txo = $this->handle_txo($data); if ($txo && $saved_obj_id) { $txos[$saved_obj_id] = $txo; $saved_obj_id = Null; } break; case XL_NOTE: $this->handle_note($data, $txos); break; case XL_FEAT11: $this->handle_feat11($data); break; case 0x809: # bofcodes # bofcodes case 0x409: # bofcodes # bofcodes case 0x209: # bofcodes # bofcodes case 0x9: # bofcodes # <HH list($version, $boftype) = array_values(unpack('v2', substr($data, 0, 4))); if ($boftype != 0x20) { Helper::log("*** Unexpected embedded BOF (0x%04x) at offset %d: version=0x%04x type=0x%04x", $rc, $bk->position - $data_len - 4, $version, $boftype); } $done = false; while (!$done) { list($code, $data_len, $data) = $this->book->readRecordParts(); if ($code == XL_EOF) { $done = true; } } break; case XL_COUNTRY: $bk->handleCountry($data); break; case XL_LABELRANGES: // @TODO die(Helper::explain_const($rc, 'XL_')); break; case XL_ARRAY: // @TODO # <HHBBBxxxxxH list($row1x, $rownx, $col1x, $colnx, $array_flags, $tokslen) = array_values(unpack("v2a/C3b/x5/vc", substr($data, 0, 14))); break; case XL_SHRFMLA: # <HHBBxBH list($row1x, $rownx, $col1x, $colnx, $nfmlas, $tokslen) = array_values(unpack("v2a/C2b/x/Cc/vD", substr($data, 0, 10))); // @TODO ? Or not? break; case XL_CONDFMT: if (!$fmt_info) { break; } assert($bv >= 80); # <6H list($num_CFs, $needs_recalc, $browx1, $browx2, $bcolx1, $bcolx2) = array_values(unpack('v6', substr($data, 0, 12))); $olist = []; # updated by the function $pos = Helper::unpack_cell_range_address_list_update_pos($olist, $data, 12, $bv, 8); break; case XL_CF: if (!$fmt_info) { break; } # <BBHHi list($cf_type, $cmp_op, $sz1, $sz2, $flags) = array_values(unpack("C2a/v2b/ic", substr($data, 0, 10))); $font_block = $flags >> 26 & 1; $bord_block = $flags >> 28 & 1; $patt_block = $flags >> 29 & 1; $pos = 12; if ($font_block) { # <64x i i H H B 3x i 4x i i i 18x list($font_height, $font_options, $weight, $escapement, $underline, $font_colour_index, $two_bits, $font_esc, $font_underl) = array_values(unpack("x64/i2a/v2b/Cc/x3/id/x4/i3e/x18", substr($data, $pos, 118))); $font_style = $two_bits > 1 & 1; $posture = $font_options > 1 & 1; $font_canc = $two_bits > 7 & 1; $cancellation = $font_options > 7 & 1; $pos += 118; } if ($bord_block) { $pos += 8; } if ($patt_block) { $pos += 8; } $fmla1 = substr($data, $pos, $sz2); $pos += $sz2; assert($pos = $data_len); // die(Helper::explain_const($rc, 'XL_')); break; case XL_DEFAULTROWHEIGHT: if ($data_len == 4) { # <HH list($bits, $this->default_row_height) = array_values(unpack('v2a', substr($data, 0, 4))); } elseif ($data_len == 2) { # <H list($this->default_row_height) = array_values(unpack('v', substr($data, 0, 2))); $bits = 0; } else { $bits = 0; } $this->default_row_height_mismatch = $bits & 1; $this->default_row_hidden = $bits >> 1 & 1; $this->default_additional_space_above = $bits >> 2 & 1; $this->default_additional_space_below = $bits >> 3 & 1; break; case XL_MERGEDCELLS: if (!$fmt_info) { continue; } $pos = Helper::unpack_cell_range_address_list_update_pos($this->merged_cells, $data, 0, $bv, 8); assert($pos == $data_len); break; case XL_WINDOW2: if ($bv >= 80 && $data_len >= 14) { # <HHHHxxHH list($options, $this->first_visible_rowx, $this->first_visible_colx, $this->gridline_colour_index, $this->cached_page_break_preview_mag_factor, $this->cached_normal_view_mag_factor) = array_values(unpack('v7a', substr($data, 0, 14))); } else { assert($bv >= 30); # BIFF3-7 # <HHH list($options, $this->first_visible_rowx, $this->first_visible_colx) = array_values(unpack('v3a', substr($data, 0, 6))); # <BBB $this->gridline_colour_rgb = array_values(unpack('C3a', substr($data, 6, 3))); $this->gridline_colour_index = Helper::nearest_colour_index($this->book->colour_map, $this->gridline_colour_rgb); $this->cached_page_break_preview_mag_factor = 0; # default (60%) $this->cached_normal_view_mag_factor = 0; # default (100%) } # options -- Bit, Mask, Contents: # 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 (6.71) describes unfrozen or frozen panes. foreach (Defs::$WINDOW2_options as $attr => $defval) { $this->{$attr} = $options & 1; $options >>= 1; } break; case XL_SCL: list($num, $den) = array_values(unpack('v2', $data)); $result = 0; if ($den) { $result = (int) ($num * 100 / $den); } if (!(10 <= $result && $result <= 40)) { $result = 100; } $this->scl_mag_factor = $result; break; case XL_PANE: list($this->vert_split_pos, $this->horz_split_pos, $this->horz_split_first_visible, $this->vert_split_first_visible, $this->split_active_pane) = array_values(unpack('v4a/Cb', substr($data, 0, 9))); $this->has_pane_record = 1; break; case XL_HORIZONTALPAGEBREAKS: if (!$fmt_info) { break; } # <H list($num_breaks) = array_values(unpack('v', substr($data, 0, 2))); assert($num_breaks * (2 + 4 * ($bv >= 80)) + 2 == $data_len); $pos = 2; if ($bv < 80) { while ($pos < $data_len) { # <H list($tmp) = array_values(unpack('v', substr($data, $pos, 2))); $this->horizontal_page_breaks[] = [$tmp, 0, 255]; $pos += 2; } } else { while ($pos < $data_len) { # <HHH $this->horizontal_page_breaks[] = array_values(unpack('v3', substr($data, $pos, 6))); $pos += 6; } } break; case XL_VERTICALPAGEBREAKS: // @TODO die(Helper::explain_const($rc, 'XL_')); break; default: if ($bv <= 45) { #### all of the following are for BIFF <= 4W } else { // $const_name = Helper::explain_const($rc, 'XL_'); } break; } if ($eof_found) { break; } } if (!$eof_found) { throw new XLSParserException("Sheet {$this->number} ({$this->name}) missing EOF record"); } $this->tidy_dimensions(); $this->update_cooked_mag_factors(); $bk->position = $oldpos; return 1; }