Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
FdfReader
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 parse
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
 decodeString
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Pdf\Core\Interactive\Form;
6
7/**
8 * FDF (Forms Data Format) file reader — ISO 32000-2 §12.7.8.
9 *
10 * Parses a standalone .fdf file and extracts field name/value pairs.
11 */
12final class FdfReader
13{
14    /**
15     * Parse FDF content and return a field name → value map.
16     *
17     * @return array<string, string>
18     */
19    public static function parse(string $fdfContent): array
20    {
21        $fields = [];
22
23        // Find field dictionaries: << /T (...) /V (...) >>
24        // Use a regex that handles escaped parentheses inside strings
25        $stringPattern = '\((?:[^()\\\\]|\\\\.)*\)';
26
27        // Match /T and /V in either order
28        $pattern = '/<<[^>]*?\/T\s*(' . $stringPattern . ')[^>]*?\/V\s*(' . $stringPattern . ')[^>]*?>>/s';
29        if (preg_match_all($pattern, $fdfContent, $matches, PREG_SET_ORDER)) {
30            foreach ($matches as $match) {
31                $name = self::decodeString($match[1]);
32                $value = self::decodeString($match[2]);
33                $fields[$name] = $value;
34            }
35        }
36
37        // Also match /V before /T
38        $pattern2 = '/<<[^>]*?\/V\s*(' . $stringPattern . ')[^>]*?\/T\s*(' . $stringPattern . ')[^>]*?>>/s';
39        if (preg_match_all($pattern2, $fdfContent, $matches, PREG_SET_ORDER)) {
40            foreach ($matches as $match) {
41                $value = self::decodeString($match[1]);
42                $name = self::decodeString($match[2]);
43                if (!isset($fields[$name])) {
44                    $fields[$name] = $value;
45                }
46            }
47        }
48
49        return $fields;
50    }
51
52    /**
53     * Decode a PDF literal string: strip outer parens, unescape sequences.
54     */
55    private static function decodeString(string $raw): string
56    {
57        // Strip outer parentheses
58        $inner = substr($raw, 1, -1);
59        // Unescape: \\ → \, \( → (, \) → )
60        return str_replace(['\\\\', '\\(', '\\)'], ['\\', '(', ')'], $inner);
61    }
62}