PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/FluentDOM.php

http://github.com/ThomasWeinert/FluentDOM
PHP | 281 lines | 141 code | 20 blank | 120 comment | 13 complexity | 402c6fd2933288a385e554f79a05deb6 MD5 | raw file
  1. <?php
  2. /*
  3. * FluentDOM
  4. *
  5. * @link https://thomas.weinert.info/FluentDOM/
  6. * @copyright Copyright 2009-2021 FluentDOM Contributors
  7. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  8. *
  9. */
  10. declare(strict_types=1);
  11. use FluentDOM\Creator;
  12. use FluentDOM\DOM\Document;
  13. use FluentDOM\Exceptions\InvalidArgument;
  14. use FluentDOM\Exceptions\InvalidSource\Variable as InvalidVariableSource;
  15. use FluentDOM\Exceptions\NoSerializer;
  16. use FluentDOM\Loadable;
  17. use FluentDOM\Loader\Lazy as LazyLoader;
  18. use FluentDOM\Serializer\Factory as SerializerFactory;
  19. use FluentDOM\Xpath\Transformer as XpathTransformer;
  20. abstract class FluentDOM {
  21. /**
  22. * @var FluentDOM\Loadable
  23. */
  24. private static $_loader;
  25. /**
  26. * @var array
  27. */
  28. private static $_xpathTransformers = [];
  29. /**
  30. * @var FluentDOM\Loadable
  31. */
  32. private static $_defaultLoaders = [];
  33. /**
  34. * @var FluentDOM\Serializer\Factory\Group
  35. */
  36. private static $_serializerFactories;
  37. /**
  38. * Load a data source into a FluentDOM\DOM\Document
  39. *
  40. * @param mixed $source
  41. * @param string $contentType
  42. * @param array $options
  43. * @return Document
  44. */
  45. public static function load($source, string $contentType = 'text/xml', array $options = []): Document {
  46. if (NULL === self::$_loader) {
  47. self::$_loader = self::getDefaultLoaders();
  48. }
  49. $result = self::$_loader->load($source, $contentType, $options);
  50. return $result->getDocument();
  51. }
  52. /**
  53. * Return a FluentDOM Creator instance, allow to create a DOM using nested function calls
  54. *
  55. * @param string $version
  56. * @param string $encoding
  57. * @return FluentDOM\Creator
  58. */
  59. public static function create(string $version = '1.0', string $encoding = 'UTF-8'): Creator {
  60. return new FluentDOM\Creator($version, $encoding);
  61. }
  62. /**
  63. * @param DOMNode|FluentDOM\Query $node
  64. * @param string $contentType
  65. * @return string
  66. * @throws NoSerializer
  67. */
  68. public static function save($node, string $contentType = 'text/xml'): string {
  69. if ($node instanceof FluentDOM\Query) {
  70. $node = $node->document;
  71. }
  72. if ($serializer = self::getSerializerFactories()->createSerializer($node, $contentType)) {
  73. return (string)$serializer;
  74. }
  75. throw new FluentDOM\Exceptions\NoSerializer($contentType);
  76. }
  77. /**
  78. * Create an FluentDOM::Query instance and load the source into it.
  79. *
  80. * @param mixed $source
  81. * @param string $contentType
  82. * @param array $options
  83. * @return FluentDOM\Query
  84. * @throws LogicException
  85. * @throws OutOfBoundsException
  86. * @throws InvalidArgumentException
  87. * @throws InvalidVariableSource
  88. */
  89. public static function Query(
  90. $source = NULL, string $contentType = 'text/xml', array $options = []
  91. ): FluentDOM\Query {
  92. $query = new FluentDOM\Query();
  93. if (NULL !== $source) {
  94. $query->load($source, $contentType, $options);
  95. }
  96. return $query;
  97. }
  98. /**
  99. * Create an FluentDOM::Query instance with a modified selector callback.
  100. * This allows to use CSS selectors instead of Xpath expression.
  101. *
  102. * @param mixed $source
  103. * @param string $contentType
  104. * @param array $options
  105. * @return FluentDOM\Query
  106. * @throws LogicException
  107. * @throws InvalidArgumentException
  108. * @throws OutOfBoundsException
  109. * @throws InvalidVariableSource
  110. * @codeCoverageIgnore
  111. */
  112. public static function QueryCss(
  113. $source = NULL, string $contentType = 'text/xml', array $options = []
  114. ): FluentDOM\Query {
  115. $builder = self::getXPathTransformer();
  116. $query = self::Query($source, $contentType, $options);
  117. $isHtml = ($query->contentType === 'text/html');
  118. $query->onPrepareSelector = static function($selector, $contextMode) use ($builder, $isHtml) {
  119. return $builder->toXpath($selector, $contextMode, $isHtml);
  120. };
  121. return $query;
  122. }
  123. /**
  124. * Set a loader used in FluentDOM::load(), FALSE will reset the loader.
  125. * If no loader is provided an FluentDOM\Loader\Standard() will be created.
  126. *
  127. * @param FluentDOM\Loadable|NULL $loader
  128. * @throws InvalidArgument
  129. */
  130. public static function setLoader(Loadable $loader = NULL): void {
  131. if (!$loader) {
  132. self::$_loader = NULL;
  133. return;
  134. }
  135. self::$_loader = $loader;
  136. }
  137. /**
  138. * Register an additional default loader
  139. *
  140. * @param FluentDOM\Loadable|callable $loader
  141. * @param string ...$contentTypes
  142. * @return FluentDOM\Loaders
  143. */
  144. public static function registerLoader($loader, string ...$contentTypes): FluentDOM\Loaders {
  145. $loaders = self::getDefaultLoaders();
  146. if (count($contentTypes) > 0) {
  147. $lazyLoader = new LazyLoader();
  148. foreach ($contentTypes as $contentType) {
  149. $lazyLoader->add($contentType, $loader);
  150. }
  151. $loaders->add($lazyLoader);
  152. } elseif (is_callable($loader)) {
  153. self::registerLoader($loader());
  154. } else {
  155. $loaders->add($loader);
  156. }
  157. self::$_loader = NULL;
  158. return $loaders;
  159. }
  160. /**
  161. * Standard loader + any registered loader.
  162. *
  163. * @return FluentDOM\Loaders
  164. * @codeCoverageIgnore
  165. */
  166. public static function getDefaultLoaders(): FluentDOM\Loaders {
  167. if (!(self::$_defaultLoaders instanceof FluentDOM\Loaders)) {
  168. self::$_defaultLoaders = new FluentDOM\Loaders(new FluentDOM\Loader\Standard());
  169. }
  170. return self::$_defaultLoaders;
  171. }
  172. /**
  173. * Register a serializer factory for a specified content type(s). This can be
  174. * a callable returning the create serializer.
  175. *
  176. * @param SerializerFactory|callable $factory
  177. * @param string ...$contentTypes
  178. */
  179. public static function registerSerializerFactory(
  180. $factory, string ...$contentTypes
  181. ): void {
  182. foreach ($contentTypes as $contentType) {
  183. self::getSerializerFactories()[$contentType] = $factory;
  184. }
  185. }
  186. /**
  187. * Return registered serializer factories
  188. *
  189. * @return FluentDOM\Serializer\Factory\Group
  190. */
  191. public static function getSerializerFactories(): FluentDOM\Serializer\Factory\Group {
  192. if (NULL === self::$_serializerFactories) {
  193. $xml = static function(DOMNode $node) {
  194. return new FluentDOM\Serializer\Xml($node);
  195. };
  196. $html = static function(DOMNode $node) {
  197. return new FluentDOM\Serializer\Html($node);
  198. };
  199. $json = static function(DOMNode $node) {
  200. return new FluentDOM\Serializer\Json($node);
  201. };
  202. self::$_serializerFactories = new FluentDOM\Serializer\Factory\Group(
  203. [
  204. 'text/html' => $html,
  205. 'html' => $html,
  206. 'text/xml' => $xml,
  207. 'xml' => $xml,
  208. 'text/json' => $json,
  209. 'json' => $json
  210. ]
  211. );
  212. }
  213. return self::$_serializerFactories;
  214. }
  215. /**
  216. * Get a xpath expression builder to convert css selectors to xpath
  217. *
  218. * @param string $errorMessage
  219. * @return XpathTransformer
  220. * @throws LogicException
  221. */
  222. public static function getXPathTransformer(
  223. string $errorMessage = 'No CSS selector support installed'
  224. ): XpathTransformer {
  225. foreach (self::$_xpathTransformers as $index => $transformer) {
  226. if (is_string($transformer) && class_exists($transformer)) {
  227. self::$_xpathTransformers[$index] = new $transformer();
  228. } elseif (is_callable($transformer)) {
  229. self::$_xpathTransformers[$index] = $transformer();
  230. }
  231. if (self::$_xpathTransformers[$index] instanceof XpathTransformer) {
  232. return self::$_xpathTransformers[$index];
  233. }
  234. unset(self::$_xpathTransformers[$index]);
  235. }
  236. throw new LogicException($errorMessage);
  237. }
  238. /**
  239. * @param string|callable|FluentDOM\Xpath\Transformer $transformer
  240. * @param bool $reset
  241. */
  242. public static function registerXpathTransformer($transformer, bool $reset = FALSE): void {
  243. if ($reset) {
  244. self::$_xpathTransformers = [];
  245. }
  246. array_unshift(self::$_xpathTransformers, $transformer);
  247. }
  248. }
  249. /**
  250. * FluentDOM function, is an Alias for the \FluentDOM\FluentDOM::Query()
  251. * factory class function.
  252. *
  253. * @param mixed $source
  254. * @param string $contentType
  255. * @param array $options
  256. * @return FluentDOM\Query
  257. * @codeCoverageIgnore
  258. */
  259. function FluentDOM($source = NULL, string $contentType = 'text/xml', array $options = []): FluentDOM\Query {
  260. return FluentDOM::Query($source, $contentType, $options);
  261. }