diff --git a/src/XML.php b/src/XML.php
new file mode 100644
index 0000000..9a8ad78
--- /dev/null
+++ b/src/XML.php
@@ -0,0 +1,279 @@
+
+ * @copyright 2015 - 2018 Timothy J. Warren
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version 2.4.1
+ * @link https://git.timshomepage.net/timw4mail/ion
+ */
+
+namespace Aviat\Ion;
+
+use DOMDocument, DOMNode, DOMNodeList, InvalidArgumentException;
+
+/**
+ * XML <=> PHP Array codec
+ */
+final class XML {
+
+ /**
+ * XML representation of the data
+ *
+ * @var string
+ */
+ private $xml;
+
+ /**
+ * PHP array version of the data
+ *
+ * @var array
+ */
+ private $data;
+
+ /**
+ * XML constructor
+ *
+ * @param string $xml
+ * @param array $data
+ */
+ public function __construct(string $xml = '', array $data = [])
+ {
+ $this->setXML($xml)->setData($data);
+ }
+
+ /**
+ * Serialize the data to an xml string
+ *
+ * @return string
+ */
+ public function __toString(): string
+ {
+ return static::toXML($this->getData());
+ }
+
+ /**
+ * Get the data parsed from the XML
+ *
+ * @return array
+ */
+ public function getData(): array
+ {
+ return $this->data;
+ }
+
+ /**
+ * Set the data to create xml from
+ *
+ * @param array $data
+ * @return self
+ */
+ public function setData(array $data): self
+ {
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * Get the xml created from the data
+ *
+ * @return string
+ */
+ public function getXML(): string
+ {
+ return $this->xml;
+ }
+
+ /**
+ * Set the xml to parse the data from
+ *
+ * @param string $xml
+ * @return self
+ */
+ public function setXML(string $xml): self
+ {
+ $this->xml = $xml;
+ return $this;
+ }
+
+ /**
+ * Parse an xml document string to a php array
+ *
+ * @param string $xml
+ * @return array
+ */
+ public static function toArray(string $xml): array
+ {
+ $data = [];
+
+ $xml = static::stripXMLWhitespace($xml);
+
+ $dom = new DOMDocument();
+ $hasLoaded = @$dom->loadXML($xml);
+
+ if ( ! $hasLoaded)
+ {
+ throw new InvalidArgumentException('Failed to load XML');
+ }
+
+ $root = $dom->documentElement;
+
+ $data[$root->tagName] = [];
+
+ if ($root->hasChildNodes())
+ {
+ static::childNodesToArray($data[$root->tagName], $root->childNodes);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Transform the array into XML
+ *
+ * @param array $data
+ * @return string
+ */
+ public static function toXML(array $data): string
+ {
+ $dom = new DOMDocument();
+ $dom->encoding = 'UTF-8';
+
+ static::arrayPropertiesToXmlNodes($dom, $dom, $data);
+
+ return $dom->saveXML();
+ }
+
+ /**
+ * Parse the xml document string to a php array
+ *
+ * @return array
+ */
+ public function parse(): array
+ {
+ $xml = $this->getXML();
+ $data = static::toArray($xml);
+ return $this->setData($data)->getData();
+ }
+
+ /**
+ * Transform the array into XML
+ *
+ * @return string
+ */
+ public function createXML(): string
+ {
+ return static::toXML($this->getData());
+ }
+
+ /**
+ * Strip whitespace from raw xml to remove irrelevant text nodes
+ *
+ * @param string $xml
+ * @return string
+ */
+ private static function stripXMLWhitespace(string $xml): string
+ {
+ // Get rid of unimportant text nodes by removing
+ // whitespace characters from between xml tags,
+ // except for the xml declaration tag, Which looks
+ // something like:
+ /* */
+ return preg_replace('/([^?])>\s+', '$1><', $xml);
+ }
+
+ /**
+ * Recursively create array structure based on xml structure
+ *
+ * @param array $root A reference to the current array location
+ * @param DOMNodeList $nodeList The current NodeList object
+ * @return void
+ */
+ private static function childNodesToArray(array &$root, DOMNodelist $nodeList): void
+ {
+ $length = $nodeList->length;
+ for ($i = 0; $i < $length; $i++)
+ {
+ $el = $nodeList->item($i);
+ $current =& $root[$el->nodeName];
+
+ // It's a top level element!
+ if (( ! $el->hasChildNodes()) || ($el->childNodes->item(0) instanceof \DomText))
+ {
+ $current = $el->textContent;
+ continue;
+ }
+
+ // An empty value at the current root
+ if ($current === NULL)
+ {
+ $current = [];
+ static::childNodesToArray($current, $el->childNodes);
+ continue;
+ }
+
+ $keys = array_keys($current);
+
+ // Wrap the array in a containing array
+ // if there are only string keys
+ if ( ! is_numeric($keys[0]))
+ {
+ // But if there is only one key, don't wrap it in
+ // an array, just recurse to parse the child nodes
+ if (count($current) === 1)
+ {
+ static::childNodesToArray($current, $el->childNodes);
+ continue;
+ }
+ $current = [$current];
+ }
+
+ $current[] = [];
+ $index = count($current) - 1;
+
+ static::childNodesToArray($current[$index], $el->childNodes);
+ }
+ }
+
+ /**
+ * Recursively create xml nodes from array properties
+ *
+ * @param DOMDocument $dom The current DOM object
+ * @param DOMNode $parent The parent element to append children to
+ * @param array $data The data for the current node
+ * @return void
+ */
+ private static function arrayPropertiesToXmlNodes(DOMDocument $dom, DOMNode $parent, array $data): void
+ {
+ foreach ($data as $key => $props)
+ {
+ // 'Flatten' the array as you create the xml
+ if (is_numeric($key))
+ {
+ foreach ($props as $k => $p)
+ {
+ break;
+ }
+ }
+
+ $node = $dom->createElement($key);
+
+ if (\is_array($props))
+ {
+ static::arrayPropertiesToXmlNodes($dom, $node, $props);
+ } else
+ {
+ $tNode = $dom->createTextNode((string)$props);
+ $node->appendChild($tNode);
+ }
+
+ $parent->appendChild($node);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/XMLTest.php b/tests/XMLTest.php
new file mode 100644
index 0000000..33ba504
--- /dev/null
+++ b/tests/XMLTest.php
@@ -0,0 +1,89 @@
+
+ * @copyright 2015 - 2018 Timothy J. Warren
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version 2.4.1
+ * @link https://git.timshomepage.net/timw4mail/ion
+ */
+
+namespace Aviat\Ion\Tests;
+
+use Aviat\Ion\XML;
+use PHPUnit\Framework\TestCase;
+
+class XMLTest extends TestCase {
+
+ protected $xml;
+ protected $expectedXml;
+ protected $object;
+ protected $array;
+
+ public function setUp()
+ {
+ $this->xml = file_get_contents(__DIR__ . '/test_data/XML/xmlTestFile.xml');
+ $this->expectedXml = file_get_contents(__DIR__ . '/test_data/XML/minifiedXmlTestFile.xml');
+
+ $this->array = [
+ 'entry' => [
+ 'foo' => [
+ 'bar' => [
+ 'baz' => 42
+ ]
+ ],
+ 'episode' => '11',
+ 'status' => 'watching',
+ 'score' => '7',
+ 'storage_type' => '1',
+ 'storage_value' => '2.5',
+ 'times_rewatched' => '1',
+ 'rewatch_value' => '3',
+ 'date_start' => '01152015',
+ 'date_finish' => '10232016',
+ 'priority' => '2',
+ 'enable_discussion' => '0',
+ 'enable_rewatching' => '1',
+ 'comments' => 'Should you say something?',
+ 'tags' => 'test tag, 2nd tag'
+ ]
+ ];
+
+ $this->object = new XML();
+ }
+
+ public function testToArray()
+ {
+ $this->assertEquals($this->array, XML::toArray($this->xml));
+ }
+
+ public function testParse()
+ {
+ $this->object->setXML($this->xml);
+ $this->assertEquals($this->array, $this->object->parse());
+ }
+
+ public function testToXML()
+ {
+ $this->assertEquals($this->expectedXml, XML::toXML($this->array));
+ }
+
+ public function testCreateXML()
+ {
+ $this->object->setData($this->array);
+ $this->assertEquals($this->expectedXml, $this->object->createXML());
+ }
+
+ public function testToString()
+ {
+ $this->object->setData($this->array);
+ $this->assertEquals($this->expectedXml, $this->object->__toString());
+ $this->assertEquals($this->expectedXml, (string)$this->object);
+ }
+}
\ No newline at end of file
diff --git a/tests/test_data/XML/minifiedXmlTestFile.xml b/tests/test_data/XML/minifiedXmlTestFile.xml
new file mode 100644
index 0000000..c0490c7
--- /dev/null
+++ b/tests/test_data/XML/minifiedXmlTestFile.xml
@@ -0,0 +1,2 @@
+
+4211watching712.5130115201510232016201Should you say something?test tag, 2nd tag
diff --git a/tests/test_data/XML/xmlTestFile.xml b/tests/test_data/XML/xmlTestFile.xml
new file mode 100644
index 0000000..b37cf96
--- /dev/null
+++ b/tests/test_data/XML/xmlTestFile.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ 42
+
+
+ 11
+ watching
+ 7
+ 1
+ 2.5
+ 1
+ 3
+ 01152015
+ 10232016
+ 2
+ 0
+ 1
+ Should you say something?
+ test tag, 2nd tag
+
\ No newline at end of file