lib_opml.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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. foreach ($opml->head->children() as $key => $value) {
  88. if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
  89. $array['head'][$key] = (string)$value;
  90. } else {
  91. throw new LibOPML_Exception(
  92. $key . 'is not part of OPML format'
  93. );
  94. }
  95. }
  96. // Then, we get body oulines. Body must contain at least one outline
  97. // element.
  98. $at_least_one_outline = false;
  99. foreach ($opml->body->children() as $key => $value) {
  100. if ($key === 'outline') {
  101. $at_least_one_outline = true;
  102. $array['body'][] = libopml_parse_outline($value);
  103. } else {
  104. throw new LibOPML_Exception(
  105. 'Body can contain only outline elements'
  106. );
  107. }
  108. }
  109. if (!$at_least_one_outline) {
  110. throw new LibOPML_Exception(
  111. 'Body must contain at least one outline element'
  112. );
  113. }
  114. return $array;
  115. }
  116. function libopml_parse_file($filename) {
  117. $file_content = file_get_contents($filename);
  118. if ($file_content === false) {
  119. throw new LibOPML_Exception(
  120. $filename . ' cannot be found'
  121. );
  122. }
  123. return libopml_parse_string($file_content);
  124. }
  125. function libopml_render_outline($parent_elt, $outline) {
  126. // Outline MUST be an array!
  127. if (!is_array($outline)) {
  128. throw new LibOPML_Exception(
  129. 'Outline element must be defined as array'
  130. );
  131. }
  132. $outline_elt = $parent_elt->addChild('outline');
  133. $text_is_present = false;
  134. foreach ($outline as $key => $value) {
  135. // Only outlines can be an array and so we consider children are also
  136. // outline elements.
  137. if ($key === '@outlines' && is_array($value)) {
  138. foreach ($value as $outline_child) {
  139. libopml_render_outline($outline_elt, $outline_child);
  140. }
  141. } elseif (is_array($value)) {
  142. throw new LibOPML_Exception(
  143. 'Type of outline elements cannot be array: ' . $key
  144. );
  145. } else {
  146. // Detect text attribute is present, that's good :)
  147. if ($key === 'text') {
  148. $text_is_present = true;
  149. }
  150. $outline_elt->addAttribute($key, $value);
  151. }
  152. }
  153. if (!$text_is_present) {
  154. throw new LibOPML_Exception(
  155. 'You must define at least a text element for all outlines'
  156. );
  157. }
  158. }
  159. function libopml_render($array, $as_xml_object = false) {
  160. $opml = new SimpleXMLElement('<opml version="2.0"></opml>');
  161. // Create head element. $array['head'] is optional but head element will
  162. // exist in the final XML object.
  163. $head = $opml->addChild('head');
  164. if (isset($array['head'])) {
  165. foreach ($array['head'] as $key => $value) {
  166. if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
  167. $head->addChild($key, $value);
  168. }
  169. }
  170. }
  171. // Check body is set and contains at least one element
  172. if (!isset($array['body'])) {
  173. throw new LibOPML_Exception(
  174. '$array must contain a body element'
  175. );
  176. }
  177. if (count($array['body']) <= 0) {
  178. throw new LibOPML_Exception(
  179. 'Body element must contain at least one element (array)'
  180. );
  181. }
  182. // Create outline elements
  183. $body = $opml->addChild('body');
  184. foreach ($array['body'] as $outline) {
  185. libopml_render_outline($body, $outline);
  186. }
  187. // And return the final result
  188. if ($as_xml_object) {
  189. return $opml;
  190. } else {
  191. $dom = dom_import_simplexml($opml)->ownerDocument;
  192. $dom->formatOutput = true;
  193. $dom->encoding = 'UTF-8';
  194. return $dom->saveXML();
  195. }
  196. }