1: <?php
2: namespace Pharborist;
3:
4: 5: 6:
7: abstract class ParentNode extends Node implements ParentNodeInterface {
8: 9: 10:
11: protected $head;
12:
13: 14: 15:
16: protected $tail;
17:
18: 19: 20:
21: protected $childCount = 0;
22:
23: protected function getProperties() {
24: $properties = get_object_vars($this);
25: unset($properties['head']);
26: unset($properties['tail']);
27: unset($properties['childCount']);
28: unset($properties['parent']);
29: unset($properties['previous']);
30: unset($properties['next']);
31: return $properties;
32: }
33:
34: public function isEmpty() {
35: return $this->childCount === 0;
36: }
37:
38: public function childCount() {
39: return $this->childCount;
40: }
41:
42: public function firstChild() {
43: return $this->head;
44: }
45:
46: public function lastChild() {
47: return $this->tail;
48: }
49:
50: 51: 52: 53: 54:
55: protected function childrenByInstance($class_name) {
56: $matches = [];
57: $child = $this->head;
58: while ($child) {
59: if ($child instanceof $class_name) {
60: $matches[] = $child;
61: }
62: $child = $child->next;
63: }
64: return $matches;
65: }
66:
67: public function children(callable $callback = NULL) {
68: $matches = [];
69: $child = $this->head;
70: while ($child) {
71: if ($callback === NULL || $callback($child)) {
72: $matches[] = $child;
73: }
74: $child = $child->next;
75: }
76: return new NodeCollection($matches, FALSE);
77: }
78:
79: public function clear() {
80: $this->head = $this->tail = NULL;
81: }
82:
83: 84: 85: 86:
87: protected function childInserted(Node $node) {
88:
89: }
90:
91: 92: 93: 94: 95:
96: protected function prependChild(Node $node) {
97: if ($this->head === NULL) {
98: $this->childCount++;
99: $node->parent = $this;
100: $node->previous = NULL;
101: $node->next = NULL;
102: $this->head = $this->tail = $node;
103: $this->childInserted($node);
104: }
105: else {
106: $this->insertBeforeChild($this->head, $node);
107: $this->head = $node;
108: }
109: return $this;
110: }
111:
112: public function prepend($nodes) {
113: if ($nodes instanceof Node) {
114: $this->prependChild($nodes);
115: }
116: elseif ($nodes instanceof NodeCollection) {
117: foreach ($nodes->reverse() as $node) {
118: $this->prependChild($node);
119: }
120: }
121: elseif (is_array($nodes)) {
122: foreach (array_reverse($nodes) as $node) {
123: $this->prependChild($node);
124: }
125: }
126: else {
127: throw new \InvalidArgumentException();
128: }
129: return $this;
130: }
131:
132: 133: 134: 135: 136:
137: protected function appendChild(Node $node) {
138: if ($this->tail === NULL) {
139: $this->prependChild($node);
140: }
141: else {
142: $this->insertAfterChild($this->tail, $node);
143: $this->tail = $node;
144: }
145: return $this;
146: }
147:
148: 149: 150: 151: 152: 153: 154: 155: 156:
157: public function addChild(Node $node, $property_name = NULL) {
158: $this->appendChild($node);
159: if ($property_name !== NULL) {
160: $this->{$property_name} = $node;
161: }
162: return $this;
163: }
164:
165: 166: 167: 168: 169: 170: 171:
172: public function addChildren(array $children) {
173: foreach ($children as $child) {
174: $this->appendChild($child);
175: }
176: }
177:
178: 179: 180: 181:
182: public function mergeNode(ParentNode $node) {
183: $child = $node->head;
184: while ($child) {
185: $next = $child->next;
186: $this->appendChild($child);
187: $child = $next;
188: }
189: foreach ($node->getProperties() as $name => $value) {
190: $this->{$name} = $value;
191: }
192: }
193:
194: public function append($nodes) {
195: if ($nodes instanceof Node) {
196: $this->appendChild($nodes);
197: }
198: elseif ($nodes instanceof NodeCollection || is_array($nodes)) {
199: foreach ($nodes as $node) {
200: $this->appendChild($node);
201: }
202: }
203: else {
204: throw new \InvalidArgumentException();
205: }
206: return $this;
207: }
208:
209: 210: 211: 212: 213: 214:
215: protected function insertBeforeChild(Node $child, Node $node) {
216: $this->childCount++;
217: $node->parent = $this;
218: if ($child->previous === NULL) {
219: $this->head = $node;
220: }
221: else {
222: $child->previous->next = $node;
223: }
224: $node->previous = $child->previous;
225: $node->next = $child;
226: $child->previous = $node;
227: $this->childInserted($node);
228: return $this;
229: }
230:
231: 232: 233: 234: 235: 236:
237: protected function insertAfterChild(Node $child, Node $node) {
238: $this->childCount++;
239: $node->parent = $this;
240: if ($child->next === NULL) {
241: $this->tail = $node;
242: }
243: else {
244: $child->next->previous = $node;
245: }
246: $node->previous = $child;
247: $node->next = $child->next;
248: $child->next = $node;
249: $this->childInserted($node);
250: return $this;
251: }
252:
253: 254: 255: 256: 257:
258: protected function removeChild(Node $child) {
259: $this->childCount--;
260: foreach ($this->getProperties() as $name => $value) {
261: if ($child === $value) {
262: $this->{$name} = NULL;
263: break;
264: }
265: }
266: if ($child->previous === NULL) {
267: $this->head = $child->next;
268: }
269: else {
270: $child->previous->next = $child->next;
271: }
272: if ($child->next === NULL) {
273: $this->tail = $child->previous;
274: }
275: else {
276: $child->next->previous = $child->previous;
277: }
278: $child->parent = NULL;
279: $child->previous = NULL;
280: $child->next = NULL;
281: return $this;
282: }
283:
284: 285: 286: 287: 288: 289:
290: protected function replaceChild(Node $child, Node $replacement) {
291: foreach ($this->getProperties() as $name => $value) {
292: if ($child === $value) {
293: $this->{$name} = $replacement;
294: break;
295: }
296: }
297: $replacement->parent = $this;
298: $replacement->previous = $child->previous;
299: $replacement->next = $child->next;
300: if ($child->previous === NULL) {
301: $this->head = $replacement;
302: }
303: else {
304: $child->previous->next = $replacement;
305: }
306: if ($child->next === NULL) {
307: $this->tail = $replacement;
308: }
309: else {
310: $child->next->previous = $replacement;
311: }
312: $child->parent = NULL;
313: $child->previous = NULL;
314: $child->next = NULL;
315: return $this;
316: }
317:
318: 319: 320: 321:
322: public function firstToken() {
323: $head = $this->head;
324: while ($head instanceof ParentNode) {
325: $head = $head->head;
326: }
327: return $head;
328: }
329:
330: 331: 332: 333:
334: public function lastToken() {
335: $tail = $this->tail;
336: while ($tail instanceof ParentNode) {
337: $tail = $tail->tail;
338: }
339: return $tail;
340: }
341:
342: public function has(callable $callback) {
343: $child = $this->head;
344: while ($child) {
345: if ($child instanceof ParentNode && $child->has($callback)) {
346: return TRUE;
347: }
348: elseif ($callback($child)) {
349: return TRUE;
350: }
351: $child = $child->next;
352: }
353: return FALSE;
354: }
355:
356: public function isDescendant(Node $node) {
357: $parent = $node->parent;
358: while ($parent) {
359: if ($parent === $this) {
360: return TRUE;
361: }
362: $parent = $parent->parent;
363: }
364: return FALSE;
365: }
366:
367: private function _find(&$matches, callable $callback) {
368: $child = $this->head;
369: while ($child) {
370: if ($callback($child)) {
371: $matches[] = $child;
372: }
373: if ($child instanceof ParentNode) {
374: $child->_find($matches, $callback);
375: }
376: $child = $child->next;
377: }
378: }
379:
380: public function find(callable $callback) {
381: $matches = [];
382: $this->_find($matches, $callback);
383: return new NodeCollection($matches, FALSE);
384: }
385:
386: public function walk(callable $callback) {
387: $callback($this);
388: $child = $this->head;
389: while($child) {
390: if($child instanceof ParentNode) {
391: $child->walk($callback);
392: }
393: else {
394: $callback($child);
395: }
396: $child = $child->next;
397: }
398: }
399:
400: public function acceptVisitor(VisitorInterface $visitor) {
401: $visitor->visit($this);
402: $child = $this->head;
403: while($child) {
404: if($child instanceof ParentNode) {
405: $child->acceptVisitor($visitor);
406: }
407: else {
408: $visitor->visit($child);
409: }
410: $child = $child->next;
411: }
412: $visitor->visitEnd($this);
413: }
414:
415: public function getSourcePosition() {
416: if ($this->head === NULL) {
417: return $this->parent->getSourcePosition();
418: }
419: $child = $this->head;
420: return $child->getSourcePosition();
421: }
422:
423: public function __clone() {
424:
425: $this->parent = NULL;
426: $this->previous = NULL;
427: $this->next = NULL;
428: $properties = $this->getProperties();
429: $children = [];
430: $child = $this->head;
431: while ($child) {
432: $key = array_search($child, $properties, TRUE);
433: if ($key !== FALSE) {
434: $children[$key] = clone $child;
435: }
436: else {
437: $children[] = clone $child;
438: }
439: $child = $child->next;
440: }
441: $keys = array_keys($children);
442: $this->head = empty($children) ? NULL : $children[$keys[0]];
443: $this->tail = $this->head;
444:
445: $prev = NULL;
446: foreach ($children as $key => $child) {
447: if (!is_int($key)) {
448: $this->{$key} = $child;
449: }
450: $this->tail = $child;
451: $child->parent = $this;
452: $child->previous = $prev;
453: if ($prev) {
454: $prev->next = $child;
455: }
456: $prev = $child;
457: }
458: }
459:
460: public function getText() {
461: $str = '';
462: $child = $this->head;
463: while ($child) {
464: $str .= $child->getText();
465: $child = $child->next;
466: }
467: return $str;
468: }
469:
470: public function __toString() {
471: return $this->getText();
472: }
473:
474: 475: 476: 477: 478:
479: public function getTree() {
480: $children = array();
481: $properties = $this->getProperties();
482: $child = $this->head;
483: $i = 0;
484: while ($child) {
485: $key = array_search($child, $properties, TRUE);
486: if (!$key) {
487: $key = $i;
488: }
489: if ($child instanceof ParentNode) {
490: $children[$key] = $child->getTree();
491: }
492: else {
493:
494: $children[$key] = array($child->getTypeName() => $child->getText());
495: }
496: $child = $child->next;
497: $i++;
498: }
499: $class_name = get_class($this);
500: return array($class_name => $children);
501: }
502: }
503: