diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b004717
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/vendor/
+/.idea
+/.phpunit.result.cache
+error.log
diff --git a/README.md b/README.md
index 58cd595..81a2dd3 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,29 @@
-All documentation @ http://labs.shikiryu.com/SRSS/#_how
+# SћíкïяўЦЯSS
+
+> PHP library reading and creating RSS
**disclaimer:**
-This class is functionnal. Anyway, use it only if you don't have other choices.
+This class is functional. Anyway, use it only if you don't have other choices.
For example, [Zend](http://framework.zend.com/manual/current/en/modules/zend.feed.introduction.html) and Symfony got their own RSS factory, don't add another one in.
+
+## :books: Table of Contents
+
+- [Installation](#package-installation)
+- [Usage](#rocket-usage)
+- [Features](#sparkles-features)
+- [Support](#hammer_and_wrench-support)
+- [Contributing](#memo-contributing)
+- [License](#scroll-license)
+
+## :package: Installation
+
+```sh
+composer install shikiryu/shikiryurss
+```
+
+## :rocket: Usage
+
----------------------------------
How to make it read RSS?
@@ -12,7 +32,7 @@ First, we need to load the RSS :
$rss = SRSS::read('http://exemple.com/rss.xml');
-Easy, right? Then you can extract general informations :
+Easy, right? Then you can extract general information :
echo $rss->title; // will display blog title
@@ -22,8 +42,7 @@ Then, you can take care of articles. You can select a precise article :
Or looping them :
- foreach($rss as $article)
- {
+ foreach($rss as $article) {
echo ''. SRSSTools::formatDate('d/m/y', $item->pubDate).' '.$item->title.'';
}
@@ -34,7 +53,11 @@ If you like arrays, you can transform the RSS into an array :
You can also save it into your server with :
$rss->save('/www/rss/rss.xml'); // example
-
+
+Or finally, you can display it with :
+
+ $rss->show();
+
----------------------------------
How to make it create RSS?
@@ -43,7 +66,7 @@ First, we need to initialize the RSS :
$rss = SRSS::create();
-Easy, right? Then you can add general informations :
+Easy, right? Then you can add general information :
$rss->title = 'My Awesome Blog';
$rss->link = 'http://shikiryu.com/devblog/';
@@ -53,7 +76,7 @@ Those 3 are mandatory to validate your RSS, other options can be added.
Then, you can add articles. Let's imagine $content contains an array from your database.
foreach($content as $item){
- $rssitem= new SRSSItem; // we create an item
+ $rssitem = new Item(); // we create an item
$rssitem->title = $item["title"]; // adding title (option)
$rssitem->link = $item['link']; // adding link (option)
$rssitem->pubDate = $item["date"]; // date automatically transformed into RSS format (option)
@@ -72,5 +95,43 @@ The other one does the opposite and add the next item in top of your RSS
----------------------------------
-Contact :
-http://shikiryu.com/contact
+## :sparkles: Features
+
+
+ - Read every RSS 2.0
+ -
+ Based on RSS 2.0 specifications.
+
+
+
+
+ - Write and validate RSS 2.0 file
+ -
+ Based on RSS 2.0 specifications.
+
+
+
+## :hammer_and_wrench: Support
+
+Please [open an issue](https://github.com/Chouchen/ShikiryuRSS/issues) for support.
+
+
+## :memo: Contributing
+
+Please contribute using [Github Flow](https://guides.github.com/introduction/flow/). Create a branch, add commits, and [open a pull request](https://github.com/Chouchen/ShikiryuRSS/pulls).
+
+## :scroll: License
+
+[Creative Commons Attribution NonCommercial (CC-BY-NC)]() © [Chouchen](https://github.com/Chouchen/)
+
+All documentation @ http://labs.shikiryu.com/SRSS/#_how.
+
+Contact : https://shikiryu.com/contact
+
+
+
+
+
+
+
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..b92702d
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "shikiryu/shikiryurss",
+ "description": "A RSS reader and writer",
+ "minimum-stability": "stable",
+ "license": "proprietary",
+ "authors": [
+ {
+ "name": "Shikiryu",
+ "email": "projets@shiki.fr",
+ "homepage": "https://shikiryu.com",
+ "role": "Developer"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Shikiryu\\SRSS\\": "src/"
+ }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9"
+ },
+ "require": {
+ "php": ">=8.0",
+ "ext-dom": "*",
+ "ext-libxml": "*"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..1454f64
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,1752 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "f9098bb855d1eaf15a0ec531f0903ae9",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9 || ^11",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.16 || ^1",
+ "phpstan/phpstan": "^1.4",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.30 || ^5.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/instantiator/issues",
+ "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-12-30T00:15:36+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3,<3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-08T13:26:56+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v4.15.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
+ "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
+ },
+ "time": "2023-03-05T19:49:14+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+ },
+ "time": "2021-07-20T11:28:43+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "9.2.26",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
+ "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.15",
+ "php": ">=7.3",
+ "phpunit/php-file-iterator": "^3.0.3",
+ "phpunit/php-text-template": "^2.0.2",
+ "sebastian/code-unit-reverse-lookup": "^2.0.2",
+ "sebastian/complexity": "^2.0",
+ "sebastian/environment": "^5.1.2",
+ "sebastian/lines-of-code": "^1.0.3",
+ "sebastian/version": "^3.0.1",
+ "theseer/tokenizer": "^1.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-03-06T12:58:08+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-12-02T12:48:52+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:58:55+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T05:33:50+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:16:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "9.6.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115",
+ "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.3.1 || ^2",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.10.1",
+ "phar-io/manifest": "^2.0.3",
+ "phar-io/version": "^3.0.2",
+ "php": ">=7.3",
+ "phpunit/php-code-coverage": "^9.2.13",
+ "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-invoker": "^3.1.1",
+ "phpunit/php-text-template": "^2.0.3",
+ "phpunit/php-timer": "^5.0.2",
+ "sebastian/cli-parser": "^1.0.1",
+ "sebastian/code-unit": "^1.0.6",
+ "sebastian/comparator": "^4.0.8",
+ "sebastian/diff": "^4.0.3",
+ "sebastian/environment": "^5.1.3",
+ "sebastian/exporter": "^4.0.5",
+ "sebastian/global-state": "^5.0.1",
+ "sebastian/object-enumerator": "^4.0.3",
+ "sebastian/resource-operations": "^3.0.3",
+ "sebastian/type": "^3.2",
+ "sebastian/version": "^3.0.2"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-27T11:43:46+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:08:49+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:08:54+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:30:19+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "4.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "fa0f136dd2334583309d32b62544682ee972b51a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
+ "reference": "fa0f136dd2334583309d32b62544682ee972b51a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/diff": "^4.0",
+ "sebastian/exporter": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2022-09-14T12:41:17+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.7",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:52:27+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:10:38+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "5.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+ "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:03:51+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "4.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
+ "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2022-09-14T06:03:37+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+ "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2022-02-14T08:28:10+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.6",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-28T06:42:11+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:12:34+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:14:26+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "4.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
+ "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:07:39+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:45:17+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+ "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:13:03+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:39:44+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2021-07-28T10:34:58+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "ext-dom": "*"
+ },
+ "platform-dev": [],
+ "plugin-api-version": "2.1.0"
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..3edcd2e
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Builder/ChannelBuilder.php b/src/Builder/ChannelBuilder.php
new file mode 100644
index 0000000..2f57ea1
--- /dev/null
+++ b/src/Builder/ChannelBuilder.php
@@ -0,0 +1,98 @@
+document = $document;
+ }
+
+ /**
+ * @param \Shikiryu\SRSS\Entity\Channel $channel
+ *
+ * @return \DOMElement|false
+ * @throws \DOMException
+ */
+ public function build(Channel $channel)
+ {
+ $node = $this->document->createElement('channel');
+ foreach (array_filter($channel->toArray(), static fn($el) => !empty($el)) as $name => $value) {
+ if ($name === 'category') {
+ /** @var \Shikiryu\SRSS\Entity\Channel\Category $category */
+ foreach ($value as $category) {
+ $node->appendChild($this->buildCategory($category));
+ }
+ } elseif ($name === 'cloud') {
+ $node->appendChild($this->buildCloud($value));
+ } elseif ($name === 'image') {
+ $node->appendChild($this->buildImage($value));
+ } else {
+ $new_node = $this->document->createElement($name, $value);
+ $node->appendChild($new_node);
+ }
+ }
+
+ return $node;
+ }
+
+ /**
+ * @param \Shikiryu\SRSS\Entity\Channel\Category $category
+ *
+ * @return bool|\DOMElement
+ * @throws \DOMException
+ */
+ private function buildCategory(Channel\Category $category): bool|DOMElement
+ {
+ $node = $this->document->createElement('category', $category->value);
+ $node->setAttribute('domain', $category->domain);
+
+ return $node;
+ }
+
+ /**
+ * @param \Shikiryu\SRSS\Entity\Channel\Cloud $cloud
+ *
+ * @return \DOMElement|false
+ * @throws \DOMException
+ */
+ private function buildCloud(Channel\Cloud $cloud)
+ {
+ $node = $this->document->createElement('cloud');
+ foreach (get_object_vars($cloud) as $name => $value) {
+ if (!is_array($value)) {
+ $node->setAttribute($name, $value);
+ }
+ }
+
+ return $node;
+ }
+
+ /**
+ * @param \Shikiryu\SRSS\Entity\Channel\Image $image
+ *
+ * @return \DOMElement|false
+ * @throws \DOMException
+ */
+ private function buildImage(Channel\Image $image)
+ {
+ $node = $this->document->createElement('image');
+ foreach (get_object_vars($image) as $name => $value) {
+ if (!is_array($value)) {
+ $node->appendChild($this->document->createElement($name, $value));
+ }
+ }
+
+ return $node;
+ }
+}
\ No newline at end of file
diff --git a/src/Builder/ItemBuilder.php b/src/Builder/ItemBuilder.php
new file mode 100644
index 0000000..b65de13
--- /dev/null
+++ b/src/Builder/ItemBuilder.php
@@ -0,0 +1,105 @@
+document = $document;
+ }
+
+ /**
+ * @param \Shikiryu\SRSS\Entity\Item $item
+ *
+ * @return \DOMElement|false
+ * @throws \DOMException
+ */
+ public function build(Item $item): bool|\DOMElement
+ {
+ $node = $this->document->createElement('item');
+
+ foreach (array_filter($item->toArray()) as $name => $value) {
+ if ($name === 'category') {
+ /** @var \Shikiryu\SRSS\Entity\Item\Category $category */
+ foreach ($value as $category) {
+ $node->appendChild($this->buildCategory($category));
+ }
+ } elseif ($name === 'medias') {
+ $group = null;
+ if (count($value) > 1) {
+ $group = $node->appendChild($this->document->createElement('media:group'));
+ }
+ foreach ($value as $media) {
+ if (null === $group) {
+ $node->appendChild($this->buildMedia($media));
+ } else {
+ $group->appendChild($this->buildMedia($media));
+ }
+ }
+ if ($group !== null) {
+ $node->appendChild($group);
+ }
+ } elseif ($name === 'enclosure') {
+ $node->appendChild($this->buildEnclosure($value));
+ } elseif ($name === 'source') {
+ $node->appendChild($this->buildSource($value));
+ } else {
+ $new_node = $this->document->createElement($name, $value);
+ $node->appendChild($new_node);
+ }
+ }
+
+ return $node;
+ }
+
+ private function buildCategory(Item\Category $category)
+ {
+ $node = $this->document->createElement('category', $category->value);
+ $node->setAttribute('domain', $category->domain);
+
+ return $node;
+ }
+
+ private function buildEnclosure(Item\Enclosure $enclosure)
+ {
+ $node = $this->document->createElement('enclosure');
+ foreach (get_object_vars($enclosure) as $name => $value) {
+ if (!is_array($value)) {
+ $node->setAttribute($name, $value);
+ }
+ }
+
+ return $node;
+ }
+
+ private function buildSource(Item\Source $source)
+ {
+ $node = $this->document->createElement('source', $source->value);
+ $node->setAttribute('url', $source->url);
+
+ return $node;
+ }
+
+ private function buildMedia(Content $media)
+ {
+ $node = $this->document->createElement('media:content');
+ foreach (get_object_vars($media) as $name => $value) {
+ if (!is_array($value)) {
+ $node->setAttribute($name, $value);
+ }
+ }
+
+ return $node;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/Builder/SRSSBuilder.php b/src/Builder/SRSSBuilder.php
new file mode 100644
index 0000000..c62ddc4
--- /dev/null
+++ b/src/Builder/SRSSBuilder.php
@@ -0,0 +1,65 @@
+createElement('rss');
+
+ $root->setAttribute('version', '2.0');
+
+ $srss->channel->generator = 'Shikiryu RSS';
+
+ $channel_builder = new ChannelBuilder($this);
+ $channel = $channel_builder->build($srss->channel);
+
+ $item_builder = new ItemBuilder($this);
+ foreach ($srss->items as $item) {
+ $channel->appendChild($item_builder->build($item));
+ }
+
+ $root->appendChild($channel);
+ $this->appendChild($root);
+ $this->encoding = 'UTF-8';
+ $this->formatOutput = true;
+ $this->preserveWhiteSpace = false;
+ // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html';
+
+ } catch (\DOMException $e) {
+ throw new DOMBuilderException($e);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @throws \Shikiryu\SRSS\Exception\DOMBuilderException
+ */
+ public function build(SRSS $srss, string $filepath): void
+ {
+ $this
+ ->buildRSS($srss)
+ ->save($filepath);
+ }
+
+ /**
+ * @return false|string
+ * @throws \Shikiryu\SRSS\Exception\DOMBuilderException
+ */
+ public function show(SRSS $srss): bool|string
+ {
+ return $this
+ ->buildRSS($srss)
+ ->saveXml();
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php
new file mode 100644
index 0000000..59aee30
--- /dev/null
+++ b/src/Entity/Channel.php
@@ -0,0 +1,112 @@
+ element is something of a mystery. You can use it to specify a search engine box. Or to allow a reader to provide feedback. Most aggregators ignore it.
+ */
+ public ?string $textInput = null;
+ /**
+ * @hour
+ */
+ public ?string $skipHours = null;
+ /**
+ * @day
+ */
+ public ?string $skipDays = null;
+
+ /**
+ * @return bool
+ * @throws ReflectionException
+ */
+ public function isValid(): bool
+ {
+ return (new Validator())->isObjectValid($this);
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray(): array
+ {
+ $vars = get_object_vars($this);
+ unset($vars['validated']);
+ return $vars;
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Channel/Category.php b/src/Entity/Channel/Category.php
new file mode 100644
index 0000000..6923c47
--- /dev/null
+++ b/src/Entity/Channel/Category.php
@@ -0,0 +1,34 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ return get_object_vars($this);
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Channel/Cloud.php b/src/Entity/Channel/Cloud.php
new file mode 100644
index 0000000..9d379f3
--- /dev/null
+++ b/src/Entity/Channel/Cloud.php
@@ -0,0 +1,46 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ return get_object_vars($this);
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Channel/Image.php b/src/Entity/Channel/Image.php
new file mode 100644
index 0000000..a4dfaaa
--- /dev/null
+++ b/src/Entity/Channel/Image.php
@@ -0,0 +1,53 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ return get_object_vars($this);
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Item.php b/src/Entity/Item.php
new file mode 100644
index 0000000..3faa0fd
--- /dev/null
+++ b/src/Entity/Item.php
@@ -0,0 +1,79 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ $vars = get_object_vars($this);
+ unset($vars['validated']);
+ return $vars;
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Item/Category.php b/src/Entity/Item/Category.php
new file mode 100644
index 0000000..fe9b493
--- /dev/null
+++ b/src/Entity/Item/Category.php
@@ -0,0 +1,34 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ return get_object_vars($this);
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Item/Enclosure.php b/src/Entity/Item/Enclosure.php
new file mode 100644
index 0000000..fa9d8fb
--- /dev/null
+++ b/src/Entity/Item/Enclosure.php
@@ -0,0 +1,40 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ return get_object_vars($this);
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Item/Source.php b/src/Entity/Item/Source.php
new file mode 100644
index 0000000..355a1e0
--- /dev/null
+++ b/src/Entity/Item/Source.php
@@ -0,0 +1,35 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ return get_object_vars($this);
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/Media/Content.php b/src/Entity/Media/Content.php
new file mode 100644
index 0000000..0cbacdc
--- /dev/null
+++ b/src/Entity/Media/Content.php
@@ -0,0 +1,82 @@
+isObjectValid($this);
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ public function toArray(): array
+ {
+ return get_object_vars($this);
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/SRSSElement.php b/src/Entity/SRSSElement.php
new file mode 100644
index 0000000..607d3b3
--- /dev/null
+++ b/src/Entity/SRSSElement.php
@@ -0,0 +1,16 @@
+ not found', $file));
+ }
+
+}
\ No newline at end of file
diff --git a/src/Exception/DOMBuilderException.php b/src/Exception/DOMBuilderException.php
new file mode 100644
index 0000000..8b3fd26
--- /dev/null
+++ b/src/Exception/DOMBuilderException.php
@@ -0,0 +1,12 @@
+getMessage()));
+ }
+
+}
\ No newline at end of file
diff --git a/src/Exception/PropertyNotFoundException.php b/src/Exception/PropertyNotFoundException.php
new file mode 100644
index 0000000..58b39b4
--- /dev/null
+++ b/src/Exception/PropertyNotFoundException.php
@@ -0,0 +1,15 @@
+Message : ' . $this->getMessage() . ' à la ligne : ' . $this->getLine();
+ }
+}
\ No newline at end of file
diff --git a/src/Exception/UnreadableRSSException.php b/src/Exception/UnreadableRSSException.php
new file mode 100644
index 0000000..ef9e3a7
--- /dev/null
+++ b/src/Exception/UnreadableRSSException.php
@@ -0,0 +1,12 @@
+childNodes as $child) {
+ if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') {
+ if ($child->nodeName === 'media:group') {
+ self::_loadChildAttributes($item, $child);
+ } elseif ($child->nodeName === 'media:content') {
+ $item->medias[] = MediaContentParser::read($child);
+ } elseif ($child->nodeName === 'category') {
+ $category = new Item\Category();
+ foreach($child->attributes as $attribute) {
+ $category->{$attribute->name} = $attribute->value;
+ }
+ $category->value = $child->nodeValue;
+ $item->category[] = $category;
+ } elseif ($child->nodeName === 'enclosure') {
+ $enclosure = new Item\Enclosure();
+ foreach($child->attributes as $attribute) {
+ $enclosure->{$attribute->name} = $attribute->value;
+ }
+ $item->enclosure = $enclosure;
+ } elseif ($child->nodeName === 'source') {
+ $source = new Item\Source();
+ foreach($child->attributes as $attribute) {
+ $source->{$attribute->name} = $attribute->value;
+ }
+ $source->value = $child->nodeValue;
+ $item->source = $source;
+ } else {
+ $item->{$child->nodeName} = trim($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;
+ }
+}
\ No newline at end of file
diff --git a/src/Parser/MediaContentParser.php b/src/Parser/MediaContentParser.php
new file mode 100644
index 0000000..9179a76
--- /dev/null
+++ b/src/Parser/MediaContentParser.php
@@ -0,0 +1,62 @@
+attributes as $attribute) {
+
+ if (property_exists(Content::class, $attribute->name)) {
+ $item->{$attribute->name} = $attribute->value;
+ }
+
+ }
+ }
+
+ /**
+ * @param DOMNode|null $node
+ *
+ * @return Content
+ */
+ public static function read(?DOMNode $node = null): Content
+ {
+ $content = new Content();
+ if ($node instanceof DOMNode) {
+ self::_loadChildAttributes($content, $node);
+ }
+
+ return $content;
+ }
+}
\ No newline at end of file
diff --git a/src/Parser/SRSSParser.php b/src/Parser/SRSSParser.php
new file mode 100644
index 0000000..684d450
--- /dev/null
+++ b/src/Parser/SRSSParser.php
@@ -0,0 +1,135 @@
+doc = new SRSS();
+ }
+
+ /**
+ * Destructor
+ * manage of libxml errors
+ */
+ public function __destruct()
+ {
+ foreach (libxml_get_errors() as $error) {
+ error_log($error->message, 3, 'error.log');
+ }
+ libxml_clear_errors();
+ }
+
+ /**
+ * @param string $link
+ *
+ * @return SRSS
+ * @throws ChannelNotFoundInRSSException
+ * @throws SRSSException
+ * @throws UnreadableRSSException
+ */
+ public function parse(string $link): SRSS
+ {
+ if (@$this->load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception.
+ $channel = $this->getElementsByTagName('channel');
+ if($channel->length === 1){ // Good URL and good RSS
+ $this->_parseChannel(); // loading channel properties
+ $this->_parseItems(); // loading all items
+
+ return $this->doc;
+ }
+
+ throw new ChannelNotFoundInRSSException($link);
+ }
+
+ throw new UnreadableRSSException($link);
+ }
+
+ /**
+ * @throws SRSSException
+ */
+ private function _parseItems(): void
+ {
+ $channel = $this->_getChannel();
+ /** @var DOMNodeList $items */
+ $items = $channel->getElementsByTagName('item');
+ $length = $items->length;
+ $this->doc->items = [];
+ if ($length > 0) {
+ for($i = 0; $i < $length; $i++) {
+ $this->doc->items[$i] = ItemParser::read($items->item($i));
+ }
+ }
+ }
+
+ /**
+ * putting all RSS attributes into the object
+ * @throws SRSSException
+ */
+ private function _parseChannel(): void
+ {
+ $node_channel = $this->_getChannel();
+ $this->doc->channel = new Channel();
+
+ foreach($node_channel->childNodes as $child) {
+ if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') {
+ if($child->nodeName === 'image') {
+ $image = new Image();
+ foreach($child->childNodes as $children) {
+ if($children->nodeType === XML_ELEMENT_NODE) {
+ $image->{$children->nodeName} = $children->nodeValue;
+ }
+ }
+ $this->doc->channel->image = $image;
+
+ } elseif($child->nodeName === 'cloud') {
+ $cloud = new Channel\Cloud();
+ foreach($child->attributes as $attribute) {
+ $cloud->{$attribute->name} = $attribute->value;
+ }
+ $this->doc->channel->cloud = $cloud;
+ } elseif($child->nodeName === 'category') {
+ $category = new Channel\Category();
+ foreach($child->attributes as $attribute) {
+ $category->{$attribute->name} = $attribute->value;
+ }
+ $category->value = $child->nodeValue;
+ $this->doc->channel->category[] = $category;
+ } else {
+ $this->doc->channel->{$child->nodeName} = $child->nodeValue;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * getter of current RSS channel
+ * @return DOMNode
+ * @throws SRSSException
+ */
+ private function _getChannel(): DOMNode
+ {
+ $channel = $this->getElementsByTagName('channel');
+ if($channel->length !== 1) {
+ throw new ChannelNotFoundInRSSException('channel node not created, or too many channel nodes');
+ }
+
+ return $channel->item(0);
+ }
+}
\ No newline at end of file
diff --git a/src/SRSS.php b/src/SRSS.php
new file mode 100644
index 0000000..082fc54
--- /dev/null
+++ b/src/SRSS.php
@@ -0,0 +1,291 @@
+items = [];
+ $this->position = 0;
+ }
+
+ /**
+ * @param string $link url of the rss
+ *
+ * @return SRSS
+ * @throws ChannelNotFoundInRSSException
+ * @throws UnreadableRSSException
+ * @throws SRSSException
+ */
+ public static function read(string $link): SRSS
+ {
+ return (new SRSSParser())->parse($link);
+ }
+
+ /**
+ * @return SRSS
+ */
+ public static function create(): SRSS
+ {
+ $doc = new SRSS;
+
+ $doc->channel = new Channel();
+ $doc->items = [];
+
+ return $doc;
+ }
+
+ /**
+ * check if current RSS is a valid one (based on specifications)
+ * @return bool
+ */
+ public function isValid(): bool
+ {
+ try {
+ $valid = true;
+ foreach ($this->getItems() as $item) {
+ if ($item->isValid() === false) {
+ $valid = false;
+ }
+ }
+
+ return ($valid && $this->channel->isValid());
+ } catch (ReflectionException) {
+ return false;
+ }
+ }
+
+ /**
+ * @param $name
+ *
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->channel->{$name});
+ }
+
+ /**
+ * setter of others attributes
+ *
+ * @param $name
+ * @param $val
+ *
+ * @throws SRSSException
+ */
+ public function __set($name, $val)
+ {
+ if (!property_exists(Channel::class, $name)) {
+ throw new PropertyNotFoundException(Channel::class, $name);
+ }
+ if ((new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) {
+ if (SRSSTools::getPropertyType(Channel::class, $name) === 'array') {
+ $this->channel->{$name}[] = $val;
+ } else {
+ $this->channel->{$name} = $val;
+ }
+ }
+ }
+
+ /**
+ * getter of others attributes
+ *
+ * @param $name
+ *
+ * @return null|string
+ */
+ public function __get($name)
+ {
+ return $this->channel->{$name} ?? null;
+ }
+
+ /**
+ * rewind from Iterator
+ */
+ public function rewind(): void
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * current from Iterator
+ */
+ public function current(): Item
+ {
+ return $this->items[$this->position];
+ }
+
+ /**
+ * key from Iterator
+ */
+ public function key(): int
+ {
+ return $this->position;
+ }
+
+ /**
+ * next from Iterator
+ */
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ /**
+ * valid from Iterator
+ */
+ public function valid(): bool
+ {
+ return isset($this->items[$this->position]);
+ }
+
+ /**
+ * getter of 1st item
+ * @return Item|null
+ */
+ public function getFirst(): ?Item
+ {
+ return $this->getItem(1);
+ }
+
+ /**
+ * getter of last item
+ * @return Item
+ */
+ public function getLast(): Item
+ {
+ $items = $this->getItems();
+
+ return $items[array_key_last($items)];
+ }
+
+ /**
+ * getter of an item
+ *
+ * @param $i int
+ *
+ * @return Item|null
+ */
+ public function getItem(int $i): ?Item
+ {
+ $i--;
+
+ return $this->items[$i] ?? null;
+ }
+
+ /**
+ * getter of all items
+ * @return Item[]
+ */
+ public function getItems(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * transform current object into an array
+ * @return array
+ */
+ public function toArray(): array
+ {
+ $doc = $this->channel->toArray();
+
+ foreach ($this->getItems() as $item) {
+ $doc['items'][] = $item->toArray();
+ }
+
+ return array_filter($doc);
+ }
+
+ /**
+ * @param Item $rssItem
+ *
+ * @return array|Item[]
+ */
+ public function addItem(Item $rssItem): array
+ {
+ $this->items[] = $rssItem;
+
+ return $this->items;
+ }
+
+ /**
+ * @param Item $firstItem
+ *
+ * @return array|Item[]
+ */
+ public function addItemBefore(Item $firstItem): array
+ {
+ array_unshift($this->items, $firstItem);
+
+ return $this->items;
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return void
+ * @throws \Shikiryu\SRSS\Exception\DOMBuilderException
+ */
+ public function save(string $path): void
+ {
+ (new SRSSBuilder('1.0', 'UTF-8'))->build($this, $path);
+ }
+
+ /**
+ * @return false|string
+ * @throws \Shikiryu\SRSS\Exception\DOMBuilderException
+ */
+ public function show(): bool|string
+ {
+ return (new SRSSBuilder('1.0', 'UTF-8'))->show($this);
+ }
+
+}
diff --git a/src/SRSSTools.php b/src/SRSSTools.php
new file mode 100644
index 0000000..5ab55f1
--- /dev/null
+++ b/src/SRSSTools.php
@@ -0,0 +1,211 @@
+getType()?->getName();
+ }
+ public static function check($check, $flag)
+ {
+ return match ($flag) {
+ 'nohtml' => self::noHTML($check),
+ 'link' => self::checkLink($check),
+ 'html' => self::HTML4XML($check),
+ 'date' => self::getRSSDate($check),
+ 'email' => self::checkEmail($check),
+ 'int' => self::checkInt($check),
+ 'hour' => self::checkHour($check),
+ 'day' => self::checkDay($check),
+ 'media_type' => self::checkMediaType($check),
+ 'media_medium' => self::checkMediaMedium($check),
+ 'bool' => self::checkBool($check),
+ 'medium_expression' => self::checkMediumExpression($check)
+ };
+ }
+
+ /**
+ * format the RSS to the wanted format
+ *
+ * @param $format string wanted format
+ * @param $date string RSS date
+ *
+ * @return string date
+ */
+ public static function formatDate(string $format, string $date): string
+ {
+ return date($format, strtotime($date));
+ }
+
+ /**
+ * format a date for RSS format
+ *
+ * @param string $date date to format
+ * @param string $format
+ *
+ * @return string
+ */
+ public static function getRSSDate(string $date, string $format = ''): string
+ {
+ $date_position = 'dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU';
+ if($format !== '' && preg_match('~^(['.$date_position.']{1})([-/])(['.$date_position.']{1})([-/])(['.$date_position.']{1})$~', $format)){
+ $datetime = DateTime::createFromFormat($format, $date);
+ if ($datetime === false) {
+ return '';
+ }
+
+ return $datetime->format(DATE_RSS);
+ }
+
+ if (strtotime($date) !==false ) {
+ return date("D, d M Y H:i:s T", strtotime($date));
+ }
+
+ if (count(explode(' ', $date)) === 2) {
+ return DateTime::createFromFormat('Y-m-d H:i:s', $date)->format(DateTimeInterface::RSS);
+ }
+
+ [$j, $m, $a] = explode('/', $date);
+
+ return date("D, d M Y H:i:s T", strtotime($a.'-'.$m.'-'.$j));
+ }
+
+ /**
+ * check if it's an url
+ *
+ * @param $check string to check
+ *
+ * @return string|boolean the filtered data, or FALSE if the filter fails.
+ */
+ public static function checkLink(string $check): bool|string
+ {
+ return filter_var($check, FILTER_VALIDATE_URL);
+ }
+
+ /**
+ * make a string XML-compatible
+ *
+ * @param $check string to format
+ *
+ * @return string formatted string
+ */
+ public static function HTML4XML(string $check): string
+ {
+ return sprintf('', htmlspecialchars($check));
+ }
+
+ /**
+ * delete html tags
+ *
+ * @param $check string to format
+ *
+ * @return string formatted string
+ */
+ public static function noHTML(string $check): string
+ {
+ return strip_tags($check);
+ }
+
+ /**
+ * check if it's a day (in RSS terms)
+ *
+ * @param $check string to check
+ *
+ * @return string the day, or empty string
+ */
+ public static function checkDay(string $check): string
+ {
+ $possibleDay = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
+ return in_array(strtolower($check), $possibleDay) ? $check : '';
+ }
+
+ /**
+ * check if it's an email
+ *
+ * @param $check string to check
+ *
+ * @return string|boolean the filtered data, or FALSE if the filter fails.
+ */
+ public static function checkEmail(string $check): bool|string
+ {
+ return filter_var($check, FILTER_VALIDATE_EMAIL);
+ }
+
+ /**
+ * check if it's an hour (in RSS terms)
+ *
+ * @param $check string to check
+ *
+ * @return string|boolean the filtered data, or FALSE if the filter fails.
+ */
+ public static function checkHour(string $check): bool|string
+ {
+ $options = [
+ 'options' => [
+ 'default' => 0,
+ 'min_range' => 0,
+ 'max_range' => 23
+ ]
+ ];
+ return filter_var($check, FILTER_VALIDATE_INT, $options);
+ }
+
+ /**
+ * check if it's an int
+ *
+ * @param $check int to check
+ *
+ * @return int|boolean the filtered data, or FALSE if the filter fails.
+ */
+ public static function checkInt(int $check): bool|int
+ {
+ return filter_var($check, FILTER_VALIDATE_INT);
+ }
+
+ /**
+ * @param $check
+ *
+ * @return mixed
+ */
+ public static function checkMediaType($check): mixed
+ {
+ return $check;
+ }
+
+ /**
+ * @param $check
+ *
+ * @return mixed|null
+ */
+ public static function checkMediaMedium($check): ?string
+ {
+ return in_array($check, ['image', 'audio', 'video', 'document', 'executable']) ? $check : null;
+ }
+
+ /**
+ * @param $check
+ *
+ * @return mixed|null
+ */
+ public static function checkBool($check): ?string
+ {
+ return in_array($check, ['true', 'false']) ? $check : null;
+ }
+
+ /**
+ * @param $check
+ *
+ * @return mixed|null
+ */
+ public static function checkMediumExpression($check): ?string
+ {
+ return in_array($check, ['sample', 'full', 'nonstop']) ? $check : null;
+ }
+}
\ No newline at end of file
diff --git a/src/Validator/HasValidator.php b/src/Validator/HasValidator.php
new file mode 100644
index 0000000..96f7de3
--- /dev/null
+++ b/src/Validator/HasValidator.php
@@ -0,0 +1,11 @@
+_getClassProperties(get_class($object)),
+ static fn($p) => $p->getName() === $property
+ );
+
+ if (count($properties) !== 1) {
+ return null;
+ }
+
+ return current($properties);
+ }
+
+ /**
+ * @param $object
+ * @param $property
+ * @param $value
+ * @return bool
+ */
+ public function isValidValueForObjectProperty($object, $property, $value): bool
+ {
+ try {
+ $property = $this->getReflectedProperty($object, $property);
+ } catch (ReflectionException) {
+ return false;
+ }
+ $propertyAnnotations = $this->_getPropertyAnnotations($property);
+
+ if (empty($value) && !in_array('required', $propertyAnnotations, true)) {
+ return true;
+ }
+
+ foreach ($propertyAnnotations as $propertyAnnotation) {
+ $annotation = explode(' ', $propertyAnnotation);
+
+ $object->validated[$property->name] = $this->_validateProperty($annotation, $value);
+ }
+
+ return count(array_filter($object->validated, static fn($v) => ($v !== null && $v === false))) === 0;
+ }
+
+ /**
+ * @param $object
+ * @param $property
+ * @return bool
+ * @throws ReflectionException
+ */
+ private function objectHasProperty($object, $property): bool
+ {
+ return $this->getReflectedProperty($object, $property) instanceof ReflectionProperty;
+ }
+
+ /**
+ * @throws ReflectionException
+ */
+ public function isPropertyValid($object, $property): bool
+ {
+ return $this->objectHasProperty($object, $property) &&
+ $this->isValidValueForObjectProperty($object, $property, $object->{$property});
+ }
+
+ /**
+ * @throws ReflectionException
+ */
+ public function isObjectValid($object): bool
+ {
+ if (!$object->validated) {
+ $object = $this->validateObject($object);
+ }
+
+ return !in_array(false, $object->validated, true);
+ }
+
+ /**
+ * @throws ReflectionException
+ */
+ public function validateObject($object)
+ {
+ $this->object = $object;
+ $properties = $this->_getClassProperties(get_class($object));
+ $properties = array_map(fn($property) => array_merge(
+ ['name' => $property->name],
+ ['rules' => $this->_getPropertyAnnotations($property)]
+ ), $properties);
+
+ foreach ($properties as $property) {
+ $propertyValue = $object->{$property['name']};
+
+ if (empty($propertyValue) && !in_array('required', $property['rules'], true)) {
+ continue;
+ }
+
+ foreach ($property['rules'] as $propertyAnnotation) {
+ $annotation = explode(' ', $propertyAnnotation);
+
+ $object->validated[$property['name']] = $this->_validateProperty($annotation, $propertyValue);
+ }
+ }
+
+ return $object;
+ }
+
+ private function _validateProperty(array $annotation, $property): bool
+ {
+ if ($annotation[0] === 'var') {
+ return true;
+ }
+
+ if (count($annotation) === 1) {
+ return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property);
+ }
+
+ $args_annotation = array_splice($annotation, 1);
+
+ return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property, ...$args_annotation);
+ }
+
+ /**
+ * @return ReflectionProperty[]
+ * @throws ReflectionException
+ */
+ private function _getClassProperties($class): array
+ {
+ return (new ReflectionClass($class))->getProperties();
+ }
+
+ private function _getPropertyAnnotations(ReflectionProperty $property): array
+ {
+ preg_match_all('#@(.*?)\n#s', $property->getDocComment(), $annotations);
+
+ return array_map(static fn($annotation) => trim($annotation), $annotations[1]);
+ }
+
+ private function _validateString($value): bool
+ {
+ return is_string($value);
+ }
+
+ private function _validateInt($value): bool
+ {
+ return is_numeric($value);
+ }
+
+ private function _validateRequired($value): bool
+ {
+ return !empty(trim($value));
+ }
+
+ private function _validateRequiredOr($value, $other_values): bool
+ {
+ if (!empty($value)) {
+ return true;
+ }
+
+ foreach ($other_values as $other_value) {
+ if (!empty($this->object->$other_value)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param $value
+ * @return bool
+ * https://cyber.harvard.edu/rss/languages.html
+ */
+ private function _validateLang($value): bool
+ {
+ return in_array(strtolower($value), [
+ 'af','sq','eu','be','bg','ca','zh-cn','zh-tw','hr','cs','da','nl','nl-be','nl-nl','en','en-au','en-bz',
+ 'en-ca','en-ie','en-jm','en-nz','en-ph','en-za','en-tt','en-gb','en-us','en-zw','et','fo','fi','fr','fr-be',
+ 'fr-ca','fr-fr','fr-lu','fr-mc','fr-ch','gl','gd','de','de-at','de-de','de-li','de-lu','de-ch','el','haw',
+ 'hu','is','in','ga','it','it-it','it-ch','ja','ko','mk','no','pl','pt','pt-br','pt-pt','ro','ro-mo','ro-ro',
+ 'ru','ru-mo','ru-ru','sr','sk','sl','es','es-ar','es-bo','es-cl','es-co','es-cr','es-do','es-ec','es-sv',
+ 'es-gt','es-hn','es-mx','es-ni','es-pa','es-py','es-pe','es-pr','es-es','es-uy','es-ve','sv','sv-fi','sv-se',
+ 'tr','uk',
+ ]);
+ }
+
+ /**
+ * @param $value
+ * @return bool
+ */
+ private function _validateNoHtml($value): bool
+ {
+ return strip_tags($value) === $value;
+ }
+
+ private function _validateUrl($value): bool
+ {
+ return filter_var($value, FILTER_VALIDATE_URL) !== false;
+ }
+
+ private function _validateDate($value): bool
+ {
+ return DateTime::createFromFormat(DateTimeInterface::RSS, $value) !== false;
+ }
+
+ private function _validateHour($value): bool
+ {
+ $options = [
+ 'options' => [
+ 'default' => 0,
+ 'min_range' => 0,
+ 'max_range' => 23
+ ]
+ ];
+ return filter_var($value, FILTER_VALIDATE_INT, $options) !== false;
+ }
+
+ private function _validateDay($value): bool
+ {
+ return in_array(
+ strtolower($value),
+ ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
+ );
+ }
+
+ private function _validateContentMedia($value): bool
+ {
+ if (is_array($value)) {
+ foreach ($value as $content) {
+ if (!$content->isValid()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if ($value instanceof Content) {
+ return $value->isValid();
+ }
+
+ return false;
+ }
+
+ private function _validateMediaType($value): bool
+ {
+ return true;
+ }
+ private function _validateMediaMedium($value): bool
+ {
+ return in_array($value, ['image', 'audio', 'video', 'document', 'executable']);
+ }
+
+ private function _validateMediaExpression($value): bool
+ {
+ return in_array($value, ['sample', 'full', 'nonstop']);
+ }
+
+ private function _validateEmail($value): bool
+ {
+ return filter_var($value, FILTER_VALIDATE_EMAIL);
+ }
+}
\ No newline at end of file
diff --git a/srss.php b/srss.php
deleted file mode 100644
index 2d3fdeb..0000000
--- a/srss.php
+++ /dev/null
@@ -1,817 +0,0 @@
- 'nohtml',
- 'link' => 'link',
- 'description' => 'html',
- 'language' => '',
- //'language' => 'lang',
- 'copyright' => 'nohtml',
- 'pubDate' => 'date',
- 'lastBuildDate' => 'date',
- 'category' => 'nohtml',
- 'docs' => 'link',
- 'cloud' => '',
- 'generator' => 'nohtml',
- 'managingEditor' => 'email',
- 'webMaster' => 'email',
- 'ttl' => 'int',
- 'image' => '',
- 'rating' => 'nohtml',
- //'textInput' => '',
- 'skipHours' => 'hour',
- 'skipDays' => 'day',
- );
-
- /**
- * Constructor
- */
- public function __construct()
- {
- libxml_use_internal_errors(true);
- parent::__construct();
- $this->xpath = new DOMXpath($this);
- $this->attr = array();
- $this->items = array();
- $this->position = 0;
- $this->formatOutput = true;
- $this->preserveWhiteSpace = false;
- }
-
- /**
- * Destructor
- * manage of libxml errors
- */
- public function __destruct()
- {
- foreach (libxml_get_errors() as $error) {
- error_log($error->message, 3, 'error.log');
- }
- libxml_clear_errors();
- }
-
- /**
- * @param $link string url of the rss
- * @throws SRSSException
- * @return SRSS
- */
- public static function read($link)
- {
- $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.
- {
- $channel = $doc->getElementsByTagName('channel');
- if($channel->length == 1){ // Good URL and good RSS
- $doc->_loadAttributes(); // loading channel properties
- $doc->getItems(); // loading all items
- return $doc;
- }
- else
- {
- throw new SRSSException('invalid file '.$link);
- }
- }
- else
- {
- throw new SRSSException('Can not open file '.$link);
- }
- }
-
- /**
- * @return SRSS
- */
- public static function create()
- {
- $doc = new SRSS;
- $root = $doc->createElement('rss');
- $root->setAttribute('version', '2.0');
- $channel = $doc->createElement('channel');
- $root->appendChild($channel);
- $doc->appendChild($root);
- $doc->encoding = "UTF-8";
- $doc->generator = 'Shikiryu RSS';
- // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html';
- return $doc;
- }
-
- /**
- * getter of "image"'s channel attributes
- * @return string|array
- */
- public function image()
- {
- $args = func_get_args();
- if(func_num_args() == 0) $args[0] = 'url';
- $img = $this->xpath->query('//channel/image');
- if($img->length != 1) return null; // is not in channel
- $img = $img->item(0);
- $r = array();
- foreach($img->childNodes as $child)
- {
- if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args))
- {
- $r[$child->nodeName] = $child->nodeValue;
- }
- }
- return (func_num_args() > 1) ? $r : $r[$args[0]];
- }
-
- /**
- * setter of "image"'s channel attributes
- * @param $url string picture's url
- * @param $title string picture's title
- * @param $link string link on the picture
- * @param $width int width
- * @param $height int height
- * @param $description string description
- */
- public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '')
- {
- $channel = $this->_getChannel();
- $array = array();
- $url = SRSSTools::checkLink($url);
- $array['url'] = $url;
- $title = SRSSTools::noHTML($title);
- $array['title'] = $title;
- $link = SRSSTools::checkLink($link);
- $array['link'] = $link;
- if($width != 0)
- {
- $width = SRSSTools::checkInt($width);
- $array['width'] = $width;
- }
- if($height != 0)
- {
- $height = SRSSTools::checkInt($height);
- $array['height'] = $height;
- }
- if($description != 0)
- {
- $description = SRSSTools::noHTML($description);
- $array['description'] = $description;
- }
- if($this->image == null)
- {
- $node = $this->createElement('image');
- $urlNode = $this->createElement('url', $url);
- $titleNode = $this->createElement('title', $title);
- $linkNode = $this->createElement('link', $link);
- $node->appendChild($urlNode);
- $node->appendChild($titleNode);
- $node->appendChild($linkNode);
- if($width != 0)
- {
- $widthNode = $this->createElement('width', $width);
- $node->appendChild($widthNode);
- }
- if($height != 0)
- {
- $heightNode = $this->createElement('height', $height);
- $node->appendChild($heightNode);
- }
- if($description != '')
- {
- $descNode = $this->createElement('description', $description);
- $node->appendChild($descNode);
- }
- $channel->appendChild($node);
- }
- $this->attr['image'] = $array;
- }
-
- /**
- * setter of "cloud"'s channel attributes
- * @param $domain string domain
- * @param $port int port
- * @param $path string path
- * @param $registerProcedure string register procedure
- * @param $protocol string protocol
- */
- public function setCloud($domain, $port, $path, $registerProcedure, $protocol)
- {
- $channel = $this->_getChannel();
- $array = array();
- $domain = SRSSTools::noHTML($domain);
- $array['domain'] = $domain;
- $port = SRSSTools::checkInt($port);
- $array['port'] = $port;
- $path = SRSSTools::noHTML($path);
- $array['path'] = $path;
- $registerProcedure = SRSSTools::noHTML($registerProcedure);
- $array['registerProcedure'] = $registerProcedure;
- $protocol = SRSSTools::noHTML($protocol);
- $array['protocol'] = $protocol;
- if($this->cloud == null)
- {
- $node = $this->createElement('cloud');
- $node->setAttribute('domain', $domain);
- $node->setAttribute('port', $port);
- $node->setAttribute('path', $path);
- $node->setAttribute('registerProcedure', $registerProcedure);
- $node->setAttribute('protocol', $protocol);
- $channel->appendChild($node);
- }
- $this->attr['cloud'] = $array;
- }
-
- /**
- * check if current RSS is a valid one (based on specifications)
- * @return bool
- */
- public function isValid()
- {
- $valid = true;
- $items = $this->getItems();
- $invalidItems = array();
- $i = 1;
- foreach($items as $item){
- if($item->isValid() === false){
- $invalidItems[] = $i;
- $valid = false;
- }
- $i++;
- }
- return ($valid && $this->title != null && $this->link != null && $this->description != null);
- }
-
- /**
- * getter of current RSS channel
- * @return DOMElement
- * @throws SRSSException
- */
- private function _getChannel()
- {
- $channel = $this->getElementsByTagName('channel');
- if($channel->length != 1) throw new SRSSException('channel node not created, or too many channel nodes');
- return $channel->item(0);
- }
-
- /**
- * setter of others attributes
- * @param $name
- * @param $val
- * @throws SRSSException
- */
- public function __set($name, $val)
- {
- $channel = $this->_getChannel();
- if(array_key_exists($name, $this->possibleAttr)){
- $flag = $this->possibleAttr[$name];
- $val = SRSSTools::check($val, $flag);
- if(!empty($val)){
- if($this->$name == null){
- $node = $this->createElement($name, $val);
- $channel->appendChild($node);
- }
- $this->attr[$name] = $val;
- }
- }else{
- throw new SRSSException($name.' is not a possible item');
- }
- }
-
- /**
- * getter of others attributes
- * @param $name
- * @return null|string
- * @throws SRSSException
- */
- public function __get($name)
- {
- if(isset($this->attr[$name]))
- return $this->attr[$name];
-// $channel = $this->_getChannel();
- if(array_key_exists($name, $this->possibleAttr)){
- $tmp = $this->xpath->query('//channel/'.$name);
- if($tmp->length != 1) return null;
- return $tmp->item(0)->nodeValue;
- }else{
- throw new SRSSException($name.' is not a possible value.');
- }
- }
-
- /**
- * add a SRSS Item as an item into current RSS as first item
- * @param SRSSItem $item
- */
- public function addItemBefore(SRSSItem $item)
- {
- $node = $this->importNode($item->getItem(), true);
- $items = $this->getElementsByTagName('item');
- if($items->length != 0){
- $firstNode = $items->item(0);
- if($firstNode != null)
- $firstNode->parentNode->insertBefore($node, $firstNode);
- else
- $this->addItem($item);
- }
- else
- $this->addItem($item);
- }
-
- /**
- * add a SRSS Item as an item into current RSS
- * @param SRSSItem $item
- */
- public function addItem(SRSSItem $item)
- {
- $node = $this->importNode($item->getItem(), true);
- $channel = $this->_getChannel();
- $channel->appendChild($node);
- }
-
- /**
- * rewind from Iterator
- */
- public function rewind() {
- $this->position = 0;
- }
-
- /**
- * current from Iterator
- */
- public function current() {
- return $this->items[$this->position];
- }
-
- /**
- * key from Iterator
- */
- public function key() {
- return $this->position;
- }
-
- /**
- * next from Iterator
- */
- public function next() {
- ++$this->position;
- }
-
- /**
- * valid from Iterator
- */
- public function valid() {
- return isset($this->items[$this->position]);
- }
-
- /**
- * getter of 1st item
- * @return SRSSItem
- */
- public function getFirst()
- {
- return $this->getItem(1);
- }
-
- /**
- * getter of last item
- * @return SRSSItem
- */
- public function getLast()
- {
- $items = $this->getItems();
- return $items[count($items)-1];
- }
-
- /**
- * getter of an item
- * @param $i int
- * @return SRSSItem
- */
- public function getItem($i)
- {
- $i--;
- return isset($this->items[$i]) ? $this->items[$i] : null;
- }
-
- /**
- * getter of all items
- * @return SRSSItem[]
- * @throws SRSSException
- */
- public function getItems()
- {
- $channel = $this->_getChannel();
- $item = $channel->getElementsByTagName('item');
- $length = $item->length;
- $this->items = array();
- if($length > 0){
- for($i = 0; $i < $length; $i++)
- {
- $this->items[$i] = new SRSSItem($item->item($i));
- }
- }
- return $this->items;
- }
-
- /**
- * display XML
- * see DomDocument's docs
- */
- public function show()
- {
- return $this->saveXml();
- }
-
-
- /**
- * putting all RSS attributes into the object
- */
- private function _loadAttributes()
- {
- $channel = $this->_getChannel();
- foreach($channel->childNodes as $child)
- {
- if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'item')
- {
- if($child->nodeName == 'image'){
- foreach($child->childNodes as $children)
- {
- if($children->nodeType == XML_ELEMENT_NODE)
- $this->attr['image'][$children->nodeName] = $children->nodeValue;
- }
- }
- else
- $this->attr[$child->nodeName] = $child->nodeValue;
- }
- }
- }
-
- /**
- * transform current object into an array
- * @return array
- */
- public function toArray()
- {
- $doc = array();
- foreach($this->attr as $attrName => $attrVal)
- {
- $doc[$attrName] = $attrVal;
- }
- foreach($this->getItems() as $item)
- {
- $doc['items'][] = $item->toArray();
- }
- return $doc;
- }
-}
-
-/**
- * @property null|string enclosure
- * @property null|string description
- */
-class SRSSItem extends DomDocument
-{
-
- /**
- * @var DOMElement
- */
- protected $node; // item node
- protected $attr; // item's properties
-
- // possible properties' names
- protected $possibilities = array(
- 'title' => 'nohtml',
- 'link' => 'link',
- 'description' => 'html',
- 'author' => 'email',
- 'category' => 'nohtml',
- 'comments' => 'link',
- 'enclosure' => '',
- 'guid' => 'nohtml',
- 'pubDate' => 'date',
- 'source' => 'link',
- );
-
- /**
- * Constructor
- * @param DomNode $node
- */
- public function __construct($node = null)
- {
- parent::__construct();
- if($node instanceof DOMElement) $this->node = $this->importNode($node, true);
- else $this->node = $this->importNode(new DomElement('item'));
- $this->_loadAttributes();
- }
-
- /**
- * putting all item attributes into the object
- */
- private function _loadAttributes()
- {
- foreach($this->node->childNodes as $child)
- {
- if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'item')
- {
- $this->{$child->nodeName} = $child->nodeValue;
- }
- }
- }
-
- /**
- * getter of item DomElement
- */
- public function getItem()
- {
- $this->appendChild($this->node);
- return $this->getElementsByTagName('item')->item(0);
- }
-
- /**
- * setter for enclosure's properties
- * @param $url string url
- * @param $length int length
- * @param $type string type
- */
- public function setEnclosure($url, $length, $type)
- {
- $array = array();
- $url = SRSSTools::checkLink($url);
- $array['url'] = $url;
- $length = SRSSTools::checkInt($length);
- $array['length'] = $length;
- $type = SRSSTools::noHTML($type);
- $array['type'] = $type;
- if($this->enclosure == null)
- {
- $node = $this->createElement('enclosure');
- $node->setAttribute('url', $url);
- $node->setAttribute('length', $length);
- $node->setAttribute('type', $type);
- $this->node->appendChild($node);
- }
- $this->attr['enclosure'] = $array;
- }
-
- /**
- * check if current item is valid (following specifications)
- * @return bool
- */
- public function isValid()
- {
- return $this->description != null ? true : false;
- }
-
- /**
- * main setter for properties
- * @param $name
- * @param $val
- * @throws SRSSException
- */
- public function __set($name, $val)
- {
- if(array_key_exists($name, $this->possibilities))
- {
- $flag = $this->possibilities[$name];
- if($flag != '')
- $val = SRSSTools::check($val, $flag);
- if(!empty($val)){
- if($this->$name == null){
- $this->node->appendChild(new DomElement($name, $val));
- }
- $this->attr[$name] = $val;
- }
- }
- else
- {
- throw New SRSSException($name.' is not a possible item : '.print_r($this->possibilities));
- }
- }
-
- /**
- * main getter for properties
- * @param $name
- * @return null|string
- * @throws SRSSException
- */
- public function __get($name)
- {
- if(isset($this->attr[$name]))
- return $this->attr[$name];
- if(array_key_exists($name, $this->possibilities))
- {
- $tmp = $this->node->getElementsByTagName($name);
- if($tmp->length != 1) return null;
- return $tmp->item(0)->nodeValue;
- }
- else
- {
- throw New SRSSException($name.' is not a possible item : '.print_r($this->possibilities));
- }
- }
-
- /**
- * display item's XML
- * see DomDocument's docs
- */
- public function show()
- {
- return $this->saveXml();
- }
-
- /**
- * transform current item's object into an array
- * @return array
- */
- public function toArray()
- {
- $infos = array();
- foreach($this->attr as $attrName => $attrVal)
- {
- $infos[$attrName] = $attrVal;
- }
- return $infos;
- }
-}
-
-class SRSSException extends Exception
-{
- public function __construct($msg)
- {
- parent :: __construct($msg);
- }
-
- public function getError()
- {
- $return = 'Une exception a été générée : Message : ' . $this->getMessage() . ' à la ligne : ' . $this->getLine();
- return $return;
- }
-}
-
-class SRSSTools
-{
- public static function check($check, $flag)
- {
- switch($flag){
- case 'nohtml':
- return self::noHTML($check);
- break;
- case 'link':
- return self::checkLink($check);
- break;
- case 'html':
- return self::HTML4XML($check);
- break;
- /*case 'lang':
- return self::noHTML($check);
- break;*/
- case 'date':
- return self::getRSSDate($check);
- break;
- case 'email':
- return self::checkEmail($check);
- break;
- case 'int':
- return self::checkInt($check);
- break;
- case 'hour':
- return self::checkHour($check);
- break;
- case 'day':
- return self::checkDay($check);
- break;
- case '':
- return $check;
- break;
- default:
- throw new SRSSEXception('flag '.$flag.' does not exist.');
- }
- }
-
- /**
- * format the RSS to the wanted format
- * @param $format string wanted format
- * @param $date string RSS date
- * @return string date
- */
- public static function formatDate($format, $date)
- {
- return date($format, strtotime($date));
- }
-
- /**
- * format a date for RSS format
- * @param string $date date to format
- * @param string $format
- * @return string
- */
- public static function getRSSDate($date, $format='')
- {
- $datepos = 'dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU';
- if($format != '' && preg_match('~^(['.$datepos.']{1})(-|/)(['.$datepos.']{1})(-|/)(['.$datepos.']{1})$~', $format, $match)){
- $sep = $match[2];
- $format = '%'.$match[1].$sep.'%'.$match[3].$sep.'%'.$match[5];
- if($dateArray = strptime($date, $format)){
- $mois = intval($dateArray['tm_mon']) + 1;
- $annee = strlen($dateArray['tm_year']) > 2 ? '20'.substr($dateArray['tm_year'], -2) : '19'.$dateArray['tm_year'];
- $date = $annee.'-'.$mois.'-'.$dateArray['tm_mday'];
- return date("D, d M Y H:i:s T", strtotime($date));
- }
- return '';
- }
- else if(strtotime($date) !==false ){
- return date("D, d M Y H:i:s T", strtotime($date));
- }
- else
- {
- list($j, $m, $a) = explode('/', $date);
- return date("D, d M Y H:i:s T", strtotime($a.'-'.$m.'-'.$j));
- }
- }
-
- /**
- * check if it's an url
- * @param $check string to check
- * @return string|boolean the filtered data, or FALSE if the filter fails.
- */
- public static function checkLink($check)
- {
- return filter_var($check, FILTER_VALIDATE_URL);
- }
-
- /**
- * make a string XML-compatible
- * @param $check string to format
- * @return string formatted string
- * TODO CDATA ?
- */
- public static function HTML4XML($check)
- {
- return htmlspecialchars($check);
- }
-
- /**
- * delete html tags
- * @param $check string to format
- * @return string formatted string
- */
- public static function noHTML($check)
- {
- return strip_tags($check);
- }
-
- /**
- * check if it's a day (in RSS terms)
- * @param $check string to check
- * @return string the day, or empty string
- */
- public static function checkDay($check)
- {
- $possibleDay = array('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday');
- return in_array(strtolower($check), $possibleDay) ? $check : '';
- }
-
- /**
- * check if it's an email
- * @param $check string to check
- * @return string|boolean the filtered data, or FALSE if the filter fails.
- */
- public static function checkEmail($check)
- {
- return filter_var($check, FILTER_VALIDATE_EMAIL);
- }
-
- /**
- * check if it's an hour (in RSS terms)
- * @param $check string to check
- * @return string|boolean the filtered data, or FALSE if the filter fails.
- */
- public static function checkHour($check)
- {
- $options = array(
- 'options' => array(
- 'default' => 0,
- 'min_range' => 0,
- 'max_range' => 23
- )
- );
- return filter_var($check, FILTER_VALIDATE_INT, $options);
- }
-
- /**
- * check if it's an int
- * @param $check int to check
- * @return int|boolean the filtered data, or FALSE if the filter fails.
- */
- public static function checkInt($check)
- {
- return filter_var($check, FILTER_VALIDATE_INT);
- }
-}
\ No newline at end of file
diff --git a/tests/BasicBuilderTest.php b/tests/BasicBuilderTest.php
new file mode 100644
index 0000000..9a84ddd
--- /dev/null
+++ b/tests/BasicBuilderTest.php
@@ -0,0 +1,56 @@
+saved)) {
+ unlink($this->saved);
+ }
+ }
+ public function testCreateBasicRSS(): void
+ {
+ $title = 'My Blog';
+ $description = 'is the best';
+ $link = 'http://shikiryu.com/devblog/';
+ $srss = SRSS::create();
+ $srss->title = $title;
+ $srss->description = $description;
+ $srss->link = $link;
+ $items = [
+ ['title' => 'title 1', 'link' => 'http://shikiryu.com/devblog/article-1', 'pubDate' => SRSSTools::getRSSDate('2012-03-05 12:02:01'), 'description' => 'description 1'],
+ ['title' => 'title 2', 'link' => 'http://shikiryu.com/devblog/article-2', 'pubDate' => SRSSTools::getRSSDate('2022-03-05 22:02:02'), 'description' => 'description 2'],
+ ['title' => 'title 3', 'link' => 'http://shikiryu.com/devblog/article-3', 'pubDate' => SRSSTools::getRSSDate('2032-03-05 32:02:03'), 'description' => 'description 3'],
+ ['title' => 'title 4', 'link' => 'http://shikiryu.com/devblog/article-4', 'pubDate' => SRSSTools::getRSSDate('2042-03-05 42:02:04'), 'description' => 'description 4'],
+ ];
+ foreach ($items as $item) {
+ $rssItem = new Item();
+ $rssItem->title = $item['title'];
+ $rssItem->link = $item['link'];
+ $rssItem->pubDate = $item['pubDate'];
+ $rssItem->description = $item['description'];
+ $srss->addItem($rssItem);
+ }
+
+ self::assertTrue($srss->isValid());
+
+ self::assertEquals($title, $srss->title);
+ self::assertEquals($description, $srss->description);
+ self::assertEquals($link, $srss->link);
+
+ $builder = new SRSSBuilder();
+ $builder->build($srss, $this->saved);
+
+ self::assertFileExists($this->saved);
+
+ self::assertIsString($srss->show());
+ }
+}
\ No newline at end of file
diff --git a/tests/BasicReaderTest.php b/tests/BasicReaderTest.php
new file mode 100644
index 0000000..1f987c5
--- /dev/null
+++ b/tests/BasicReaderTest.php
@@ -0,0 +1,99 @@
+title);
+ $first_item = $rss->getFirst();
+ self::assertNotNull($first_item);
+ self::assertEquals('RSS Tutorial', $first_item->title);
+
+ self::assertTrue($rss->isValid());
+ }
+
+ public function testSpecificationExampleRSS(): void
+ {
+ $rss = SRSS::read(__DIR__.'/resources/harvard.xml');
+ self::assertEquals('Liftoff News', $rss->title);
+ self::assertEquals('http://liftoff.msfc.nasa.gov/', $rss->link);
+ self::assertEquals('Liftoff to Space Exploration.', $rss->description);
+ self::assertEquals('en-us', $rss->language);
+ self::assertEquals('Tue, 10 Jun 2003 04:00:00 GMT', $rss->pubDate);
+ self::assertEquals('Tue, 10 Jun 2003 09:41:01 GMT', $rss->lastBuildDate);
+ self::assertEquals('http://blogs.law.harvard.edu/tech/rss', $rss->docs);
+ self::assertEquals('Weblog Editor 2.0', $rss->generator);
+ self::assertEquals('editor@example.com', $rss->managingEditor);
+ self::assertEquals('webmaster@example.com', $rss->webMaster);
+ self::assertCount(4, $rss->items);
+ self::assertEquals('Star City', $rss->getFirst()->title);
+ self::assertEquals('http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp', $rss->getLast()->link);
+ self::assertEquals('Fri, 30 May 2003 11:06:42 GMT', $rss->getItem(2)->pubDate);
+
+ self::assertTrue($rss->isValid());
+ }
+
+ public function testChannelImage(): void
+ {
+ $rss = SRSS::read(__DIR__.'/resources/media/cnn.xml');
+ $image = $rss->image;
+ self::assertInstanceOf(Image::class, $image);
+ self::assertEquals('http://i2.cdn.turner.com/cnn/2015/images/09/24/cnn.digital.png', $image->url, var_export($image, true));
+ self::assertEquals('CNN.com - RSS Channel - Entertainment', $image->title, var_export($image, true));
+ self::assertEquals('https://www.cnn.com/entertainment/index.html', $image->link, var_export($image, true));
+ }
+
+ public function testChannelCategory(): void
+ {
+ $rss = SRSS::read(__DIR__.'/resources/cloud.xml');
+ $categories = $rss->category;
+ self::assertCount(1, $categories);
+ $category = $categories[0];
+ self::assertInstanceOf(Category::class, $category);
+ self::assertEquals('http://www.weblogs.com/rssUpdates/changes.xml', $category->domain, var_export($category, true));
+ self::assertEquals('rssUpdates', $category->value, var_export($category, true));
+ }
+
+ public function testCloud(): void
+ {
+ $rss = SRSS::read(__DIR__.'/resources/cloud.xml');
+ $cloud = $rss->cloud;
+ self::assertInstanceOf(Cloud::class, $cloud);
+ self::assertEquals('radio.xmlstoragesystem.com', $cloud->domain, var_export($cloud, true));
+ self::assertEquals('80', $cloud->port, var_export($cloud, true));
+ self::assertEquals('/RPC2', $cloud->path, var_export($cloud, true));
+ self::assertEquals('xmlStorageSystem.rssPleaseNotify', $cloud->registerProcedure, var_export($cloud, true));
+ self::assertEquals('xml-rpc', $cloud->protocol, var_export($cloud, true));
+ }
+
+ public function testSource(): void
+ {
+ $rss = SRSS::read(__DIR__.'/resources/basic.xml');
+ $firstItem = $rss->getFirst();
+ self::assertInstanceOf(Item::class, $firstItem);
+ $source = $firstItem->source;
+ self::assertInstanceOf(Item\Source::class, $source);
+ self::assertEquals('http://www.tomalak.org/links2.xml', $source->url);
+ self::assertEquals('Tomalak\'s Realm', $source->value);
+ }
+
+ public function testEnclosure(): void
+ {
+ $rss = SRSS::read(__DIR__.'/resources/basic.xml');
+ $item = $rss->getItem(2);
+ self::assertInstanceOf(Item::class, $item);
+ $enclosure = $item->enclosure;
+ self::assertInstanceOf(Item\Enclosure::class, $enclosure);
+ self::assertEquals('http://www.scripting.com/mp3s/touchOfGrey.mp3', $enclosure->url);
+ self::assertEquals('5588242', $enclosure->length);
+ self::assertEquals('audio/mpeg', $enclosure->type);
+ }
+}
\ No newline at end of file
diff --git a/tests/CompleteBuilderTest.php b/tests/CompleteBuilderTest.php
new file mode 100644
index 0000000..06c6b17
--- /dev/null
+++ b/tests/CompleteBuilderTest.php
@@ -0,0 +1,101 @@
+better';
+ $language = 'en-us';
+ $copyright = 'Shikiryu';
+ $managingEditor = 'editor';
+ $webMaster = 'Shikiryu';
+ $pubDate = (new DateTime())->format(DATE_RSS);
+ $lastBuildDate = $pubDate;
+ $category = new Category();
+ $category->domain = $link;
+ $category->value = 'Test Category';
+ $generator = 'SRSS';
+ $docs = $link;
+ $cloud = new Cloud();
+ $cloud->domain = $link;
+ $cloud->port = 80;
+ $cloud->path = '/test';
+ $ttl = 3660;
+ $image = new Image();
+ $image->link = $link;
+ $image->title = 'title of image';
+ $rating = 'yes';
+ $textInput = 'ignore';
+ $skipDays = 'monday';
+ $skipHours = '8';
+
+ $srss = SRSS::create();
+ $srss->title = $title;
+ $srss->link = $link;
+ $srss->description = $description;
+ $srss->language = $language;
+ $srss->copyright = $copyright;
+ $srss->managingEditor = $managingEditor;
+ $srss->webMaster = $webMaster;
+ $srss->pubDate = $pubDate;
+ $srss->lastBuildDate = $lastBuildDate;
+ $srss->category = $category;
+ $srss->generator = $generator;
+ $srss->docs = $docs;
+ $srss->cloud = $cloud;
+ $srss->ttl = $ttl;
+ $srss->image = $image;
+ $srss->rating = $rating;
+ $srss->textInput = $textInput;
+ $srss->skipDays = $skipDays;
+ $srss->skipHours = $skipHours;
+
+ $item_title = 'item title';
+ $item_link = 'https://example.com';
+ $item_description = 'item description';
+ $item_author = 'shikiryu@shikiryu.com';
+ $item_category = new \Shikiryu\SRSS\Entity\Item\Category();
+ $item_category->domain = 'https://shikiryu.com';
+ $item_category->value = 'category shikiryu';
+ $item_comments = $link.'/comments';
+ $item_enclosure = new Enclosure();
+ $item_enclosure->url = $item_link;
+ $item_enclosure->length = 5023;
+ $item_enclosure->type = 'audio/mp3';
+ $item_guid = '123456';
+ $item_pubdate = $pubDate;
+ $item_source = new Source();
+ $item_source->url = $item_link;
+ $item_source->value = 'source';
+ $item_media = new Shikiryu\SRSS\Entity\Media\Content();
+ $item_media->url = $item_link;
+ $item_media->type = 'image/jpg';
+ $item = new Shikiryu\SRSS\Entity\Item();
+ $item->title = $item_title;
+ $item->link = $item_link;
+ $item->description = $item_description;
+ $item->author = $item_author;
+ $item->category[] = $item_category;
+ $item->comments = $item_comments;
+ $item->enclosure = $item_enclosure;
+ $item->guid = $item_guid;
+ $item->pubDate = $item_pubdate;
+ $item->source = $item_source;
+ $item->medias[] = $item_media;
+
+ $srss->addItem($item);
+
+ self::assertTrue($srss->isValid(), var_export($srss->channel->validated + array_map(static fn ($item) => $item->validated, $srss->items), true));
+ }
+}
\ No newline at end of file
diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php
new file mode 100644
index 0000000..19fca5a
--- /dev/null
+++ b/tests/ExceptionTest.php
@@ -0,0 +1,29 @@
+expectException(PropertyNotFoundException::class);
+ $srss->notfound = 'true';
+ }
+
+ public function testRssNotFound(): void
+ {
+ $this->expectException(UnreadableRSSException::class);
+ SRSS::read('not_found.xml');
+ }
+
+ public function testMissingChannel(): void
+ {
+ $this->expectException(ChannelNotFoundInRSSException::class);
+ SRSS::read(__DIR__ . '/resources/invalid-no-channel.xml');
+ }
+}
\ No newline at end of file
diff --git a/tests/MediaTest.php b/tests/MediaTest.php
new file mode 100644
index 0000000..0abb611
--- /dev/null
+++ b/tests/MediaTest.php
@@ -0,0 +1,31 @@
+title);
+
+ $first_item = $rss->getFirst();
+ 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->medias[0]->url);
+ self::assertTrue($rss->isValid(), var_export($rss->channel->validated, true));
+ }
+
+ public function testMusicVideo(): void
+ {
+ $rss = SRSS::read(__DIR__.'/resources/media/music-video.xml');
+ self::assertEquals('Music Videos 101', $rss->title);
+
+ self::assertCount(1, $rss->items);
+
+ $first_item = $rss->getFirst();
+ self::assertEquals('http://www.foo.com/movie.mov', $first_item->medias[0]->url);
+ self::assertTrue($rss->isValid());
+ }
+}
\ No newline at end of file
diff --git a/tests/OriginalReaderSRSSTest.php b/tests/OriginalReaderSRSSTest.php
new file mode 100644
index 0000000..0b1bd7a
--- /dev/null
+++ b/tests/OriginalReaderSRSSTest.php
@@ -0,0 +1,41 @@
+saved)) {
+ unlink($this->saved);
+ }
+ }
+
+ public function testOriginalReader(): void
+ {
+ $rss = SRSS::read($this->original);
+ self::assertEquals('Liftoff News', $rss->title);
+
+ $article1 = $rss->getItem(1);
+ $articleFirst = $rss->getFirst();
+ self::assertEquals($article1, $articleFirst);
+
+ $links = [];
+ foreach($rss as $article) {
+ $links[] = sprintf('%s %s', $article->link, SRSSTools::formatDate('d/m/y', $article->pubDate), $article->title);
+ }
+ self::assertCount(4, $links, var_export($links, true));
+
+ $rssArray = $rss->toArray();
+ self::assertCount(11, $rssArray, var_export($rssArray, true)); // 11 elements in RSS
+
+ $rss->save($this->saved);
+
+ self::assertFileExists($this->saved);
+ }
+}
\ No newline at end of file
diff --git a/tests/OriginalWriterSRSSTest.php b/tests/OriginalWriterSRSSTest.php
new file mode 100644
index 0000000..c57cc73
--- /dev/null
+++ b/tests/OriginalWriterSRSSTest.php
@@ -0,0 +1,45 @@
+title = 'My Awesome Blog';
+ $rss->link = 'http://shikiryu.com/devblog/';
+ $rss->description = 'is awesome';
+
+ $items = [
+ ['title' => 'title 1', 'link' => 'http://shikiryu.com/devblog/article-1', 'pubDate' => SRSSTools::getRSSDate('2012-03-05 12:02:01'), 'description' => 'description 1'],
+ ['title' => 'title 2', 'link' => 'http://shikiryu.com/devblog/article-2', 'pubDate' => SRSSTools::getRSSDate('2022-03-05 22:02:02'), 'description' => 'description 2'],
+ ['title' => 'title 3', 'link' => 'http://shikiryu.com/devblog/article-3', 'pubDate' => SRSSTools::getRSSDate('2032-03-05 32:02:03'), 'description' => 'description 3'],
+ ['title' => 'title 4', 'link' => 'http://shikiryu.com/devblog/article-4', 'pubDate' => SRSSTools::getRSSDate('2042-03-05 42:02:04'), 'description' => 'description 4'],
+ ];
+
+ foreach($items as $item){
+ $rss_item = new Item();
+ $rss_item->title = $item["title"];
+ $rss_item->link = $item['link'];
+ $rss_item->pubDate = $item["pubDate"];
+ $rss_item->description = $item["description"];
+ $rss->addItem($rss_item);
+ }
+
+ $firstItem = new Item();
+ $firstItem->title = 'title 0';
+ $firstItem->link = 'http://shikiryu.com/devblog/article-0';
+ $firstItem->pubDate = SRSSTools::getRSSDate('2011-03-05 12:02:01');
+ $firstItem->description = 'description 0';
+ $rss->addItemBefore($firstItem);
+
+ self::assertCount(5, $rss->items, var_export($rss->items, true));
+ self::assertEquals('title 0', $rss->getFirst()->title, var_export($rss->items, true));
+ self::assertEquals('title 1', $rss->getItem(2)->title, var_export($rss->items, true));
+
+ }
+}
\ No newline at end of file
diff --git a/tests/resources/basic.xml b/tests/resources/basic.xml
new file mode 100644
index 0000000..d2329e1
--- /dev/null
+++ b/tests/resources/basic.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ test Home Page
+ https://www.test.com
+ Free web building tutorials
+ -
+ RSS Tutorial
+
+ https://www.test.com/xml/xml_rss.asp
+ New RSS tutorial on test
+
+ -
+ XML Tutorial
+
+ https://www.test.com/xml
+ New XML tutorial on test
+
+
+
+
\ No newline at end of file
diff --git a/tests/resources/cloud.xml b/tests/resources/cloud.xml
new file mode 100644
index 0000000..2c4a12e
--- /dev/null
+++ b/tests/resources/cloud.xml
@@ -0,0 +1,78 @@
+
+
+
+
+ Dave's Handsome Radio Blog!
+ http://radio.weblogs.com/0001015/
+ A non-smoking weblog since June 14, 2002.
+ en-us
+ Copyright 2003 Dave Winer
+ Mon, 13 Oct 2003 18:54:10 GMT
+ http://backend.userland.com/rss
+ Radio UserLand v8.0.5
+ dave@userland.com
+ dave@userland.com
+ rssUpdates
+
+ 0
+ 23
+ 1
+ 2
+ 3
+ 22
+ 17
+ 11
+
+
+ 60
+ -
+ http://radio.weblogs.com/0001015/2003/10/13.html#a1866
+ <A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A>
+ http://radio.weblogs.com/0001015/2003/10/13.html#a1866
+ Mon, 13 Oct 2003 18:54:09 GMT
+ http://blogs.law.harvard.edu/comments?u=1015&p=1866&link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F13.html%23a1866
+
+ -
+ http://radio.weblogs.com/0001015/2003/10/12.html#a1865
+ <A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A><A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A><A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A><A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A>
+ http://radio.weblogs.com/0001015/2003/10/12.html#a1865
+ Sun, 12 Oct 2003 20:42:30 GMT
+ http://blogs.law.harvard.edu/comments?u=1015&p=1865&link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F12.html%23a1865
+
+ -
+ http://radio.weblogs.com/0001015/2003/10/08.html#a1864
+ <A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A>
+ http://radio.weblogs.com/0001015/2003/10/08.html#a1864
+ Wed, 08 Oct 2003 13:22:16 GMT
+ http://blogs.law.harvard.edu/comments?u=1015&p=1864&link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F08.html%23a1864
+
+ -
+ http://radio.weblogs.com/0001015/2003/10/06.html#a1863
+ <A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A>
+ http://radio.weblogs.com/0001015/2003/10/06.html#a1863
+ Mon, 06 Oct 2003 13:37:40 GMT
+ http://blogs.law.harvard.edu/comments?u=1015&p=1863&link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F06.html%23a1863
+
+ -
+ http://radio.weblogs.com/0001015/2003/10/01.html#a1862
+ <A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A>
+ http://radio.weblogs.com/0001015/2003/10/01.html#a1862
+ Wed, 01 Oct 2003 18:26:11 GMT
+ http://blogs.law.harvard.edu/comments?u=1015&p=1862&link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F01.html%23a1862
+
+ -
+ http://radio.weblogs.com/0001015/2003/09/26.html#a1861
+ <A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A>
+ http://radio.weblogs.com/0001015/2003/09/26.html#a1861
+ Fri, 26 Sep 2003 13:29:36 GMT
+ http://blogs.law.harvard.edu/comments?u=1015&p=1861&link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F09%2F26.html%23a1861
+
+ -
+ http://radio.weblogs.com/0001015/2003/09/25.html#a1860
+ <A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A>
+ http://radio.weblogs.com/0001015/2003/09/25.html#a1860
+ Thu, 25 Sep 2003 14:08:26 GMT
+ http://blogs.law.harvard.edu/comments?u=1015&p=1860&link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F09%2F25.html%23a1860
+
+
+
diff --git a/tests/resources/harvard.xml b/tests/resources/harvard.xml
new file mode 100644
index 0000000..48dcb54
--- /dev/null
+++ b/tests/resources/harvard.xml
@@ -0,0 +1,41 @@
+
+
+
+ Liftoff News
+ http://liftoff.msfc.nasa.gov/
+ Liftoff to Space Exploration.
+ en-us
+ Tue, 10 Jun 2003 04:00:00 GMT
+ Tue, 10 Jun 2003 09:41:01 GMT
+ http://blogs.law.harvard.edu/tech/rss
+ Weblog Editor 2.0
+ editor@example.com
+ webmaster@example.com
+ -
+ Star City
+ http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp
+ 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 <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>.
+ Tue, 03 Jun 2003 09:39:21 GMT
+ http://liftoff.msfc.nasa.gov/2003/06/03.html#item573
+
+ -
+ Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm">partial eclipse of the Sun</a> on Saturday, May 31st.
+ Fri, 30 May 2003 11:06:42 GMT
+ http://liftoff.msfc.nasa.gov/2003/05/30.html#item572
+
+ -
+ The Engine That Does More
+ http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp
+ 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.
+ Tue, 27 May 2003 08:37:32 GMT
+ http://liftoff.msfc.nasa.gov/2003/05/27.html#item571
+
+ -
+ Astronauts' Dirty Laundry
+ http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp
+ Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.
+ Tue, 20 May 2003 08:56:02 GMT
+ http://liftoff.msfc.nasa.gov/2003/05/20.html#item570
+
+
+
\ No newline at end of file
diff --git a/tests/resources/invalid-no-channel.xml b/tests/resources/invalid-no-channel.xml
new file mode 100644
index 0000000..e7cf96f
--- /dev/null
+++ b/tests/resources/invalid-no-channel.xml
@@ -0,0 +1,18 @@
+
+
+
+ test Home Page
+ https://www.test.com
+ Free web building tutorials
+ -
+ RSS Tutorial
+ https://www.test.com/xml/xml_rss.asp
+ New RSS tutorial on test
+
+ -
+ XML Tutorial
+ https://www.test.com/xml
+ New XML tutorial on test
+
+
+
\ No newline at end of file
diff --git a/tests/resources/media/cnn.xml b/tests/resources/media/cnn.xml
new file mode 100644
index 0000000..f0ad899
--- /dev/null
+++ b/tests/resources/media/cnn.xml
@@ -0,0 +1,1383 @@
+
+
+
+
+
+
+ https://www.cnn.com/entertainment/index.html
+
+ http://i2.cdn.turner.com/cnn/2015/images/09/24/cnn.digital.png
+ CNN.com - RSS Channel - Entertainment
+ https://www.cnn.com/entertainment/index.html
+
+ coredev-bumblebee
+ Wed, 05 Apr 2023 08:57:36 GMT
+ Tue, 06 Dec 2022 20:13:31 GMT
+
+
+ 10
+ -
+
+
+
+ https://www.cnn.com/2022/12/05/entertainment/kirstie-alley-obit/index.html
+ https://www.cnn.com/2022/12/05/entertainment/kirstie-alley-obit/index.html
+ Tue, 06 Dec 2022 12:59:28 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/06/entertainment/john-travolta-kirstie-alley/index.html
+
+ https://www.cnn.com/2022/12/06/entertainment/john-travolta-kirstie-alley/index.html
+
+ Tue, 06 Dec 2022 14:22:24 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/06/entertainment/chelsea-handler-leslie-jones-daily-show/index.html
+
+ https://www.cnn.com/2022/12/06/entertainment/chelsea-handler-leslie-jones-daily-show/index.html
+
+ Tue, 06 Dec 2022 20:30:42 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/06/entertainment/ashton-kutcher-michael/index.html
+ https://www.cnn.com/2022/12/06/entertainment/ashton-kutcher-michael/index.html
+
+ Tue, 06 Dec 2022 15:42:43 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/06/entertainment/bong-joon-ho-mickey-17-trailer-new-movie/index.html
+
+
+ https://www.cnn.com/2022/12/06/entertainment/bong-joon-ho-mickey-17-trailer-new-movie/index.html
+
+ Tue, 06 Dec 2022 17:01:45 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/06/entertainment/blackpink-time-entertainer-of-the-year-2022-intl-scli/index.html
+
+
+ https://www.cnn.com/2022/12/06/entertainment/blackpink-time-entertainer-of-the-year-2022-intl-scli/index.html
+
+ Tue, 06 Dec 2022 11:18:19 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/05/entertainment/gabourey-sidibe-married-brandon-frankel/index.html
+
+ https://www.cnn.com/2022/12/05/entertainment/gabourey-sidibe-married-brandon-frankel/index.html
+
+ Mon, 05 Dec 2022 21:53:05 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/05/entertainment/harry-meghan-netflix-date/index.html
+ https://www.cnn.com/2022/12/05/entertainment/harry-meghan-netflix-date/index.html
+
+ Mon, 05 Dec 2022 14:13:14 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/05/entertainment/jill-scott-tour-music/index.html
+ https://www.cnn.com/2022/12/05/entertainment/jill-scott-tour-music/index.html
+
+ Mon, 05 Dec 2022 18:20:24 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ https://www.cnn.com/2022/12/05/entertainment/adam-sandler-chris-farley-song-snl/index.html
+
+ https://www.cnn.com/2022/12/05/entertainment/adam-sandler-chris-farley-song-snl/index.html
+
+ Mon, 05 Dec 2022 15:04:40 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/05/entertainment/neil-diamond-sweet-caroline-broadway-sing/index.html
+
+
+ https://www.cnn.com/2022/12/05/entertainment/neil-diamond-sweet-caroline-broadway-sing/index.html
+
+ Tue, 06 Dec 2022 13:41:03 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/04/entertainment/george-clooney-star-social-media/index.html
+
+ https://www.cnn.com/2022/12/04/entertainment/george-clooney-star-social-media/index.html
+
+ Sun, 04 Dec 2022 23:42:59 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/04/entertainment/ben-affleck-netflix-assembly-line/index.html
+
+ https://www.cnn.com/2022/12/04/entertainment/ben-affleck-netflix-assembly-line/index.html
+
+ Mon, 05 Dec 2022 02:21:54 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/04/entertainment/keke-palmer-baby-bump-snl-monologue/index.html
+
+ https://www.cnn.com/2022/12/04/entertainment/keke-palmer-baby-bump-snl-monologue/index.html
+
+ Sun, 04 Dec 2022 18:33:52 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/01/entertainment/christine-mcvie-music/index.html
+ https://www.cnn.com/2022/12/01/entertainment/christine-mcvie-music/index.html
+
+ Thu, 01 Dec 2022 19:08:38 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ https://www.cnn.com/2022/12/05/entertainment/axl-rose-microphone/index.html
+ https://www.cnn.com/2022/12/05/entertainment/axl-rose-microphone/index.html
+ Mon, 05 Dec 2022 14:15:44 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/01/entertainment/indiana-jones-dial-of-destiny-teaser-trailer/index.html
+
+
+ https://www.cnn.com/2022/12/01/entertainment/indiana-jones-dial-of-destiny-teaser-trailer/index.html
+
+ Fri, 02 Dec 2022 03:04:00 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/11/30/entertainment/willow-review/index.html
+ https://www.cnn.com/2022/11/30/entertainment/willow-review/index.html
+ Wed, 30 Nov 2022 17:23:24 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/12/01/entertainment/love-actually-richard-curtis-diversity-scli-intl/index.html
+
+
+ https://www.cnn.com/2022/12/01/entertainment/love-actually-richard-curtis-diversity-scli-intl/index.html
+
+ Thu, 01 Dec 2022 14:24:40 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/11/30/entertainment/joe-pesci-burns-home-alone-2/index.html
+
+ https://www.cnn.com/2022/11/30/entertainment/joe-pesci-burns-home-alone-2/index.html
+
+ Thu, 01 Dec 2022 01:02:12 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/12/01/entertainment/stevie-nicks-christine-mcvie-friendship-cec/index.html
+
+
+ https://www.cnn.com/2022/12/01/entertainment/stevie-nicks-christine-mcvie-friendship-cec/index.html
+
+ Thu, 01 Dec 2022 17:54:53 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/11/29/entertainment/will-smith-daily-show/index.html
+ https://www.cnn.com/2022/11/29/entertainment/will-smith-daily-show/index.html
+
+ Tue, 29 Nov 2022 17:02:36 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/11/30/entertainment/super-mario-bros-movie-trailer-two/index.html
+
+ https://www.cnn.com/2022/11/30/entertainment/super-mario-bros-movie-trailer-two/index.html
+
+ Wed, 30 Nov 2022 17:10:09 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+ https://www.cnn.com/2022/11/29/entertainment/the-phantom-of-the-opera-broadway-longer-extension/index.html
+
+
+ https://www.cnn.com/2022/11/29/entertainment/the-phantom-of-the-opera-broadway-longer-extension/index.html
+
+ Tue, 29 Nov 2022 18:40:18 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/11/29/entertainment/kim-kardashian-kanye-west-divorce/index.html
+
+ https://www.cnn.com/2022/11/29/entertainment/kim-kardashian-kanye-west-divorce/index.html
+
+ Wed, 30 Nov 2022 15:56:36 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/11/23/entertainment/marsai-martin-omari-hardwick-fantasy-football/index.html
+
+
+ https://www.cnn.com/2022/11/23/entertainment/marsai-martin-omari-hardwick-fantasy-football/index.html
+
+ Wed, 23 Nov 2022 14:10:45 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ https://www.cnn.com/2022/11/22/entertainment/wednesday-review/index.html
+ https://www.cnn.com/2022/11/22/entertainment/wednesday-review/index.html
+ Tue, 22 Nov 2022 14:45:05 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/11/23/entertainment/glass-onion-a-knives-out-mystery-review/index.html
+
+ https://www.cnn.com/2022/11/23/entertainment/glass-onion-a-knives-out-mystery-review/index.html
+
+ Wed, 23 Nov 2022 14:00:36 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ https://www.cnn.com/2022/11/22/entertainment/love-actually-20th-anniversary-abc-special/index.html
+
+
+ https://www.cnn.com/2022/11/22/entertainment/love-actually-20th-anniversary-abc-special/index.html
+
+ Wed, 23 Nov 2022 23:02:35 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+ https://www.cnn.com/2022/11/22/entertainment/james-cameron-leonardo-dicaprio-kate-winslet-titanic/index.html
+
+
+ https://www.cnn.com/2022/11/22/entertainment/james-cameron-leonardo-dicaprio-kate-winslet-titanic/index.html
+
+ Wed, 23 Nov 2022 15:24:00 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/resources/media/music-video.xml b/tests/resources/media/music-video.xml
new file mode 100644
index 0000000..9563047
--- /dev/null
+++ b/tests/resources/media/music-video.xml
@@ -0,0 +1,32 @@
+
+
+
+ Music Videos 101
+ http://www.foo.com
+ Discussions of great videos
+ -
+ The latest video from an artist
+ http://www.foo.com/item1.htm
+
+
+ dfdec888b72151965a34b4b59031290a
+ producer's name
+ artist's name
+
+ music/artistname/album/song
+
+
+ Oh, say, can you see, by the dawn's early light
+
+ nonadult
+
+ start=2002-10-13T09:00+01:00;
+ end=2002-10-17T17:00+01:00;
+ scheme=W3C-DTF
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/resources/media/trailer.xml b/tests/resources/media/trailer.xml
new file mode 100644
index 0000000..91dd506
--- /dev/null
+++ b/tests/resources/media/trailer.xml
@@ -0,0 +1,19 @@
+
+
+
+ My Movie Review Site
+ http://www.foo.com
+ I review movies.
+ -
+ Movie Title: Is this a good movie?
+ http://www.foo.com/item1.htm
+
+
+ http://www.creativecommons.org/licenses/by-nc/1.0
+
+ nonadult
+
+
+
\ No newline at end of file
diff --git a/tests/resources/tmp/build/.gitkeep b/tests/resources/tmp/build/.gitkeep
new file mode 100644
index 0000000..e69de29