<?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');
