mirror of
https://github.com/ezyang/htmlpurifier.git
synced 2024-12-22 08:21:52 +00:00
Implementation of a Zipper, for efficient splice.
Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
This commit is contained in:
parent
a5fc37d8c3
commit
f17490f009
@ -73,6 +73,7 @@ require 'HTMLPurifier/URISchemeRegistry.php';
|
||||
require 'HTMLPurifier/UnitConverter.php';
|
||||
require 'HTMLPurifier/VarParser.php';
|
||||
require 'HTMLPurifier/VarParserException.php';
|
||||
require 'HTMLPurifier/Zipper.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS.php';
|
||||
require 'HTMLPurifier/AttrDef/Clone.php';
|
||||
require 'HTMLPurifier/AttrDef/Enum.php';
|
||||
|
@ -67,6 +67,7 @@ require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php';
|
||||
require_once $__dir . '/HTMLPurifier/UnitConverter.php';
|
||||
require_once $__dir . '/HTMLPurifier/VarParser.php';
|
||||
require_once $__dir . '/HTMLPurifier/VarParserException.php';
|
||||
require_once $__dir . '/HTMLPurifier/Zipper.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/Clone.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php';
|
||||
|
145
library/HTMLPurifier/Zipper.php
Normal file
145
library/HTMLPurifier/Zipper.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A zipper is a purely-functional data structure which contains
|
||||
* a focus that can be efficiently manipulated. It is known as
|
||||
* a "one-hole context". This mutable variant implements a zipper
|
||||
* for a list as a pair of two arrays, laid out as follows:
|
||||
*
|
||||
* Base list: 1 2 3 4 [ ] 6 7 8 9
|
||||
* Front list: 1 2 3 4
|
||||
* Back list: 9 8 7 6
|
||||
*
|
||||
* User is expected to keep track of the "current element" and properly
|
||||
* fill it back in as necessary. (ToDo: Maybe it's more user friendly
|
||||
* to implicitly track the current element?)
|
||||
*
|
||||
* Nota bene: the current class gets confused if you try to store NULLs
|
||||
* in the list.
|
||||
*/
|
||||
|
||||
class HTMLPurifier_Zipper
|
||||
{
|
||||
private $front, $back;
|
||||
|
||||
public function __construct($front, $back) {
|
||||
$this->front = $front;
|
||||
$this->back = $back;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a zipper from an array, with a hole in the
|
||||
* 0-index position.
|
||||
* @param Array to zipper-ify.
|
||||
* @return Tuple of zipper and element of first position.
|
||||
*/
|
||||
static public function fromArray($array) {
|
||||
$z = new self(array(), array_reverse($array));
|
||||
$t = $z->delete(); // delete the "dummy hole"
|
||||
return array($z, $t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert zipper back into a normal array, optionally filling in
|
||||
* the hole with a value. (Usually you should supply a $t, unless you
|
||||
* are at the end of the array.)
|
||||
*/
|
||||
public function toArray($t = NULL) {
|
||||
$a = $this->front;
|
||||
if ($t !== NULL) $a[] = $t;
|
||||
for ($i = count($this->back)-1; $i >= 0; $i--) {
|
||||
$a[] = $this->back[$i];
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move hole to the next element.
|
||||
* @param $t Element to fill hole with
|
||||
* @return Original contents of new hole.
|
||||
*/
|
||||
public function next($t) {
|
||||
if ($t !== NULL) array_push($this->front, $t);
|
||||
return empty($this->back) ? NULL : array_pop($this->back);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterated hole advancement.
|
||||
* @param $t Element to fill hole with
|
||||
* @param $i How many forward to advance hole
|
||||
* @return Original contents of new hole, i away
|
||||
*/
|
||||
public function advance($t, $n) {
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$t = $this->next($t);
|
||||
}
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move hole to the previous element
|
||||
* @param $t Element to fill hole with
|
||||
* @return Original contents of new hole.
|
||||
*/
|
||||
public function prev($t) {
|
||||
if ($t !== NULL) array_push($this->back, $t);
|
||||
return empty($this->front) ? NULL : array_pop($this->front);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete contents of current hole, shifting hole to
|
||||
* next element.
|
||||
* @return Original contents of new hole.
|
||||
*/
|
||||
public function delete() {
|
||||
return empty($this->back) ? NULL : array_pop($this->back);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert element before hole.
|
||||
* @param Element to insert
|
||||
*/
|
||||
public function insertBefore($t) {
|
||||
if ($t !== NULL) array_push($this->front, $t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert element after hole.
|
||||
* @param Element to insert
|
||||
*/
|
||||
public function insertAfter($t) {
|
||||
if ($t !== NULL) array_push($this->back, $t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splice in multiple elements at hole. Functional specification
|
||||
* in terms of array_splice:
|
||||
*
|
||||
* $r1 = array_splice($arr, $i, $delete, $replacement);
|
||||
*
|
||||
* list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
|
||||
* $t = $z->advance($t, $i);
|
||||
* $t = $z->splice($t, $delete, $replacement);
|
||||
* $r2 = $z->toArray($t);
|
||||
*
|
||||
* assert($r1 === $r2);
|
||||
*
|
||||
* NB: the absolute index location after this operation is
|
||||
* *unchanged!*
|
||||
*
|
||||
* @param Current contents of hole.
|
||||
*/
|
||||
public function splice($t, $delete, $replacement) {
|
||||
// delete
|
||||
$r = $t;
|
||||
for ($i = $delete; $i > 0; $i--) {
|
||||
$r = $this->delete();
|
||||
}
|
||||
// insert
|
||||
for ($i = count($replacement)-1; $i >= 0; $i--) {
|
||||
$this->insertAfter($r);
|
||||
$r = $replacement[$i];
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
}
|
25
tests/HTMLPurifier/ZipperTest.php
Normal file
25
tests/HTMLPurifier/ZipperTest.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ZipperTest extends HTMLPurifier_Harness
|
||||
{
|
||||
public function testBasicNavigation() {
|
||||
list($z, $t) = HTMLPurifier_Zipper::fromArray(array(0,1,2,3));
|
||||
$this->assertIdentical($t, 0);
|
||||
$t = $z->next($t);
|
||||
$this->assertIdentical($t, 1);
|
||||
$t = $z->prev($t);
|
||||
$this->assertIdentical($t, 0);
|
||||
$t = $z->advance($t, 2);
|
||||
$this->assertIdentical($t, 2);
|
||||
$t = $z->delete();
|
||||
$this->assertIdentical($t, 3);
|
||||
$z->insertBefore(4);
|
||||
$z->insertAfter(5);
|
||||
$this->assertIdentical($z->toArray($t), array(0,1,4,3,5));
|
||||
$t = $z->splice($t, 2, array(6,7));
|
||||
$this->assertIdentical($t, 6);
|
||||
$this->assertIdentical($z->toArray($t), array(0,1,4,6,7));
|
||||
}
|
||||
|
||||
// ToDo: QuickCheck style test comparing with array_splice
|
||||
}
|
Loading…
Reference in New Issue
Block a user