summaryrefslogtreecommitdiff
path: root/lib/phptal/PHPTAL/Dom/Element.php
diff options
context:
space:
mode:
authoremkael <emkael@tlen.pl>2016-10-31 21:58:33 +0100
committeremkael <emkael@tlen.pl>2016-10-31 21:59:22 +0100
commitd216b3147bc3f37cf2337acab5767c6a4f74aa2e (patch)
tree6090989e5071db101a1112131e2b075a02dccbc4 /lib/phptal/PHPTAL/Dom/Element.php
parentb23bfbb17d1d5f6852a1690f246a84c2d38ae969 (diff)
* PHPTAL library
Diffstat (limited to 'lib/phptal/PHPTAL/Dom/Element.php')
-rw-r--r--lib/phptal/PHPTAL/Dom/Element.php521
1 files changed, 521 insertions, 0 deletions
diff --git a/lib/phptal/PHPTAL/Dom/Element.php b/lib/phptal/PHPTAL/Dom/Element.php
new file mode 100644
index 0000000..574c830
--- /dev/null
+++ b/lib/phptal/PHPTAL/Dom/Element.php
@@ -0,0 +1,521 @@
+<?php
+/**
+ * PHPTAL templating engine
+ *
+ * PHP Version 5
+ *
+ * @category HTML
+ * @package PHPTAL
+ * @author Laurent Bedubourg <lbedubourg@motion-twin.com>
+ * @author Kornel LesiƄski <kornel@aardvarkmedia.co.uk>
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
+ * @version SVN: $Id$
+ * @link http://phptal.org/
+ */
+
+
+/**
+ * Document Tag representation.
+ *
+ * @package PHPTAL
+ * @subpackage Dom
+ */
+class PHPTAL_Dom_Element extends PHPTAL_Dom_Node
+{
+ protected $qualifiedName, $namespace_uri;
+ private $attribute_nodes = array();
+ protected $replaceAttributes = array();
+ protected $contentAttributes = array();
+ protected $surroundAttributes = array();
+ public $headFootDisabled = false;
+ public $headPrintCondition = false;
+ public $footPrintCondition = false;
+ public $hidden = false;
+
+ // W3C DOM interface
+ public $childNodes = array();
+ public $parentNode;
+
+ /**
+ * @param string $qname qualified name of the element, e.g. "tal:block"
+ * @param string $namespace_uri namespace of this element
+ * @param array $attribute_nodes array of PHPTAL_Dom_Attr elements
+ * @param object $xmlns object that represents namespaces/prefixes known in element's context
+ */
+ public function __construct($qname, $namespace_uri, array $attribute_nodes, PHPTAL_Dom_XmlnsState $xmlns)
+ {
+ $this->qualifiedName = $qname;
+ $this->attribute_nodes = $attribute_nodes;
+ $this->namespace_uri = $namespace_uri;
+ $this->xmlns = $xmlns;
+
+ // implements inheritance of element's namespace to tal attributes (<metal: use-macro>)
+ foreach ($attribute_nodes as $index => $attr) {
+ // it'll work only when qname == localname, which is good
+ if ($this->xmlns->isValidAttributeNS($namespace_uri, $attr->getQualifiedName())) {
+ $this->attribute_nodes[$index] = new PHPTAL_Dom_Attr($attr->getQualifiedName(), $namespace_uri, $attr->getValueEscaped(), $attr->getEncoding());
+ }
+ }
+
+ if ($this->xmlns->isHandledNamespace($this->namespace_uri)) {
+ $this->headFootDisabled = true;
+ }
+
+ $talAttributes = $this->separateAttributes();
+ $this->orderTalAttributes($talAttributes);
+ }
+
+ /**
+ * returns object that represents namespaces known in element's context
+ */
+ public function getXmlnsState()
+ {
+ return $this->xmlns;
+ }
+
+ /**
+ * Replace <script> foo &gt; bar </script>
+ * with <script>/*<![CDATA[* / foo > bar /*]]>* /</script>
+ * This avoids gotcha in text/html.
+ *
+ * Note that PHPTAL_Dom_CDATASection::generate() does reverse operation, if needed!
+ *
+ * @return void
+ */
+ private function replaceTextWithCDATA()
+ {
+ $isCDATAelement = PHPTAL_Dom_Defs::getInstance()->isCDATAElementInHTML($this->getNamespaceURI(), $this->getLocalName());
+
+ if (!$isCDATAelement) {
+ return;
+ }
+
+ $valueEscaped = ''; // sometimes parser generates split text nodes. "normalisation" is needed.
+ $value = '';
+ foreach ($this->childNodes as $node) {
+ // leave it alone if there is CDATA, comment, or anything else.
+ if (!$node instanceof PHPTAL_Dom_Text) return;
+
+ $value .= $node->getValue();
+ $valueEscaped .= $node->getValueEscaped();
+
+ $encoding = $node->getEncoding(); // encoding of all nodes is the same
+ }
+
+ // only add cdata if there are entities
+ // and there's no ${structure} (because it may rely on cdata syntax)
+ if (false === strpos($valueEscaped, '&') || preg_match('/<\?|\${structure/', $value)) {
+ return;
+ }
+
+ $this->childNodes = array();
+
+ // appendChild sets parent
+ $this->appendChild(new PHPTAL_Dom_Text('/*', $encoding));
+ $this->appendChild(new PHPTAL_Dom_CDATASection('*/'.$value.'/*', $encoding));
+ $this->appendChild(new PHPTAL_Dom_Text('*/', $encoding));
+ }
+
+ public function appendChild(PHPTAL_Dom_Node $child)
+ {
+ if ($child->parentNode) $child->parentNode->removeChild($child);
+ $child->parentNode = $this;
+ $this->childNodes[] = $child;
+ }
+
+ public function removeChild(PHPTAL_Dom_Node $child)
+ {
+ foreach ($this->childNodes as $k => $node) {
+ if ($child === $node) {
+ $child->parentNode = null;
+ array_splice($this->childNodes, $k, 1);
+ return;
+ }
+ }
+ throw new PHPTAL_Exception("Given node is not child of ".$this->getQualifiedName());
+ }
+
+ public function replaceChild(PHPTAL_Dom_Node $newElement, PHPTAL_Dom_Node $oldElement)
+ {
+ foreach ($this->childNodes as $k => $node) {
+ if ($node === $oldElement) {
+ $oldElement->parentNode = NULL;
+
+ if ($newElement->parentNode) $newElement->parentNode->removeChild($child);
+ $newElement->parentNode = $this;
+
+ $this->childNodes[$k] = $newElement;
+ return;
+ }
+ }
+ throw new PHPTAL_Exception("Given node is not child of ".$this->getQualifiedName());
+ }
+
+ public function generateCode(PHPTAL_Php_CodeWriter $codewriter)
+ {
+ try
+ {
+ /// self-modifications
+
+ if ($codewriter->getOutputMode() === PHPTAL::XHTML) {
+ $this->replaceTextWithCDATA();
+ }
+
+ /// code generation
+
+ if ($this->getSourceLine()) {
+ $codewriter->doComment('tag "'.$this->qualifiedName.'" from line '.$this->getSourceLine());
+ }
+
+ $this->generateSurroundHead($codewriter);
+
+ if (count($this->replaceAttributes)) {
+ foreach ($this->replaceAttributes as $att) {
+ $att->before($codewriter);
+ $att->after($codewriter);
+ }
+ } elseif (!$this->hidden) {
+ // a surround tag may decide to hide us (tal:define for example)
+ $this->generateHead($codewriter);
+ $this->generateContent($codewriter);
+ $this->generateFoot($codewriter);
+ }
+
+ $this->generateSurroundFoot($codewriter);
+ }
+ catch(PHPTAL_TemplateException $e) {
+ $e->hintSrcPosition($this->getSourceFile(), $this->getSourceLine());
+ throw $e;
+ }
+ }
+
+ /**
+ * Array with PHPTAL_Dom_Attr objects
+ *
+ * @return array
+ */
+ public function getAttributeNodes()
+ {
+ return $this->attribute_nodes;
+ }
+
+ /**
+ * Replace all attributes
+ *
+ * @param array $nodes array of PHPTAL_Dom_Attr objects
+ */
+ public function setAttributeNodes(array $nodes)
+ {
+ $this->attribute_nodes = $nodes;
+ }
+
+ /** Returns true if the element contains specified PHPTAL attribute. */
+ public function hasAttribute($qname)
+ {
+ foreach($this->attribute_nodes as $attr) if ($attr->getQualifiedName() == $qname) return true;
+ return false;
+ }
+
+ public function hasAttributeNS($ns_uri, $localname)
+ {
+ return null !== $this->getAttributeNodeNS($ns_uri, $localname);
+ }
+
+ public function getAttributeNodeNS($ns_uri, $localname)
+ {
+ foreach ($this->attribute_nodes as $attr) {
+ if ($attr->getNamespaceURI() === $ns_uri && $attr->getLocalName() === $localname) return $attr;
+ }
+ return null;
+ }
+
+ public function removeAttributeNS($ns_uri, $localname)
+ {
+ foreach ($this->attribute_nodes as $k => $attr) {
+ if ($attr->getNamespaceURI() === $ns_uri && $attr->getLocalName() === $localname) {
+ unset($this->attribute_nodes[$k]);
+ return;
+ }
+ }
+ }
+
+ public function getAttributeNode($qname)
+ {
+ foreach($this->attribute_nodes as $attr) if ($attr->getQualifiedName() === $qname) return $attr;
+ return null;
+ }
+
+ /**
+ * If possible, use getAttributeNodeNS and setAttributeNS.
+ *
+ * NB: This method doesn't handle namespaces properly.
+ */
+ public function getOrCreateAttributeNode($qname)
+ {
+ if ($attr = $this->getAttributeNode($qname)) return $attr;
+
+ $attr = new PHPTAL_Dom_Attr($qname, "", null, 'UTF-8'); // FIXME: should find namespace and encoding
+ $this->attribute_nodes[] = $attr;
+ return $attr;
+ }
+
+ /** Returns textual (unescaped) value of specified element attribute. */
+ public function getAttributeNS($namespace_uri, $localname)
+ {
+ if ($n = $this->getAttributeNodeNS($namespace_uri, $localname)) {
+ return $n->getValue();
+ }
+ return '';
+ }
+
+ /**
+ * Set attribute value. Creates new attribute if it doesn't exist yet.
+ *
+ * @param string $namespace_uri full namespace URI. "" for default namespace
+ * @param string $qname prefixed qualified name (e.g. "atom:feed") or local name (e.g. "p")
+ * @param string $value unescaped value
+ *
+ * @return void
+ */
+ public function setAttributeNS($namespace_uri, $qname, $value)
+ {
+ $localname = preg_replace('/^[^:]*:/', '', $qname);
+ if (!($n = $this->getAttributeNodeNS($namespace_uri, $localname))) {
+ $this->attribute_nodes[] = $n = new PHPTAL_Dom_Attr($qname, $namespace_uri, null, 'UTF-8'); // FIXME: find encoding
+ }
+ $n->setValue($value);
+ }
+
+ /**
+ * Returns true if this element or one of its PHPTAL attributes has some
+ * content to print (an empty text node child does not count).
+ *
+ * @return bool
+ */
+ public function hasRealContent()
+ {
+ if (count($this->contentAttributes) > 0) return true;
+
+ foreach ($this->childNodes as $node) {
+ if (!$node instanceof PHPTAL_Dom_Text || $node->getValueEscaped() !== '') return true;
+ }
+ return false;
+ }
+
+ public function hasRealAttributes()
+ {
+ if ($this->hasAttributeNS('http://xml.zope.org/namespaces/tal', 'attributes')) return true;
+ foreach ($this->attribute_nodes as $attr) {
+ if ($attr->getReplacedState() !== PHPTAL_Dom_Attr::HIDDEN) return true;
+ }
+ return false;
+ }
+
+ // ~~~~~ Generation methods may be called by some PHPTAL attributes ~~~~~
+
+ public function generateSurroundHead(PHPTAL_Php_CodeWriter $codewriter)
+ {
+ foreach ($this->surroundAttributes as $att) {
+ $att->before($codewriter);
+ }
+ }
+
+ public function generateHead(PHPTAL_Php_CodeWriter $codewriter)
+ {
+ if ($this->headFootDisabled) return;
+ if ($this->headPrintCondition) {
+ $codewriter->doIf($this->headPrintCondition);
+ }
+
+ $html5mode = ($codewriter->getOutputMode() === PHPTAL::HTML5);
+
+ if ($html5mode) {
+ $codewriter->pushHTML('<'.$this->getLocalName());
+ } else {
+ $codewriter->pushHTML('<'.$this->qualifiedName);
+ }
+
+ $this->generateAttributes($codewriter);
+
+ if (!$html5mode && $this->isEmptyNode($codewriter->getOutputMode())) {
+ $codewriter->pushHTML('/>');
+ } else {
+ $codewriter->pushHTML('>');
+ }
+
+ if ($this->headPrintCondition) {
+ $codewriter->doEnd('if');
+ }
+ }
+
+ public function generateContent(PHPTAL_Php_CodeWriter $codewriter = null, $realContent=false)
+ {
+ if (!$this->isEmptyNode($codewriter->getOutputMode())) {
+ if ($realContent || !count($this->contentAttributes)) {
+ foreach($this->childNodes as $child) {
+ $child->generateCode($codewriter);
+ }
+ }
+ else foreach($this->contentAttributes as $att) {
+ $att->before($codewriter);
+ $att->after($codewriter);
+ }
+ }
+ }
+
+ public function generateFoot(PHPTAL_Php_CodeWriter $codewriter)
+ {
+ if ($this->headFootDisabled)
+ return;
+ if ($this->isEmptyNode($codewriter->getOutputMode()))
+ return;
+
+ if ($this->footPrintCondition) {
+ $codewriter->doIf($this->footPrintCondition);
+ }
+
+ if ($codewriter->getOutputMode() === PHPTAL::HTML5) {
+ $codewriter->pushHTML('</'.$this->getLocalName().'>');
+ } else {
+ $codewriter->pushHTML('</'.$this->getQualifiedName().'>');
+ }
+
+ if ($this->footPrintCondition) {
+ $codewriter->doEnd('if');
+ }
+ }
+
+ public function generateSurroundFoot(PHPTAL_Php_CodeWriter $codewriter)
+ {
+ for ($i = (count($this->surroundAttributes)-1); $i >= 0; $i--) {
+ $this->surroundAttributes[$i]->after($codewriter);
+ }
+ }
+
+ // ~~~~~ Private members ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ private function generateAttributes(PHPTAL_Php_CodeWriter $codewriter)
+ {
+ $html5mode = ($codewriter->getOutputMode() === PHPTAL::HTML5);
+
+ foreach ($this->getAttributeNodes() as $attr) {
+
+ // xmlns:foo is not allowed in text/html
+ if ($html5mode && $attr->isNamespaceDeclaration()) {
+ continue;
+ }
+
+ switch ($attr->getReplacedState()) {
+ case PHPTAL_Dom_Attr::NOT_REPLACED:
+ $codewriter->pushHTML(' '.$attr->getQualifiedName());
+ if ($codewriter->getOutputMode() !== PHPTAL::HTML5
+ || !PHPTAL_Dom_Defs::getInstance()->isBooleanAttribute($attr->getQualifiedName())) {
+ $html = $codewriter->interpolateHTML($attr->getValueEscaped());
+ $codewriter->pushHTML('='.$codewriter->quoteAttributeValue($html));
+ }
+ break;
+
+ case PHPTAL_Dom_Attr::HIDDEN:
+ break;
+
+ case PHPTAL_Dom_Attr::FULLY_REPLACED:
+ $codewriter->pushHTML($attr->getValueEscaped());
+ break;
+
+ case PHPTAL_Dom_Attr::VALUE_REPLACED:
+ $codewriter->pushHTML(' '.$attr->getQualifiedName().'="');
+ $codewriter->pushHTML($attr->getValueEscaped());
+ $codewriter->pushHTML('"');
+ break;
+ }
+ }
+ }
+
+ private function isEmptyNode($mode)
+ {
+ return (($mode === PHPTAL::XHTML || $mode === PHPTAL::HTML5) && PHPTAL_Dom_Defs::getInstance()->isEmptyTagNS($this->getNamespaceURI(), $this->getLocalName())) ||
+ ( $mode === PHPTAL::XML && !$this->hasContent());
+ }
+
+ private function hasContent()
+ {
+ return count($this->childNodes) > 0 || count($this->contentAttributes) > 0;
+ }
+
+ private function separateAttributes()
+ {
+ $talAttributes = array();
+ foreach ($this->attribute_nodes as $index => $attr) {
+ // remove handled xml namespaces
+ if (PHPTAL_Dom_Defs::getInstance()->isHandledXmlNs($attr->getQualifiedName(), $attr->getValueEscaped())) {
+ unset($this->attribute_nodes[$index]);
+ } else if ($this->xmlns->isHandledNamespace($attr->getNamespaceURI())) {
+ $talAttributes[$attr->getQualifiedName()] = $attr;
+ $attr->hide();
+ } else if (PHPTAL_Dom_Defs::getInstance()->isBooleanAttribute($attr->getQualifiedName())) {
+ $attr->setValue($attr->getLocalName());
+ }
+ }
+ return $talAttributes;
+ }
+
+ private function orderTalAttributes(array $talAttributes)
+ {
+ $temp = array();
+ foreach ($talAttributes as $key => $domattr) {
+ $nsattr = PHPTAL_Dom_Defs::getInstance()->getNamespaceAttribute($domattr->getNamespaceURI(), $domattr->getLocalName());
+ if (array_key_exists($nsattr->getPriority(), $temp)) {
+ throw new PHPTAL_TemplateException(sprintf("Attribute conflict in < %s > '%s' cannot appear with '%s'",
+ $this->qualifiedName,
+ $key,
+ $temp[$nsattr->getPriority()][0]->getNamespace()->getPrefix() . ':' . $temp[$nsattr->getPriority()][0]->getLocalName()
+ ), $this->getSourceFile(), $this->getSourceLine());
+ }
+ $temp[$nsattr->getPriority()] = array($nsattr, $domattr);
+ }
+ ksort($temp);
+
+ $this->talHandlers = array();
+ foreach ($temp as $prio => $dat) {
+ list($nsattr, $domattr) = $dat;
+ $handler = $nsattr->createAttributeHandler($this, $domattr->getValue());
+ $this->talHandlers[$prio] = $handler;
+
+ if ($nsattr instanceof PHPTAL_NamespaceAttributeSurround)
+ $this->surroundAttributes[] = $handler;
+ else if ($nsattr instanceof PHPTAL_NamespaceAttributeReplace)
+ $this->replaceAttributes[] = $handler;
+ else if ($nsattr instanceof PHPTAL_NamespaceAttributeContent)
+ $this->contentAttributes[] = $handler;
+ else
+ throw new PHPTAL_ParserException("Unknown namespace attribute class ".get_class($nsattr),
+ $this->getSourceFile(), $this->getSourceLine());
+
+ }
+ }
+
+ function getQualifiedName()
+ {
+ return $this->qualifiedName;
+ }
+
+ function getNamespaceURI()
+ {
+ return $this->namespace_uri;
+ }
+
+ function getLocalName()
+ {
+ $n = explode(':', $this->qualifiedName, 2);
+ return end($n);
+ }
+
+ function __toString()
+ {
+ return '<{'.$this->getNamespaceURI().'}:'.$this->getLocalName().'>';
+ }
+
+ function setValueEscaped($e) {
+ throw new PHPTAL_Exception("Not supported");
+ }
+}