<?php
/** @file
 * XML extensions. See QPXML.
 */
/**
 * Provide QueryPath with additional XML tools.
 *
 * @author M Butcher <matt@aleph-null.tv>
 * @author Xander Guzman <theshadow@shadowpedia.info>
 * @license http://opensource.org/licenses/lgpl-2.1.php LGPL or MIT-like license.
 * @see QueryPathExtension
 * @see QueryPathExtensionRegistry::extend()
 * @see QPXML
 * @ingroup querypath_extensions
 */
class QPXML implements QueryPathExtension {

	protected $qp;

	public function __construct(QueryPath $qp) {
		$this->qp = $qp;
	}

	public function schema($file) {
		$doc = $this->qp->branch()->top()->get(0)->ownerDocument;

		if (!$doc->schemaValidate($file)) {
			throw new QueryPathException('Document did not validate against the schema.');
		}
	}

	/**
	 * Get or set a CDATA section.
	 *
	 * If this is given text, it will create a CDATA section in each matched element,
	 * setting that item's value to $text.
	 *
	 * If no parameter is passed in, this will return the first CDATA section that it
	 * finds in the matched elements.
	 *
	 * @param string $text
	 *  The text data to insert into the current matches. If this is NULL, then the first
	 *  CDATA will be returned.
	 *
	 * @return mixed
	 *  If $text is not NULL, this will return a {@link QueryPath}. Otherwise, it will
	 *  return a string. If no CDATA is found, this will return NULL.
	 * @see comment()
	 * @see QueryPath::text()
	 * @see QueryPath::html()
	 */
	public function cdata($text = NULL) {
		if (isset($text)) {
			// Add this text as CDATA in the current elements.
			foreach ($this->qp->get() as $element) {
				$cdata = $element->ownerDocument->createCDATASection($text);
				$element->appendChild($cdata);
			}
			return $this->qp;;
		}

		// Look for CDATA sections.
		foreach ($this->qp->get() as $ele) {
			foreach ($ele->childNodes as $node) {
				if ($node->nodeType == XML_CDATA_SECTION_NODE) {
					// Return first match.
					return $node->textContent;
				}
			}
		}
		return NULL;
		// Nothing found
	}

	/**
	 * Get or set a comment.
	 *
	 * This function is used to get or set comments in an XML or HTML document.
	 * If a $text value is passed in (and is not NULL), then this will add a comment
	 * (with the value $text) to every match in the set.
	 *
	 * If no text is passed in, this will return the first comment in the set of matches.
	 * If no comments are found, NULL will be returned.
	 *
	 * @param string $text
	 *  The text of the comment. If set, a new comment will be created in every item
	 *  wrapped by the current {@link QueryPath}.
	 * @return mixed
	 *  If $text is set, this will return a {@link QueryPath}. If no text is set, this
	 *  will search for a comment and attempt to return the string value of the first
	 *  comment it finds. If no comment is found, NULL will be returned.
	 * @see cdata()
	 */
	public function comment($text = NULL) {
		if (isset($text)) {
			foreach ($this->qp->get() as $element) {
				$comment = $element->ownerDocument->createComment($text);
				$element->appendChild($comment);
			}
			return $this->qp;
		}
		foreach ($this->qp->get() as $ele) {
			foreach ($ele->childNodes as $node) {
				if ($node->nodeType == XML_COMMENT_NODE) {
					// Return first match.
					return $node->textContent;
				}
			}
		}
	}

	/**
	 * Get or set a processor instruction.
	 */
	public function pi($prefix = NULL, $text = NULL) {
		if (isset($text)) {
			foreach ($this->qp->get() as $element) {
				$comment = $element->ownerDocument->createProcessingInstruction($prefix, $text);
				$element->appendChild($comment);
			}
			return $this->qp;
		}
		foreach ($this->qp->get() as $ele) {
			foreach ($ele->childNodes as $node) {
				if ($node->nodeType == XML_PI_NODE) {

					if (isset($prefix)) {
						if ($node->tagName == $prefix) {
							return $node->textContent;
						}
					}
					else {
						// Return first match.
						return $node->textContent;
					}
				}
			} // foreach
		} // foreach
	}
	public function toXml() {
		return $this->qp->document()->saveXml();
	}

	/**
	 * Create a NIL element.
	 *
	 * @param string $text
	 * @param string $value
	 * @reval object $element
	 */
	public function createNilElement($text, $value) {
		$value = ($value)? 'true':'false';
		$element = $this->qp->createElement($text);
		$element->attr('xsi:nil', $value);
		return $element;
	}

	/**
	 * Create an element with the given namespace.
	 *
	 * @param string $text
	 * @param string $nsUri
	 *   The namespace URI for the given element.
	 * @retval object
	 */
	public function createElement($text, $nsUri = null) {
		if (isset ($text)) {
			foreach ($this->qp->get() as $element) {
				if ($nsUri === null && strpos($text, ':') !== false) {
					$ns = array_shift(explode(':', $text));
					$nsUri = $element->ownerDocument->lookupNamespaceURI($ns);

					if ($nsUri === null) {
						throw new QueryPathException("Undefined namespace for: " . $text);
					}
				}

				$node = null;
				if ($nsUri !== null) {
					$node = $element->ownerDocument->createElementNS(
					$nsUri,
					$text
					);
				} else {
					$node = $element->ownerDocument->createElement($text);
				}
				return qp($node);
			}
		}
		return;
	}

	/**
	 * Append an element.
	 *
	 * @param string $text
	 * @retval object QueryPath
	 */
	public function appendElement($text) {
		if (isset ($text)) {
			foreach ($this->qp->get() as $element) {
				$node = $this->qp->createElement($text);
				qp($element)->append($node);
			}
		}
		return $this->qp;
	}
}
QueryPathExtensionRegistry::extend('QPXML');
