QueryPath::with()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/** @file
3
 * The Query Path package provides tools for manipulating a structured document.
4
 * Typically, the sort of structured document is one using a Document Object Model
5
 * (DOM).
6
 * The two major DOMs are the XML DOM and the HTML DOM. Using Query Path, you can
7
 * build, parse, search, and modify DOM documents.
8
 *
9
 * To use QueryPath, only one file must be imported: qp.php. This file defines
10
 * the `qp()` function, and also registers an autoloader if necessary.
11
 *
12
 * Standard usage:
13
 *
14
 * @code
15
 * <?php
16
 * require 'qp.php';
17
 *
18
 * $xml = '<?xml version="1.0"?><test><foo id="myID"/></test>';
19
 *
20
 * // Procedural call a la jQuery:
21
 * $qp = qp($xml, '#myID');
22
 * $qp->append('<new><elements/></new>')->writeHTML();
23
 *
24
 * // Object-oriented version with a factory:
25
 * $qp = QueryPath::with($xml)->find('#myID')
26
 * $qp->append('<new><elements/></new>')->writeHTML();
27
 * ?>
28
 * @endcode
29
 *
30
 * The above would print (formatted for readability):
31
 * @code
32
 * <?xml version="1.0"?>
33
 * <test>
34
 *  <foo id="myID">
35
 *    <new>
36
 *      <element/>
37
 *    </new>
38
 *  </foo>
39
 * </test>
40
 * @endcode
41
 *
42
 * ## Discovering the Library
43
 *
44
 * To gain familiarity with QueryPath, the following three API docs are
45
 * the best to start with:
46
 *
47
 *- qp(): This function constructs new queries, and is the starting point
48
 *  for manipulating a document. htmlqp() is an alias tuned for HTML
49
 *  documents (especially old HTML), and QueryPath::with(), QueryPath::withXML()
50
 *  and QueryPath::withHTML() all perform a similar role, but in a purely
51
 *  object oriented way.
52
 *- QueryPath: This is the top-level class for the library. It defines the
53
 *  main factories and some useful functions.
54
 *- QueryPath::Query: This defines all of the functions in QueryPath. When
55
 *  working with HTML and XML, the QueryPath::DOMQuery is the actual
56
 *  implementation that you work with.
57
 *
58
 * Included with the source code for QueryPath is a complete set of unit tests
59
 * as well as some example files. Those are good resources for learning about
60
 * how to apply QueryPath's tools. The full API documentation can be generated
61
 * from these files using Doxygen, or you can view it online at
62
 * http://api.querypath.org.
63
 *
64
 * If you are interested in building extensions for QueryPath, see the
65
 * QueryPath and QueryPath::Extension classes. There you will find information on adding
66
 * your own tools to QueryPath.
67
 *
68
 * QueryPath also comes with a full CSS 3 selector implementation (now
69
 * with partial support for the current draft of the CSS 4 selector spec). If
70
 * you are interested in reusing that in other code, you will want to start
71
 * with QueryPath::CSS::EventHandler.php, which is the event interface for the parser.
72
 *
73
 * All of the code in QueryPath is licensed under an MIT-style license
74
 * license. All of the code is Copyright, 2012 by Matt Butcher.
75
 *
76
 * @author    M Butcher <matt @aleph-null.tv>
77
 * @license   MIT
78
 * @see       QueryPath
79
 * @see       qp()
80
 * @see       http://querypath.org The QueryPath home page.
81
 * @see       http://api.querypath.org An online version of the API docs.
82
 * @see       http://technosophos.com For how-tos and examples.
83
 * @copyright Copyright (c) 2009-2012, Matt Butcher.
84
 * @version   -UNSTABLE% (3.x.x)
85
 *
86
 */
87
namespace QueryPath;
88
89
use \Masterminds\HTML5;
90
use QueryPath\ExtensionRegistry;
91
92
/**
93
 *
94
 */
