]>
Commit | Line | Data |
---|---|---|
1 | <?php\r | |
2 | \r | |
3 | /**\r | |
4 | * Takes tokens makes them well-formed (balance end tags, etc.)\r | |
5 | *\r | |
6 | * Specification of the armor attributes this strategy uses:\r | |
7 | *\r | |
8 | * - MakeWellFormed_TagClosedError: This armor field is used to\r | |
9 | * suppress tag closed errors for certain tokens [TagClosedSuppress],\r | |
10 | * in particular, if a tag was generated automatically by HTML\r | |
11 | * Purifier, we may rely on our infrastructure to close it for us\r | |
12 | * and shouldn't report an error to the user [TagClosedAuto].\r | |
13 | */\r | |
14 | class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy\r | |
15 | {\r | |
16 | \r | |
17 | /**\r | |
18 | * Array stream of tokens being processed.\r | |
19 | * @type HTMLPurifier_Token[]\r | |
20 | */\r | |
21 | protected $tokens;\r | |
22 | \r | |
23 | /**\r | |
24 | * Current token.\r | |
25 | * @type HTMLPurifier_Token\r | |
26 | */\r | |
27 | protected $token;\r | |
28 | \r | |
29 | /**\r | |
30 | * Zipper managing the true state.\r | |
31 | * @type HTMLPurifier_Zipper\r | |
32 | */\r | |
33 | protected $zipper;\r | |
34 | \r | |
35 | /**\r | |
36 | * Current nesting of elements.\r | |
37 | * @type array\r | |
38 | */\r | |
39 | protected $stack;\r | |
40 | \r | |
41 | /**\r | |
42 | * Injectors active in this stream processing.\r | |
43 | * @type HTMLPurifier_Injector[]\r | |
44 | */\r | |
45 | protected $injectors;\r | |
46 | \r | |
47 | /**\r | |
48 | * Current instance of HTMLPurifier_Config.\r | |
49 | * @type HTMLPurifier_Config\r | |
50 | */\r | |
51 | protected $config;\r | |
52 | \r | |
53 | /**\r | |
54 | * Current instance of HTMLPurifier_Context.\r | |
55 | * @type HTMLPurifier_Context\r | |
56 | */\r | |
57 | protected $context;\r | |
58 | \r | |
59 | /**\r | |
60 | * @param HTMLPurifier_Token[] $tokens\r | |
61 | * @param HTMLPurifier_Config $config\r | |
62 | * @param HTMLPurifier_Context $context\r | |
63 | * @return HTMLPurifier_Token[]\r | |
64 | * @throws HTMLPurifier_Exception\r | |
65 | */\r | |
66 | public function execute($tokens, $config, $context)\r | |
67 | {\r | |
68 | $definition = $config->getHTMLDefinition();\r | |
69 | \r | |
70 | // local variables\r | |
71 | $generator = new HTMLPurifier_Generator($config, $context);\r | |
72 | $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');\r | |
73 | // used for autoclose early abortion\r | |
74 | $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);\r | |
75 | $e = $context->get('ErrorCollector', true);\r | |
76 | $i = false; // injector index\r | |
77 | list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);\r | |
78 | if ($token === NULL) {\r | |
79 | return array();\r | |
80 | }\r | |
81 | $reprocess = false; // whether or not to reprocess the same token\r | |
82 | $stack = array();\r | |
83 | \r | |
84 | // member variables\r | |
85 | $this->stack =& $stack;\r | |
86 | $this->tokens =& $tokens;\r | |
87 | $this->token =& $token;\r | |
88 | $this->zipper =& $zipper;\r | |
89 | $this->config = $config;\r | |
90 | $this->context = $context;\r | |
91 | \r | |
92 | // context variables\r | |
93 | $context->register('CurrentNesting', $stack);\r | |
94 | $context->register('InputZipper', $zipper);\r | |
95 | $context->register('CurrentToken', $token);\r | |
96 | \r | |
97 | // -- begin INJECTOR --\r | |
98 | \r | |
99 | $this->injectors = array();\r | |
100 | \r | |
101 | $injectors = $config->getBatch('AutoFormat');\r | |
102 | $def_injectors = $definition->info_injector;\r | |
103 | $custom_injectors = $injectors['Custom'];\r | |
104 | unset($injectors['Custom']); // special case\r | |
105 | foreach ($injectors as $injector => $b) {\r | |
106 | // XXX: Fix with a legitimate lookup table of enabled filters\r | |
107 | if (strpos($injector, '.') !== false) {\r | |
108 | continue;\r | |
109 | }\r | |
110 | $injector = "HTMLPurifier_Injector_$injector";\r | |
111 | if (!$b) {\r | |
112 | continue;\r | |
113 | }\r | |
114 | $this->injectors[] = new $injector;\r | |
115 | }\r | |
116 | foreach ($def_injectors as $injector) {\r | |
117 | // assumed to be objects\r | |
118 | $this->injectors[] = $injector;\r | |
119 | }\r | |
120 | foreach ($custom_injectors as $injector) {\r | |
121 | if (!$injector) {\r | |
122 | continue;\r | |
123 | }\r | |
124 | if (is_string($injector)) {\r | |
125 | $injector = "HTMLPurifier_Injector_$injector";\r | |
126 | $injector = new $injector;\r | |
127 | }\r | |
128 | $this->injectors[] = $injector;\r | |
129 | }\r | |
130 | \r | |
131 | // give the injectors references to the definition and context\r | |
132 | // variables for performance reasons\r | |
133 | foreach ($this->injectors as $ix => $injector) {\r | |
134 | $error = $injector->prepare($config, $context);\r | |
135 | if (!$error) {\r | |
136 | continue;\r | |
137 | }\r | |
138 | array_splice($this->injectors, $ix, 1); // rm the injector\r | |
139 | trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);\r | |
140 | }\r | |
141 | \r | |
142 | // -- end INJECTOR --\r | |
143 | \r | |
144 | // a note on reprocessing:\r | |
145 | // In order to reduce code duplication, whenever some code needs\r | |
146 | // to make HTML changes in order to make things "correct", the\r | |
147 | // new HTML gets sent through the purifier, regardless of its\r | |
148 | // status. This means that if we add a start token, because it\r | |
149 | // was totally necessary, we don't have to update nesting; we just\r | |
150 | // punt ($reprocess = true; continue;) and it does that for us.\r | |
151 | \r | |
152 | // isset is in loop because $tokens size changes during loop exec\r | |
153 | for (;;\r | |
154 | // only increment if we don't need to reprocess\r | |
155 | $reprocess ? $reprocess = false : $token = $zipper->next($token)) {\r | |
156 | \r | |
157 | // check for a rewind\r | |
158 | if (is_int($i)) {\r | |
159 | // possibility: disable rewinding if the current token has a\r | |
160 | // rewind set on it already. This would offer protection from\r | |
161 | // infinite loop, but might hinder some advanced rewinding.\r | |
162 | $rewind_offset = $this->injectors[$i]->getRewindOffset();\r | |
163 | if (is_int($rewind_offset)) {\r | |
164 | for ($j = 0; $j < $rewind_offset; $j++) {\r | |
165 | if (empty($zipper->front)) break;\r | |
166 | $token = $zipper->prev($token);\r | |
167 | // indicate that other injectors should not process this token,\r | |
168 | // but we need to reprocess it\r | |
169 | unset($token->skip[$i]);\r | |
170 | $token->rewind = $i;\r | |
171 | if ($token instanceof HTMLPurifier_Token_Start) {\r | |
172 | array_pop($this->stack);\r | |
173 | } elseif ($token instanceof HTMLPurifier_Token_End) {\r | |
174 | $this->stack[] = $token->start;\r | |
175 | }\r | |
176 | }\r | |
177 | }\r | |
178 | $i = false;\r | |
179 | }\r | |
180 | \r | |
181 | // handle case of document end\r | |
182 | if ($token === NULL) {\r | |
183 | // kill processing if stack is empty\r | |
184 | if (empty($this->stack)) {\r | |
185 | break;\r | |
186 | }\r | |
187 | \r | |
188 | // peek\r | |
189 | $top_nesting = array_pop($this->stack);\r | |
190 | $this->stack[] = $top_nesting;\r | |
191 | \r | |
192 | // send error [TagClosedSuppress]\r | |
193 | if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {\r | |
194 | $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);\r | |
195 | }\r | |
196 | \r | |
197 | // append, don't splice, since this is the end\r | |
198 | $token = new HTMLPurifier_Token_End($top_nesting->name);\r | |
199 | \r | |
200 | // punt!\r | |
201 | $reprocess = true;\r | |
202 | continue;\r | |
203 | }\r | |
204 | \r | |
205 | //echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack);\r | |
206 | //flush();\r | |
207 | \r | |
208 | // quick-check: if it's not a tag, no need to process\r | |
209 | if (empty($token->is_tag)) {\r | |
210 | if ($token instanceof HTMLPurifier_Token_Text) {\r | |
211 | foreach ($this->injectors as $i => $injector) {\r | |
212 | if (isset($token->skip[$i])) {\r | |
213 | continue;\r | |
214 | }\r | |
215 | if ($token->rewind !== null && $token->rewind !== $i) {\r | |
216 | continue;\r | |
217 | }\r | |
218 | // XXX fuckup\r | |
219 | $r = $token;\r | |
220 | $injector->handleText($r);\r | |
221 | $token = $this->processToken($r, $i);\r | |
222 | $reprocess = true;\r | |
223 | break;\r | |
224 | }\r | |
225 | }\r | |
226 | // another possibility is a comment\r | |
227 | continue;\r | |
228 | }\r | |
229 | \r | |
230 | if (isset($definition->info[$token->name])) {\r | |
231 | $type = $definition->info[$token->name]->child->type;\r | |
232 | } else {\r | |
233 | $type = false; // Type is unknown, treat accordingly\r | |
234 | }\r | |
235 | \r | |
236 | // quick tag checks: anything that's *not* an end tag\r | |
237 | $ok = false;\r | |
238 | if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {\r | |
239 | // claims to be a start tag but is empty\r | |
240 | $token = new HTMLPurifier_Token_Empty(\r | |
241 | $token->name,\r | |
242 | $token->attr,\r | |
243 | $token->line,\r | |
244 | $token->col,\r | |
245 | $token->armor\r | |
246 | );\r | |
247 | $ok = true;\r | |
248 | } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {\r | |
249 | // claims to be empty but really is a start tag\r | |
250 | // NB: this assignment is required\r | |
251 | $old_token = $token;\r | |
252 | $token = new HTMLPurifier_Token_End($token->name);\r | |
253 | $token = $this->insertBefore(\r | |
254 | new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)\r | |
255 | );\r | |
256 | // punt (since we had to modify the input stream in a non-trivial way)\r | |
257 | $reprocess = true;\r | |
258 | continue;\r | |
259 | } elseif ($token instanceof HTMLPurifier_Token_Empty) {\r | |
260 | // real empty token\r | |
261 | $ok = true;\r | |
262 | } elseif ($token instanceof HTMLPurifier_Token_Start) {\r | |
263 | // start tag\r | |
264 | \r | |
265 | // ...unless they also have to close their parent\r | |
266 | if (!empty($this->stack)) {\r | |
267 | \r | |
268 | // Performance note: you might think that it's rather\r | |
269 | // inefficient, recalculating the autoclose information\r | |
270 | // for every tag that a token closes (since when we\r | |
271 | // do an autoclose, we push a new token into the\r | |
272 | // stream and then /process/ that, before\r | |
273 | // re-processing this token.) But this is\r | |
274 | // necessary, because an injector can make an\r | |
275 | // arbitrary transformations to the autoclosing\r | |
276 | // tokens we introduce, so things may have changed\r | |
277 | // in the meantime. Also, doing the inefficient thing is\r | |
278 | // "easy" to reason about (for certain perverse definitions\r | |
279 | // of "easy")\r | |
280 | \r | |
281 | $parent = array_pop($this->stack);\r | |
282 | $this->stack[] = $parent;\r | |
283 | \r | |
284 | $parent_def = null;\r | |
285 | $parent_elements = null;\r | |
286 | $autoclose = false;\r | |
287 | if (isset($definition->info[$parent->name])) {\r | |
288 | $parent_def = $definition->info[$parent->name];\r | |
289 | $parent_elements = $parent_def->child->getAllowedElements($config);\r | |
290 | $autoclose = !isset($parent_elements[$token->name]);\r | |
291 | }\r | |
292 | \r | |
293 | if ($autoclose && $definition->info[$token->name]->wrap) {\r | |
294 | // Check if an element can be wrapped by another\r | |
295 | // element to make it valid in a context (for\r | |
296 | // example, <ul><ul> needs a <li> in between)\r | |
297 | $wrapname = $definition->info[$token->name]->wrap;\r | |
298 | $wrapdef = $definition->info[$wrapname];\r | |
299 | $elements = $wrapdef->child->getAllowedElements($config);\r | |
300 | if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {\r | |
301 | $newtoken = new HTMLPurifier_Token_Start($wrapname);\r | |
302 | $token = $this->insertBefore($newtoken);\r | |
303 | $reprocess = true;\r | |
304 | continue;\r | |
305 | }\r | |
306 | }\r | |
307 | \r | |
308 | $carryover = false;\r | |
309 | if ($autoclose && $parent_def->formatting) {\r | |
310 | $carryover = true;\r | |
311 | }\r | |
312 | \r | |
313 | if ($autoclose) {\r | |
314 | // check if this autoclose is doomed to fail\r | |
315 | // (this rechecks $parent, which his harmless)\r | |
316 | $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);\r | |
317 | if (!$autoclose_ok) {\r | |
318 | foreach ($this->stack as $ancestor) {\r | |
319 | $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);\r | |
320 | if (isset($elements[$token->name])) {\r | |
321 | $autoclose_ok = true;\r | |
322 | break;\r | |
323 | }\r | |
324 | if ($definition->info[$token->name]->wrap) {\r | |
325 | $wrapname = $definition->info[$token->name]->wrap;\r | |
326 | $wrapdef = $definition->info[$wrapname];\r | |
327 | $wrap_elements = $wrapdef->child->getAllowedElements($config);\r | |
328 | if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {\r | |
329 | $autoclose_ok = true;\r | |
330 | break;\r | |
331 | }\r | |
332 | }\r | |
333 | }\r | |
334 | }\r | |
335 | if ($autoclose_ok) {\r | |
336 | // errors need to be updated\r | |
337 | $new_token = new HTMLPurifier_Token_End($parent->name);\r | |
338 | $new_token->start = $parent;\r | |
339 | // [TagClosedSuppress]\r | |
340 | if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {\r | |
341 | if (!$carryover) {\r | |
342 | $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);\r | |
343 | } else {\r | |
344 | $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);\r | |
345 | }\r | |
346 | }\r | |
347 | if ($carryover) {\r | |
348 | $element = clone $parent;\r | |
349 | // [TagClosedAuto]\r | |
350 | $element->armor['MakeWellFormed_TagClosedError'] = true;\r | |
351 | $element->carryover = true;\r | |
352 | $token = $this->processToken(array($new_token, $token, $element));\r | |
353 | } else {\r | |
354 | $token = $this->insertBefore($new_token);\r | |
355 | }\r | |
356 | } else {\r | |
357 | $token = $this->remove();\r | |
358 | }\r | |
359 | $reprocess = true;\r | |
360 | continue;\r | |
361 | }\r | |
362 | \r | |
363 | }\r | |
364 | $ok = true;\r | |
365 | }\r | |
366 | \r | |
367 | if ($ok) {\r | |
368 | foreach ($this->injectors as $i => $injector) {\r | |
369 | if (isset($token->skip[$i])) {\r | |
370 | continue;\r | |
371 | }\r | |
372 | if ($token->rewind !== null && $token->rewind !== $i) {\r | |
373 | continue;\r | |
374 | }\r | |
375 | $r = $token;\r | |
376 | $injector->handleElement($r);\r | |
377 | $token = $this->processToken($r, $i);\r | |
378 | $reprocess = true;\r | |
379 | break;\r | |
380 | }\r | |
381 | if (!$reprocess) {\r | |
382 | // ah, nothing interesting happened; do normal processing\r | |
383 | if ($token instanceof HTMLPurifier_Token_Start) {\r | |
384 | $this->stack[] = $token;\r | |
385 | } elseif ($token instanceof HTMLPurifier_Token_End) {\r | |
386 | throw new HTMLPurifier_Exception(\r | |
387 | 'Improper handling of end tag in start code; possible error in MakeWellFormed'\r | |
388 | );\r | |
389 | }\r | |
390 | }\r | |
391 | continue;\r | |
392 | }\r | |
393 | \r | |
394 | // sanity check: we should be dealing with a closing tag\r | |
395 | if (!$token instanceof HTMLPurifier_Token_End) {\r | |
396 | throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');\r | |
397 | }\r | |
398 | \r | |
399 | // make sure that we have something open\r | |
400 | if (empty($this->stack)) {\r | |
401 | if ($escape_invalid_tags) {\r | |
402 | if ($e) {\r | |
403 | $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');\r | |
404 | }\r | |
405 | $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));\r | |
406 | } else {\r | |
407 | if ($e) {\r | |
408 | $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');\r | |
409 | }\r | |
410 | $token = $this->remove();\r | |
411 | }\r | |
412 | $reprocess = true;\r | |
413 | continue;\r | |
414 | }\r | |
415 | \r | |
416 | // first, check for the simplest case: everything closes neatly.\r | |
417 | // Eventually, everything passes through here; if there are problems\r | |
418 | // we modify the input stream accordingly and then punt, so that\r | |
419 | // the tokens get processed again.\r | |
420 | $current_parent = array_pop($this->stack);\r | |
421 | if ($current_parent->name == $token->name) {\r | |
422 | $token->start = $current_parent;\r | |
423 | foreach ($this->injectors as $i => $injector) {\r | |
424 | if (isset($token->skip[$i])) {\r | |
425 | continue;\r | |
426 | }\r | |
427 | if ($token->rewind !== null && $token->rewind !== $i) {\r | |
428 | continue;\r | |
429 | }\r | |
430 | $r = $token;\r | |
431 | $injector->handleEnd($r);\r | |
432 | $token = $this->processToken($r, $i);\r | |
433 | $this->stack[] = $current_parent;\r | |
434 | $reprocess = true;\r | |
435 | break;\r | |
436 | }\r | |
437 | continue;\r | |
438 | }\r | |
439 | \r | |
440 | // okay, so we're trying to close the wrong tag\r | |
441 | \r | |
442 | // undo the pop previous pop\r | |
443 | $this->stack[] = $current_parent;\r | |
444 | \r | |
445 | // scroll back the entire nest, trying to find our tag.\r | |
446 | // (feature could be to specify how far you'd like to go)\r | |
447 | $size = count($this->stack);\r | |
448 | // -2 because -1 is the last element, but we already checked that\r | |
449 | $skipped_tags = false;\r | |
450 | for ($j = $size - 2; $j >= 0; $j--) {\r | |
451 | if ($this->stack[$j]->name == $token->name) {\r | |
452 | $skipped_tags = array_slice($this->stack, $j);\r | |
453 | break;\r | |
454 | }\r | |
455 | }\r | |
456 | \r | |
457 | // we didn't find the tag, so remove\r | |
458 | if ($skipped_tags === false) {\r | |
459 | if ($escape_invalid_tags) {\r | |
460 | if ($e) {\r | |
461 | $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');\r | |
462 | }\r | |
463 | $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));\r | |
464 | } else {\r | |
465 | if ($e) {\r | |
466 | $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');\r | |
467 | }\r | |
468 | $token = $this->remove();\r | |
469 | }\r | |
470 | $reprocess = true;\r | |
471 | continue;\r | |
472 | }\r | |
473 | \r | |
474 | // do errors, in REVERSE $j order: a,b,c with </a></b></c>\r | |
475 | $c = count($skipped_tags);\r | |
476 | if ($e) {\r | |
477 | for ($j = $c - 1; $j > 0; $j--) {\r | |
478 | // notice we exclude $j == 0, i.e. the current ending tag, from\r | |
479 | // the errors... [TagClosedSuppress]\r | |
480 | if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {\r | |
481 | $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);\r | |
482 | }\r | |
483 | }\r | |
484 | }\r | |
485 | \r | |
486 | // insert tags, in FORWARD $j order: c,b,a with </a></b></c>\r | |
487 | $replace = array($token);\r | |
488 | for ($j = 1; $j < $c; $j++) {\r | |
489 | // ...as well as from the insertions\r | |
490 | $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);\r | |
491 | $new_token->start = $skipped_tags[$j];\r | |
492 | array_unshift($replace, $new_token);\r | |
493 | if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {\r | |
494 | // [TagClosedAuto]\r | |
495 | $element = clone $skipped_tags[$j];\r | |
496 | $element->carryover = true;\r | |
497 | $element->armor['MakeWellFormed_TagClosedError'] = true;\r | |
498 | $replace[] = $element;\r | |
499 | }\r | |
500 | }\r | |
501 | $token = $this->processToken($replace);\r | |
502 | $reprocess = true;\r | |
503 | continue;\r | |
504 | }\r | |
505 | \r | |
506 | $context->destroy('CurrentToken');\r | |
507 | $context->destroy('CurrentNesting');\r | |
508 | $context->destroy('InputZipper');\r | |
509 | \r | |
510 | unset($this->injectors, $this->stack, $this->tokens);\r | |
511 | return $zipper->toArray($token);\r | |
512 | }\r | |
513 | \r | |
514 | /**\r | |
515 | * Processes arbitrary token values for complicated substitution patterns.\r | |
516 | * In general:\r | |
517 | *\r | |
518 | * If $token is an array, it is a list of tokens to substitute for the\r | |
519 | * current token. These tokens then get individually processed. If there\r | |
520 | * is a leading integer in the list, that integer determines how many\r | |
521 | * tokens from the stream should be removed.\r | |
522 | *\r | |
523 | * If $token is a regular token, it is swapped with the current token.\r | |
524 | *\r | |
525 | * If $token is false, the current token is deleted.\r | |
526 | *\r | |
527 | * If $token is an integer, that number of tokens (with the first token\r | |
528 | * being the current one) will be deleted.\r | |
529 | *\r | |
530 | * @param HTMLPurifier_Token|array|int|bool $token Token substitution value\r | |
531 | * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if\r | |
532 | * this is not an injector related operation.\r | |
533 | * @throws HTMLPurifier_Exception\r | |
534 | */\r | |
535 | protected function processToken($token, $injector = -1)\r | |
536 | {\r | |
537 | // normalize forms of token\r | |
538 | if (is_object($token)) {\r | |
539 | $token = array(1, $token);\r | |
540 | }\r | |
541 | if (is_int($token)) {\r | |
542 | $token = array($token);\r | |
543 | }\r | |
544 | if ($token === false) {\r | |
545 | $token = array(1);\r | |
546 | }\r | |
547 | if (!is_array($token)) {\r | |
548 | throw new HTMLPurifier_Exception('Invalid token type from injector');\r | |
549 | }\r | |
550 | if (!is_int($token[0])) {\r | |
551 | array_unshift($token, 1);\r | |
552 | }\r | |
553 | if ($token[0] === 0) {\r | |
554 | throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');\r | |
555 | }\r | |
556 | \r | |
557 | // $token is now an array with the following form:\r | |
558 | // array(number nodes to delete, new node 1, new node 2, ...)\r | |
559 | \r | |
560 | $delete = array_shift($token);\r | |
561 | list($old, $r) = $this->zipper->splice($this->token, $delete, $token);\r | |
562 | \r | |
563 | if ($injector > -1) {\r | |
564 | // determine appropriate skips\r | |
565 | $oldskip = isset($old[0]) ? $old[0]->skip : array();\r | |
566 | foreach ($token as $object) {\r | |
567 | $object->skip = $oldskip;\r | |
568 | $object->skip[$injector] = true;\r | |
569 | }\r | |
570 | }\r | |
571 | \r | |
572 | return $r;\r | |
573 | \r | |
574 | }\r | |
575 | \r | |
576 | /**\r | |
577 | * Inserts a token before the current token. Cursor now points to\r | |
578 | * this token. You must reprocess after this.\r | |
579 | * @param HTMLPurifier_Token $token\r | |
580 | */\r | |
581 | private function insertBefore($token)\r | |
582 | {\r | |
583 | // NB not $this->zipper->insertBefore(), due to positioning\r | |
584 | // differences\r | |
585 | $splice = $this->zipper->splice($this->token, 0, array($token));\r | |
586 | \r | |
587 | return $splice[1];\r | |
588 | }\r | |
589 | \r | |
590 | /**\r | |
591 | * Removes current token. Cursor now points to new token occupying previously\r | |
592 | * occupied space. You must reprocess after this.\r | |
593 | */\r | |
594 | private function remove()\r | |
595 | {\r | |
596 | return $this->zipper->delete();\r | |
597 | }\r | |
598 | }\r | |
599 | \r | |
600 | // vim: et sw=4 sts=4\r |