1<?php
2
3/**
4 * Parses string hash files. File format is as such:
5 *
6 *      DefaultKeyValue
7 *      KEY: Value
8 *      KEY2: Value2
9 *      --MULTILINE-KEY--
10 *      Multiline
11 *      value.
12 *
13 * Which would output something similar to:
14 *
15 *      array(
16 *          'ID' => 'DefaultKeyValue',
17 *          'KEY' => 'Value',
18 *          'KEY2' => 'Value2',
19 *          'MULTILINE-KEY' => "Multiline\nvalue.\n",
20 *      )
21 *
22 * We use this as an easy to use file-format for configuration schema
23 * files, but the class itself is usage agnostic.
24 *
25 * You can use ---- to forcibly terminate parsing of a single string-hash;
26 * this marker is used in multi string-hashes to delimit boundaries.
27 */
28class HTMLPurifier_StringHashParser
29{
30
31    /**
32     * @type string
33     */
34    public $default = 'ID';
35
36    /**
37     * Parses a file that contains a single string-hash.
38     * @param string $file
39     * @return array
40     */
41    public function parseFile($file)
42    {
43        if (!file_exists($file)) {
44            return false;
45        }
46        $fh = fopen($file, 'r');
47        if (!$fh) {
48            return false;
49        }
50        $ret = $this->parseHandle($fh);
51        fclose($fh);
52        return $ret;
53    }
54
55    /**
56     * Parses a file that contains multiple string-hashes delimited by '----'
57     * @param string $file
58     * @return array
59     */
60    public function parseMultiFile($file)
61    {
62        if (!file_exists($file)) {
63            return false;
64        }
65        $ret = array();
66        $fh = fopen($file, 'r');
67        if (!$fh) {
68            return false;
69        }
70        while (!feof($fh)) {
71            $ret[] = $this->parseHandle($fh);
72        }
73        fclose($fh);
74        return $ret;
75    }
76
77    /**
78     * Internal parser that acepts a file handle.
79     * @note While it's possible to simulate in-memory parsing by using
80     *       custom stream wrappers, if such a use-case arises we should
81     *       factor out the file handle into its own class.
82     * @param resource $fh File handle with pointer at start of valid string-hash
83     *            block.
84     * @return array
85     */
86    protected function parseHandle($fh)
87    {
88        $state   = false;
89        $single  = false;
90        $ret     = array();
91        do {
92            $line = fgets($fh);
93            if ($line === false) {
94                break;
95            }
96            $line = rtrim($line, "\n\r");
97            if (!$state && $line === '') {
98                continue;
99            }
100            if ($line === '----') {
101                break;
102            }
103            if (strncmp('--#', $line, 3) === 0) {
104                // Comment
105                continue;
106            } elseif (strncmp('--', $line, 2) === 0) {
107                // Multiline declaration
108                $state = trim($line, '- ');
109                if (!isset($ret[$state])) {
110                    $ret[$state] = '';
111                }
112                continue;
113            } elseif (!$state) {
114                $single = true;
115                if (strpos($line, ':') !== false) {
116                    // Single-line declaration
117                    list($state, $line) = explode(':', $line, 2);
118                    $line = trim($line);
119                } else {
120                    // Use default declaration
121                    $state  = $this->default;
122                }
123            }
124            if ($single) {
125                $ret[$state] = $line;
126                $single = false;
127                $state  = false;
128            } else {
129                $ret[$state] .= "$line\n";
130            }
131        } while (!feof($fh));
132        return $ret;
133    }
134}
135
136// vim: et sw=4 sts=4
137