95
class QueryPath
96
{
97
98
    /**
99
     * The version string for this version of QueryPath.
100
     *
101
     * Standard releases will be of the following form: <MAJOR>.<MINOR>[.<PATCH>][-STABILITY].
102
     *
103
     * Examples:
104
     * - 2.0
105
     * - 2.1.1
106
     * - 2.0-alpha1
107
     *
108
     * Developer releases will always be of the form dev-<DATE>.
109
     *
110
     * @since 2.0
111
     */
112
    public const VERSION = '3.0.x';
113
114
    /**
115
     * Major version number.
116
     *
117
     * Examples:
118
     * - 3
119
     * - 4
120
     *
121
     * @since 3.0.1
122
     */
123
    public const VERSION_MAJOR = 3;
124
125
    /**
126
     * This is a stub HTML 4.01 document.
127
     *
128
     * <b>Using {@link QueryPath::XHTML_STUB} is preferred.</b>
129
     *
130
     * This is primarily for generating legacy HTML content. Modern web applications
131
     * should use QueryPath::XHTML_STUB.
132
     *
133
     * Use this stub with the HTML familiy of methods (QueryPath::Query::html(),
134
     * QueryPath::Query::writeHTML(), QueryPath::Query::innerHTML()).
135
     */
136
    public const HTML_STUB = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
137
  <html lang="en">
138
  <head>
139
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
140
  <title>Untitled</title>
141
  </head>
142
  <body></body>
143
  </html>';
144
145
    public const HTML5_STUB = '<!DOCTYPE html>
146
    <html>
147
    <head>
148
    <title>Untitled</title>
149
    </head>
150
    <body></body>
151
    </html>';
152
153
    /**
154
     * This is a stub XHTML document.
155
     *
156
     * Since XHTML is an XML format, you should use XML functions with this document
157
     * fragment. For example, you should use {@link xml()}, {@link innerXML()}, and
158
     * {@link writeXML()}.
159
     *
160
     * This can be passed into {@link qp()} to begin a new basic HTML document.
161
     *
162
     * Example:
163
     *
164
     * @code
165
     * $qp = qp(QueryPath::XHTML_STUB); // Creates a new XHTML document
166
     * $qp->writeXML(); // Writes the document as well-formed XHTML.
167
     * @endcode
168
     * @since 2.0
169
     */
170
    public const XHTML_STUB = '<?xml version="1.0"?>
171
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
172
  <html xmlns="http://www.w3.org/1999/xhtml">
173
  <head>
174
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
175
  <title>Untitled</title>
176
  </head>
177
  <body></body>
178
  </html>';
179
180
181
    /**
182
     * @param null $document
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $document is correct as it would always require null to be passed?
Loading history...
183
     * @param null $selector
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $selector is correct as it would always require null to be passed?
Loading history...
184
     * @param array $options
185
     * @return mixed|\QueryPath\DOMQuery
186
     */
187
    public static function with($document = NULL, $selector = NULL, array $options = [])
188
    {
189
        $qpClass = $options['QueryPath_class'] ?? '\QueryPath\DOMQuery';
190
191
        return new $qpClass($document, $selector, $options);
192
    }
193
194
    public static function withXML($source = NULL, $selector = NULL, array $options = [])
195
    {
196
        $options += [
197
            'use_parser' => 'xml',
198
        ];
199
200
        return self::with($source, $selector, $options);
201
    }
202
203
    public static function withHTML($source = NULL, $selector = NULL, array $options = [])
204
    {
205
        // Need a way to force an HTML parse instead of an XML parse when the
206
        // doctype is XHTML, since many XHTML documents are not valid XML
207
        // (because of coding errors, not by design).
208
209
        $options += [
210
            'ignore_parser_warnings' => true,
211
            'convert_to_encoding'    => 'ISO-8859-1',
212
            'convert_from_encoding'  => 'auto',
213
            //'replace_entities' => TRUE,
214
            'use_parser'             => 'html',
215
            // This is stripping actually necessary low ASCII.
216
            //'strip_low_ascii' => TRUE,
217
        ];
218
219
        return @self::with($source, $selector, $options);
220
    }
221
222
    /**
223
     * Parse HTML5 documents.
224
     *
225
     * This uses HTML5-PHP to parse the document. In actuality, this parser does
226
     * a fine job with pre-HTML5 documents in most cases, though really old HTML
227
     * (like 2.0) may have some substantial quirks.
228
     *
229
     * <b>Supported Options</b>
230
     * Any options supported by HTML5-PHP are allowed here. Additionally, the
231
     * following options have meaning to QueryPath.
232
     * - QueryPath_class
233
     *
234
     *
235
     * @param mixed $source
236
     *   A document as an HTML string, or a path/URL. For compatibility with
237
     *   existing functions, a DOMDocument, SimpleXMLElement, DOMNode or array
238
     *   of DOMNodes will be passed through as well. However, these types are not
239
     *   validated in any way.
240
     *
241
     * @param string $selector
242
     *   A CSS3 selector.
243
     *
244
     * @param array $options
245
     *   An associative array of options, which is passed on into HTML5-PHP. Note
246
     *   that the standard QueryPath options may be ignored for this function,
247
     *   since it uses a different parser.
248
     *
249
     * @return QueryPath
250
     */
251
    public static function withHTML5($source = NULL, $selector = NULL, $options = [])
252
    {
253
        $qpClass = $options['QueryPath_class'] ?? '\QueryPath\DOMQuery';
254
255
        if (is_string($source)) {
256
            $html5 = new HTML5();
257
            if (strpos($source, '<') !== false && strpos($source, '>') !== false) {
258
                $source = $html5->loadHTML($source);
259
            } else {
260
                $source = $html5->load($source);
261
            }
262
        }
263
264
        $qp = new $qpClass($source, $selector, $options);
265
266
        return $qp;
267
    }
268
269
    /**
270
     * Enable one or more extensions.
271
     *
272
     * Extensions provide additional features to QueryPath. To enable and
273
     * extension, you can use this method.
274
     *
275
     * In this example, we enable the QPTPL extension:
276
     *
277
     * @code
278
     * <?php
279
     * QueryPath::enable('\QueryPath\QPTPL');
280
     * ?>
281
     * @endcode
282
     *
283
     * Note that the name is a fully qualified class name.
284
     *
285
     * We can enable more than one extension at a time like this:
286
     *
287
     * @code
288
     * <?php
289
     * $extensions = array('\QueryPath\QPXML', '\QueryPath\QPDB');
290
     * QueryPath::enable($extensions);
291
     * ?>
292
     * @endcode
293
     *
294
     * @attention If you are not using an autoloader, you will need to
295
     * manually `require` or `include` the files that contain the
296
     * extensions.
297
     *
298
     * @param mixed $extensionNames
299
     *   The name of an extension or an array of extension names.
300
     *   QueryPath assumes that these are extension class names,
301
     *   and attempts to register these as QueryPath extensions.
302
     */
303
    public static function enable($extensionNames): void
304
    {
305
        if (is_array($extensionNames)) {
306
            foreach ($extensionNames as $extension) {
307
                ExtensionRegistry::extend($extension);
308
            }
309
        } else {
310
            ExtensionRegistry::extend($extensionNames);
311
        }
312
    }
313
314
    /**
315
     * Get a list of all of the enabled extensions.
316
     *
317
     * This example dumps a list of extensions to standard output:
318
     *
319
     * @code
320
     * <?php
321
     * $extensions = QueryPath::enabledExtensions();
322
     * print_r($extensions);
323
     * ?>
324
     * @endcode
325
     *
326
     * @return array
327
     *   An array of extension names.
328
     *
329
     * @see QueryPath::ExtensionRegistry
330
     */
331
    public static function enabledExtensions() : array
332
    {
333
        return ExtensionRegistry::extensionNames();
334
    }
335
336
337
    /**
338
     * A static function for transforming data into a Data URL.
339
     *
340
     * This can be used to create Data URLs for injection into CSS, JavaScript, or other
341
     * non-XML/HTML content. If you are working with QP objects, you may want to use
342
     * dataURL() instead.
343
     *
344
     * @param mixed $data
345
     *    The contents to inject as the data. The value can be any one of the following:
346
     *    - A URL: If this is given, then the subsystem will read the content from that URL. THIS
347
     *    MUST BE A FULL URL, not a relative path.
348
     *    - A string of data: If this is given, then the subsystem will encode the string.
349
     *    - A stream or file handle: If this is given, the stream's contents will be encoded
350
     *    and inserted as data.
351
     *    (Note that we make the assumption here that you would never want to set data to be
352
     *    a URL. If this is an incorrect assumption, file a bug.)
353
     * @param string $mime
354
     *    The MIME type of the document.
355
     * @param resource $context
356
     *    A valid context. Use this only if you need to pass a stream context. This is only necessary
357
     *    if $data is a URL. (See {@link stream_context_create()}).
358
     * @return string An encoded data URL.
359
     */
360
    public static function encodeDataURL($data, $mime = 'application/octet-stream', $context = NULL) : string
361
    {
362
        if (is_resource($data)) {
363
            $data = stream_get_contents($data);
364
        } elseif (filter_var($data, FILTER_VALIDATE_URL)) {
365
            $data = file_get_contents($data, false, $context);
366
        }
367
368
        $encoded = base64_encode($data);
369
370
        return 'data:' . $mime . ';base64,' . $encoded;
371
    }
372
373
}
374