Vendor/phpspreadsheet/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php
private function readSst()
    {
        // offset within (spliced) record data
        $pos = 0;

        // get spliced record data
        $splicedRecordData = $this->getSplicedRecordData();

        $recordData = $splicedRecordData['recordData'];
        $spliceOffsets = $splicedRecordData['spliceOffsets'];

        // offset: 0; size: 4; total number of strings in the workbook
        $pos += 4;

        // offset: 4; size: 4; number of following strings ($nm)
        $nm = self::getInt4d($recordData, 4);
        $pos += 4;

        // loop through the Unicode strings (16-bit length)
        for ($i = 0; $i < $nm; ++$i) {
            // number of characters in the Unicode string
            $numChars = self::getUInt2d($recordData, $pos);
            $pos += 2;

            // option flags
            $optionFlags = ord($recordData[$pos]);
            ++$pos;

            // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
            $isCompressed = (($optionFlags & 0x01) == 0);

            // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
            $hasAsian = (($optionFlags & 0x04) != 0);

            // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
            $hasRichText = (($optionFlags & 0x08) != 0);

            if ($hasRichText) {
                // number of Rich-Text formatting runs
                $formattingRuns = self::getUInt2d($recordData, $pos);
                $pos += 2;
            }

            if ($hasAsian) {
                // size of Asian phonetic setting
                $extendedRunLength = self::getInt4d($recordData, $pos);
                $pos += 4;
            }

            // expected byte length of character array if not split
            $len = ($isCompressed) ? $numChars : $numChars * 2;

            // look up limit position
            foreach ($spliceOffsets as $spliceOffset) {
                // it can happen that the string is empty, therefore we need
                // <= and not just <
                if ($pos <= $spliceOffset) {
                    $limitpos = $spliceOffset;

                    break;
                }
            }

            if ($pos + $len <= $limitpos) {
                // character array is not split between records

                $retstr = substr($recordData, $pos, $len);
                $pos += $len;
            } else {
                // character array is split between records

                // first part of character array
                $retstr = substr($recordData, $pos, $limitpos - $pos);

                $bytesRead = $limitpos - $pos;

                // remaining characters in Unicode string
                $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));

                $pos = $limitpos;

                // keep reading the characters
                while ($charsLeft > 0) {
                    // look up next limit position, in case the string span more than one continue record
                    foreach ($spliceOffsets as $spliceOffset) {
                        if ($pos < $spliceOffset) {
                            $limitpos = $spliceOffset;

                            break;
                        }
                    }

                    // repeated option flags
                    // OpenOffice.org documentation 5.21
                    $option = ord($recordData[$pos]);
                    ++$pos;

                    if ($isCompressed && ($option == 0)) {
                        // 1st fragment compressed
                        // this fragment compressed
                        $len = min($charsLeft, $limitpos - $pos);
                        $retstr .= substr($recordData, $pos, $len);
                        $charsLeft -= $len;
                        $isCompressed = true;
                    } elseif (!$isCompressed && ($option != 0)) {
                        // 1st fragment uncompressed
                        // this fragment uncompressed
                        $len = min($charsLeft * 2, $limitpos - $pos);
                        $retstr .= substr($recordData, $pos, $len);
                        $charsLeft -= $len / 2;
                        $isCompressed = false;
                    } elseif (!$isCompressed && ($option == 0)) {
                        // 1st fragment uncompressed
                        // this fragment compressed
                        $len = min($charsLeft, $limitpos - $pos);
                        for ($j = 0; $j < $len; ++$j) {
                            $retstr .= $recordData[$pos + $j]
                            . chr(0);
                        }
                        $charsLeft -= $len;
                        $isCompressed = false;
                    } else {
                        // 1st fragment compressed
                        // this fragment uncompressed
                        $newstr = '';
                        $jMax = strlen($retstr);
                        for ($j = 0; $j < $jMax; ++$j) {
                            $newstr .= $retstr[$j] . chr(0);
                        }
                        $retstr = $newstr;
                        $len = min($charsLeft * 2, $limitpos - $pos);
                        $retstr .= substr($recordData, $pos, $len);
                        $charsLeft -= $len / 2;
                        $isCompressed = false;
                    }

                    $pos += $len;
                }
            }

            // convert to UTF-8
            $retstr = self::encodeUTF16($retstr, $isCompressed);

            // read additional Rich-Text information, if any
            $fmtRuns = [];
            if ($hasRichText) {
                // list of formatting runs
                for ($j = 0; $j < $formattingRuns; ++$j) {
                    // first formatted character; zero-based
                    $charPos = self::getUInt2d($recordData, $pos + $j * 4);

                    // index to font record
                    $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4);

                    $fmtRuns[] = [
                        'charPos' => $charPos,
                        'fontIndex' => $fontIndex,
                    ];
                }
                $pos += 4 * $formattingRuns;
            }

            // read additional Asian phonetics information, if any
            if ($hasAsian) {
                // For Asian phonetic settings, we skip the extended string data
                $pos += $extendedRunLength;
            }

            // store the shared sting
            $this->sst[] = [
                'value' => $retstr,
                'fmtRuns' => $fmtRuns,
            ];
        }

        // getSplicedRecordData() takes care of moving current position in data stream
    }
