1: <?php
2:
3: class HTMLPurifier_ConfigSchema_InterchangeBuilder
4: {
5:
6: /**
7: * Used for processing DEFAULT, nothing else.
8: * @type HTMLPurifier_VarParser
9: */
10: protected $varParser;
11:
12: /**
13: * @param HTMLPurifier_VarParser $varParser
14: */
15: public function __construct($varParser = null)
16: {
17: $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
18: }
19:
20: /**
21: * @param string $dir
22: * @return HTMLPurifier_ConfigSchema_Interchange
23: */
24: public static function buildFromDirectory($dir = null)
25: {
26: $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
27: $interchange = new HTMLPurifier_ConfigSchema_Interchange();
28: return $builder->buildDir($interchange, $dir);
29: }
30:
31: /**
32: * @param HTMLPurifier_ConfigSchema_Interchange $interchange
33: * @param string $dir
34: * @return HTMLPurifier_ConfigSchema_Interchange
35: */
36: public function buildDir($interchange, $dir = null)
37: {
38: if (!$dir) {
39: $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
40: }
41: if (file_exists($dir . '/info.ini')) {
42: $info = parse_ini_file($dir . '/info.ini');
43: $interchange->name = $info['name'];
44: }
45:
46: $files = array();
47: $dh = opendir($dir);
48: while (false !== ($file = readdir($dh))) {
49: if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') {
50: continue;
51: }
52: $files[] = $file;
53: }
54: closedir($dh);
55:
56: sort($files);
57: foreach ($files as $file) {
58: $this->buildFile($interchange, $dir . '/' . $file);
59: }
60: return $interchange;
61: }
62:
63: /**
64: * @param HTMLPurifier_ConfigSchema_Interchange $interchange
65: * @param string $file
66: */
67: public function buildFile($interchange, $file)
68: {
69: $parser = new HTMLPurifier_StringHashParser();
70: $this->build(
71: $interchange,
72: new HTMLPurifier_StringHash($parser->parseFile($file))
73: );
74: }
75:
76: /**
77: * Builds an interchange object based on a hash.
78: * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build
79: * @param HTMLPurifier_StringHash $hash source data
80: * @throws HTMLPurifier_ConfigSchema_Exception
81: */
82: public function build($interchange, $hash)
83: {
84: if (!$hash instanceof HTMLPurifier_StringHash) {
85: $hash = new HTMLPurifier_StringHash($hash);
86: }
87: if (!isset($hash['ID'])) {
88: throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID');
89: }
90: if (strpos($hash['ID'], '.') === false) {
91: if (count($hash) == 2 && isset($hash['DESCRIPTION'])) {
92: $hash->offsetGet('DESCRIPTION'); // prevent complaining
93: } else {
94: throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace');
95: }
96: } else {
97: $this->buildDirective($interchange, $hash);
98: }
99: $this->_findUnused($hash);
100: }
101:
102: /**
103: * @param HTMLPurifier_ConfigSchema_Interchange $interchange
104: * @param HTMLPurifier_StringHash $hash
105: * @throws HTMLPurifier_ConfigSchema_Exception
106: */
107: public function buildDirective($interchange, $hash)
108: {
109: $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();
110:
111: // These are required elements:
112: $directive->id = $this->id($hash->offsetGet('ID'));
113: $id = $directive->id->toString(); // convenience
114:
115: if (isset($hash['TYPE'])) {
116: $type = explode('/', $hash->offsetGet('TYPE'));
117: if (isset($type[1])) {
118: $directive->typeAllowsNull = true;
119: }
120: $directive->type = $type[0];
121: } else {
122: throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined");
123: }
124:
125: if (isset($hash['DEFAULT'])) {
126: try {
127: $directive->default = $this->varParser->parse(
128: $hash->offsetGet('DEFAULT'),
129: $directive->type,
130: $directive->typeAllowsNull
131: );
132: } catch (HTMLPurifier_VarParserException $e) {
133: throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'");
134: }
135: }
136:
137: if (isset($hash['DESCRIPTION'])) {
138: $directive->description = $hash->offsetGet('DESCRIPTION');
139: }
140:
141: if (isset($hash['ALLOWED'])) {
142: $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED')));
143: }
144:
145: if (isset($hash['VALUE-ALIASES'])) {
146: $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES'));
147: }
148:
149: if (isset($hash['ALIASES'])) {
150: $raw_aliases = trim($hash->offsetGet('ALIASES'));
151: $aliases = preg_split('/\s*,\s*/', $raw_aliases);
152: foreach ($aliases as $alias) {
153: $directive->aliases[] = $this->id($alias);
154: }
155: }
156:
157: if (isset($hash['VERSION'])) {
158: $directive->version = $hash->offsetGet('VERSION');
159: }
160:
161: if (isset($hash['DEPRECATED-USE'])) {
162: $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE'));
163: }
164:
165: if (isset($hash['DEPRECATED-VERSION'])) {
166: $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION');
167: }
168:
169: if (isset($hash['EXTERNAL'])) {
170: $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL')));
171: }
172:
173: $interchange->addDirective($directive);
174: }
175:
176: /**
177: * Evaluates an array PHP code string without array() wrapper
178: * @param string $contents
179: */
180: protected function evalArray($contents)
181: {
182: return eval('return array(' . $contents . ');');
183: }
184:
185: /**
186: * Converts an array list into a lookup array.
187: * @param array $array
188: * @return array
189: */
190: protected function lookup($array)
191: {
192: $ret = array();
193: foreach ($array as $val) {
194: $ret[$val] = true;
195: }
196: return $ret;
197: }
198:
199: /**
200: * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id
201: * object based on a string Id.
202: * @param string $id
203: * @return HTMLPurifier_ConfigSchema_Interchange_Id
204: */
205: protected function id($id)
206: {
207: return HTMLPurifier_ConfigSchema_Interchange_Id::make($id);
208: }
209:
210: /**
211: * Triggers errors for any unused keys passed in the hash; such keys
212: * may indicate typos, missing values, etc.
213: * @param HTMLPurifier_StringHash $hash Hash to check.
214: */
215: protected function _findUnused($hash)
216: {
217: $accessed = $hash->getAccessed();
218: foreach ($hash as $k => $v) {
219: if (!isset($accessed[$k])) {
220: trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE);
221: }
222: }
223: }
224: }
225:
226: // vim: et sw=4 sts=4
227: