1
0
mirror of https://github.com/Chouchen/ShikiryuRSS.git synced 2024-05-19 18:21:31 +02:00

Starts refactoring reader

Still missing media:group
This commit is contained in:
Shikiryu 2023-04-06 00:38:26 +02:00
parent a55b05d734
commit 4783124a1d
12 changed files with 288 additions and 111 deletions

31
src/Entity/Channel.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace Shikiryu\SRSS\Entity;
use Shikiryu\SRSS\Entity\Channel\Image;
class Channel
{
public string $title;
public string $link;
public string $description;
public ?string $language;
public ?string $copyright;
public ?string $managingEditor;
public ?string $webMaster;
public ?string $pubDate;
public ?string $lastBuildDate;
public ?string $category;
public ?string $generator;
public ?string $docs;
public ?string $cloud;
public ?string $ttl;
public ?Image $image;
public ?string $rating;
public ?string $textInput;
public ?string $skipHours;
public ?string $skipDays;
public array $required = ['title', 'link', 'description'];
}

View File

@ -0,0 +1,15 @@
<?php
namespace Shikiryu\SRSS\Entity\Channel;
class Image
{
public string $url;
public string $title;
public string $link;
public int $width; // Maximum value for width is 144, default value is 88.
public int $height; //Maximum value for height is 400, default value is 31.
public string $description;
public array $required = ['url', 'title', 'link'];
}

19
src/Entity/Item.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace Shikiryu\SRSS\Entity;
class Item
{
public ?string $title;
public ?string $link;
public ?string $description;
public ?string $author;
public ?string $category;
public ?string $comments;
public ?string $enclosure;
public ?string $guid;
public ?string $pubDate;
public ?string $source;
public array $required = ['description'];
}

View File

