File indexing completed on 2024-12-22 05:36:22

0001 <?php
0002 
0003 /**
0004  * A zipper is a purely-functional data structure which contains
0005  * a focus that can be efficiently manipulated.  It is known as
0006  * a "one-hole context".  This mutable variant implements a zipper
0007  * for a list as a pair of two arrays, laid out as follows:
0008  *
0009  *      Base list: 1 2 3 4 [ ] 6 7 8 9
0010  *      Front list: 1 2 3 4
0011  *      Back list: 9 8 7 6
0012  *
0013  * User is expected to keep track of the "current element" and properly
0014  * fill it back in as necessary.  (ToDo: Maybe it's more user friendly
0015  * to implicitly track the current element?)
0016  *
0017  * Nota bene: the current class gets confused if you try to store NULLs
0018  * in the list.
0019  */
0020 
0021 class HTMLPurifier_Zipper
0022 {
0023     public $front, $back;
0024 
0025     public function __construct($front, $back) {
0026         $this->front = $front;
0027         $this->back = $back;
0028     }
0029 
0030     /**
0031      * Creates a zipper from an array, with a hole in the
0032      * 0-index position.
0033      * @param Array to zipper-ify.
0034      * @return Tuple of zipper and element of first position.
0035      */
0036     static public function fromArray($array) {
0037         $z = new self(array(), array_reverse($array));
0038         $t = $z->delete(); // delete the "dummy hole"
0039         return array($z, $t);
0040     }
0041 
0042     /**
0043      * Convert zipper back into a normal array, optionally filling in
0044      * the hole with a value. (Usually you should supply a $t, unless you
0045      * are at the end of the array.)
0046      */
0047     public function toArray($t = NULL) {
0048         $a = $this->front;
0049         if ($t !== NULL) $a[] = $t;
0050         for ($i = count($this->back)-1; $i >= 0; $i--) {
0051             $a[] = $this->back[$i];
0052         }
0053         return $a;
0054     }
0055 
0056     /**
0057      * Move hole to the next element.
0058      * @param $t Element to fill hole with
0059      * @return Original contents of new hole.
0060      */
0061     public function next($t) {
0062         if ($t !== NULL) array_push($this->front, $t);
0063         return empty($this->back) ? NULL : array_pop($this->back);
0064     }
0065 
0066     /**
0067      * Iterated hole advancement.
0068      * @param $t Element to fill hole with
0069      * @param $i How many forward to advance hole
0070      * @return Original contents of new hole, i away
0071      */
0072     public function advance($t, $n) {
0073         for ($i = 0; $i < $n; $i++) {
0074             $t = $this->next($t);
0075         }
0076         return $t;
0077     }
0078 
0079     /**
0080      * Move hole to the previous element
0081      * @param $t Element to fill hole with
0082      * @return Original contents of new hole.
0083      */
0084     public function prev($t) {
0085         if ($t !== NULL) array_push($this->back, $t);
0086         return empty($this->front) ? NULL : array_pop($this->front);
0087     }
0088 
0089     /**
0090      * Delete contents of current hole, shifting hole to
0091      * next element.
0092      * @return Original contents of new hole.
0093      */
0094     public function delete() {
0095         return empty($this->back) ? NULL : array_pop($this->back);
0096     }
0097 
0098     /**
0099      * Returns true if we are at the end of the list.
0100      * @return bool
0101      */
0102     public function done() {
0103         return empty($this->back);
0104     }
0105 
0106     /**
0107      * Insert element before hole.
0108      * @param Element to insert
0109      */
0110     public function insertBefore($t) {
0111         if ($t !== NULL) array_push($this->front, $t);
0112     }
0113 
0114     /**
0115      * Insert element after hole.
0116      * @param Element to insert
0117      */
0118     public function insertAfter($t) {
0119         if ($t !== NULL) array_push($this->back, $t);
0120     }
0121 
0122     /**
0123      * Splice in multiple elements at hole.  Functional specification
0124      * in terms of array_splice:
0125      *
0126      *      $arr1 = $arr;
0127      *      $old1 = array_splice($arr1, $i, $delete, $replacement);
0128      *
0129      *      list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
0130      *      $t = $z->advance($t, $i);
0131      *      list($old2, $t) = $z->splice($t, $delete, $replacement);
0132      *      $arr2 = $z->toArray($t);
0133      *
0134      *      assert($old1 === $old2);
0135      *      assert($arr1 === $arr2);
0136      *
0137      * NB: the absolute index location after this operation is
0138      * *unchanged!*
0139      *
0140      * @param Current contents of hole.
0141      */
0142     public function splice($t, $delete, $replacement) {
0143         // delete
0144         $old = array();
0145         $r = $t;
0146         for ($i = $delete; $i > 0; $i--) {
0147             $old[] = $r;
0148             $r = $this->delete();
0149         }
0150         // insert
0151         for ($i = count($replacement)-1; $i >= 0; $i--) {
0152             $this->insertAfter($r);
0153             $r = $replacement[$i];
0154         }
0155         return array($old, $r);
0156     }
0157 }