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 }