Browse Source

Add abstract Feed class as common base for XML and JSON feeds

master
J. King 6 years ago
parent
commit
26ceb68acd
  1. 61
      lib/Feed.php
  2. 26
      lib/XML/Construct.php
  3. 37
      lib/XML/Feed.php
  4. 32
      lib/XML/XPath.php

61
lib/Feed.php

@ -0,0 +1,61 @@
<?php
/** @license MIT
* Copyright 2018 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax;
use JKingWeb\Lax\Person\Person;
use JKingWeb\Lax\Person\Collection as PersonCollection;
abstract class Feed {
public $type;
public $version;
public $url;
public $link;
public $title;
public $summary;
public $categories;
public $people;
public $author;
/** Constructs a parsed feed */
abstract public function __construct(string $data, string $contentType = "", string $url = "");
/** Parses the feed to extract sundry metadata */
protected function parse() {
$this->id = $this->getId();
$this->link = $this->getLink();
$this->title = $this->getTitle();
$this->summary = $this->getSummary();
$this->people = $this->getPeople();
$this->author = $this->people->primary();
// do a second pass on missing data we'd rather fill in
$this->link = strlen($this->link) ? $this->link : $this->url;
$this->title = strlen($this->title) ? $this->title : $this->link;
}
/** General function to fetch the feed title */
abstract public function getTitle(): string;
/** General function to fetch the feed's Web-representation URL */
abstract public function getLink(): string;
/** General function to fetch the description of a feed */
abstract public function getSummary(): string;
/** General function to fetch the categories of a feed
*
* If the $grouped parameter is true, and array of arrays will be returned, keyed by taxonomy/scheme
*
* The $humanFriendly parameter only affects Atom categories
*/
abstract public function getCategories(bool $grouped = false, bool $humanFriendly = true): array;
/** General function to fetch the feed identifier */
abstract public function getId(): string;
/** General function to fetch a collection of people associated with a feed */
abstract public function getPeople(): PersonCollection;
}

26
lib/XML/Construct.php