private function readSst()
    {
        // offset within (spliced) record data
        $pos = 0;

        // get spliced record data
        $splicedRecordData = $this->getSplicedRecordData();

        $recordData = $splicedRecordData['recordData'];
        $spliceOffsets = $splicedRecordData['spliceOffsets'];

        // offset: 0; size: 4; total number of strings in the workbook
        $pos += 4;

        // offset: 4; size: 4; number of following strings ($nm)
        $nm = self::getInt4d($recordData, 4);
        $pos += 4;

        // loop through the Unicode strings (16-bit length)
        for ($i = 0; $i < $nm; ++$i) {
            // number of characters in the Unicode string
            $numChars = self::getUInt2d($recordData, $pos);
            $pos += 2;

            // option flags
            $optionFlags = ord($recordData[$pos]);
            ++$pos;

            // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
            $isCompressed = (($optionFlags & 0x01) == 0);

            // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
            $hasAsian = (($optionFlags & 0x04) != 0);

            // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
            $hasRichText = (($optionFlags & 0x08) != 0);

            if ($hasRichText) {
                // number of Rich-Text formatting runs
                $formattingRuns = self::getUInt2d($recordData, $pos);
                $pos += 2;
            }

            if ($hasAsian) {
                // size of Asian phonetic setting
                $extendedRunLength = self::getInt4d($recordData, $pos);
                $pos += 4;
            }

            // expected byte length of character array if not split
            $len = ($isCompressed) ? $numChars : $numChars * 2;

            // look up limit position
            foreach ($spliceOffsets as $spliceOffset) {
                // it can happen that the string is empty, therefore we need
                // <= and not just <
                if ($pos <= $spliceOffset) {
                    $limitpos = $spliceOffset;

                    break;
                }
            }

            if ($pos + $len <= $limitpos) {
                // character array is not split between records

                $retstr = substr($recordData, $pos, $len);
                $pos += $len;
            } else {
                // character array is split between records

                // first part of character array
                $retstr = substr($recordData, $pos, $limitpos - $pos);

                $bytesRead = $limitpos - $pos;

                // remaining characters in Unicode string
                $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));

                $pos = $limitpos;

                // keep reading the characters
                while ($charsLeft > 0) {
                    // look up next limit position, in case the string span more than one continue record
                    foreach ($spliceOffsets as $spliceOffset) {
                        if ($pos < $spliceOffset) {
                            $limitpos = $spliceOffset;

                            break;
                        }
                    }

                    // repeated option flags
                    // OpenOffice.org documentation 5.21
                    $option = ord($recordData[$pos]);
                    ++$pos;

                    if ($isCompressed && ($option == 0)) {
                        // 1st fragment compressed
                        // this fragment compressed
                        $len = min($charsLeft, $limitpos - $pos);
                        $retstr .= substr($recordData, $pos, $len);
                        $charsLeft -= $len;
                        $isCompressed = true;
                    } elseif (!$isCompressed && ($option != 0)) {
                        // 1st fragment uncompressed
                        // this fragment uncompressed
                        $len = min($charsLeft * 2, $limitpos - $pos);
                        $retstr .= substr($recordData, $pos, $len);
                        $charsLeft -= $len / 2;
                        $isCompressed = false;
                    } elseif (!$isCompressed && ($option == 0)) {
                        // 1st fragment uncompressed
                        // this fragment compressed
                        $len = min($charsLeft, $limitpos - $pos);
                        for ($j = 0; $j < $len; ++$j) {
                            $retstr .= $recordData[$pos + $j]
                            . chr(0);
                        }
                        $charsLeft -= $len;
                        $isCompressed = false;
                    } else {
                        // 1st fragment compressed
                        // this fragment uncompressed
                        $newstr = '';
                        $jMax = strlen($retstr);
                        for ($j = 0; $j < $jMax; ++$j) {
                            $newstr .= $retstr[$j] . chr(0);
                        }
                        $retstr = $newstr;
                        $len = min($charsLeft * 2, $limitpos - $pos);
                        $retstr .= substr($recordData, $pos, $len);
                        $charsLeft -= $len / 2;
                        $isCompressed = false;
                    }

                    $pos += $len;
                }
            }

            // convert to UTF-8
            $retstr = self::encodeUTF16($retstr, $isCompressed);

            // read additional Rich-Text information, if any
            $fmtRuns = [];
            if ($hasRichText) {
                // list of formatting runs
                for ($j = 0; $j < $formattingRuns; ++$j) {
                    // first formatted character; zero-based
                    $charPos = self::getUInt2d($recordData, $pos + $j * 4);

                    // index to font record
                    $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4);

                    $fmtRuns[] = [
                        'charPos' => $charPos,
                        'fontIndex' => $fontIndex,
                    ];
                }
                $pos += 4 * $formattingRuns;
            }

            // read additional Asian phonetics information, if any
            if ($hasAsian) {
                // For Asian phonetic settings, we skip the extended string data
                $pos += $extendedRunLength;
            }

            // store the shared sting
            $this->sst[] = [
                'value' => $retstr,
                'fmtRuns' => $fmtRuns,
            ];
        }

        // getSplicedRecordData() takes care of moving current position in data stream
    }
private function readSst ( ) { // offset within (spliced) record data $ pos = 0 ; // get spliced record data $ splicedRecordData = $ this -> getSplicedRecordData ( ) ; $ recordData = $ splicedRecordData [ 'recordData' ] ; $ spliceOffsets = $ splicedRecordData [ 'spliceOffsets' ] ; // offset: 0; size: 4; total number of strings in the workbook $ pos += 4 ; // offset: 4; size: 4; number of following strings ($nm) $ nm = self :: getInt4d ( $ recordData , 4 ) ; $ pos += 4 ; // loop through the Unicode strings (16-bit length) for ( $ i = 0 ; $ i < $ nm ; ++ $ i ) { // number of characters in the Unicode string $ numChars = self :: getUInt2d ( $ recordData , $ pos ) ; $ pos += 2 ; // option flags $ optionFlags = ord ( $ recordData [ $ pos ] ) ; ++ $ pos ; // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed $ isCompressed = ( ( $ optionFlags & 0x01 ) == 0 ) ; // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic $ hasAsian = ( ( $ optionFlags & 0x04 ) != 0 ) ; // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text $ hasRichText = ( ( $ optionFlags & 0x08 ) != 0 ) ; if ( $ hasRichText ) { // number of Rich-Text formatting runs $ formattingRuns = self :: getUInt2d ( $ recordData , $ pos ) ; $ pos += 2 ; } if ( $ hasAsian ) { // size of Asian phonetic setting $ extendedRunLength = self :: getInt4d ( $ recordData , $ pos ) ; $ pos += 4 ; } // expected byte length of character array if not split $ len = ( $ isCompressed ) ? $ numChars : $ numChars * 2 ; // look up limit position foreach ( $ spliceOffsets as $ spliceOffset ) { // it can happen that the string is empty, therefore we need // <= and not just < if ( $ pos <= $ spliceOffset ) { $ limitpos = $ spliceOffset ; break ; } } if ( $ pos + $ len <= $ limitpos ) { // character array is not split between records $ retstr = substr ( $ recordData , $ pos , $ len ) ; $ pos += $ len ; } else { // character array is split between records // first part of character array $ retstr = substr ( $ recordData , $ pos , $ limitpos - $ pos ) ; $ bytesRead = $ limitpos - $ pos ; // remaining characters in Unicode string $ charsLeft = $ numChars - ( ( $ isCompressed ) ? $ bytesRead : ( $ bytesRead / 2 ) ) ; $ pos = $ limitpos ; // keep reading the characters while ( $ charsLeft > 0 ) { // look up next limit position, in case the string span more than one continue record foreach ( $ spliceOffsets as $ spliceOffset ) { if ( $ pos < $ spliceOffset ) { $ limitpos = $ spliceOffset ; break ; } } // repeated option flags // OpenOffice.org documentation 5.21 $ option = ord ( $ recordData [ $ pos ] ) ; ++ $ pos ; if ( $ isCompressed && ( $ option == 0 ) ) { // 1st fragment compressed // this fragment compressed $ len = min ( $ charsLeft , $ limitpos - $ pos ) ; $ retstr .= substr ( $ recordData , $ pos , $ len ) ; $ charsLeft -= $ len ; $ isCompressed = true ; } elseif ( ! $ isCompressed && ( $ option != 0 ) ) { // 1st fragment uncompressed // this fragment uncompressed $ len = min ( $ charsLeft * 2 , $ limitpos - $ pos ) ; $ retstr .= substr ( $ recordData , $ pos , $ len ) ; $ charsLeft -= $ len / 2 ; $ isCompressed = false ; } elseif ( ! $ isCompressed && ( $ option == 0 ) ) { // 1st fragment uncompressed // this fragment compressed $ len = min ( $ charsLeft , $ limitpos - $ pos ) ; for ( $ j = 0 ; $ j < $ len ; ++ $ j ) { $ retstr .= $ recordData [ $ pos + $ j ] . chr ( 0 ) ; } $ charsLeft -= $ len ; $ isCompressed = false ; } else { // 1st fragment compressed // this fragment uncompressed $ newstr = '' ; $ jMax = strlen ( $ retstr ) ; for ( $ j = 0 ; $ j < $ jMax ; ++ $ j ) { $ newstr .= $ retstr [ $ j ] . chr ( 0 ) ; } $ retstr = $ newstr ; $ len = min ( $ charsLeft * 2 , $ limitpos - $ pos ) ; $ retstr .= substr ( $ recordData , $ pos , $ len ) ; $ charsLeft -= $ len / 2 ; $ isCompressed = false ; } $ pos += $ len ; } } // convert to UTF-8 $ retstr = self :: encodeUTF16 ( $ retstr , $ isCompressed ) ; // read additional Rich-Text information, if any $ fmtRuns = [ ] ; if ( $ hasRichText ) { // list of formatting runs for ( $ j = 0 ; $ j < $ formattingRuns ; ++ $ j ) { // first formatted character; zero-based $ charPos = self :: getUInt2d ( $ recordData , $ pos + $ j * 4 ) ; // index to font record $ fontIndex = self :: getUInt2d ( $ recordData , $ pos + 2 + $ j * 4 ) ; $ fmtRuns [ ] = [ 'charPos' => $ charPos , 'fontIndex' => $ fontIndex , ] ; } $ pos += 4 * $ formattingRuns ; } // read additional Asian phonetics information, if any if ( $ hasAsian ) { // For Asian phonetic settings, we skip the extended string data $ pos += $ extendedRunLength ; } // store the shared sting $ this -> sst [ ] = [ 'value' => $ retstr , 'fmtRuns' => $ fmtRuns , ] ; } // getSplicedRecordData() takes care of moving current position in data stream }
SST - Shared String Table.

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.

--    "OpenOffice.org's Documentation of the Microsoft
Excel File Format"
SST - Shared String Table .