Skip to content

Commit

Permalink
Improve code quality and add docstrings
Browse files Browse the repository at this point in the history
  • Loading branch information
danmichaelo committed Jul 1, 2017
1 parent a265cd0 commit 5164789
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 60 deletions.
218 changes: 160 additions & 58 deletions src/Danmichaelo/QuiteSimpleXMLElement/QuiteSimpleXMLElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,19 @@
* $b = $a->xpath('d:b');
* echo trim((string)$b[0]);
*
* And while we're at it, we can add a few convenience methods...
* And while we're at it, we can add a few convenience methods.
*
* SimpleXmlElement reference:
* https://github.com/draffter/FollowFunctionPHP/blob/master/_php/SimpleXML.php
*/

namespace Danmichaelo\QuiteSimpleXMLElement;

class InvalidXMLException extends \Exception
use Exception;
use InvalidArgumentException;
use SimpleXMLElement;

class InvalidXMLException extends Exception
{
public function __construct($message = null, $code = 0, Exception $previous = null)
{
Expand All @@ -52,45 +59,77 @@ class QuiteSimpleXMLElement
public $namespaces;
public $el;

//
// See https://github.com/draffter/FollowFunctionPHP/blob/master/_php/SimpleXML.php
// for list of functions with arguments
//
public function __construct($elem, $inherit_from = null)
/**
* QuiteSimpleXMLElement constructor.
*
* @param string|SimpleXMLElement|QuiteSimpleXMLElement $elem
* @param QuiteSimpleXMLElement $inherit_from
* @throws InvalidXMLException
* @throws InvalidArgumentException
*/
public function __construct($elem, QuiteSimpleXMLElement $inherit_from = null)
{
$this->namespaces = [];

$this->el = $this->getElement($elem);

if (is_null($inherit_from)) {
$this->namespaces = $this->el->getNamespaces(true);
} else {
foreach ($inherit_from->namespaces as $prefix => $uri) {
$this->registerXPathNamespace($prefix, $uri);
}
}
}

/**
* Internal helper method to get a SimpleXMLElement from either a string
* or a SimpleXMLElement/QuiteSimpleXMLElement object.
*
* @param string|SimpleXMLElement|QuiteSimpleXMLElement $elem
* @return SimpleXMLElement
* @throws InvalidXMLException
* @throws InvalidArgumentException
*/
protected function getElement($elem)
{
if (gettype($elem) == 'string') {
try {
$this->el = new \SimpleXMLElement($elem);
} catch (\Exception $e) {
return new SimpleXMLElement($elem);
} catch (Exception $e) {
throw new InvalidXMLException('Invalid XML encountered: ' . $elem);
}
} elseif (gettype($elem) == 'object') {
if (in_array(get_class($elem), ['Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement', 'SimpleXMLElement'])) {
$this->el = $elem; // assume it's a SimpleXMLElement
} else {
throw new \InvalidArgumentException('Unknown object given to QuiteSimpleXMLElement. Expected SimpleXMLElement or QuiteSimpleXMLElement.');
}
} else {
throw new \InvalidArgumentException('QuiteSimpleXMLElement expects a string or a QuiteSimpleXMLElement/SimpleXMLElement object.');
}

if ($inherit_from != null) {
foreach ($inherit_from->namespaces as $prefix => $uri) {
$this->registerXPathNamespace($prefix, $uri);
if (gettype($elem) == 'object') {
if (in_array(get_class($elem),
['Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement', 'SimpleXMLElement'])) {
return $elem; // assume it's a SimpleXMLElement
} else {
throw new InvalidArgumentException('Unknown object given to QuiteSimpleXMLElement. Expected SimpleXMLElement or QuiteSimpleXMLElement.');
}
} else {
$this->namespaces = $this->el->getNamespaces(true);
}

throw new InvalidArgumentException('QuiteSimpleXMLElement expects a string or a QuiteSimpleXMLElement/SimpleXMLElement object.');
}

/**
* Register a new xpath namespace.
*
* @param string $prefix
* @param string $uri
*/
public function registerXPathNamespace($prefix, $uri)
{
$this->el->registerXPathNamespace($prefix, $uri);
$this->namespaces[$prefix] = $uri;
}

/**
* Register an array of new xpath namespaces.
*
* @param array $namespaces
*/
public function registerXPathNamespaces($namespaces)
{
// Convenience method to add multiple namespaces at once
Expand All @@ -99,27 +138,39 @@ public function registerXPathNamespaces($namespaces)
}
}

/*
* Convenience method for getting the text of the first
* node matching an xpath. The text is trimmed by default,
* but setting the second argument to false will return
* the untrimmed text.
/**
* Get the text of the first node matching an XPath query. By default,
* the text will be trimmed, but if you want the untrimmed text, set
* the second paramter to False.
*
* @param string $path
* @param bool $trim
* @return string
*/
public function text($path = '.', $trim = true)
public function text($path='.', $trim=true)
{
$text = strval($this->first($path));

return $trim ? trim($text) : $text;
}

/*
* Convenience method for getting an attribute of a node
/**
* Get a node attribute value.
*
* @param string $attribute
* @return string
*/
public function attr($attribute)
{
return trim((string) $this->el->attributes()->{$attribute});
}

/**
* Get the first node matching an XPath query, or null if no match.
*
* @param string $path
* @return QuiteSimpleXMLElement
*/
public function first($path)
{
// Convenience method
Expand All @@ -128,8 +179,11 @@ public function first($path)
return count($x) ? $x[0] : null;
}

/*
* Convenience method for checking if a node exists
/**
* Check if the document has at least one node matching an XPath query.
*
* @param string $path
* @return bool
*/
public function has($path)
{
Expand All @@ -138,16 +192,20 @@ public function has($path)
return count($x) ? true : false;
}

/**
* Get the wrapped SimpleXMLElement object.
*
* @return SimpleXMLElement
*/
public function el()
{
return $this->el;
}

/**
* Returns an array of QuiteSimpleXMLElement instances.
*
* @param $path
* Get all nodes matching an XPath query.
*
* @param string $path
* @return QuiteSimpleXMLElement[]
*/
public function xpath($path)
Expand All @@ -158,10 +216,9 @@ public function xpath($path)
}

/**
* Alias for xpath().
* Alias for `xpath()`.
*
* @param $path
*
* @return QuiteSimpleXMLElement[]
*/
public function all($path)
Expand All @@ -171,29 +228,30 @@ public function all($path)

/**
* Returns the *untrimmed* text content of the node.
* @return string
*/
public function __toString()
{
return (string) $this->el;
}

/* The original children and count methods are quite flawed. The count() method
only return the count of children _with no namespace_. The children() method
can take namespace prefix as argument, but doesn't use the document's prefixes,
not the registered ones.
And it returns a "pseudo array" instead of a real iterator... making it quite hard
to work with, there's no next() method for instance...
We're returning a real array instead, even though that might not be what you want
in _all_ situations.
An alternative could be to use xpath('child::node()')
*/
public function children($ns = null)
/**
* Returns the child elements.
*
* Note: By default, only children without namespace will be returned. You can
* specify a namespace prefix to get children with that namespace prefix.
*
* Tip: You could also use `xpath('child::node()')`.
*
* @param null $ns
* @return QuiteSimpleXMLElement[]
*/
public function children($ns=null)
{
$ch = $ns
? $this->el->children($this->namespaces[$ns])
: $this->el->children();
$ch = is_null($ns)
? $this->el->children()
: $this->el->children($this->namespaces[$ns]);

$o = [];
foreach ($ch as $c) {
$o[] = new self($c, $this);
Expand All @@ -202,41 +260,76 @@ public function children($ns = null)
return $o;
}

/**
* Returns the number of child elements.
*
* Note: By default, only children without namespace will be counted. You can
* specify a namespace prefix to count children with that namespace prefix.
*
* @param null $ns
* @return int
*/
public function count($ns = null)
{
return $ns
? count($this->el->children($this->namespaces[$ns]))
: count($this->el->children());
return count($this->children($ns));
}

/**
* Returns and element's attributes.
*
* @return SimpleXMLElement a `SimpleXMLElement` object that can be
* iterated over to loop through the attributes on the tag.
*/
public function attributes()
{
return $this->el->attributes();
}

/**
* Returns the XML as text.
*
* @return string
*/
public function asXML()
{
return $this->el->asXML();
}

/**
* Get the element name.
*
* @return string
*/
public function getName()
{
return $this->el->getName();
}

public function getNamespaces($recursive = false)
/**
* Return a namespace array.
*
* @return array
*/
public function getNamespaces()
{
return $this->el->getNamespaces($recursive);
return $this->namespaces;
}

/**
* Set the node value.
*
* @param string $value
*/
public function setValue($value)
{
$this->el[0] = $value;
}

/**
* Return the current object as DOMElement.
*
* @return \DOMElement
*/
public function asDOMElement()
{
return dom_import_simplexml($this->el);
Expand All @@ -245,6 +338,8 @@ public function asDOMElement()
/**
* Replaces the current node. Thanks to @hakre
* <http://stackoverflow.com/questions/17661167/how-to-replace-xml-node-with-simplexmlelement-php>.
*
* @param QuiteSimpleXMLElement $element
*/
public function replace(QuiteSimpleXMLElement $element)
{
Expand All @@ -256,6 +351,13 @@ public function replace(QuiteSimpleXMLElement $element)
$oldNode->parentNode->replaceChild($newNode, $oldNode);
}

/**
* Static helper method to make initialization easier.
*
* @param $input
* @param array $ns
* @return QuiteSimpleXMLElement
*/
public static function make($input, $ns = [])
{
$elem = new self($input);
Expand Down
5 changes: 3 additions & 2 deletions tests/QuiteSimpleXMLElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,9 @@ public function testChildCountWithRegisteredNamespaces()
</ns1:CheckOutItemResponse>
</ns1:NCIPMessage>';
$dom = new QuiteSimpleXMLElement($xml);
$ns = ['n' => 'http://www.niso.org/2008/ncip'];
$dom->registerXPathNamespaces($ns);
$dom->registerXPathNamespaces([
'n' => 'http://www.niso.org/2008/ncip'
]);

$node1 = $dom->first('/n:NCIPMessage');
$node2 = $dom->first('/n:NCIPMessage/n:CheckOutItemResponse/n:DateDue');
Expand Down

0 comments on commit 5164789

Please sign in to comment.