@ -8,37 +8,13 @@ namespace JKingWeb\Lax\XML;
use JKingWeb\Lax\Person\Person;
abstract class Construct {
trait Construct {
/** @var \DOMDocument */
public $document;
/** @var \DOMXPath */
protected $xpath;
/** @var \DOMElement */
protected $subject;
const NS = [
'atom' => "http://www.w3.org/2005/Atom", // Atom syndication format https://tools.ietf.org/html/rfc4287
'rss1' => "http://purl.org/rss/1.0/", // RDF site summary 1.0 http://purl.org/rss/1.0/spec
'rss0' => "http://channel.netscape.com/rdf/simple/0.9/", // RDF Site Summary 0.90 http://www.rssboard.org/rss-0-9-0
'dc' => "http://purl.org/dc/elements/1.1/", // Dublin Core metadata http://purl.org/rss/1.0/modules/dc/
'sched' => "http://purl.org/rss/1.0/modules/syndication/", // Syndication schedule extension http://purl.org/rss/1.0/modules/syndication/
'enc' => "http://purl.org/rss/1.0/modules/content/", // Explicitly encoded content extension http://purl.org/rss/1.0/modules/content/
'media' => "http://search.yahoo.com/mrss/", // Embedded media extension http://www.rssboard.org/media-rss
// RSS 2.0 does not have a namespace // Really Simple Syndication 2.0.11 http://www.rssboard.org/rss-specification
'rdf' => "http://www.w3.org/1999/02/22-rdf-syntax-ns#", // Resource Description Framework
'xhtml' => "http://www.w3.org/1999/xhtml", // XHTML
'apple' => "http://www.itunes.com/dtds/podcast-1.0.dtd", // iTunes podcasts https://help.apple.com/itc/podcasts_connect/#/itcb54353390
'gplay' => "http://www.google.com/schemas/play-podcasts/1.0", // Google Play podcasts https://support.google.com/googleplay/podcasts/answer/6260341
];
/** Returns an XPath processor with various necessary namespace prefixes defined */
public static function getXPathProcessor(\DOMDocument $doc): \DOMXPath {
$proc = new \DOMXPath($doc);
foreach (self::NS as $prefix => $url) {
$proc->registerNamespace($prefix, $url);
}
return $proc;
}
/** Trims plain text and collapses whitespace */
protected function trimText(string $text): string {

37
lib/XML/Feed.php

@ -9,19 +9,10 @@ namespace JKingWeb\Lax\XML;
use JKingWeb\Lax\Person\Person;
use JKingWeb\Lax\Person\Collection as PersonCollection;
class Feed extends Construct {
class Feed extends \JKingWeb\Lax\Feed {
use Construct;
use Primitives\Construct;
use Primitives\Feed;
public $type;
public $version;
public $url;
public $link;
public $title;
public $summary;
public $categories;
public $people;
public $author;
/** Constructs a parsed feed */
public function __construct(string $data, string $contentType = "", string $url = "") {
@ -34,7 +25,7 @@ class Feed extends Construct {
$this->document = new \DOMDocument();
$this->document->loadXML($data, \LIBXML_BIGLINES | \LIBXML_COMPACT);
$this->document->documentURI = $url;
$this->xpath = self::getXPathProcessor($this->document);
$this->xpath = new XPath($this->document);
$this->subject = $this->document->documentElement;
$ns = $this->subject->namespaceURI;
$name = $this->subject->localName;
@ -42,39 +33,25 @@ class Feed extends Construct {
$this->subject = $this->fetchElement("./channel") ?? $this->subject;
$this->type = "rss";
$this->version = $this->document->documentElement->getAttribute("version");
} elseif ($ns==self::NS['rdf'] && $name=="RDF") {
} elseif ($ns==XPath::NS['rdf'] && $name=="RDF") {
$this->type = "rdf";
$channel = $this->fetchElement("./rss1:channel|./rss0:channel");
if ($channel) {
$this->subject = $channel;
$this->version = ($channel->namespaceURI==self::NS['rss1']) ? "1.0" : "0.90";
$this->version = ($channel->namespaceURI==XPath::NS['rss1']) ? "1.0" : "0.90";
} else {
$element = $this->fetchElement("./rss1:item|./rss0:item|./rss1:image|./rss0:image");
if ($element) {
$this->version = ($element->namespaceURI==self::NS['rss1']) ? "1.0" : "0.90";
$this->version = ($element->namespaceURI==XPath::NS['rss1']) ? "1.0" : "0.90";
}
}
} elseif ($ns==self::NS['atom'] && $name=="feed") {
} elseif ($ns==XPath::NS['atom'] && $name=="feed") {
$this->type = "atom";
$this->version = "1.0";
} else {
throw new \Exception;
}
$this->url = $url;
}
/** Parses the feed to extract sundry metadata */
protected function parse() {
$this->id = $this->getId();
$this->link = $this->getLink();
$this->title = $this->getTitle();
$this->summary = $this->getSummary();
$this->people = $this->getPeople();
$this->author = $this->people->primary();
// do a second pass on missing data we'd rather fill in
$this->link = strlen($this->link) ? $this->link : $this->url;
$this->title = strlen($this->title) ? $this->title : $this->link;
}
/** General function to fetch the feed title */

32
lib/XML/XPath.php

@ -0,0 +1,32 @@
<?php
/** @license MIT
* Copyright 2018 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax\XML;
class XPath extends \DOMXpath {
const NS = [
'atom' => "http://www.w3.org/2005/Atom", // Atom syndication format https://tools.ietf.org/html/rfc4287
'rss1' => "http://purl.org/rss/1.0/", // RDF site summary 1.0 http://purl.org/rss/1.0/spec
'rss0' => "http://channel.netscape.com/rdf/simple/0.9/", // RDF Site Summary 0.90 http://www.rssboard.org/rss-0-9-0
'dc' => "http://purl.org/dc/elements/1.1/", // Dublin Core metadata http://purl.org/rss/1.0/modules/dc/
'sched' => "http://purl.org/rss/1.0/modules/syndication/", // Syndication schedule extension http://purl.org/rss/1.0/modules/syndication/
'enc' => "http://purl.org/rss/1.0/modules/content/", // Explicitly encoded content extension http://purl.org/rss/1.0/modules/content/
'media' => "http://search.yahoo.com/mrss/", // Embedded media extension http://www.rssboard.org/media-rss
// RSS 2.0 does not have a namespace // Really Simple Syndication 2.0.11 http://www.rssboard.org/rss-specification
'rdf' => "http://www.w3.org/1999/02/22-rdf-syntax-ns#", // Resource Description Framework
'xhtml' => "http://www.w3.org/1999/xhtml", // XHTML
'apple' => "http://www.itunes.com/dtds/podcast-1.0.dtd", // iTunes podcasts https://help.apple.com/itc/podcasts_connect/#/itcb54353390
'gplay' => "http://www.google.com/schemas/play-podcasts/1.0", // Google Play podcasts https://support.google.com/googleplay/podcasts/answer/6260341
];
/** Returns an XPath processor with various necessary namespace prefixes defined */
public function __construct(\DOMDocument $doc) {
parent::__construct($doc);
foreach (XPath::NS as $prefix => $url) {
$this->registerNamespace($prefix, $url);
}
}
}
Loading…
Cancel
Save