lib_opml.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. /* *
  3. * lib_opml is a free library to manage OPML format in PHP.
  4. * It takes in consideration only version 2.0 (http://dev.opml.org/spec2.html).
  5. * Basically it means "text" attribute for outline elements is required.
  6. *
  7. * lib_opml requires SimpleXML (http://php.net/manual/en/book.simplexml.php)
  8. *
  9. * Usages:
  10. * > include('lib_opml.php');
  11. * > $filename = 'my_opml_file.xml';
  12. * > $opml_array = libopml_parse_file($filename);
  13. * > print_r($opml_array);
  14. *
  15. * > $opml_string = [...];
  16. * > $opml_array = libopml_parse_string($opml_string);
  17. * > print_r($opml_array);
  18. *
  19. * > $opml_array = [...];
  20. * > $opml_string = libopml_render($opml_array);
  21. * > $opml_object = libopml_render($opml_array, true);
  22. * > echo $opml_string;
  23. * > print_r($opml_object);
  24. *
  25. * If parsing fails for any reason (e.g. not an XML string, does not match with
  26. * the specifications), a LibOPML_Exception is raised.
  27. *
  28. * Author: Marien Fressinaud <dev@marienfressinaud.fr>
  29. * Url: https://github.com/marienfressinaud/lib_opml
  30. * Version: 0.1
  31. * Date: 2014-03-29
  32. * License: public domain
  33. *
  34. * */
  35. class LibOPML_Exception extends Exception {}
  36. // These elements are optional
  37. define('HEAD_ELEMENTS', serialize(array(
  38. 'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail',
  39. 'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop',
  40. 'windowLeft', 'windowBottom', 'windowRight'
  41. )));
  42. function libopml_parse_outline($outline_xml) {
  43. $outline = array();
  44. // An outline may contain any kind of attributes but "text" attribute is
  45. // required !
  46. $text_is_present = false;
  47. foreach ($outline_xml->attributes() as $key => $value) {
  48. $outline[$key] = (string)$value;
  49. if ($key === 'text') {
  50. $text_is_present = true;
  51. }
  52. }
  53. if (!$text_is_present) {
  54. throw new LibOPML_Exception(
  55. 'Outline does not contain any text attribute'
  56. );
  57. }
  58. foreach ($outline_xml->children() as $key => $value) {
  59. // An outline may contain any number of outline children
  60. if ($key === 'outline') {
  61. $outline['@outlines'][] = libopml_parse_outline($value);
  62. } else {
  63. throw new LibOPML_Exception(
  64. 'Body can contain only outline elements'
  65. );
  66. }
  67. }
  68. return $outline;
  69. }
  70. function libopml_parse_string($xml) {
  71. $dom = new DOMDocument();
  72. $dom->recover = true;
  73. $dom->strictErrorChecking = false;
  74. $dom->loadXML($xml);
  75. $dom->encoding = 'UTF-8';
  76. $opml = simplexml_import_dom($dom);
  77. if (!$opml) {
  78. throw new LibOPML_Exception();
  79. }
  80. $array = array(
  81. 'version' => (string)$opml['version'],
  82. 'head' => array(),
  83. 'body' => array()
  84. );
  85. // First, we get all "head" elements. Head is required but its sub-elements
  86. // are optional.
  87. // TODO: test head exists!
  88. foreach ($opml->head->children() as $key => $value) {
  89. if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
  90. $array['head'][$key] = (string)$value;
  91. } else {
  92. throw new LibOPML_Exception(
  93. $key . 'is not part of OPML format'
  94. );
  95. }
  96. }
  97. // Then, we get body oulines. Body must contain at least one outline
  98. // element.
  99. $at_least_one_outline = false;
  100. // TODO: test body exists!
  101. foreach ($opml->body->children() as $key => $value) {
  102. if ($key === 'outline') {
  103. $at_least_one_outline = true;
  104. $array['body'][] = libopml_parse_outline($value);
  105. } else {
  106. throw new LibOPML_Exception(
  107. 'Body can contain only outline elements'
  108. );
  109. }
  110. }
  111. if (!$at_least_one_outline) {
  112. throw new LibOPML_Exception(
  113. 'Body must contain at least one outline element'
  114. );
  115. }
  116. return $array;
  117. }
  118. function libopml_parse_file($filename) {
  119. $file_content = file_get_contents($filename);
  120. if ($file_content === false) {
  121. throw new LibOPML_Exception(
  122. $filename . ' cannot be found'
  123. );
  124. }
  125. return libopml_parse_string($file_content);
  126. }
  127. function libopml_render_outline($parent_elt, $outline) {
  128. // Outline MUST be an array!
  129. if (!is_array($outline)) {
  130. throw new LibOPML_Exception(
  131. 'Outline element must be defined as array'
  132. );
  133. }
  134. $outline_elt = $parent_elt->addChild('outline');
  135. $text_is_present = false;
  136. foreach ($outline as $key => $value) {
  137. // Only outlines can be an array and so we consider children are also
  138. // outline elements.
  139. if ($key === '@outlines' && is_array($value)) {
  140. foreach ($value as $outline_child) {
  141. libopml_render_outline($outline_elt, $outline_child);
  142. }
  143. } elseif (is_array($value)) {
  144. throw new LibOPML_Exception(
  145. 'Type of outline elements cannot be array: ' . $key
  146. );
  147. } else {
  148. // Detect text attribute is present, that's good :)
  149. if ($key === 'text') {
  150. $text_is_present = true;
  151. }
  152. $outline_elt->addAttribute($key, $value);
  153. }
  154. }
  155. if (!$text_is_present) {
  156. throw new LibOPML_Exception(
  157. 'You must define at least a text element for all outlines'
  158. );
  159. }
  160. }
  161. function libopml_render($array, $as_xml_object = false) {
  162. $opml = new SimpleXMLElement('<opml version="2.0"></opml>');
  163. // Create head element. $array['head'] is optional but head element will
  164. // exist in the final XML object.
  165. $head = $opml->addChild('head');
  166. if (isset($array['head'])) {
  167. foreach ($array['head'] as $key => $value) {
  168. if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
  169. $head->addChild($key, $value);
  170. }
  171. }
  172. }
  173. // Check body is set and contains at least one element
  174. if (!isset($array['body'])) {
  175. throw new LibOPML_Exception(
  176. '$array must contain a body element'
  177. );
  178. }
  179. if (count($array['body']) <= 0) {
  180. throw new LibOPML_Exception(
  181. 'Body element must contain at least one element (array)'
  182. );
  183. }
  184. // Create outline elements
  185. $body = $opml->addChild('body');
  186. foreach ($array['body'] as $outline) {
  187. libopml_render_outline($body, $outline);
  188. }
  189. // And return the final result
  190. if ($as_xml_object) {
  191. return $opml;
  192. } else {
  193. $dom = dom_import_simplexml($opml)->ownerDocument;
  194. $dom->formatOutput = true;
  195. $dom->encoding = 'UTF-8';
  196. return $dom->saveXML();
  197. }
  198. }