@ -1,13 +1,14 @@
<?php <?php
namespace Shikiryu\SRSS\Media; namespace Shikiryu\SRSS\Entity\Media;
use DOMDocument;
use DOMElement; use DOMElement;
use DOMNode; use DOMNode;
use Shikiryu\SRSS\SRSSException; use Shikiryu\SRSS\SRSSException;
use Shikiryu\SRSS\SRSSTools; use Shikiryu\SRSS\SRSSTools;
class Content extends DOMElement class Content extends DomDocument
{ {
protected array $possibilities = [ protected array $possibilities = [
'url' => 'link', 'url' => 'link',
@ -27,6 +28,8 @@ class Content extends DOMElement
]; ];
private array $attr = []; private array $attr = [];
private DOMNode $node;
/** /**
* Constructor * Constructor
* *
@ -34,16 +37,21 @@ class Content extends DOMElement
*/ */
public function __construct(?\DOMNode $node = null) public function __construct(?\DOMNode $node = null)
{ {
parent::__construct('media:content'); parent::__construct();
if ($node instanceof DOMElement) {
$this->node = $this->importNode($node, true);
} else {
$this->node = $this->importNode(new DomElement('item'));
}
$this->_loadAttributes(); $this->_loadAttributes();
} }
/** /**
* @return void * @return void
*/ */
private function _loadAttributes() private function _loadAttributes(): void
{ {
foreach ($this->attributes as $attributes) { foreach ($this->node->attributes as $attributes) {
if (array_key_exists($attributes->name, $this->possibilities)) { if (array_key_exists($attributes->name, $this->possibilities)) {
$this->{$attributes->name} = $attributes->value; $this->{$attributes->name} = $attributes->value;
} }
@ -102,8 +110,11 @@ class Content extends DOMElement
} }
$flag = $this->possibilities[$name]; $flag = $this->possibilities[$name];
if ($flag !== '')
if ($flag !== '') {
$val = SRSSTools::check($val, $flag); $val = SRSSTools::check($val, $flag);
}
if (!empty($val)) { if (!empty($val)) {
if ($this->$name === null) { if ($this->$name === null) {
$this->node->appendChild(new DomElement($name, $val)); $this->node->appendChild(new DomElement($name, $val));

View File

@ -0,0 +1,8 @@
<?php
namespace Shikiryu\SRSS\Entity\Media;
class Group
{
}

View File

@ -1,8 +0,0 @@
<?php
namespace Shikiryu\SRSS\Media;
class Group
{
}

View File

@ -3,16 +3,23 @@
namespace Shikiryu\SRSS; namespace Shikiryu\SRSS;
use DOMDocument; use DOMDocument;
use DOMNodeList;
use DOMXPath; use DOMXPath;
use Iterator; use Iterator;
use ReturnTypeWillChange;
use Shikiryu\SRSS\Entity\Channel;
use Shikiryu\SRSS\Entity\Channel\Image;
use Shikiryu\SRSS\Entity\Item;
class SRSS extends DomDocument implements Iterator class SRSS extends DomDocument implements Iterator
{ {
protected $xpath; // xpath engine protected DOMXPath $xpath; // xpath engine
protected $items; // array of SRSSItems protected array $items; // array of SRSSItems
protected $attr; // array of RSS attributes protected $attr; // array of RSS attributes
private $position; // Iterator position private $position; // Iterator position
private Channel $channel;
// lists of possible attributes for RSS // lists of possible attributes for RSS
protected $possibleAttr = [ protected $possibleAttr = [
'title' => 'nohtml', 'title' => 'nohtml',
@ -45,8 +52,8 @@ class SRSS extends DomDocument implements Iterator
libxml_use_internal_errors(true); libxml_use_internal_errors(true);
parent::__construct(); parent::__construct();
$this->xpath = new DOMXpath($this); $this->xpath = new DOMXpath($this);
$this->attr = array(); $this->attr = [];
$this->items = array(); $this->items = [];
$this->position = 0; $this->position = 0;
$this->formatOutput = true; $this->formatOutput = true;
$this->preserveWhiteSpace = false; $this->preserveWhiteSpace = false;
@ -65,19 +72,19 @@ class SRSS extends DomDocument implements Iterator
} }
/** /**
* @param $link string url of the rss * @param string $link url of the rss
* @throws SRSSException * @throws SRSSException
* @return SRSS * @return SRSS
*/ */
public static function read($link): SRSS public static function read(string $link): SRSS
{ {
$doc = new SRSS; $doc = new SRSS;
if(@$doc->load($link)) // We don't want the warning in case of bad XML. Let's manage it with an exception. if(@$doc->load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception.
{
$channel = $doc->getElementsByTagName('channel'); $channel = $doc->getElementsByTagName('channel');
if($channel->length == 1){ // Good URL and good RSS if($channel->length == 1){ // Good URL and good RSS
$doc->_loadAttributes(); // loading channel properties $doc->_loadAttributes(); // loading channel properties
$doc->getItems(); // loading all items $doc->getItems(); // loading all items
return $doc; return $doc;
} }
@ -89,6 +96,7 @@ class SRSS extends DomDocument implements Iterator
/** /**
* @return SRSS * @return SRSS
* @throws \DOMException
*/ */
public static function create() public static function create()
{ {
@ -101,25 +109,31 @@ class SRSS extends DomDocument implements Iterator
$doc->encoding = "UTF-8"; $doc->encoding = "UTF-8";
$doc->generator = 'Shikiryu RSS'; $doc->generator = 'Shikiryu RSS';
// $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html';
$doc->channel = new Channel();
$doc->items = [];
return $doc; return $doc;
} }
/** /**
* getter of "image"'s channel attributes * getter of "image"'s channel attributes
* @return string|array * @return string|array
* TODO
*/ */
public function image() public function image()
{ {
$args = func_get_args(); $args = func_get_args();
if(func_num_args() == 0) $args[0] = 'url'; if (func_num_args() == 0) {
$args[0] = 'url';
}
$img = $this->xpath->query('//channel/image'); $img = $this->xpath->query('//channel/image');
if($img->length != 1) return null; // <image> is not in channel if($img->length != 1) { // <image> is not in channel
return null;
}
$img = $img->item(0); $img = $img->item(0);
$r = array(); $r = [];
foreach($img->childNodes as $child) foreach($img->childNodes as $child) {
{ if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) {
if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args))
{
$r[$child->nodeName] = $child->nodeValue; $r[$child->nodeName] = $child->nodeValue;
} }
} }
@ -134,6 +148,7 @@ class SRSS extends DomDocument implements Iterator
* @param $width int width * @param $width int width
* @param $height int height * @param $height int height
* @param $description string description * @param $description string description
* TODO
*/ */
public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '') public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '')
{ {
@ -196,6 +211,7 @@ class SRSS extends DomDocument implements Iterator
* @param $path string path * @param $path string path
* @param $registerProcedure string register procedure * @param $registerProcedure string register procedure
* @param $protocol string protocol * @param $protocol string protocol
* TODO
*/ */
public function setCloud($domain, $port, $path, $registerProcedure, $protocol) public function setCloud($domain, $port, $path, $registerProcedure, $protocol)
{ {
@ -227,12 +243,13 @@ class SRSS extends DomDocument implements Iterator
/** /**
* check if current RSS is a valid one (based on specifications) * check if current RSS is a valid one (based on specifications)
* @return bool * @return bool
* TODO use required
*/ */
public function isValid() public function isValid()
{ {
$valid = true; $valid = true;
$items = $this->getItems(); $items = $this->getItems();
$invalidItems = array(); $invalidItems = [];
$i = 1; $i = 1;
foreach($items as $item){ foreach($items as $item){
if($item->isValid() === false){ if($item->isValid() === false){
@ -246,13 +263,16 @@ class SRSS extends DomDocument implements Iterator
/** /**
* getter of current RSS channel * getter of current RSS channel
* @return DOMElement * @return \DOMNode
* @throws SRSSException * @throws SRSSException
*/ */
private function _getChannel() private function _getChannel(): \DOMNode
{ {
$channel = $this->getElementsByTagName('channel'); $channel = $this->getElementsByTagName('channel');
if($channel->length != 1) throw new SRSSException('channel node not created, or too many channel nodes'); if($channel->length != 1) {
throw new SRSSException('channel node not created, or too many channel nodes');
}
return $channel->item(0); return $channel->item(0);
} }
@ -298,16 +318,19 @@ class SRSS extends DomDocument implements Iterator
*/ */
public function __get($name) public function __get($name)
{ {
if(isset($this->attr[$name])) if (isset($this->channel->{$name})) {
return $this->attr[$name]; return $this->channel->{$name};
}
// $channel = $this->_getChannel(); // $channel = $this->_getChannel();
if(array_key_exists($name, $this->possibleAttr)){ if(array_key_exists($name, $this->possibleAttr)){
$tmp = $this->xpath->query('//channel/'.$name); $tmp = $this->xpath->query('//channel/'.$name);
if($tmp->length != 1) return null; if($tmp->length != 1) {
return null;
}
return $tmp->item(0)->nodeValue; return $tmp->item(0)->nodeValue;
}else{
throw new SRSSException($name.' is not a possible value.');
} }
throw new SRSSException($name.' is not a possible value.');
} }
/** /**
@ -357,71 +380,80 @@ class SRSS extends DomDocument implements Iterator
/** /**
* key from Iterator * key from Iterator
*/ */
public function key() { #[ReturnTypeWillChange] public function key(): int
{
return $this->position; return $this->position;
} }
/** /**
* next from Iterator * next from Iterator
*/ */
public function next() { #[ReturnTypeWillChange] public function next(): void
{
++$this->position; ++$this->position;
} }
/** /**
* valid from Iterator * valid from Iterator
*/ */
public function valid() { #[ReturnTypeWillChange] public function valid(): bool
{
return isset($this->items[$this->position]); return isset($this->items[$this->position]);
} }
/** /**
* getter of 1st item * getter of 1st item
* @return SRSSItem * @return Item
*/ */
public function getFirst() public function getFirst(): ?Item
{ {
return $this->getItem(1); return $this->getItem(1);
} }
/** /**
* getter of last item * getter of last item
* @return SRSSItem * @return Item
*/ */
public function getLast() public function getLast(): Item
{ {
$items = $this->getItems(); $items = $this->getItems();
return $items[count($items)-1]; return $items[array_key_last($items)];
} }
/** /**
* getter of an item * getter of an item
* @param $i int * @param $i int
* @return SRSSItem *
* @return Item|null
*/ */
public function getItem($i) public function getItem(int $i): ?Item
{ {
$i--; $i--;
return isset($this->items[$i]) ? $this->items[$i] : null; return $this->items[$i] ?? null;
} }
/** /**
* getter of all items * getter of all items
* @return SRSSItem[] * @return Item[]
* @throws SRSSException * @throws SRSSException
*/ */
public function getItems() public function getItems(): array
{ {
if (!empty($this->items)) {
return $this->items;
}
$channel = $this->_getChannel(); $channel = $this->_getChannel();
$item = $channel->getElementsByTagName('item'); /** @var DOMNodeList $items */
$length = $item->length; $items = $channel->getElementsByTagName('item');
$length = $items->length;
$this->items = []; $this->items = [];
if($length > 0){ if ($length > 0) {
for($i = 0; $i < $length; $i++) for($i = 0; $i < $length; $i++) {
{ $this->items[$i] = SRSSItem::read($items->item($i));
$this->items[$i] = new SRSSItem($item->item($i));
} }
} }
return $this->items; return $this->items;
} }
@ -429,31 +461,36 @@ class SRSS extends DomDocument implements Iterator
* display XML * display XML
* see DomDocument's docs * see DomDocument's docs
*/ */
public function show() public function show(): bool|string
{ {
// TODO build
return $this->saveXml(); return $this->saveXml();
} }
/** /**
* putting all RSS attributes into the object * putting all RSS attributes into the object
* @throws SRSSException
*/ */
private function _loadAttributes() private function _loadAttributes(): void
{ {
$channel = $this->_getChannel(); $node_channel = $this->_getChannel();
foreach($channel->childNodes as $child) $this->channel = new Channel();
{
if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'item') foreach($node_channel->childNodes as $child) {
{ if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName !== 'item') {
if($child->nodeName == 'image'){ if($child->nodeName == 'image') {
foreach($child->childNodes as $children) $image = new Image();
{ foreach($child->childNodes as $children) {
if($children->nodeType == XML_ELEMENT_NODE) if($children->nodeType == XML_ELEMENT_NODE) {
$this->attr['image'][$children->nodeName] = $children->nodeValue; $image->{$child->nodeName} = $children->nodeValue;
}
} }
$this->channel->image = $image;
} else {
$this->channel->{$child->nodeName} = $child->nodeValue;
} }
else
$this->attr[$child->nodeName] = $child->nodeValue;
} }
} }
} }
@ -461,18 +498,16 @@ class SRSS extends DomDocument implements Iterator
/** /**
* transform current object into an array * transform current object into an array
* @return array * @return array
* @throws SRSSException
*/ */
public function toArray() public function toArray(): array
{ {
$doc = array(); $doc = $this->channel->toArray();
foreach($this->attr as $attrName => $attrVal)
{ foreach($this->getItems() as $item) {
$doc[$attrName] = $attrVal;
}
foreach($this->getItems() as $item)
{
$doc['items'][] = $item->toArray(); $doc['items'][] = $item->toArray();
} }
return $doc; return $doc;
} }
} }

8
src/SRSSBuilder.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace Shikiryu\SRSS;
class SRSSBuilder
{
}

View File

@ -4,18 +4,22 @@ namespace Shikiryu\SRSS;
use DOMDocument; use DOMDocument;
use DOMElement; use DOMElement;
use Shikiryu\SRSS\Media\Content; use DOMException;
use DOMNode;
use Shikiryu\SRSS\Entity\Item;
use Shikiryu\SRSS\Entity\Media\Content;
/**
* @property string|null $description
*/
class SRSSItem extends DomDocument class SRSSItem extends DomDocument
{ {
/**
* @var DOMElement protected DOMNode $node; // item node
*/
protected $node; // item node
protected $attr; // item's properties protected $attr; // item's properties
// possible properties' names // possible properties' names
protected $possibilities = [ protected static $possibilities = [
'title' => 'nohtml', 'title' => 'nohtml',
'link' => 'link', 'link' => 'link',
'description' => 'html', 'description' => 'html',
@ -38,44 +42,53 @@ class SRSSItem extends DomDocument
public function __construct($node = null) public function __construct($node = null)
{ {
parent::__construct(); parent::__construct();
if ($node instanceof DOMElement) $this->node = $this->importNode($node, true); // if ($node instanceof DOMElement) $this->node = $this->importNode($node, true);
else $this->node = $this->importNode(new DomElement('item')); // else $this->node = $this->importNode(new DomElement('item'));
$this->_loadAttributes(); // $this->_loadAttributes();
}
/**
* putting all item attributes into the object
*/
private function _loadAttributes(): void
{
$this->_loadChildAttributes($this->node->childNodes);
} }
/** /**
* @param Item $item
* @param $nodes * @param $nodes
* *
* @return void * @return void
*/ */
private function _loadChildAttributes($nodes): void private static function _loadChildAttributes(Item $item, $nodes): void
{ {
foreach ($nodes as $child) { foreach ($nodes->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') { if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') {
if (array_key_exists($child->nodeName, $this->possibilities) && $this->possibilities[$child->nodeName] === 'folder') { if (array_key_exists($child->nodeName, self::$possibilities) && self::$possibilities[$child->nodeName] === 'folder') {
$this->_loadChildAttributes($child->childNodes); self::_loadChildAttributes($item, $child);
} } elseif ($child->nodeName === 'media:group') {
if ($child->nodeName === 'media:content') { // TODO
$this->{$child->nodeName} = new Content($child); } elseif ($child->nodeName === 'media:content') {
$item->{$child->nodeName} = new Content($child);
} else { } else {
$this->{$child->nodeName} = $child->nodeValue; $item->{$child->nodeName} = $child->nodeValue;
} }
} }
} }
} }
/**
* @param DOMNode|null $node
*
* @return Item
*/
public static function read(?DOMNode $node = null): Item
{
$item = new Item();
if ($node instanceof DOMNode) {
self::_loadChildAttributes($item, $node);
}
return $item;
}
/** /**
* getter of item DomElement * getter of item DomElement
*/ */
public function getItem() public function getItem(): ?\DOMNode
{ {
$this->appendChild($this->node); $this->appendChild($this->node);
@ -88,8 +101,9 @@ class SRSSItem extends DomDocument
* @param $url string url * @param $url string url
* @param $length int length * @param $length int length
* @param $type string type * @param $type string type
* @throws DOMException
*/ */
public function setEnclosure($url, $length, $type) public function setEnclosure(string $url, int $length, string $type): void
{ {
$array = []; $array = [];
$url = SRSSTools::checkLink($url); $url = SRSSTools::checkLink($url);
@ -112,9 +126,9 @@ class SRSSItem extends DomDocument
* check if current item is valid (following specifications) * check if current item is valid (following specifications)
* @return bool * @return bool
*/ */
public function isValid() public function isValid(): bool
{ {
return $this->description != null ? true : false; return $this->description != null;
} }
/** /**
@ -133,7 +147,7 @@ class SRSSItem extends DomDocument
* @param $name * @param $name
* @param $val * @param $val
* *
* @throws SRSSException * @throws SRSSException|DOMException
*/ */
public function __set($name, $val) public function __set($name, $val)
{ {
@ -145,7 +159,9 @@ class SRSSItem extends DomDocument
if ($flag !== '') if ($flag !== '')
$val = SRSSTools::check($val, $flag); $val = SRSSTools::check($val, $flag);
if (!empty($val)) { if (!empty($val)) {
if ($this->$name == null) { if ($val instanceof DOMElement) {
$this->node->appendChild($val);
} elseif ($this->$name == null) {
$this->node->appendChild(new DomElement($name, $val)); $this->node->appendChild(new DomElement($name, $val));
} }
$this->attr[$name] = $val; $this->attr[$name] = $val;

View File

@ -11,6 +11,7 @@ class BasicReader extends TestCase
$rss = SRSS::read(__DIR__.'/resources/basic.xml'); $rss = SRSS::read(__DIR__.'/resources/basic.xml');
self::assertEquals('test Home Page', $rss->title); self::assertEquals('test Home Page', $rss->title);
$first_item = $rss->getFirst(); $first_item = $rss->getFirst();
self::assertNotNull($first_item);
self::assertEquals('RSS Tutorial', $first_item->title); self::assertEquals('RSS Tutorial', $first_item->title);
} }

View File

@ -13,6 +13,6 @@ class MediaTest extends TestCase
$first_item = $rss->getFirst(); $first_item = $rss->getFirst();
self::assertEquals('Kirstie Alley, \'Cheers\' and \'Veronica\'s Closet\' star, dead at 71', $first_item->title); self::assertEquals('Kirstie Alley, \'Cheers\' and \'Veronica\'s Closet\' star, dead at 71', $first_item->title);
self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->{'media:group'}->{'media:content'}->url); self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->{'media:content'}->url);
} }
} }

View File

@ -0,0 +1,41 @@
<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>Liftoff News</title>
<link>http://liftoff.msfc.nasa.gov/</link>
<description>Liftoff to Space Exploration.</description>
<language>en-us</language>
<pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
<lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<generator>Weblog Editor 2.0</generator>
<managingEditor>editor@example.com</managingEditor>
<webMaster>webmaster@example.com</webMaster>
<item>
<title>Star City</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
<description>How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's &lt;a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm"&gt;Star City&lt;/a&gt;.</description>
<pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
</item>
<item>
<description>Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a &lt;a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm"&gt;partial eclipse of the Sun&lt;/a&gt; on Saturday, May 31st.</description>
<pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>
</item>
<item>
<title>The Engine That Does More</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp</link>
<description>Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.</description>
<pubDate>Tue, 27 May 2003 08:37:32 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/27.html#item571</guid>
</item>
<item>
<title>Astronauts' Dirty Laundry</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp</link>
<description>Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.</description>
<pubDate>Tue, 20 May 2003 08:56:02 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570</guid>
</item>
</channel>
</rss>