Hoa central
Xyl.php
Go to the documentation of this file.
1 <?php
2 
37 namespace Hoa\Xyl;
38 
39 use Hoa\Core;
40 use Hoa\Locale;
41 use Hoa\Router;
42 use Hoa\Stream;
43 use Hoa\Translate;
44 use Hoa\View;
45 use Hoa\Xml;
46 
55 class Xyl
56  extends Xml
57  implements Element,
60 {
66  const NAMESPACE_ID = 'http://hoa-project.net/xyl/xylophone';
67 
73  const TYPE_DOCUMENT = 0;
74 
80  const TYPE_DEFINITION = 1;
81 
87  const TYPE_OVERLAY = 2;
88 
94  const TYPE_FRAGMENT = 4;
95 
101  const SELECTOR_PATH = 0;
102 
108  const SELECTOR_QUERY = 1;
109 
115  const SELECTOR_XPATH = 2;
116 
122  const SELECTOR_FILE = 4;
123 
129  protected static $_parameters = null;
130 
136  protected $_data = null;
137 
143  protected $_isDataComputed = false;
144 
150  protected $_concrete = null;
151 
157  protected static $_xe = null;
158 
164  protected $_out = null;
165 
171  protected $_interpreter = null;
172 
178  protected $_router = null;
179 
186  protected $_mowgli = null;
187 
193  protected $_type = null;
194 
200  protected $_stylesheets = [];
201 
207  protected $_metas = [];
208 
214  protected $_overlays = [];
215 
221  protected $_fragments = [];
222 
228  protected $_locale = null;
229 
235  protected $_translations = [];
236 
242  private $_i = 0;
243 
249  private static $_ci = 0;
250 
257  private $_innerOpen = false;
258 
259 
260 
276  public function __construct(
277  Stream\IStream\In $in,
278  Stream\IStream\Out $out,
279  Interpreter $interpreter,
280  Router\Http $router = null,
281  $entityResolver = null,
282  Array $parameters = []
283  ) {
284  parent::__construct(
285  '\Hoa\Xyl\Element\Basic',
286  $in,
287  true,
288  $entityResolver
289  );
290 
291  if (false === $this->namespaceExists(self::NAMESPACE_ID)) {
292  throw new Exception(
293  'The XYL file %s has no XYL namespace (%s) declared.',
294  0,
295  [$in->getStreamName(), self::NAMESPACE_ID]
296  );
297  }
298 
299  if (null === self::$_parameters) {
300  self::$_parameters = new Core\Parameter(
301  $this,
302  [
303  'theme' => 'classic'
304  ],
305  [
306  'theme' => '(:theme:lU:)'
307  ]
308  );
309  $this->getParameters()->setParameters($parameters);
310  }
311 
312  $this->_i = self::$_ci++;
313  $this->_data = new Core\Data();
314  $this->_out = $out;
315  $this->_interpreter = $interpreter;
316  $this->_router = $router;
317  $this->_mowgli = $this->getStream()->readDOM()->ownerDocument;
318 
319  switch (strtolower($this->getName())) {
320  case 'document':
321  $this->_type = self::TYPE_DOCUMENT;
322 
323  break;
324 
325  case 'definition':
326  $this->_type = self::TYPE_DEFINITION;
327 
328  break;
329 
330  case 'overlay':
331  $this->_type = self::TYPE_OVERLAY;
332 
333  break;
334 
335  case 'fragment':
336  $this->_type = self::TYPE_FRAGMENT;
337 
338  break;
339 
340  default:
341  throw new Exception('Unknown document <%s>.', 1, $this->getName());
342  }
343 
344  $this->useNamespace(self::NAMESPACE_ID);
345  $protocol = Core::getInstance()->getProtocol();
346  $protocol['Library'][] = new _Protocol(
347  'Xyl[' . $this->_i . ']',
348  'Xyl' . DS . 'Interpreter' . DS . $this->_interpreter->getResourcePath()
349  );
350 
351  if (null !== $router && false === $router->ruleExists('_resource')) {
352  $router->_get('_resource', '/(?<theme>)/(?<resource>)');
353  }
354 
355  return;
356  }
357 
363  public function getParameters()
364  {
365  return self::$_parameters;
366  }
367 
373  public function getData()
374  {
375  return $this->_data;
376  }
377 
384  public function setOutputStream(Stream\IStream\Out $out)
385  {
386  $old = $this->_out;
387  $this->_out = $out;
388 
389  return $old;
390  }
391 
397  public function getOutputStream()
398  {
399  return $this->_out;
400  }
401 
408  public function getType()
409  {
410  return $this->_type;
411  }
412 
419  public function getTypeAsString()
420  {
421  return strtolower($this->getName());
422  }
423 
429  public function addUse($href)
430  {
431  $this->_mowgli->insertBefore(
432  new \DOMProcessingInstruction(
433  'xyl-use',
434  'href="' . str_replace('"', '\"', $href) . '"'
435  ),
436  $this->_mowgli->documentElement
437  );
438 
439  return;
440  }
441 
452  protected function computeUse(
453  \DOMDocument $ownerDocument = null,
454  \DOMDocument $receiptDocument = null,
455  Xyl $self = null
456  ) {
457  if (null === $ownerDocument) {
458  $ownerDocument = $this->_mowgli;
459  }
460 
461  if (null === $receiptDocument) {
462  $receiptDocument = $this->_mowgli;
463  }
464 
465  if (null === $self) {
466  $self = $this;
467  }
468 
469  $streamClass = get_class($self->getInnerStream());
470  $dirname = dirname($self->getInnerStream()->getStreamName());
471  $type = $this->getType();
472  $remove = self::TYPE_DOCUMENT == $type || self::TYPE_FRAGMENT == $type;
473  $hrefs = [];
474  $uses = [];
475  $xpath = new \DOMXPath($ownerDocument);
476  $xyl_use = $xpath->query('/processing-instruction(\'xyl-use\')');
477  unset($xpath);
478 
479  $this->computeStylesheet($ownerDocument);
480  $this->computeMeta($ownerDocument);
481 
482  if (0 === $xyl_use->length) {
483  return false;
484  }
485 
486  for ($i = 0, $m = $xyl_use->length; $i < $m; ++$i) {
487  $item = $xyl_use->item($i);
488  $use = $item;
489  $remove and $ownerDocument->removeChild($item);
490  $useParsed = new Xml\Attribute($use->data);
491 
492  if (false === $useParsed->attributeExists('href')) {
493  unset($useParsed);
494 
495  continue;
496  }
497 
498  $href = $this->computeLink($useParsed->readAttribute('href'), true);
499  unset($useParsed);
500 
501  if (0 === preg_match('#^(([^:]+://)|([A-Z]:)|/)#', $href)) {
502  $href = $dirname . DS . $href;
503  }
504 
505  if (false === file_exists($href)) {
506  throw new Exception('File %s is not found, cannot use it.', 2, $href);
507  }
508 
509  if (true === in_array($href, $hrefs)) {
510  continue;
511  }
512 
513  $hrefs[] = $href;
514  $fragment = new static(
515  new $streamClass($href),
516  $this->getOutputStream(),
519  );
520 
521  if (self::TYPE_DEFINITION != $fragment->getType()) {
522  throw new Exception(
523  '%s must only contain <definition> of <yield> (and some ' .
524  '<?xyl-use) elements.',
525  3,
526  $href
527  );
528  }
529 
530  foreach ($fragment->xpath('//__current_ns:yield[@name]') as $yield) {
531  $receiptDocument->documentElement->appendChild(
532  $receiptDocument->importNode($yield->readDOM(), true)
533  );
534  }
535 
536  $fragment->computeUse(
537  $fragment->readDOM()->ownerDocument,
538  $receiptDocument,
539  $fragment
540  );
541  $this->_stylesheets = array_merge(
542  $this->_stylesheets,
543  $fragment->getStylesheets()
544  );
545  }
546 
547  return true;
548  }
549 
555  protected function computeYielder()
556  {
557  $stream = $this->getStream();
558  $streamClass = get_class($this->getInnerStream());
559  $openedFragments = [];
560  $type = $this->getType();
561  $remove = self::TYPE_DOCUMENT == $type ||
562  self::TYPE_FRAGMENT == $type;
563 
564  foreach ($stream->xpath('//__current_ns:yield[@select]') as $yield) {
565  $yieldomized = $yield->readDOM();
566  $select = $yield->readAttribute('select');
567 
568  if (self::SELECTOR_FILE != static::getSelector($select, $matches)) {
569  continue;
570  }
571 
572  if (empty($matches[2])) {
573  throw new Exception(
574  'Fragment selection %s is incomplet, an ID ' .
575  'must be specified.',
576  4,
577  $select
578  );
579  }
580 
581  list(, $as, $sId) = $matches;
582 
583  if (!isset($this->_fragments[$as])) {
584  throw new Exception(
585  'Fragment alias %s in selector %s does not exist.',
586  5,
587  [$as, $select]
588  );
589  }
590 
591  $href = $this->_fragments[$as];
592 
593  if (!isset($openedFragments[$href])) {
594  $openedFragments[$href] = new static(
595  new $streamClass($href),
596  $this->getOutputStream(),
599  );
600  }
601 
602  $fragment = $openedFragments[$href];
603  $snippet = $fragment->xpath(
604  '//__current_ns:snippet[@id="' . $sId . '"]'
605  );
606 
607  if (empty($snippet)) {
608  throw new Exception(
609  'Snippet %s does not exist in fragment %s.',
610  6,
611  [$sId, $href]
612  );
613  }
614 
615  $yieldomized->parentNode->insertBefore(
616  $this->_mowgli->importNode($snippet[0]->readDOM(), true),
617  $yieldomized
618  );
619  $yieldomized->parentNode->removeChild($yieldomized);
620  }
621 
622  foreach ($stream->xpath('//__current_ns:yield[@name]') as $yield) {
623  $yieldomized = $yield->readDOM();
624  $name = $yieldomized->getAttribute('name');
625 
626  if (true === $remove) {
627  $yieldomized->removeAttribute('name');
628  $yieldomized->removeAttribute('bind');
629  }
630 
631  foreach ($stream->xpath('//__current_ns:' . $name) as $ciao) {
632  $placeholder = $ciao->readDOM();
633  $xpath = new \DOMXpath($placeholder->ownerDocument);
634  $parent = $placeholder->parentNode;
635  $handle = $yieldomized->cloneNode(true);
636  $_yield = simplexml_import_dom($handle, '\Hoa\Xyl\Element\Basic');
637  $_yield->useNamespace(self::NAMESPACE_ID);
638  $ciao->useNamespace(self::NAMESPACE_ID);
639 
640  if (false === $remove) {
641  $handle->removeAttribute('name');
642  $handle->removeAttribute('bind');
643  }
644 
645  if (true === $placeholder->hasAttribute('bind')) {
646  $handle->setAttribute(
647  'bind',
648  $placeholder->getAttribute('bind')
649  );
650  }
651 
652  $selects = $_yield->xpath('.//__current_ns:yield[@select]');
653 
654  foreach ($selects as $select) {
655  $selectdomized = $select->readDOM();
656  $_select = $select->readAttribute('select');
657 
658  switch (static::getSelector($_select, $matches)) {
659  case self::SELECTOR_QUERY:
661  $_->compile(':root ' . $matches[1]);
662  $_select = $_->getXPath();
663 
664  break;
665 
666  case self::SELECTOR_XPATH:
667  $_select = $matches[1];
668 
669  break;
670 
671  default:
672  throw new Exception(
673  'Selector %s is not supported in a @select ' .
674  'attribute of the <yield /> component.',
675  7,
676  $_select
677  );
678  }
679 
680  $result = $xpath->query('./' . $_select, $placeholder);
681 
682  foreach ($result ?: [] as $selected) {
683  $selectdomized->parentNode->insertBefore(
684  $selected,
685  $selectdomized
686  );
687  }
688 
689  $selectdomized->parentNode->removeChild($selectdomized);
690  }
691 
692  $attributesSelects = $_yield->xpath(
693  './/__current_ns:*[@yield-select]'
694  );
695 
696  foreach ($attributesSelects as $select) {
697  $_select = $select->readAttribute('yield-select');
698 
699  switch (static::getSelector($_select, $matches)) {
700  case self::SELECTOR_XPATH:
701  $_select = $matches[1];
702 
703  break;
704 
705  default:
706  throw new Exception(
707  'Selector %s is not supported in a ' .
708  '@yield-select attribute.',
709  8,
710  $_select
711  );
712  }
713 
714  foreach ($ciao->xpath($_select) as $_el) {
715  foreach ($_el->readAttributes() as $key => $value) {
716  $select->writeAttribute($key, $value);
717  }
718  }
719 
720  $select->removeAttribute('yield-select');
721  }
722 
723  $parent->replaceChild($handle, $placeholder);
724  }
725 
726  $remove and $yieldomized->parentNode->removeChild($yieldomized);
727  }
728 
729  return;
730  }
731 
738  public function addOverlay($href)
739  {
740  $this->_mowgli->insertBefore(
741  new \DOMProcessingInstruction(
742  'xyl-overlay',
743  'href="' . str_replace('"', '\"', $href) . '"'
744  ),
745  $this->_mowgli->documentElement
746  );
747 
748  return;
749  }
750 
751  public function removeOverlay($href)
752  {
753  $href = str_replace('"', '\"', $href);
754  $ownerDocument = $this->_mowgli;
755  $xpath = new \DOMXpath($ownerDocument);
756  $xyl_overlay = $xpath->query('/processing-instruction(\'xyl-overlay\')');
757 
758  for ($i = 0, $m = $xyl_overlay->length; $i < $m; ++$i) {
759  $item = $xyl_overlay->item($i);
760  $overlayParsed = new Xml\Attribute($item->data);
761 
762  if (false === $overlayParsed->attributeExists('href')) {
763  continue;
764  }
765 
766  if ($overlayParsed->readAttribute('href') !== $href) {
767  continue;
768  }
769 
770  $ownerDocument->removeChild($item);
771 
772  break;
773  }
774 
775  if (!isset($this->_overlays[$href])) {
776  return false;
777  }
778 
779  foreach (array_reverse($this->_overlays[$href]) as $overlay) {
780  $overlay->parentNode->removeChild($overlay);
781  }
782 
783  unset($this->_overlays[$href]);
784  $this->partiallyUninterprete();
785 
786  return true;
787  }
788 
799  protected function computeOverlay(
800  \DOMDocument $ownerDocument = null,
801  \DOMDocument $receiptDocument = null,
802  Xyl $self = null
803  ) {
804  if (null === $ownerDocument) {
805  $ownerDocument = $this->_mowgli;
806  }
807 
808  if (null === $receiptDocument) {
809  $receiptDocument = $this->_mowgli;
810  }
811 
812  if (null === $self) {
813  $self = $this;
814  }
815 
816  $streamClass = get_class($self->getInnerStream());
817  $dirname = dirname($self->getInnerStream()->getStreamName());
818  $type = $this->getType();
819  $remove = self::TYPE_DOCUMENT == $type || self::TYPE_FRAGMENT == $type;
820  $hrefs = [];
821  $xpath = new \DOMXPath($ownerDocument);
822  $xyl_overlay = $xpath->query('/processing-instruction(\'xyl-overlay\')');
823  unset($xpath);
824 
825  if (0 === $xyl_overlay->length) {
826  return false;
827  }
828 
829  for ($i = 0, $m = $xyl_overlay->length; $i < $m; ++$i) {
830  $item = $xyl_overlay->item($i);
831  $overlay = $item;
832  $remove and $ownerDocument->removeChild($item);
833  $overlayParsed = new Xml\Attribute($overlay->data);
834 
835  if (false === $overlayParsed->attributeExists('href')) {
836  unset($overlayParsed);
837 
838  continue;
839  }
840 
841  $_href = $overlayParsed->readAttribute('href');
842  $href = $this->computeLink($_href, true);
843  unset($overlayParsed);
844 
845  if (0 === preg_match('#^(([^:]+://)|([A-Z]:)|/)#', $href)) {
846  $href = $dirname . DS . $href;
847  }
848 
849  if (false === file_exists($href)) {
850  throw new Exception('File %s is not found, cannot use it.', 9, $href);
851  }
852 
853  if (true === in_array($href, $hrefs)) {
854  continue;
855  }
856 
857  $hrefs[] = $href;
858  $fragment = new static(
859  new $streamClass($href),
860  $this->getOutputStream(),
863  );
864 
865  if (self::TYPE_OVERLAY !== $fragment->getType()) {
866  throw new Exception(
867  '%s must only contain <overlay> (and some <?xyl-overlay) ' .
868  'elements.',
869  10,
870  $href
871  );
872  }
873 
874  $this->_overlays[$_href] = [];
875  $fod = $fragment->readDOM()->ownerDocument;
876  $this->computeFragment($fod, $fragment);
877 
878  foreach ($fragment->selectChildElements() as $element) {
879  $this->_computeOverlay(
880  $receiptDocument->documentElement,
881  $receiptDocument->importNode($element->readDOM(), true),
882  $this->_overlays[$_href]
883  );
884  }
885 
886  $this->computeUse($fod, $receiptDocument, $fragment);
887  $this->computeOverlay($fod, $receiptDocument, $fragment);
888  }
889 
890  return true;
891  }
892 
901  private function _computeOverlay(
902  \DOMElement $from,
903  \DOMElement $to,
904  Array &$overlays
905  ) {
906  if (false === $to->hasAttribute('id')) {
907  return $this->_computeOverlayPosition($from, $to, $overlays);
908  }
909 
910  $xpath = new \DOMXPath($from->ownerDocument);
911  $query = $xpath->query('//*[@id="' . $to->getAttribute('id') . '"]');
912 
913  if (0 === $query->length) {
914  if ($from->parentNode == $this->_mowgli) {
915  // reference component
916  return null;
917  } else {
918  return $this->_computeOverlayPosition($from, $to, $overlays);
919  }
920  }
921 
922  $from = $query->item(0);
923 
924  foreach ($to->attributes as $name => $node) {
925  switch ($name) {
926  case 'id':
927  break;
928 
929  case 'class':
930  if (false === $from->hasAttribute('class')) {
931  $from->setAttribute('class', $node->value);
932 
933  break;
934  }
935 
936  $classListTo = explode(' ', $node->value);
937  $classListFrom = explode(' ', $from->getAttribute('class'));
938 
939  $from->setAttribute(
940  'class',
941  implode(
942  ' ',
943  array_unique(
944  array_merge($classListFrom, $classListTo)
945  )
946  )
947  );
948 
949  break;
950 
951  default:
952  $from->setAttribute($name, $node->value);
953  }
954  }
955 
956  $children = [];
957 
958  for ($h = $to->childNodes, $i = 0, $m = $h->length; $i < $m; ++$i) {
959  $element = $h->item($i);
960 
961  if (XML_ELEMENT_NODE != $element->nodeType) {
962  continue;
963  }
964 
965  $children[] = $element;
966  }
967 
968  foreach ($children as $child) {
969  $this->_computeOverlay($from, $child, $overlays);
970  }
971 
972  return;
973  }
974 
983  private function _computeOverlayPosition(
984  \DOMElement $from,
985  \DOMElement $to,
986  Array &$overlays
987  ) {
988  if (false === $to->hasAttribute('position')) {
989  $from->appendChild($to);
990  $overlays[] = $to;
991 
992  return;
993  }
994 
995  $children = $from->childNodes;
996  $positions = [];
997  $e = 0;
998  $search = [];
999  $replace = [];
1000  $child = null;
1001 
1002  for ($i = 0, $m = $children->length; $i < $m; ++$i) {
1003  $child = $children->item($i);
1004 
1005  if (XML_ELEMENT_NODE != $child->nodeType) {
1006  continue;
1007  }
1008 
1009  $positions[$e] = $i;
1010 
1011  if ($child->hasAttribute('id')) {
1012  $search[] = 'element(#' . $child->getAttribute('id') . ')';
1013  $replace[] = $e;
1014  }
1015 
1016  ++$e;
1017  }
1018 
1019  $last = count($positions);
1020  $search[] = 'last()';
1021  $replace[] = $last;
1022  $handle = str_replace($search, $replace, $to->getAttribute('position'));
1023  $position = max(0, (int) static::evaluateXPath($handle));
1024 
1025  if ($position < $last) {
1026  $from->insertBefore(
1027  $to,
1028  $from->childNodes->item($positions[$position])
1029  );
1030  } else {
1031  $from->appendChild($to);
1032  }
1033 
1034  $to->removeAttribute('position');
1035  $overlays[] = $to;
1036 
1037  return;
1038  }
1039 
1047  public function addFragment($href, $as = null)
1048  {
1049  $this->_mowgli->insertBefore(
1050  new \DOMProcessingInstruction(
1051  'xyl-fragment',
1052  'href="' . str_replace('"', '\"', $href) . '"' .
1053  (!empty($as)
1054  ? ' as="' . str_replace('"', '\"', $as) . '"'
1055  : '')
1056  ),
1057  $this->_mowgli->documentElement
1058  );
1059 
1060  return;
1061  }
1062 
1071  protected function computeFragment(
1072  \DOMDocument $ownerDocument = null,
1073  Xyl $self = null
1074  ) {
1075  if (null === $ownerDocument) {
1076  $ownerDocument = $this->_mowgli;
1077  }
1078 
1079  if (null === $self) {
1080  $self = $this;
1081  }
1082 
1083  $dirname = dirname($self->getInnerStream()->getStreamName());
1084  $type = $this->getType();
1085  $remove = self::TYPE_DOCUMENT == $type || self::TYPE_FRAGMENT == $type;
1086  $xpath = new \DOMXPath($ownerDocument);
1087  $xyl_fragment = $xpath->query('/processing-instruction(\'xyl-fragment\')');
1088  $xpath->registerNamespace('__current_ns', self::NAMESPACE_ID);
1089  $fragments = [];
1090 
1091  if (0 === $xyl_fragment->length) {
1092  return false;
1093  }
1094 
1095  for ($i = 0, $m = $xyl_fragment->length; $i < $m; ++$i) {
1096  $item = $xyl_fragment->item($i);
1097  $fragment = $item;
1098  $remove and $ownerDocument->removeChild($item);
1099  $fragmentParsed = new Xml\Attribute($fragment->data);
1100 
1101  if (false === $fragmentParsed->attributeExists('href')) {
1102  unset($fragmentParsed);
1103 
1104  continue;
1105  }
1106 
1107  $href = $this->computeLink(
1108  $fragmentParsed->readAttribute('href'),
1109  true
1110  );
1111 
1112  if (false === $fragmentParsed->attributeExists('as')) {
1113  $as = $href;
1114  } else {
1115  $as = $fragmentParsed->readAttribute('as');
1116  }
1117 
1118  if (0 === preg_match('#^(([^:]+://)|([A-Z]:)|/)#', $href)) {
1119  $href = $dirname . DS . $href;
1120  }
1121 
1122  if (false === file_exists($href)) {
1123  throw new Exception('File %s is not found, cannot use it.', 11, $href);
1124  }
1125 
1126  unset($fragmentParsed);
1127 
1128  if (isset($fragments[$as])) {
1129  throw new Exception(
1130  'Alias %s already exists for fragment %s, cannot ' .
1131  'redeclare it for fragment %s in the same document.',
1132  12,
1133  [$as, $fragments[$as], $href]
1134  );
1135  }
1136 
1137  if (!isset($this->_fragments[$as])) {
1138  $this->_fragments[$as] = $href;
1139 
1140  continue;
1141  }
1142 
1143  while (isset($this->_fragments[$newAs = uniqid() . '-' . $as]));
1144 
1145  $renamed = $xpath->query('//__current_ns:yield[' .
1146  'starts-with(@select, "?f:' . $as . '") or ' .
1147  'starts-with(@select, "?file:' . $as . '")' .
1148  ']');
1149 
1150  if (0 === $renamed->length) {
1151  continue;
1152  }
1153 
1154  for ($j = 0, $n = $renamed->length; $j < $n; ++$j) {
1155  $handle = $renamed->item($j);
1156  $select = $handle->getAttribute('select');
1157  $handle->setAttribute(
1158  'select',
1159  '?f:' . $newAs . substr(
1160  $select,
1161  strpos($select, '#')
1162  )
1163  );
1164  }
1165 
1166  $fragments[$newAs] = $href;
1167  $this->_fragments[$newAs] = $href;
1168  }
1169 
1170  unset($xpath);
1171 
1172  return true;
1173  }
1174 
1181  public function addStylesheet($href)
1182  {
1183  $this->_mowgli->insertBefore(
1184  new \DOMProcessingInstruction(
1185  'xyl-stylesheet',
1186  'href="' . str_replace('"', '\"', $href) . '"'
1187  ),
1188  $this->_mowgli->documentElement
1189  );
1190 
1191  return;
1192  }
1193 
1200  protected function computeStylesheet(\DOMDocument $ownerDocument)
1201  {
1202  $xpath = new \DOMXPath($ownerDocument);
1203  $xyl_style = $xpath->query('/processing-instruction(\'xyl-stylesheet\')');
1204  unset($xpath);
1205 
1206  if (0 === $xyl_style->length) {
1207  return;
1208  }
1209 
1210  for ($i = 0, $m = $xyl_style->length; $i < $m; ++$i) {
1211  $item = $xyl_style->item($i);
1212  $styleParsed = new Xml\Attribute($item->data);
1213 
1214  if (true === $styleParsed->attributeExists('href')) {
1215  $href = $this->computeLink(
1216  $styleParsed->readAttribute('href'),
1217  true
1218  );
1219 
1220  if (true === $styleParsed->attributeExists('position')) {
1221  $position = max(0, (int) static::evaluateXPath(str_replace(
1222  'last()',
1223  ($k = key($this->_stylesheets)) ? $k + 1 : 0,
1224  $styleParsed->readAttribute('position')
1225  )));
1226 
1227  if (isset($this->_stylesheets[$position])) {
1228  $handle = [];
1229 
1230  foreach ($this->_stylesheets as $i => $foo) {
1231  if ($position > $i) {
1232  $handle[$i] = $foo;
1233  unset($this->_stylesheets[$i]);
1234  } else {
1235  break;
1236  }
1237  }
1238 
1239  $handle[$position] = $href;
1240 
1241  foreach ($this->_stylesheets as $i => $foo) {
1242  if ($i === $position) {
1243  $handle[$position = $i + 1] = $foo;
1244  unset($this->_stylesheets[$i]);
1245  } else {
1246  break;
1247  }
1248  }
1249 
1250  $this->_stylesheets = $handle + $this->_stylesheets;
1251  } else {
1252  $this->_stylesheets[$position] = $href;
1253  ksort($this->_stylesheets, SORT_NUMERIC);
1254  }
1255  } else {
1256  $this->_stylesheets[] = $href;
1257  }
1258  }
1259 
1260  $ownerDocument->removeChild($item);
1261  unset($styleParsed);
1262  }
1263 
1264  return;
1265  }
1266 
1273  public function addMeta(Array $attributes)
1274  {
1275  $handle = null;
1276 
1277  foreach ($attributes as $key => $value) {
1278  $handle .= $key . '="' . str_replace('"', '\"', $value) . '" ';
1279  }
1280 
1281  $this->_mowgli->insertBefore(
1282  new \DOMProcessingInstruction('xyl-meta', substr($handle, 0, -1)),
1283  $this->_mowgli->documentElement
1284  );
1285 
1286  return;
1287  }
1288 
1295  protected function computeMeta(\DOMDocument $ownerDocument)
1296  {
1297  $xpath = new \DOMXPath($ownerDocument);
1298  $xyl_meta = $xpath->query('/processing-instruction(\'xyl-meta\')');
1299  unset($xpath);
1300 
1301  if (0 === $xyl_meta->length) {
1302  return;
1303  }
1304 
1305  for ($i = 0, $m = $xyl_meta->length; $i < $m; ++$i) {
1306  $item = $xyl_meta->item($i);
1307  $this->_metas[] = new Xml\Attribute($item->data);
1308  $ownerDocument->removeChild($item);
1309  }
1310 
1311  return;
1312  }
1313 
1321  protected function computeConcrete(Interpreter $interpreter = null)
1322  {
1323  if (null !== $this->_concrete) {
1324  return;
1325  }
1326 
1327  if (null === $interpreter) {
1328  $interpreter = $this->_interpreter;
1329  }
1330 
1331  $rank = $interpreter->getRank();
1332  $root = $this->getStream();
1333  $name = strtolower($root->getName());
1334 
1335  if (false === array_key_exists($name, $rank)) {
1336  throw new Exception(
1337  'Cannot create the concrete tree because the root <%s> is ' .
1338  'unknown from the rank.',
1339  13,
1340  $name
1341  );
1342  }
1343 
1344  $class = $rank[$name];
1345  $this->_concrete = new $class($root, $this, $rank, self::NAMESPACE_ID);
1346 
1347  return;
1348  }
1349 
1357  protected function computeDataBinding(Element\Concrete $element)
1358  {
1359  if (true === $this->isInnerOpened()) {
1360  return;
1361  }
1362 
1363  $this->_isDataComputed = true;
1364  $data = $this->getData()->toArray();
1365 
1366  return $element->computeDataBinding($data);
1367  }
1368 
1376  public function computeLink($link, $late = false)
1377  {
1378  // Router.
1379  if (0 !== preg_match('#^@(?:([^:]+):([^\#]+)|([^:\#]+):?)(?:\#(.+))?$#', $link, $matches)) {
1380  $router = $this->getRouter();
1381 
1382  if (null === $router) {
1383  return $link;
1384  }
1385 
1386  if (!empty($matches[3])) {
1387  if (!empty($matches[4])) {
1388  return $router->unroute(
1389  $matches[3],
1390  ['_fragment' => $matches[4]]
1391  );
1392  }
1393 
1394  return $router->unroute($matches[3]);
1395  }
1396 
1397  $id = $matches[1];
1398  parse_str($matches[2], $kv);
1399 
1400  if (!empty($matches[4])) {
1401  $kv['_fragment'] = $matches[4];
1402  }
1403 
1404  return $router->unroute($id, $kv);
1405  }
1406 
1407  // hoa://.
1408  if ('hoa://' === substr($link, 0, 6)) {
1409  if (0 !== preg_match('#^hoa://Library/Xyl/(.*)$#', $link, $m)) {
1410  $handle = 'hoa://Application/Public/' . $m[1];
1411  $_link = $this->resolve($handle);
1412 
1413  if (true !== file_exists($_link)) {
1414  $dirname = dirname($_link);
1415 
1416  if (true !== is_dir($dirname) &&
1417  false === @mkdir($dirname, 0755, true)) {
1418  throw new Exception(
1419  'Cannot create directory for the resource %s.',
1420  14,
1421  $handle
1422  );
1423  }
1424 
1425  if (false === @copy($this->resolve($link), $_link)) {
1426  throw new Exception(
1427  'Resource %s can not be copied to %s.',
1428  15,
1429  [$link, $_link]
1430  );
1431  }
1432  }
1433 
1434  $link = $handle;
1435  }
1436 
1437  if (0 !== preg_match('#^hoa://Application/Public/(.+/.+)$#', $link, $m)) {
1438  $theme = $this->getParameters()->getFormattedParameter('theme');
1439  list($type, $resource) = explode('/', $m[1], 2);
1440  $rule = '_' . strtolower($type);
1441  $router = $this->getRouter();
1442 
1443  if (null === $router) {
1444  throw new Exception('Need a router to compute %s.', 16, $link);
1445  }
1446 
1447  if (false === $router->ruleExists($rule)) {
1448  if (false === $router->ruleExists('_resource')) {
1449  throw new Exception(
1450  'Cannot compute %s because the rule _resource ' .
1451  'does not exist in the router.',
1452  17,
1453  $link
1454  );
1455  }
1456 
1457  $rule = '_resource';
1458  $resource = $m[1];
1459  }
1460 
1461  return $router->unroute(
1462  $rule,
1463  ['theme' => $theme, 'resource' => $resource]
1464  );
1465  }
1466 
1467  return $this->resolve($link, $late);
1468  }
1469 
1470  return $link;
1471  }
1472 
1482  public function interprete(
1483  Interpreter $interpreter = null,
1484  $computeData = false
1485  ) {
1486  $this->computeUse();
1487  $this->computeFragment();
1488  $this->computeOverlay();
1489  $this->computeYielder();
1490  $this->computeConcrete($interpreter);
1491 
1492  if (true === $computeData) {
1493  $this->computeDataBinding($this->_concrete);
1494  }
1495 
1496  return $this;
1497  }
1498 
1504  public function partiallyUninterprete()
1505  {
1506  $this->_isDataComputed = false;
1507  $this->_concrete = null;
1508 
1509  return $this;
1510  }
1511 
1520  public function render(Element\Concrete $element = null, $force = false)
1521  {
1522  if (null === $element) {
1523  $element = $this->_concrete;
1524  }
1525 
1526  if (null === $element) {
1527  $this->interprete(null, true);
1528  $element = $this->_concrete;
1529  } elseif (false !== $force) {
1530  $this->partiallyUninterprete();
1531 
1532  return $this->render();
1533  }
1534 
1535  if (false === $this->_isDataComputed) {
1536  $this->computeDataBinding($element);
1537  }
1538 
1539  return $element->render($this->getOutputStream());
1540  }
1541 
1550  public function open($streamName, $interprete = true)
1551  {
1552  $in = get_class($this->getInnerStream());
1553  $new = new static(
1554  new $in($streamName),
1555  $this->getOutputStream(),
1557  $this->getRouter()
1558  );
1559  $new->_innerOpen = true;
1560 
1561  if (true === $interprete) {
1562  $new->interprete();
1563  }
1564 
1565  return $new;
1566  }
1567 
1573  public function getConcrete()
1574  {
1575  return $this->_concrete;
1576  }
1577 
1584  public function setTheme($theme)
1585  {
1586  $old = $this->getTheme();
1587  $this->getParameters()->setKeyword('theme', $theme);
1588 
1589  return $old;
1590  }
1591 
1597  public function getTheme()
1598  {
1599  return $this->getParameters()->getKeyword('theme');
1600  }
1601 
1607  public function getStylesheets()
1608  {
1609  return $this->_stylesheets;
1610  }
1611 
1617  public function getMetas()
1618  {
1619  return $this->_metas;
1620  }
1621 
1628  public function setRouter(Router\Http $router)
1629  {
1630  $old = $this->_router;
1631  $this->_router = $router;
1632 
1633  return $old;
1634  }
1635 
1641  public function getRouter()
1642  {
1643  return $this->_router;
1644  }
1645 
1652  public function getSnippet($id)
1653  {
1654  $handle = $this->xpath(
1655  '/__current_ns:fragment/__current_ns:snippet[@id="' . $id . '"]'
1656  );
1657 
1658  if (empty($handle)) {
1659  throw new Exception('Snippet %s does not exist.', 18, $id);
1660  }
1661 
1662  if (null === $concrete = $this->getConcrete()) {
1663  throw new Exception(
1664  'Take care to interprete the document before getting a ' .
1665  'snippet.',
1666  19
1667  );
1668  }
1669 
1670  return $concrete->getConcreteElement($handle[0]);
1671  }
1672 
1679  public function getElement($id)
1680  {
1681  $handle = $this->xpath('//__current_ns:*[@id="' . $id . '"]');
1682 
1683  if (empty($handle)) {
1684  throw new Exception(
1685  'Element with ID %s does not exist.',
1686  20,
1687  $id
1688  );
1689  }
1690 
1691  if (null === $concrete = $this->getConcrete()) {
1692  throw new Exception(
1693  'Take care to interprete the document before getting a form.',
1694  21
1695  );
1696  }
1697 
1698  return $concrete->getConcreteElement($handle[0]);
1699  }
1700 
1707  public function setLocale(Locale $locale)
1708  {
1709  $old = $this->_locale;
1710  $this->_locale = $locale;
1711 
1712  return $old;
1713  }
1714 
1720  public function getLocale()
1721  {
1722  return $this->_locale;
1723  }
1724 
1732  public function addTranslation(Translate $translation, $id = '__main__')
1733  {
1734  $this->_translations[$id] = $translation;
1735 
1736  return;
1737  }
1738 
1745  public function getTranslation($id = '__main__')
1746  {
1747  if (!isset($this->_translations[$id])) {
1748  return null;
1749  }
1750 
1751  return $this->_translations[$id];
1752  }
1753 
1760  public static function evaluateXPath($expression)
1761  {
1762  if (null === static::$_xe) {
1763  static::$_xe = new \DOMXpath(new \DOMDocument());
1764  }
1765 
1766  return static::$_xe->evaluate($expression);
1767  }
1768 
1775  public function isInnerOpened()
1776  {
1777  return $this->_innerOpen;
1778  }
1779 
1789  public function resolve($hoa, $late = false)
1790  {
1791  $exists = false;
1792 
1793  if (0 !== preg_match('#^hoa://Library/Xyl(/.*|$)#', $hoa, $matches)) {
1794  $hoa = 'hoa://Library/Xyl[' . $this->_i . ']' . $matches[1];
1795  $exists = true;
1796  }
1797 
1798  if (0 !== preg_match('#^hoa://Application/Public(/.*)#', $hoa, $matches)) {
1799  $hoa =
1800  'hoa://Application/Public/' .
1801  $this->getParameters()->getFormattedParameter('theme') .
1802  $matches[1];
1803  }
1804 
1805  if (true === $late) {
1806  return $hoa;
1807  }
1808 
1809  return resolve($hoa, $exists);
1810  }
1811 
1819  public static function getSelector($selector, &$matches = false)
1820  {
1821 
1822  // ?q:a b c
1823  // ?query:a b c
1824  if (0 !== preg_match('#^\?q(?:uery)?:(.*)$#i', $selector, $matches)) {
1825  return self::SELECTOR_QUERY;
1826  }
1827 
1828  // ?x:a/b/c
1829  // ?xpath:a/b/c
1830  elseif (0 !== preg_match('#^\?x(?:path)?:(.*)$#i', $selector, $matches)) {
1831  return self::SELECTOR_XPATH;
1832  }
1833 
1834  // ?f:a/b/c
1835  // ?file:a/b/c
1836  elseif (0 !== preg_match('#^\?f(?:ile)?:([^\#]+)(?:\#(.*))?$#i', $selector, $matches)) {
1837  return self::SELECTOR_FILE;
1838  }
1839 
1840  // ?a/b/c
1841  // ?p:a/b/c
1842  // ?path:a/b/c
1843  elseif (0 !== preg_match('#^\?(?:p(?:ath)?:)?(.*)$#i', $selector, $matches)) {
1844  return self::SELECTOR_PATH;
1845  }
1846 
1847  throw new Exception(
1848  'Selector %s is not a valid selector.', 22, $selector);
1849  }
1850 
1856  public function __destruct()
1857  {
1858  $protocol = Core::getInstance()->getProtocol();
1859  unset($protocol['Library']['Xyl[' . $this->_i . ']']);
1860 
1861  return;
1862  }
1863 }
1864 
1873 class _Protocol extends Core\Protocol
1874 {
1875 }
1876 
1880 Core\Consistency::flexEntity('Hoa\Xyl\Xyl');
1881 
1882 event('hoa://Event/Exception')
1883  ->attach(xcallable('Hoa\Xyl\Interpreter\Common\Debug', 'receiveException'));
static getCssToXPathInstance()
Definition: Basic.php:430
addTranslation(Translate $translation, $id= '__main__')
Definition: Xyl.php:1732
getStylesheets()
Definition: Xyl.php:1607
addUse($href)
Definition: Xyl.php:429
_computeOverlay(\DOMElement $from,\DOMElement $to, Array &$overlays)
Definition: Xyl.php:901
computeMeta(\DOMDocument $ownerDocument)
Definition: Xyl.php:1295
const TYPE_DEFINITION
Definition: Xyl.php:80
const TYPE_FRAGMENT
Definition: Xyl.php:94
addOverlay($href)
Definition: Xyl.php:738
computeFragment(\DOMDocument $ownerDocument=null, Xyl $self=null)
Definition: Xyl.php:1071
static $_ci
Definition: Xyl.php:249
$_translations
Definition: Xyl.php:235
getTranslation($id= '__main__')
Definition: Xyl.php:1745
xpath($path)
Definition: Xml.php:428
getElement($id)
Definition: Xyl.php:1679
$_isDataComputed
Definition: Xyl.php:143
$_interpreter
Definition: Xyl.php:171
setTheme($theme)
Definition: Xyl.php:1584
const TYPE_OVERLAY
Definition: Xyl.php:87
static getSelector($selector, &$matches=false)
Definition: Xyl.php:1819
setRouter(Router\Http $router)
Definition: Xyl.php:1628
computeStylesheet(\DOMDocument $ownerDocument)
Definition: Xyl.php:1200
partiallyUninterprete()
Definition: Xyl.php:1504
useNamespace($namespace)
Definition: Xml.php:270
isInnerOpened()
Definition: Xyl.php:1775
const SELECTOR_FILE
Definition: Xyl.php:122
addMeta(Array $attributes)
Definition: Xyl.php:1273
getParameters()
Definition: Xyl.php:363
removeOverlay($href)
Definition: Xyl.php:751
namespaceExists($namespace)
Definition: Xml.php:258
addFragment($href, $as=null)
Definition: Xyl.php:1047
computeDataBinding(Element\Concrete $element)
Definition: Xyl.php:1357
const SELECTOR_QUERY
Definition: Xyl.php:108
interprete(Interpreter $interpreter=null, $computeData=false)
Definition: Xyl.php:1482
const NAMESPACE_ID
Definition: Xyl.php:66
computeLink($link, $late=false)
Definition: Xyl.php:1376
static getInstance()
Definition: Core.php:193
getName()
Definition: Xml.php:531
$_fragments
Definition: Xyl.php:221
getType()
Definition: Xyl.php:408
render(Element\Concrete $element=null, $force=false)
Definition: Xyl.php:1520
count()
Definition: Xml.php:541
__construct(Stream\IStream\In $in, Stream\IStream\Out $out, Interpreter $interpreter, Router\Http $router=null, $entityResolver=null, Array $parameters=[])
Definition: Xyl.php:276
computeUse(\DOMDocument $ownerDocument=null,\DOMDocument $receiptDocument=null, Xyl $self=null)
Definition: Xyl.php:452
getOutputStream()
Definition: Xyl.php:397
computeOverlay(\DOMDocument $ownerDocument=null,\DOMDocument $receiptDocument=null, Xyl $self=null)
Definition: Xyl.php:799
addStylesheet($href)
Definition: Xyl.php:1181
resolve($hoa, $late=false)
Definition: Xyl.php:1789
const SELECTOR_PATH
Definition: Xyl.php:101
static $_parameters
Definition: Xyl.php:129
$_innerOpen
Definition: Xyl.php:257
computeYielder()
Definition: Xyl.php:555
_computeOverlayPosition(\DOMElement $from,\DOMElement $to, Array &$overlays)
Definition: Xyl.php:983
setOutputStream(Stream\IStream\Out $out)
Definition: Xyl.php:384
static evaluateXPath($expression)
Definition: Xyl.php:1760
getTheme()
Definition: Xyl.php:1597
$_overlays
Definition: Xyl.php:214
getLocale()
Definition: Xyl.php:1720
open($streamName, $interprete=true)
Definition: Xyl.php:1550
getConcrete()
Definition: Xyl.php:1573
const SELECTOR_XPATH
Definition: Xyl.php:115
setLocale(Locale $locale)
Definition: Xyl.php:1707
getSnippet($id)
Definition: Xyl.php:1652
const TYPE_DOCUMENT
Definition: Xyl.php:73
static $_xe
Definition: Xyl.php:157
getData()
Definition: Xyl.php:373
getTypeAsString()
Definition: Xyl.php:419
$_stylesheets
Definition: Xyl.php:200
__destruct()
Definition: Xyl.php:1856
getMetas()
Definition: Xyl.php:1617
computeConcrete(Interpreter $interpreter=null)
Definition: Xyl.php:1321
$_concrete
Definition: Xyl.php:150
getRouter()
Definition: Xyl.php:1641