xref: /plugin/totext/vendor/prinsfrank/pdfparser/src/Document/Dictionary/DictionaryParser.php (revision b4ecfc82b02c9a76205381a28b47006474ccf569)
1<?php
2declare(strict_types=1);
3
4namespace PrinsFrank\PdfParser\Document\Dictionary;
5
6use PrinsFrank\PdfParser\Document\Dictionary\DictionaryParseContext\DictionaryParseContext;
7use PrinsFrank\PdfParser\Document\Dictionary\DictionaryParseContext\NestingContext;
8use PrinsFrank\PdfParser\Document\Generic\Character\DelimiterCharacter;
9use PrinsFrank\PdfParser\Document\Generic\Character\LiteralStringEscapeCharacter;
10use PrinsFrank\PdfParser\Document\Generic\Character\WhitespaceCharacter;
11use PrinsFrank\PdfParser\Document\Generic\Parsing\RollingCharBuffer;
12use PrinsFrank\PdfParser\Document\Security\EncryptionContext;
13use PrinsFrank\PdfParser\Exception\PdfParserException;
14use PrinsFrank\PdfParser\Stream\Stream;
15
16/** @internal */
17class DictionaryParser {
18    /**
19     * @phpstan-assert int<0, max> $startPos
20     * @phpstan-assert int<1, max> $nrOfBytes
21     *
22     * @throws PdfParserException
23     */
24    public static function parse(?EncryptionContext $encryptionContext, Stream $stream, int $startPos, int $nrOfBytes): Dictionary {
25        $dictionaryArray = [];
26        $rollingCharBuffer = new RollingCharBuffer(6);
27        $nestingContext = (new NestingContext())->setContext(DictionaryParseContext::ROOT);
28        $arrayNestingLevel = 0;
29        $contextBeforeComment = $previousIndexLevelDecrease = $previousIndexLevelIncrease = null;
30        foreach ($stream->chars($startPos, $nrOfBytes) as $index => $char) {
31            $rollingCharBuffer->next($char);
32            if ($char === DelimiterCharacter::LESS_THAN_SIGN->value
33                && $rollingCharBuffer->getPreviousCharacter() === DelimiterCharacter::LESS_THAN_SIGN->value
34                && $rollingCharBuffer->getPreviousCharacter(2) !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value
35                && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS
36                && $previousIndexLevelIncrease !== $index - 1) {
37                if ($nestingContext->getContext() === DictionaryParseContext::KEY) {
38                    $nestingContext->removeFromKeyBuffer();
39                }
40
41                $previousIndexLevelIncrease = $index;
42                $nestingContext->setContext(DictionaryParseContext::DICTIONARY)->incrementNesting()->setContext(DictionaryParseContext::DICTIONARY);
43            } elseif ($char === DelimiterCharacter::LESS_THAN_SIGN->value && $nestingContext->getContext() === DictionaryParseContext::KEY) {
44                $nestingContext->setContext(DictionaryParseContext::VALUE);
45            } elseif ($char === DelimiterCharacter::GREATER_THAN_SIGN->value
46                && $rollingCharBuffer->getPreviousCharacter() === DelimiterCharacter::GREATER_THAN_SIGN->value
47                && $rollingCharBuffer->getPreviousCharacter(2) !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value
48                && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS
49                && $previousIndexLevelDecrease !== $index - 1) {
50                $nestingContext->removeFromValueBuffer();
51                self::flush($dictionaryArray, $nestingContext);
52                $previousIndexLevelDecrease = $index;
53                $nestingContext->decrementNesting()->flush();
54            } elseif ($char === DelimiterCharacter::SOLIDUS->value
55                && $rollingCharBuffer->getPreviousCharacter() !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value
56                && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) {
57                if ($nestingContext->getContext() === DictionaryParseContext::DICTIONARY) {
58                    $nestingContext->setContext(DictionaryParseContext::KEY);
59                } elseif ($nestingContext->getContext() === DictionaryParseContext::VALUE) {
60                    self::flush($dictionaryArray, $nestingContext);
61                    $nestingContext->setContext(DictionaryParseContext::KEY);
62                } elseif ($nestingContext->getContext() === DictionaryParseContext::KEY || $nestingContext->getContext() === DictionaryParseContext::KEY_VALUE_SEPARATOR) {
63                    $nestingContext->setContext(DictionaryParseContext::VALUE);
64                }
65            } elseif ($char === WhitespaceCharacter::LINE_FEED->value && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) {
66                if ($nestingContext->getContext() === DictionaryParseContext::KEY) {
67                    $nestingContext->setContext(DictionaryParseContext::KEY_VALUE_SEPARATOR);
68                } elseif ($nestingContext->getContext() === DictionaryParseContext::VALUE) {
69                    self::flush($dictionaryArray, $nestingContext);
70                } elseif ($nestingContext->getContext() === DictionaryParseContext::COMMENT) {
71                    $nestingContext->setContext($contextBeforeComment ?? DictionaryParseContext::DICTIONARY);
72                    $contextBeforeComment = null;
73                }
74            } elseif ($char === DelimiterCharacter::PERCENT_SIGN->value && $rollingCharBuffer->getPreviousCharacter() !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_PARENTHESES) {
75                if ($nestingContext->getContext() === DictionaryParseContext::VALUE) {
76                    self::flush($dictionaryArray, $nestingContext);
77                    $contextBeforeComment = DictionaryParseContext::DICTIONARY;
78                } else {
79                    $contextBeforeComment = $nestingContext->getContext();
80                }
81                $nestingContext->setContext(DictionaryParseContext::COMMENT);
82            } elseif (WhitespaceCharacter::tryFrom($char) !== null && $nestingContext->getContext() === DictionaryParseContext::KEY) {
83                $nestingContext->setContext(DictionaryParseContext::KEY_VALUE_SEPARATOR);
84            } elseif ($char === DelimiterCharacter::LEFT_PARENTHESIS->value && (in_array($nestingContext->getContext(), [DictionaryParseContext::KEY, DictionaryParseContext::KEY_VALUE_SEPARATOR, DictionaryParseContext::VALUE], true))) {
85                $nestingContext->setContext(DictionaryParseContext::VALUE_IN_PARENTHESES);
86            } elseif ($char === DelimiterCharacter::RIGHT_PARENTHESIS->value && $rollingCharBuffer->getPreviousCharacter() !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value && $nestingContext->getContext() === DictionaryParseContext::VALUE_IN_PARENTHESES) {
87                $nestingContext->setContext(DictionaryParseContext::VALUE);
88            } elseif ($char === DelimiterCharacter::LEFT_SQUARE_BRACKET->value && (in_array($nestingContext->getContext(), [DictionaryParseContext::KEY, DictionaryParseContext::KEY_VALUE_SEPARATOR, DictionaryParseContext::VALUE, DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS], true))) {
89                $nestingContext->setContext(DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS);
90                $arrayNestingLevel++;
91            } elseif ($char === DelimiterCharacter::RIGHT_SQUARE_BRACKET->value && $nestingContext->getContext() === DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) {
92                $arrayNestingLevel--;
93                if ($arrayNestingLevel === 0) {
94                    $nestingContext->setContext(DictionaryParseContext::VALUE);
95                }
96            } elseif (trim($char) !== '' && $nestingContext->getContext() === DictionaryParseContext::KEY_VALUE_SEPARATOR) {
97                $nestingContext->setContext(DictionaryParseContext::VALUE);
98            }
99
100            match ($nestingContext->getContext()) {
101                DictionaryParseContext::KEY => $nestingContext->addToKeyBuffer($char),
102                DictionaryParseContext::VALUE_IN_PARENTHESES,
103                DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS,
104                DictionaryParseContext::VALUE => $nestingContext->addToValueBuffer($char),
105                default => null,
106            };
107        }
108
109        return DictionaryFactory::fromArray($encryptionContext, $dictionaryArray);
110    }
111
112    /** @param array<string, mixed> $dictionaryArray */
113    private static function flush(array &$dictionaryArray, NestingContext $nestingContext): void {
114        if ($nestingContext->getValueBuffer()->isEmpty() || $nestingContext->getKeyBuffer()->isEmpty()) {
115            return;
116        }
117
118        $dictionaryArrayPointer = &$dictionaryArray;
119        $keys = $nestingContext->getKeysFromRoot();
120        foreach ($keys as $index => $key) {
121            if ($key === (string) $nestingContext->getKeyBuffer() && $index === array_key_last($keys)) {
122                break;
123            }
124
125            /** @phpstan-ignore offsetAccess.nonOffsetAccessible */
126            $dictionaryArrayPointer = &$dictionaryArrayPointer[trim($key)];
127        }
128
129        /** @phpstan-ignore offsetAccess.nonOffsetAccessible */
130        $dictionaryArrayPointer[(string) $nestingContext->getKeyBuffer()] = trim((string) $nestingContext->getValueBuffer());
131        $nestingContext->flush();
132    }
133}
134