From 62c06b77313ff0d65228aa2252c1a9661ddbc592 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Wed, 11 Jan 2017 19:35:51 -0500 Subject: [PATCH] Start of integration with My Anime List --- src/API/MAL.php | 49 +++++ src/API/MAL/Enum/AnimeWatchingStatus.php | 30 ++++ src/API/MAL/Enum/MangaReadingStatus.php | 30 ++++ src/API/XML.php | 220 +++++++++++++++++++++++ tests/API/XMLTest.php | 84 +++++++++ tests/test_data/minifiedXmlTestFile.xml | 2 + tests/test_data/xmlTestFile.xml | 22 +++ 7 files changed, 437 insertions(+) create mode 100644 src/API/MAL.php create mode 100644 src/API/MAL/Enum/AnimeWatchingStatus.php create mode 100644 src/API/MAL/Enum/MangaReadingStatus.php create mode 100644 src/API/XML.php create mode 100644 tests/API/XMLTest.php create mode 100644 tests/test_data/minifiedXmlTestFile.xml create mode 100644 tests/test_data/xmlTestFile.xml diff --git a/src/API/MAL.php b/src/API/MAL.php new file mode 100644 index 00000000..d6eb2199 --- /dev/null +++ b/src/API/MAL.php @@ -0,0 +1,49 @@ + + * @copyright 2015 - 2017 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.0 + * @link https://github.com/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\API; + +use Aviat\AnimeClient\Enum\{AnimeWatchingStatus, MangaReadingStatus}; + +/** + * Constants and mappings for the My Anime List API + */ +class MAL { + const AUTH_URL = 'https://myanimelist.net/api/account/verify_credentials.xml'; + const BASE_URL = 'https://myanimelist.net/api/'; + + public static function getIdToWatchingStatusMap() + { + return [ + 1 => AnimeWatchingStatus::WATCHING, + 2 => AnimeWatchingStatus::COMPLETED, + 3 => AnimeWatchingStatus::ON_HOLD, + 4 => AnimeWatchingStatus::DROPPED, + 5 => AnimeWatchingStatus::PLAN_TO_WATCH + ]; + } + + public static function getIdToReadingStatusMap() + { + return [ + 1 => MangaReadingStatus::READING, + 2 => MangaReadingStatus::COMPLETED, + 3 => MangaReadingStatus::ON_HOLD, + 4 => MangaReadingStatus::DROPPED, + 5 => MangaReadingStatus::PLAN_TO_READ + ]; + } +} \ No newline at end of file diff --git a/src/API/MAL/Enum/AnimeWatchingStatus.php b/src/API/MAL/Enum/AnimeWatchingStatus.php new file mode 100644 index 00000000..b12e2657 --- /dev/null +++ b/src/API/MAL/Enum/AnimeWatchingStatus.php @@ -0,0 +1,30 @@ + + * @copyright 2015 - 2017 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.0 + * @link https://github.com/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\API\MAL\Enum; + +use Aviat\Ion\Enum as BaseEnum; + +/** + * Possible values for watching status for the current anime + */ +class AnimeWatchingStatus extends BaseEnum { + const WATCHING = 'watching'; + const COMPLETED = 'completed'; + const ON_HOLD = 'onhold'; + const DROPPED = 'dropped'; + const PLAN_TO_WATCH = 'plantowatch'; +} \ No newline at end of file diff --git a/src/API/MAL/Enum/MangaReadingStatus.php b/src/API/MAL/Enum/MangaReadingStatus.php new file mode 100644 index 00000000..edf82ddd --- /dev/null +++ b/src/API/MAL/Enum/MangaReadingStatus.php @@ -0,0 +1,30 @@ + + * @copyright 2015 - 2017 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.0 + * @link https://github.com/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\API\MAL\Enum; + +use Aviat\Ion\Enum as BaseEnum; + +/** + * Possible values for watching status for the current anime + */ +class MangaReadingStatus extends BaseEnum { + const READING = 'reading'; + const COMPLETED = 'completed'; + const ON_HOLD = 'onhold'; + const DROPPED = 'dropped'; + const PLAN_TO_READ = 'plantoread'; +} \ No newline at end of file diff --git a/src/API/XML.php b/src/API/XML.php new file mode 100644 index 00000000..e8148737 --- /dev/null +++ b/src/API/XML.php @@ -0,0 +1,220 @@ + + * @copyright 2015 - 2017 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.0 + * @link https://github.com/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\API; + +use DOMDocument, DOMNode, DOMNodelist; + +/** + * XML <=> PHP Array codec + */ +class XML { + + /** + * XML representation of the data + * + * @var string + */ + private $xml; + + /** + * PHP array version of the data + * + * @var array + */ + private $data; + + /** + * XML constructor + */ + public function __construct(string $xml = '', array $data = []) + { + $this->setXML($xml)->setData($data); + } + + /** + * Serialize the data to an xml 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 $this + */ + 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 $this + */ + 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 = []; + + // Get rid of unimportant text nodes by removing + // whitespace characters from between xml tags, + // except for the xml declaration tag, Which looks + // something like: + /* */ + $xml = preg_replace('/([^\?])>\s+<', $xml); + + $dom = new DOMDocument(); + $dom->loadXML($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()); + } + + /** + * 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) + { + $length = $nodeList->length; + for ($i = 0; $i < $length; $i++) + { + $el = $nodeList->item($i); + if (is_a($el->childNodes->item(0), 'DomText') || ( ! $el->hasChildNodes())) + { + $root[$el->nodeName] = $el->textContent; + } + else + { + $root[$el->nodeName] = []; + static::childNodesToArray($root[$el->nodeName], $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) + { + foreach($data as $key => $props) + { + $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/API/XMLTest.php b/tests/API/XMLTest.php new file mode 100644 index 00000000..50cebb37 --- /dev/null +++ b/tests/API/XMLTest.php @@ -0,0 +1,84 @@ + + * @copyright 2015 - 2017 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.0 + * @link https://github.com/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Tests\API; + +use Aviat\AnimeClient\API\XML; +use PHPUnit\Framework\TestCase; + +class XMLTest extends TestCase { + + public function setUp() + { + $this->xml = file_get_contents(__DIR__ . '/../test_data/xmlTestFile.xml'); + $this->expectedXml = file_get_contents(__DIR__ . '/../test_data/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/minifiedXmlTestFile.xml b/tests/test_data/minifiedXmlTestFile.xml new file mode 100644 index 00000000..c0490c7c --- /dev/null +++ b/tests/test_data/minifiedXmlTestFile.xml @@ -0,0 +1,2 @@ + +4211watching712.5130115201510232016201Should you say something?test tag, 2nd tag diff --git a/tests/test_data/xmlTestFile.xml b/tests/test_data/xmlTestFile.xml new file mode 100644 index 00000000..b37cf964 --- /dev/null +++ b/tests/test_data/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