A free, self-hostable aggregator…
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

233 lines
5.7 KiB

<?php
/* *
* lib_opml is a free library to manage OPML format in PHP.
* It takes in consideration only version 2.0 (http://dev.opml.org/spec2.html).
* Basically it means "text" attribute for outline elements is required.
*
* lib_opml requires SimpleXML (http://php.net/manual/en/book.simplexml.php)
*
* Usages:
* > include('lib_opml.php');
* > $filename = 'my_opml_file.xml';
* > $opml_array = libopml_parse_file($filename);
* > print_r($opml_array);
*
* > $opml_string = [...];
* > $opml_array = libopml_parse_string($opml_string);
* > print_r($opml_array);
*
* > $opml_array = [...];
* > $opml_string = libopml_render($opml_array);
* > $opml_object = libopml_render($opml_array, true);
* > echo $opml_string;
* > print_r($opml_object);
*
* If parsing fails for any reason (e.g. not an XML string, does not match with
* the specifications), a LibOPML_Exception is raised.
*
* Author: Marien Fressinaud <dev@marienfressinaud.fr>
* Url: https://github.com/marienfressinaud/lib_opml
* Version: 0.1
* Date: 2014-03-29
* License: public domain
*
* */
class LibOPML_Exception extends Exception {}
// These elements are optional
define('HEAD_ELEMENTS', serialize(array(
'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail',
'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop',
'windowLeft', 'windowBottom', 'windowRight'
)));
function libopml_parse_outline($outline_xml) {
$outline = array();
// An outline may contain any kind of attributes but "text" attribute is
// required !
$text_is_present = false;
foreach ($outline_xml->attributes() as $key => $value) {
$outline[$key] = (string)$value;
if ($key === 'text') {
$text_is_present = true;
}
}
if (!$text_is_present) {
throw new LibOPML_Exception(
'Outline does not contain any text attribute'
);
}
foreach ($outline_xml->children() as $key => $value) {
// An outline may contain any number of outline children
if ($key === 'outline') {
$outline['@outlines'][] = libopml_parse_outline($value);
} else {
throw new LibOPML_Exception(
'Body can contain only outline elements'
);
}
}
return $outline;
}
function libopml_parse_string($xml) {
$dom = new DOMDocument();
$dom->recover = true;
$dom->strictErrorChecking = false;
$dom->loadXML($xml);
$dom->encoding = 'UTF-8';
$opml = simplexml_import_dom($dom);
if (!$opml) {
throw new LibOPML_Exception();
}
$array = array(
'version' => (string)$opml['version'],
'head' => array(),
'body' => array()
);
// First, we get all "head" elements. Head is required but its sub-elements
// are optional.
// TODO: test head exists!
foreach ($opml->head->children() as $key => $value) {
if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
$array['head'][$key] = (string)$value;
} else {
throw new LibOPML_Exception(
$key . 'is not part of OPML format'
);
}
}
// Then, we get body oulines. Body must contain at least one outline
// element.
$at_least_one_outline = false;
// TODO: test body exists!
foreach ($opml->body->children() as $key => $value) {
if ($key === 'outline') {
$at_least_one_outline = true;
$array['body'][] = libopml_parse_outline($value);
} else {
throw new LibOPML_Exception(
'Body can contain only outline elements'
);
}
}
if (!$at_least_one_outline) {
throw new LibOPML_Exception(
'Body must contain at least one outline element'
);
}
return $array;
}
function libopml_parse_file($filename) {
$file_content = file_get_contents($filename);
if ($file_content === false) {
throw new LibOPML_Exception(
$filename . ' cannot be found'
);
}
return libopml_parse_string($file_content);
}
function libopml_render_outline($parent_elt, $outline) {
// Outline MUST be an array!
if (!is_array($outline)) {
throw new LibOPML_Exception(
'Outline element must be defined as array'
);
}
$outline_elt = $parent_elt->addChild('outline');
$text_is_present = false;
foreach ($outline as $key => $value) {
// Only outlines can be an array and so we consider children are also
// outline elements.
if ($key === '@outlines' && is_array($value)) {
foreach ($value as $outline_child) {
libopml_render_outline($outline_elt, $outline_child);
}
} elseif (is_array($value)) {
throw new LibOPML_Exception(
'Type of outline elements cannot be array: ' . $key
);
} else {
// Detect text attribute is present, that's good :)
if ($key === 'text') {
$text_is_present = true;
}
$outline_elt->addAttribute($key, $value);
}
}
if (!$text_is_present) {
throw new LibOPML_Exception(
'You must define at least a text element for all outlines'
);
}
}
function libopml_render($array, $as_xml_object = false) {
$opml = new SimpleXMLElement('<opml version="2.0"></opml>');
// Create head element. $array['head'] is optional but head element will
// exist in the final XML object.
$head = $opml->addChild('head');
if (isset($array['head'])) {
foreach ($array['head'] as $key => $value) {
if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
$head->addChild($key, $value);
}
}
}
// Check body is set and contains at least one element
if (!isset($array['body'])) {
throw new LibOPML_Exception(
'$array must contain a body element'
);
}
if (count($array['body']) <= 0) {
throw new LibOPML_Exception(
'Body element must contain at least one element (array)'
);
}
// Create outline elements
$body = $opml->addChild('body');
foreach ($array['body'] as $outline) {
libopml_render_outline($body, $outline);
}
// And return the final result
if ($as_xml_object) {
return $opml;
} else {
$dom = dom_import_simplexml($opml)->ownerDocument;
$dom->formatOutput = true;
$dom->encoding = 'UTF-8';
return $dom->saveXML();
}
}