2014-07-23 15:52:50 +02:00
|
|
|
<?php
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
/**
|
2014-07-23 15:52:50 +02:00
|
|
|
* lib_opml is a free library to manage OPML format in PHP.
|
|
|
|
*
|
2015-02-08 18:55:48 +01:00
|
|
|
* By default, it takes in consideration version 2.0 but can be compatible with
|
|
|
|
* OPML 1.0. More information on http://dev.opml.org.
|
|
|
|
* Difference is "text" attribute is optional in version 1.0. It is highly
|
|
|
|
* recommended to use this attribute.
|
|
|
|
*
|
|
|
|
* lib_opml requires SimpleXML (php.net/simplexml) and DOMDocument (php.net/domdocument)
|
|
|
|
*
|
|
|
|
* @author Marien Fressinaud <dev@marienfressinaud.fr>
|
|
|
|
* @link https://github.com/marienfressinaud/lib_opml
|
|
|
|
* @version 0.2
|
|
|
|
* @license public domain
|
2014-07-23 15:52:50 +02:00
|
|
|
*
|
|
|
|
* 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);
|
|
|
|
*
|
2015-02-08 18:55:48 +01:00
|
|
|
* You can set $strict argument to false if you want to bypass "text" attribute
|
|
|
|
* requirement.
|
|
|
|
*
|
2014-07-23 15:52:50 +02:00
|
|
|
* If parsing fails for any reason (e.g. not an XML string, does not match with
|
|
|
|
* the specifications), a LibOPML_Exception is raised.
|
|
|
|
*
|
2015-02-08 18:55:48 +01:00
|
|
|
* lib_opml array format is described here:
|
|
|
|
* $array = array(
|
|
|
|
* 'head' => array( // 'head' element is optional (but recommended)
|
|
|
|
* 'key' => 'value', // key must be a part of available OPML head elements
|
|
|
|
* ),
|
|
|
|
* 'body' => array( // body is required
|
|
|
|
* array( // this array represents an outline (at least one)
|
|
|
|
* 'text' => 'value', // 'text' element is required if $strict is true
|
|
|
|
* 'key' => 'value', // key and value are what you want (optional)
|
|
|
|
* '@outlines' = array( // @outlines is a special value and represents sub-outlines
|
|
|
|
* array(
|
|
|
|
* [...] // where [...] is a valid outline definition
|
|
|
|
* ),
|
|
|
|
* ),
|
|
|
|
* ),
|
|
|
|
* array( // other outline definitions
|
|
|
|
* [...]
|
|
|
|
* ),
|
|
|
|
* [...],
|
|
|
|
* )
|
|
|
|
* )
|
2014-07-23 15:52:50 +02:00
|
|
|
*
|
2015-02-08 18:55:48 +01:00
|
|
|
*/
|
2014-07-23 15:52:50 +02:00
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
/**
|
|
|
|
* A simple Exception class which represents any kind of OPML problem.
|
|
|
|
* Message should precise the current problem.
|
|
|
|
*/
|
2014-07-23 15:52:50 +02:00
|
|
|
class LibOPML_Exception extends Exception {}
|
|
|
|
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
// Define the list of available head attributes. All of them are optional.
|
2014-07-23 15:52:50 +02:00
|
|
|
define('HEAD_ELEMENTS', serialize(array(
|
|
|
|
'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail',
|
|
|
|
'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop',
|
|
|
|
'windowLeft', 'windowBottom', 'windowRight'
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
/**
|
|
|
|
* Parse an XML object as an outline object and return corresponding array
|
|
|
|
*
|
|
|
|
* @param SimpleXMLElement $outline_xml the XML object we want to parse
|
|
|
|
* @param bool $strict true if "text" attribute is required, false else
|
|
|
|
* @return array corresponding to an outline and following format described above
|
|
|
|
* @throws LibOPML_Exception
|
|
|
|
* @access private
|
|
|
|
*/
|
|
|
|
function libopml_parse_outline($outline_xml, $strict = true) {
|
2014-07-23 15:52:50 +02:00
|
|
|
$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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
if (!$text_is_present && $strict) {
|
2014-07-23 15:52:50 +02:00
|
|
|
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') {
|
2015-02-08 18:55:48 +01:00
|
|
|
$outline['@outlines'][] = libopml_parse_outline($value, $strict);
|
2014-07-23 15:52:50 +02:00
|
|
|
} else {
|
|
|
|
throw new LibOPML_Exception(
|
|
|
|
'Body can contain only outline elements'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $outline;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
/**
|
|
|
|
* Parse a string as a XML one and returns the corresponding array
|
|
|
|
*
|
|
|
|
* @param string $xml is the string we want to parse
|
|
|
|
* @param bool $strict true if "text" attribute is required, false else
|
|
|
|
* @return array corresponding to the XML string and following format described above
|
|
|
|
* @throws LibOPML_Exception
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
function libopml_parse_string($xml, $strict = true) {
|
2014-07-23 15:52:50 +02:00
|
|
|
$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.
|
|
|
|
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;
|
|
|
|
foreach ($opml->body->children() as $key => $value) {
|
|
|
|
if ($key === 'outline') {
|
|
|
|
$at_least_one_outline = true;
|
2015-02-08 18:55:48 +01:00
|
|
|
$array['body'][] = libopml_parse_outline($value, $strict);
|
2014-07-23 15:52:50 +02:00
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
/**
|
|
|
|
* Parse a string contained into a file as a XML string and returns the corresponding array
|
|
|
|
*
|
|
|
|
* @param string $filename should indicates a valid XML file
|
|
|
|
* @param bool $strict true if "text" attribute is required, false else
|
|
|
|
* @return array corresponding to the file content and following format described above
|
|
|
|
* @throws LibOPML_Exception
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
function libopml_parse_file($filename, $strict = true) {
|
2014-07-23 15:52:50 +02:00
|
|
|
$file_content = file_get_contents($filename);
|
|
|
|
|
|
|
|
if ($file_content === false) {
|
|
|
|
throw new LibOPML_Exception(
|
|
|
|
$filename . ' cannot be found'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
return libopml_parse_string($file_content, $strict);
|
2014-07-23 15:52:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
/**
|
|
|
|
* Create a XML outline object in a parent object.
|
|
|
|
*
|
|
|
|
* @param SimpleXMLElement $parent_elt is the parent object of current outline
|
|
|
|
* @param array $outline array representing an outline object
|
|
|
|
* @param bool $strict true if "text" attribute is required, false else
|
|
|
|
* @throws LibOPML_Exception
|
|
|
|
* @access private
|
|
|
|
*/
|
|
|
|
function libopml_render_outline($parent_elt, $outline, $strict) {
|
2014-07-23 15:52:50 +02:00
|
|
|
// 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) {
|
2015-02-08 18:55:48 +01:00
|
|
|
libopml_render_outline($outline_elt, $outline_child, $strict);
|
2014-07-23 15:52:50 +02:00
|
|
|
}
|
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
if (!$text_is_present && $strict) {
|
2014-07-23 15:52:50 +02:00
|
|
|
throw new LibOPML_Exception(
|
|
|
|
'You must define at least a text element for all outlines'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-02-08 18:55:48 +01:00
|
|
|
/**
|
|
|
|
* Render an array as an OPML string or a XML object.
|
|
|
|
*
|
|
|
|
* @param array $array is the array we want to render and must follow structure defined above
|
|
|
|
* @param bool $as_xml_object false if function must return a string, true for a XML object
|
|
|
|
* @param bool $strict true if "text" attribute is required, false else
|
|
|
|
* @return string|SimpleXMLElement XML string corresponding to $array or XML object
|
|
|
|
* @throws LibOPML_Exception
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
function libopml_render($array, $as_xml_object = false, $strict = true) {
|
|
|
|
$opml = new SimpleXMLElement('<opml></opml>');
|
|
|
|
$opml->addAttribute('version', $strict ? '2.0' : '1.0');
|
2014-07-23 15:52:50 +02:00
|
|
|
|
|
|
|
// 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) {
|
2015-02-08 18:55:48 +01:00
|
|
|
libopml_render_outline($body, $outline, $strict);
|
2014-07-23 15:52:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|