PluginProbe
Friends / 4.3.2
Friends v4.3.2
4.3.2 4.3.1 4.3.0 4.2.2 4.2.1 4.2.0 4.1.0 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.1 All 88 releases
← All changes | libs/Mf2/Parser.php +1103 -986 2.8.14.3.2 View file →
@@ -6,9 +6,9 @@
6 6 use DOMElement;
7 7 use DOMXPath;
8 8 use DOMNode;
9 9 use DOMNodeList;
10 -use \Exception;
10 +use Exception;
11 11 use SplObjectStorage;
12 12 use stdClass;
13 13
14 14 /**
@@ -36,15 +36,15 @@
36 36 * "rels": {}
37 37 * }
38 38 *
39 39 * @param string|DOMDocument $input The HTML string or DOMDocument object to parse
40 - * @param string $url The URL the input document was found at, for relative URL resolution
41 - * @param bool $convertClassic whether or not to convert classic microformats
40 + * @param string $url The URL the input document was found at, for relative URL resolution
41 + * @param bool $convertClassic whether or not to convert classic microformats
42 42 * @return array Canonical MF2 array structure
43 43 */
44 -function parse( $input, $url = null, $convertClassic = true ) {
45 - $parser = new Parser( $input, $url );
46 - return $parser->parse( $convertClassic );
44 +function parse($input, $url = null, $convertClassic = true) {
45 + $parser = new Parser($input, $url);
46 + return $parser->parse($convertClassic);
47 47 }
48 48
49 49 /**
50 50 * Fetch microformats2
@@ -57,49 +57,44 @@
57 57 * h-entry resulting in a 410 Gone page with a stub h-entry explaining the reason for deletion). Look in $curlInfo['http_code']
58 58 * for the actual value.
59 59 *
60 60 * @param string $url The URL to fetch
61 - * @param bool $convertClassic (optional, default true) whether or not to convert classic microformats
62 - * @param array $curlInfo (optional) the results of curl_getinfo will be placed in this variable for debugging
61 + * @param bool $convertClassic (optional, default true) whether or not to convert classic microformats
62 + * @param &array $curlInfo (optional) the results of curl_getinfo will be placed in this variable for debugging
63 63 * @return array|null canonical microformats2 array structure on success, null on failure
64 64 */
65 -function fetch( $url, $convertClassic = true, &$curlInfo = null ) {
66 - // Friends modification: use wp_safe_remote_get() instead of curl directly.
67 - $response = wp_safe_remote_get(
68 - $url,
69 - array(
70 - 'timeout' => 20,
71 - 'redirection' => 5,
72 - 'headers' => array(
73 - 'Accept: text/html',
74 - ),
75 - )
76 - );
77 - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
78 - return null;
79 - }
80 - $html = wp_remote_retrieve_body( $response );
81 - $headers = wp_remote_retrieve_headers( $response );
65 +function fetch($url, $convertClassic = true, &$curlInfo=null) {
66 + $ch = curl_init();
67 + curl_setopt($ch, CURLOPT_URL, $url);
68 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
69 + curl_setopt($ch, CURLOPT_HEADER, 0);
70 + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
71 + curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
72 + curl_setopt($ch, CURLOPT_HTTPHEADER, array(
73 + 'Accept: text/html'
74 + ));
75 + $html = curl_exec($ch);
76 + $info = $curlInfo = curl_getinfo($ch);
77 + curl_close($ch);
82 78
83 - if ( stripos( $headers['content-type'], 'html' ) === false ) {
79 + if (strpos(strtolower($info['content_type']), 'html') === false) {
84 80 // The content was not delivered as HTML, do not attempt to parse it.
85 81 return null;
86 82 }
87 83
88 - // ensure the final URL is used to resolve relative URLs
89 - $url = $response['http_response']->get_response_object()->url;
90 - // end modification.
91 - return parse( $html, $url, $convertClassic );
84 + # ensure the final URL is used to resolve relative URLs
85 + $url = $info['url'];
86 +
87 + return parse($html, $url, $convertClassic);
92 88 }
93 89
94 90 /**
95 91 * Unicode to HTML Entities
96 - *
97 92 * @param string $input String containing characters to convert into HTML entities
98 93 * @return string
99 94 */
100 -function unicodeToHtmlEntities( $input ) {
101 - return \mb_convert_encoding( $input, 'HTML-ENTITIES', \mb_detect_encoding( $input ) );
95 +function unicodeToHtmlEntities($input) {
96 + return mb_convert_encoding($input, 'HTML-ENTITIES', mb_detect_encoding($input));
102 97 }
103 98
104 99 /**
105 100 * Collapse Whitespace
@@ -110,17 +105,17 @@
110 105 * @deprecated since v0.2.3
111 106 * @param string $str
112 107 * @return string
113 108 */
114 -function collapseWhitespace( $str ) {
115 - return preg_replace( '/[\s|\n]+/', ' ', $str );
109 +function collapseWhitespace($str) {
110 + return preg_replace('/[\s|\n]+/', ' ', $str);
116 111 }
117 112
118 -function unicodeTrim( $str ) {
113 +function unicodeTrim($str) {
119 114 // this is cheating. TODO: find a better way if this causes any problems
120 - $str = str_replace( \mb_convert_encoding( ' ', 'UTF-8', 'HTML-ENTITIES' ), ' ', $str );
121 - $str = preg_replace( '/^\s+/', '', $str );
122 - return preg_replace( '/\s+$/', '', $str );
115 + $str = str_replace(mb_convert_encoding(' ', 'UTF-8', 'HTML-ENTITIES'), ' ', $str);
116 + $str = preg_replace('/^\s+/', '', $str);
117 + return preg_replace('/\s+$/', '', $str);
123 118 }
124 119
125 120 /**
126 121 * Microformat Name From Class string
@@ -131,19 +126,19 @@
131 126 * @param string $class A space delimited list of classnames
132 127 * @param string $prefix The prefix to look for
133 128 * @return string|array The prefixed name of the first microfomats class found or false
134 129 */
135 -function mfNamesFromClass( $class, $prefix = 'h-' ) {
136 - $class = str_replace( array( ' ', ' ', "\n" ), ' ', $class );
137 - $classes = explode( ' ', $class );
138 - $classes = preg_grep( '#^(h|p|u|dt|e)-([a-z0-9]+-)?[a-z]+(-[a-z]+)*$#', $classes );
130 +function mfNamesFromClass($class, $prefix='h-') {
131 + $class = str_replace(array(' ', ' ', "\n"), ' ', $class);
132 + $classes = explode(' ', $class);
133 + $classes = preg_grep('#^(h|p|u|dt|e)-([a-z0-9]+-)?[a-z]+(-[a-z]+)*$#', $classes);
139 134 $matches = array();
140 135
141 - foreach ( $classes as $classname ) {
136 + foreach ($classes as $classname) {
142 137 $compare_classname = ' ' . $classname;
143 138 $compare_prefix = ' ' . $prefix;
144 - if ( strstr( $compare_classname, $compare_prefix ) !== false && ( $compare_classname != $compare_prefix ) ) {
145 - $matches[] = ( $prefix === 'h-' ) ? $classname : substr( $classname, strlen( $prefix ) );
139 + if (strstr($compare_classname, $compare_prefix) !== false && ($compare_classname != $compare_prefix)) {
140 + $matches[] = ($prefix === 'h-') ? $classname : substr($classname, strlen($prefix));
146 141 }
147 142 }
148 143
149 144 return $matches;
@@ -149,8 +144,17 @@
149 144 return $matches;
150 145 }
151 146
152 147 /**
148 + * Registered with the XPath object and used within XPaths for finding root elements.
149 + * @param string $class
150 + * @return bool
151 + */
152 +function classHasMf2RootClassname($class) {
153 + return count(mfNamesFromClass($class, 'h-')) > 0;
154 +}
155 +
156 +/**
153 157 * Get Nested µf Property Name From Class
154 158 *
155 159 * Returns all the p-, u-, dt- or e- prefixed classnames it finds in a
156 160 * space-separated string.
@@ -157,25 +161,21 @@
157 161 *
158 162 * @param string $class
159 163 * @return array
160 164 */
161 -function nestedMfPropertyNamesFromClass( $class ) {
162 - $prefixes = array( 'p-', 'u-', 'dt-', 'e-' );
165 +function nestedMfPropertyNamesFromClass($class) {
166 + $prefixes = array('p-', 'u-', 'dt-', 'e-');
163 167 $propertyNames = array();
164 168
165 - $class = str_replace( array( ' ', ' ', "\n" ), ' ', $class );
166 - foreach ( explode( ' ', $class ) as $classname ) {
167 - foreach ( $prefixes as $prefix ) {
168 - // Check if $classname is a valid property classname for $prefix.
169 - if ( \mb_substr( $classname, 0, \mb_strlen( $prefix ) ) == $prefix && $classname != $prefix ) {
170 - $propertyName = \mb_substr( $classname, \mb_strlen( $prefix ) );
171 - $propertyNames[ $propertyName ][] = $prefix;
172 - }
169 + foreach ($prefixes as $prefix) {
170 + $classes = mfNamesFromClass($class, $prefix);
171 + foreach ($classes as $property) {
172 + $propertyNames[$property][] = $prefix;
173 173 }
174 174 }
175 175
176 - foreach ( $propertyNames as $property => $prefixes ) {
177 - $propertyNames[ $property ] = array_unique( $prefixes );
176 + foreach ($propertyNames as $property => $prefixes) {
177 + $propertyNames[$property] = array_unique($prefixes);
178 178 }
179 179
180 180 return $propertyNames;
181 181 }
@@ -183,64 +183,64 @@
183 183 /**
184 184 * Wraps mfNamesFromClass to handle an element as input (common)
185 185 *
186 186 * @param DOMElement $e The element to get the classname for
187 - * @param string $prefix The prefix to look for
187 + * @param string $prefix The prefix to look for
188 188 * @return mixed See return value of mf2\Parser::mfNameFromClass()
189 189 */
190 -function mfNamesFromElement( \DOMElement $e, $prefix = 'h-' ) {
191 - $class = $e->getAttribute( 'class' );
192 - return mfNamesFromClass( $class, $prefix );
190 +function mfNamesFromElement(\DOMElement $e, $prefix = 'h-') {
191 + $class = $e->getAttribute('class');
192 + return mfNamesFromClass($class, $prefix);
193 193 }
194 194
195 195 /**
196 196 * Wraps nestedMfPropertyNamesFromClass to handle an element as input
197 197 */
198 -function nestedMfPropertyNamesFromElement( \DOMElement $e ) {
199 - $class = $e->getAttribute( 'class' );
200 - return nestedMfPropertyNamesFromClass( $class );
198 +function nestedMfPropertyNamesFromElement(\DOMElement $e) {
199 + $class = $e->getAttribute('class');
200 + return nestedMfPropertyNamesFromClass($class);
201 201 }
202 202
203 203 /**
204 204 * Converts various time formats to HH:MM
205 - *
206 205 * @param string $time The time to convert
207 206 * @return string
208 207 */
209 -function convertTimeFormat( $time ) {
208 +function convertTimeFormat($time) {
210 209 $hh = $mm = $ss = '';
211 - preg_match( '/(\d{1,2}):?(\d{2})?:?(\d{2})?(a\.?m\.?|p\.?m\.?)?/i', $time, $matches );
210 + preg_match('/(\d{1,2}):?(\d{2})?:?(\d{2})?(a\.?m\.?|p\.?m\.?)?/i', $time, $matches);
212 211
213 212 // If no am/pm is specified:
214 - if ( empty( $matches[4] ) ) {
213 + if (empty($matches[4])) {
215 214 return $time;
216 215 } else {
217 216 // Otherwise, am/pm is specified.
218 - $meridiem = strtolower( str_replace( '.', '', $matches[4] ) );
217 + $meridiem = strtolower(str_replace('.', '', $matches[4]));
219 218
220 219 // Hours.
221 220 $hh = $matches[1];
222 221
223 222 // Add 12 to hours if pm applies.
224 - if ( $meridiem == 'pm' && ( $hh < 12 ) ) {
223 + if ($meridiem == 'pm' && ($hh < 12)) {
225 224 $hh += 12;
226 225 }
227 226
228 - $hh = str_pad( $hh, 2, '0', STR_PAD_LEFT );
227 + $hh = str_pad($hh, 2, '0', STR_PAD_LEFT);
229 228
230 229 // Minutes.
231 - $mm = ( empty( $matches[2] ) ) ? '00' : $matches[2];
230 + $mm = (empty($matches[2]) ) ? '00' : $matches[2];
232 231
233 232 // Seconds, only if supplied.
234 - if ( ! empty( $matches[3] ) ) {
233 + if (!empty($matches[3])) {
235 234 $ss = $matches[3];
236 235 }
237 236
238 - if ( empty( $ss ) ) {
239 - return sprintf( '%s:%s', $hh, $mm );
240 - } else {
241 - return sprintf( '%s:%s:%s', $hh, $mm, $ss );
237 + if (empty($ss)) {
238 + return sprintf('%s:%s', $hh, $mm);
242 239 }
240 + else {
241 + return sprintf('%s:%s:%s', $hh, $mm, $ss);
242 + }
243 243 }
244 244 }
245 245
246 246 /**
@@ -246,20 +246,19 @@
246 246 /**
247 247 * Normalize an ordinal date to YYYY-MM-DD
248 248 * This function should only be called after validating the $dtValue
249 249 * matches regex \d{4}-\d{2}
250 - *
251 250 * @param string $dtValue
252 251 * @return string
253 252 */
254 -function normalizeOrdinalDate( $dtValue ) {
255 - list($year, $day) = explode( '-', $dtValue, 2 );
256 - $day = intval( $day );
257 - if ( $day < 367 && $day > 0 ) {
258 - $date = \DateTime::createFromFormat( 'Y-z', $dtValue );
259 - $date->modify( '-1 day' ); // 'z' format is zero-based so need to adjust
260 - if ( $date->format( 'Y' ) === $year ) {
261 - return $date->format( 'Y-m-d' );
253 +function normalizeOrdinalDate($dtValue) {
254 + list($year, $day) = explode('-', $dtValue, 2);
255 + $day = intval($day);
256 + if ($day < 367 && $day > 0) {
257 + $date = \DateTime::createFromFormat('Y-z', $dtValue);
258 + $date->modify('-1 day'); # 'z' format is zero-based so need to adjust
259 + if ($date->format('Y') === $year) {
260 + return $date->format('Y-m-d');
262 261 }
263 262 }
264 263 return '';
265 264 }
@@ -265,16 +264,15 @@
265 264 }
266 265
267 266 /**
268 267 * If a date value has a timezone offset, normalize it.
269 - *
270 268 * @param string $dtValue
271 269 * @return string isolated, normalized TZ offset for implied TZ for other dt- properties
272 270 */
273 -function normalizeTimezoneOffset( &$dtValue ) {
274 - preg_match( '/Z|[+-]\d{1,2}:?(\d{2})?$/i', $dtValue, $matches );
271 +function normalizeTimezoneOffset(&$dtValue) {
272 + preg_match('/Z|[+-]\d{1,2}:?(\d{2})?$/i', $dtValue, $matches);
275 273
276 - if ( empty( $matches ) ) {
274 + if (empty($matches)) {
277 275 return null;
278 276 }
279 277
280 278 $timezoneOffset = null;
@@ -279,42 +277,33 @@
279 277
280 278 $timezoneOffset = null;
281 279
282 280 if ( $matches[0] != 'Z' ) {
283 - $timezoneString = str_replace( ':', '', $matches[0] );
284 - $plus_minus = substr( $timezoneString, 0, 1 );
285 - $timezoneOffset = substr( $timezoneString, 1 );
286 - if ( strlen( $timezoneOffset ) <= 2 ) {
281 + $timezoneString = str_replace(':', '', $matches[0]);
282 + $plus_minus = substr($timezoneString, 0, 1);
283 + $timezoneOffset = substr($timezoneString, 1);
284 + if ( strlen($timezoneOffset) <= 2 ) {
287 285 $timezoneOffset .= '00';
288 286 }
289 - $timezoneOffset = str_pad( $timezoneOffset, 4, 0, STR_PAD_LEFT );
287 + $timezoneOffset = str_pad($timezoneOffset, 4, 0, STR_PAD_LEFT);
290 288 $timezoneOffset = $plus_minus . $timezoneOffset;
291 - $dtValue = preg_replace( '/Z?[+-]\d{1,2}:?(\d{2})?$/i', $timezoneOffset, $dtValue );
289 + $dtValue = preg_replace('/Z?[+-]\d{1,2}:?(\d{2})?$/i', $timezoneOffset, $dtValue);
292 290 }
293 291
294 292 return $timezoneOffset;
295 293 }
296 294
297 -function applySrcsetUrlTransformation( $srcset, $transformation ) {
298 - return implode(
299 - ', ',
300 - array_filter(
301 - array_map(
302 - function ( $srcsetPart ) use ( $transformation ) {
303 - $parts = explode( " \t\n\r\0\x0B", trim( $srcsetPart ), 2 );
304 - $parts[0] = rtrim( $parts[0] );
295 +function applySrcsetUrlTransformation($srcset, $transformation) {
296 + return implode(', ', array_filter(array_map(function ($srcsetPart) use ($transformation) {
297 + $parts = explode(" \t\n\r\0\x0B", trim($srcsetPart), 2);
298 + $parts[0] = rtrim($parts[0]);
305 299
306 - if ( empty( $parts[0] ) ) {
307 - return false; }
300 + if (empty($parts[0])) { return false; }
308 301
309 - $parts[0] = call_user_func( $transformation, $parts[0] );
302 + $parts[0] = call_user_func($transformation, $parts[0]);
310 303
311 - return $parts[0] . ( empty( $parts[1] ) ? '' : ' ' . $parts[1] );
312 - },
313 - explode( ',', trim( $srcset ) )
314 - )
315 - )
316 - );
304 + return $parts[0] . (empty($parts[1]) ? '' : ' ' . $parts[1]);
305 + }, explode(',', trim($srcset)))));
317 306 }
318 307
319 308 /**
320 309 * Microformats2 Parser
@@ -352,9 +341,8 @@
352 341 public $enableAlternates = false;
353 342
354 343 /**
355 344 * Elements upgraded to mf2 during backcompat
356 - *
357 345 * @var SplObjectStorage
358 346 */
359 347 protected $upgraded;
360 348
@@ -359,9 +347,8 @@
359 347 protected $upgraded;
360 348
361 349 /**
362 350 * Whether to convert classic microformats
363 - *
364 351 * @var bool
365 352 */
366 353 public $convertClassic;
367 354
@@ -368,38 +355,50 @@
368 355 /**
369 356 * Constructor
370 357 *
371 358 * @param DOMDocument|string $input The data to parse. A string of HTML or a DOMDocument
372 - * @param string $url The URL of the parsed document, for relative URL resolution
373 - * @param boolean $jsonMode Whether or not to use a stdClass instance for an empty `rels` dictionary. This breaks PHP looping over rels, but allows the output to be correctly serialized as JSON.
359 + * @param string $url The URL of the parsed document, for relative URL resolution
360 + * @param boolean $jsonMode Whether or not to use a stdClass instance for an empty `rels` dictionary. This breaks PHP looping over rels, but allows the output to be correctly serialized as JSON.
374 361 */
375 - public function __construct( $input, $url = null, $jsonMode = false ) {
376 - libxml_use_internal_errors( true );
377 - set_error_handler( '__return_null' );
378 - if ( is_string( $input ) ) {
379 - if ( class_exists( 'Masterminds\\HTML5' ) ) {
380 - $doc = new \Masterminds\HTML5( array( 'disable_html_ns' => true ) );
381 - $doc = $doc->loadHTML( $input );
362 + public function __construct($input, $url = null, $jsonMode = false) {
363 + $emptyDocDefault = '<html><body></body></html>';
364 + libxml_use_internal_errors(true);
365 + if (is_string($input)) {
366 + if (empty($input)) {
367 + $input = $emptyDocDefault;
368 + }
369 +
370 + if (class_exists('Masterminds\\HTML5')) {
371 + $doc = new \Masterminds\HTML5(array('disable_html_ns' => true));
372 + $doc = $doc->loadHTML($input);
382 373 } else {
383 374 $doc = new DOMDocument();
384 - $doc->loadHTML( unicodeToHtmlEntities( $input ) );
375 + @$doc->loadHTML(unicodeToHtmlEntities($input), \LIBXML_NOWARNING);
385 376 }
386 - } elseif ( is_a( $input, 'DOMDocument' ) ) {
377 + } elseif (is_a($input, 'DOMDocument')) {
387 378 $doc = clone $input;
388 379 } else {
389 380 $doc = new DOMDocument();
390 - $doc->loadHTML( '' );
381 + @$doc->loadHTML($emptyDocDefault);
391 382 }
392 - restore_error_handler();
393 383
394 - $this->xpath = new DOMXPath( $doc );
384 + // Create an XPath object and allow some PHP functions to be used within XPath queries.
385 + $this->xpath = new DOMXPath($doc);
386 + $this->xpath->registerNamespace('php', 'http://php.net/xpath');
387 + $this->xpath->registerPhpFunctions('\\Friends\\Mf2\\classHasMf2RootClassname');
395 388
396 389 $baseurl = $url;
397 - foreach ( $this->xpath->query( '//base[@href]' ) as $base ) {
398 - $baseElementUrl = $base->getAttribute( 'href' );
390 + foreach ($this->xpath->query('//base[@href]') as $base) {
391 + $baseElementUrl = $base->getAttribute('href');
399 392
400 - if ( parse_url( $baseElementUrl, PHP_URL_SCHEME ) === null ) {
401 - $baseurl = resolveUrl( $url, $baseElementUrl );
393 + if (parse_url($baseElementUrl, PHP_URL_SCHEME) === null) {
394 + /* The base element URL is relative to the document URL.
395 + *
396 + * :/
397 + *
398 + * Perhaps the author was high? */
399 +
400 + $baseurl = resolveUrl($url, $baseElementUrl);
402 401 } else {
403 402 $baseurl = $baseElementUrl;
404 403 }
405 404 break;
@@ -405,10 +404,10 @@
405 404 break;
406 405 }
407 406
408 407 // Ignore <template> elements as per the HTML5 spec
409 - foreach ( $this->xpath->query( '//template' ) as $templateEl ) {
410 - $templateEl->parentNode->removeChild( $templateEl );
408 + foreach ($this->xpath->query('//template') as $templateEl) {
409 + $templateEl->parentNode->removeChild($templateEl);
411 410 }
412 411
413 412 $this->baseurl = $baseurl;
414 413 $this->doc = $doc;
@@ -416,33 +415,31 @@
416 415 $this->upgraded = new SplObjectStorage();
417 416 $this->jsonMode = $jsonMode;
418 417 }
419 418
420 - private function elementPrefixParsed( \DOMElement $e, $prefix ) {
421 - if ( ! $this->parsed->contains( $e ) ) {
422 - $this->parsed->attach( $e, array() );
423 - }
419 + private function elementPrefixParsed(\DOMElement $e, $prefix) {
420 + if (!$this->parsed->offsetExists($e))
421 + $this->parsed->offsetSet($e, array());
424 422
425 - $prefixes = $this->parsed[ $e ];
423 + $prefixes = $this->parsed[$e];
426 424 $prefixes[] = $prefix;
427 - $this->parsed[ $e ] = $prefixes;
425 + $this->parsed[$e] = $prefixes;
428 426 }
429 427
430 428 /**
431 429 * Determine if the element has already been parsed
432 - *
433 430 * @param DOMElement $e
434 - * @param string $prefix
431 + * @param string $prefix
435 432 * @return bool
436 433 */
437 - private function isElementParsed( \DOMElement $e, $prefix ) {
438 - if ( ! $this->parsed->contains( $e ) ) {
434 + private function isElementParsed(\DOMElement $e, $prefix) {
435 + if (!$this->parsed->offsetExists($e)) {
439 436 return false;
440 437 }
441 438
442 - $prefixes = $this->parsed[ $e ];
439 + $prefixes = $this->parsed[$e];
443 440
444 - if ( ! in_array( $prefix, $prefixes ) ) {
441 + if (!in_array($prefix, $prefixes)) {
445 442 return false;
446 443 }
447 444
448 445 return true;
@@ -449,16 +446,15 @@
449 446 }
450 447
451 448 /**
452 449 * Determine if the element's specified property has already been upgraded during backcompat
453 - *
454 450 * @param DOMElement $el
455 - * @param string $property
451 + * @param string $property
456 452 * @return bool
457 453 */
458 - private function isElementUpgraded( \DOMElement $el, $property ) {
459 - if ( $this->upgraded->contains( $el ) ) {
460 - if ( in_array( $property, $this->upgraded[ $el ] ) ) {
454 + private function isElementUpgraded(\DOMElement $el, $property) {
455 + if ( $this->upgraded->offsetExists($el) ) {
456 + if ( in_array($property, $this->upgraded[$el]) ) {
461 457 return true;
462 458 }
463 459 }
464 460
@@ -464,111 +460,129 @@
464 460
465 461 return false;
466 462 }
467 463
468 - private function resolveChildUrls( DOMElement $el ) {
469 - $hyperlinkChildren = $this->xpath->query( './/*[@src or @href or @data]', $el );
464 + private function resolveChildUrls(DOMElement $el) {
465 + $hyperlinkChildren = $this->xpath->query('.//*[@src or @href or @data]', $el);
470 466
471 - foreach ( $hyperlinkChildren as $child ) {
472 - if ( $child->hasAttribute( 'href' ) ) {
473 - $child->setAttribute( 'href', $this->resolveUrl( $child->getAttribute( 'href' ) ) );
474 - }
475 - if ( $child->hasAttribute( 'src' ) ) {
476 - $child->setAttribute( 'src', $this->resolveUrl( $child->getAttribute( 'src' ) ) );
477 - }
478 - if ( $child->hasAttribute( 'srcset' ) ) {
479 - $child->setAttribute( 'srcset', applySrcsetUrlTransformation( $child->getAttribute( 'href' ), array( $this, 'resolveUrl' ) ) );
480 - }
481 - if ( $child->hasAttribute( 'data' ) ) {
482 - $child->setAttribute( 'data', $this->resolveUrl( $child->getAttribute( 'data' ) ) );
483 - }
467 + foreach ($hyperlinkChildren as $child) {
468 + if ($child->hasAttribute('href'))
469 + $child->setAttribute('href', $this->resolveUrl($child->getAttribute('href')));
470 + if ($child->hasAttribute('src'))
471 + $child->setAttribute('src', $this->resolveUrl($child->getAttribute('src')));
472 + if ($child->hasAttribute('srcset'))
473 + $child->setAttribute('srcset', applySrcsetUrlTransformation($child->getAttribute('href'), array($this, 'resolveUrl')));
474 + if ($child->hasAttribute('data'))
475 + $child->setAttribute('data', $this->resolveUrl($child->getAttribute('data')));
484 476 }
485 477 }
486 478
487 479 /**
488 480 * The following two methods implements plain text parsing.
489 - *
490 481 * @param DOMElement $element
491 - * @param bool $implied
482 + * @param bool $implied
492 483 * @see https://wiki.zegnat.net/media/textparsing.html
493 484 **/
494 - public function textContent( DOMElement $element, $implied = false ) {
485 + public function textContent(DOMElement $element, $implied=false)
486 + {
495 487 return preg_replace(
496 - '/(^[\t\n\f\r ]+| +(?=\n)|(?<=\n) +| +(?= )|[\t\n\f\r ]+$)/',
497 - '',
498 - $this->elementToString( $element, $implied )
488 + '/(^[\t\n\f\r ]+| +(?=\n)|(?<=\n) +| +(?= )|[\t\n\f\r ]+$)/',
489 + '',
490 + $this->elementToString($element, $implied)
499 491 );
500 492 }
501 - private function elementToString( DOMElement $input, $implied = false ) {
493 + private function elementToString(DOMElement $input, $implied=false)
494 + {
502 495 $output = '';
503 - foreach ( $input->childNodes as $child ) {
504 - if ( $child->nodeType === XML_TEXT_NODE ) {
505 - $output .= str_replace( array( "\t", "\n", "\r" ), ' ', $child->textContent );
506 - } elseif ( $child->nodeType === XML_ELEMENT_NODE ) {
507 - $tagName = strtoupper( $child->tagName );
508 - if ( in_array( $tagName, array( 'SCRIPT', 'STYLE' ) ) ) {
509 - continue;
510 - } elseif ( $tagName === 'IMG' ) {
511 - if ( $child->hasAttribute( 'alt' ) ) {
512 - $output .= ' ' . trim( $child->getAttribute( 'alt' ), "\t\n\f\r " ) . ' ';
513 - } elseif ( ! $implied && $child->hasAttribute( 'src' ) ) {
514 - $output .= ' ' . $this->resolveUrl( trim( $child->getAttribute( 'src' ), "\t\n\f\r " ) ) . ' ';
496 + foreach ($input->childNodes as $child) {
497 + if ($child->nodeType === XML_TEXT_NODE) {
498 + $output .= str_replace(array("\t", "\n", "\r") , ' ', $child->textContent);
499 + } else if ($child->nodeType === XML_ELEMENT_NODE) {
500 + $tagName = strtoupper($child->tagName);
501 + if (in_array($tagName, array('SCRIPT', 'STYLE'))) {
502 + continue;
503 + } else if ($tagName === 'IMG') {
504 + if ($child->hasAttribute('alt')) {
505 + $output .= ' ' . trim($child->getAttribute('alt'), "\t\n\f\r ") . ' ';
506 + } else if (!$implied && $child->hasAttribute('src')) {
507 + $output .= ' ' . $this->resolveUrl(trim($child->getAttribute('src'), "\t\n\f\r ")) . ' ';
508 + }
509 + } else if ($tagName === 'BR') {
510 + $output .= "\n";
511 + } else if ($tagName === 'P') {
512 + $output .= "\n" . $this->elementToString($child);
513 + } else {
514 + $output .= $this->elementToString($child);
515 + }
515 516 }
516 - } elseif ( $tagName === 'BR' ) {
517 - $output .= "\n";
518 - } elseif ( $tagName === 'P' ) {
519 - $output .= "\n" . $this->elementToString( $child );
520 - } else {
521 - $output .= $this->elementToString( $child );
522 - }
523 517 }
524 - }
525 518 return $output;
526 519 }
527 520
528 521 /**
522 + * Given an img property, parse its value and/or alt text
523 + * @param DOMElement $el
524 + * @access public
525 + * @return string|array
526 + */
527 + public function parseImg(DOMElement $el)
528 + {
529 + if ($el->hasAttribute('alt')) {
530 + return [
531 + 'value' => $this->resolveUrl( $el->getAttribute('src') ),
532 + 'alt' => $el->getAttribute('alt')
533 + ];
534 + }
535 + return $el->getAttribute('src');
536 + }
537 + /**
529 538 * This method parses the language of an element
530 - *
531 539 * @param DOMElement $el
532 540 * @access public
533 541 * @return string
534 542 */
535 - public function language( DOMElement $el ) {
543 + public function language(DOMElement $el)
544 + {
536 545 // element has a lang attribute; use it
537 - if ( $el->hasAttribute( 'lang' ) ) {
538 - return unicodeTrim( $el->getAttribute( 'lang' ) );
546 + if ($el->hasAttribute('lang')) {
547 + return unicodeTrim($el->getAttribute('lang'));
539 548 }
540 549
541 - if ( $el->tagName == 'html' ) {
550 + if ($el->tagName == 'html') {
542 551 // we're at the <html> element and no lang; check <meta> http-equiv Content-Language
543 - foreach ( $this->xpath->query( './/meta[@http-equiv]' ) as $node ) {
544 - if ( $node->hasAttribute( 'http-equiv' ) && $node->hasAttribute( 'content' ) && strtolower( $node->getAttribute( 'http-equiv' ) ) == 'content-language' ) {
545 - return unicodeTrim( $node->getAttribute( 'content' ) );
552 + foreach ( $this->xpath->query('.//meta[@http-equiv]') as $node )
553 + {
554 + if ($node->hasAttribute('http-equiv') && $node->hasAttribute('content') && strtolower($node->getAttribute('http-equiv')) == 'content-language') {
555 + return unicodeTrim($node->getAttribute('content'));
546 556 }
547 557 }
548 - } elseif ( $el->parentNode instanceof DOMElement ) {
558 + } elseif ($el->parentNode instanceof DOMElement) {
549 559 // check the parent node
550 - return $this->language( $el->parentNode );
560 + return $this->language($el->parentNode);
551 561 }
552 562
553 563 return '';
554 - } // end method language()
564 + } # end method language()
555 565
556 566 // TODO: figure out if this has problems with sms: and geo: URLs
557 - public function resolveUrl( $url ) {
567 + public function resolveUrl($url) {
568 + // If not a string then return.
569 + if (!is_string($url)){
570 + return $url;
571 + }
558 572 // If the URL is seriously malformed it’s probably beyond the scope of this
559 573 // parser to try to do anything with it.
560 - if ( parse_url( $url ) === false ) {
574 + if (parse_url($url) === false) {
561 575 return $url;
562 576 }
563 577
564 578 // per issue #40 valid URLs could have a space on either side
565 - $url = trim( $url );
579 + $url = trim($url);
566 580
567 - $scheme = parse_url( $url, PHP_URL_SCHEME );
581 + $scheme = parse_url($url, PHP_URL_SCHEME);
568 582
569 - if ( empty( $scheme ) and ! empty( $this->baseurl ) ) {
570 - return resolveUrl( $this->baseurl, $url );
583 + if (empty($scheme) and !empty($this->baseurl)) {
584 + return resolveUrl($this->baseurl, $url);
571 585 } else {
572 586 return $url;
573 587 }
574 588 }
@@ -579,34 +593,34 @@
579 593 * Parse value-class/value-title on an element, joining with $separator if
580 594 * there are multiple.
581 595 *
582 596 * @param \DOMElement $e
583 - * @param string $separator = '' if multiple value-title elements, join with this string
597 + * @param string $separator = '' if multiple value-title elements, join with this string
584 598 * @return string|null the parsed value or null if value-class or -title aren’t in use
585 599 */
586 - public function parseValueClassTitle( \DOMElement $e, $separator = '' ) {
587 - $valueClassElements = $this->xpath->query( './*[contains(concat(" ", @class, " "), " value ")]', $e );
600 + public function parseValueClassTitle(\DOMElement $e, $separator = '') {
601 + $valueClassElements = $this->xpath->query('./*[contains(concat(" ", normalize-space(@class), " "), " value ")]', $e);
588 602
589 - if ( $valueClassElements->length !== 0 ) {
603 + if ($valueClassElements->length !== 0) {
590 604 // Process value-class stuff
591 605 $val = '';
592 - foreach ( $valueClassElements as $el ) {
593 - $val .= $this->textContent( $el );
606 + foreach ($valueClassElements as $el) {
607 + $val .= $this->textContent($el);
594 608 }
595 609
596 - return unicodeTrim( $val );
610 + return unicodeTrim($val);
597 611 }
598 612
599 - $valueTitleElements = $this->xpath->query( './*[contains(concat(" ", @class, " "), " value-title ")]', $e );
613 + $valueTitleElements = $this->xpath->query('./*[contains(concat(" ", normalize-space(@class), " "), " value-title ")]', $e);
600 614
601 - if ( $valueTitleElements->length !== 0 ) {
615 + if ($valueTitleElements->length !== 0) {
602 616 // Process value-title stuff
603 617 $val = '';
604 - foreach ( $valueTitleElements as $el ) {
605 - $val .= $el->getAttribute( 'title' );
618 + foreach ($valueTitleElements as $el) {
619 + $val .= $el->getAttribute('title');
606 620 }
607 621
608 - return unicodeTrim( $val );
622 + return unicodeTrim($val);
609 623 }
610 624
611 625 // No value-title or -class in this element
612 626 return null;
@@ -618,27 +632,27 @@
618 632 * @param DOMElement $p The element to parse
619 633 * @return string The plaintext value of $p, dependant on type
620 634 * @todo Make this adhere to value-class
621 635 */
622 - public function parseP( \DOMElement $p ) {
623 - $classTitle = $this->parseValueClassTitle( $p, ' ' );
636 + public function parseP(\DOMElement $p) {
637 + $classTitle = $this->parseValueClassTitle($p, ' ');
624 638
625 - if ( $classTitle !== null ) {
639 + if ($classTitle !== null) {
626 640 return $classTitle;
627 641 }
628 642
629 - $this->resolveChildUrls( $p );
643 + $this->resolveChildUrls($p);
630 644
631 - if ( $p->tagName == 'img' and $p->hasAttribute( 'alt' ) ) {
632 - $pValue = $p->getAttribute( 'alt' );
633 - } elseif ( $p->tagName == 'area' and $p->hasAttribute( 'alt' ) ) {
634 - $pValue = $p->getAttribute( 'alt' );
635 - } elseif ( ( $p->tagName == 'abbr' or $p->tagName == 'link' ) and $p->hasAttribute( 'title' ) ) {
636 - $pValue = $p->getAttribute( 'title' );
637 - } elseif ( in_array( $p->tagName, array( 'data', 'input' ) ) and $p->hasAttribute( 'value' ) ) {
638 - $pValue = $p->getAttribute( 'value' );
645 + if ($p->tagName == 'img' and $p->hasAttribute('alt')) {
646 + $pValue = $p->getAttribute('alt');
647 + } elseif ($p->tagName == 'area' and $p->hasAttribute('alt')) {
648 + $pValue = $p->getAttribute('alt');
649 + } elseif (($p->tagName == 'abbr' or $p->tagName == 'link') and $p->hasAttribute('title')) {
650 + $pValue = $p->getAttribute('title');
651 + } elseif (in_array($p->tagName, array('data', 'input')) and $p->hasAttribute('value')) {
652 + $pValue = $p->getAttribute('value');
639 653 } else {
640 - $pValue = $this->textContent( $p );
654 + $pValue = $this->textContent($p);
641 655 }
642 656
643 657 return $pValue;
644 658 }
@@ -649,27 +663,33 @@
649 663 * @param DOMElement $u The element to parse
650 664 * @return string The plaintext value of $u, dependant on type
651 665 * @todo make this adhere to value-class
652 666 */
653 - public function parseU( \DOMElement $u ) {
654 - if ( ( $u->tagName == 'a' or $u->tagName == 'area' or $u->tagName == 'link' ) and $u->hasAttribute( 'href' ) ) {
655 - $uValue = $u->getAttribute( 'href' );
656 - } elseif ( in_array( $u->tagName, array( 'img', 'audio', 'video', 'source' ) ) and $u->hasAttribute( 'src' ) ) {
657 - $uValue = $u->getAttribute( 'src' );
658 - } elseif ( $u->tagName == 'video' and ! $u->hasAttribute( 'src' ) and $u->hasAttribute( 'poster' ) ) {
659 - $uValue = $u->getAttribute( 'poster' );
660 - } elseif ( $u->tagName == 'object' and $u->hasAttribute( 'data' ) ) {
661 - $uValue = $u->getAttribute( 'data' );
662 - } elseif ( ( $classTitle = $this->parseValueClassTitle( $u ) ) !== null ) {
667 + public function parseU(\DOMElement $u) {
668 + if (($u->tagName == 'a' or $u->tagName == 'area' or $u->tagName == 'link') and $u->hasAttribute('href')) {
669 + $uValue = $u->getAttribute('href');
670 + } elseif ( $u->tagName == 'img' and $u->hasAttribute('src') ) {
671 + $uValue = $this->parseImg($u);
672 + if ( is_array( $uValue ) && isset( $uValue['value'] ) ) {
673 + $uValue = $uValue['value'];
674 + }
675 + } elseif (in_array($u->tagName, array('audio', 'video', 'source', 'iframe')) and $u->hasAttribute('src')) {
676 + $uValue = $u->getAttribute('src');
677 + } elseif ($u->tagName == 'video' and !$u->hasAttribute('src') and $u->hasAttribute('poster')) {
678 + $uValue = $u->getAttribute('poster');
679 + } elseif ($u->tagName == 'object' and $u->hasAttribute('data')) {
680 + $uValue = $u->getAttribute('data');
681 + } elseif (($classTitle = $this->parseValueClassTitle($u)) !== null) {
663 682 $uValue = $classTitle;
664 - } elseif ( ( $u->tagName == 'abbr' or $u->tagName == 'link' ) and $u->hasAttribute( 'title' ) ) {
665 - $uValue = $u->getAttribute( 'title' );
666 - } elseif ( in_array( $u->tagName, array( 'data', 'input' ) ) and $u->hasAttribute( 'value' ) ) {
667 - $uValue = $u->getAttribute( 'value' );
683 + } elseif (($u->tagName == 'abbr' or $u->tagName == 'link') and $u->hasAttribute('title')) {
684 + $uValue = $u->getAttribute('title');
685 + } elseif (in_array($u->tagName, array('data', 'input')) and $u->hasAttribute('value')) {
686 + $uValue = $u->getAttribute('value');
668 687 } else {
669 - $uValue = $this->textContent( $u );
688 + $uValue = $this->textContent($u);
670 689 }
671 - return $this->resolveUrl( $uValue );
690 +
691 + return $this->resolveUrl($uValue);
672 692 }
673 693
674 694 /**
675 695 * Given an element with class="dt-*", get the value of the datetime as a php date object
@@ -674,54 +694,59 @@
674 694 /**
675 695 * Given an element with class="dt-*", get the value of the datetime as a php date object
676 696 *
677 697 * @param DOMElement $dt The element to parse
678 - * @param array $dates Array of dates processed so far
679 - * @param string $impliedTimezone
698 + * @param array $dates Array of dates processed so far
699 + * @param string $impliedTimezone
680 700 * @return string The datetime string found
681 701 */
682 - public function parseDT( \DOMElement $dt, &$dates = array(), &$impliedTimezone = null ) {
702 + public function parseDT(\DOMElement $dt, &$dates = array(), &$impliedTimezone = null) {
683 703 // Check for value-class pattern
684 - $valueClassChildren = $this->xpath->query( './*[contains(concat(" ", @class, " "), " value ") or contains(concat(" ", @class, " "), " value-title ")]', $dt );
704 + $valueClassChildren = $this->xpath->query('./*[contains(concat(" ", normalize-space(@class), " "), " value ") or contains(concat(" ", normalize-space(@class), " "), " value-title ")]', $dt);
685 705 $dtValue = false;
686 706
687 - if ( $valueClassChildren->length > 0 ) {
707 + if ($valueClassChildren->length > 0) {
688 708 // They’re using value-class
689 709 $dateParts = array();
690 710
691 - foreach ( $valueClassChildren as $e ) {
692 - if ( strstr( ' ' . $e->getAttribute( 'class' ) . ' ', ' value-title ' ) ) {
693 - $title = $e->getAttribute( 'title' );
694 - if ( ! empty( $title ) ) {
711 + foreach ($valueClassChildren as $e) {
712 + if (strstr(' ' . $e->getAttribute('class') . ' ', ' value-title ')) {
713 + $title = $e->getAttribute('title');
714 + if (!empty($title)) {
695 715 $dateParts[] = $title;
696 716 }
697 - } elseif ( $e->tagName == 'img' or $e->tagName == 'area' ) {
717 + }
718 + elseif ($e->tagName == 'img' or $e->tagName == 'area') {
698 719 // Use @alt
699 - $alt = $e->getAttribute( 'alt' );
700 - if ( ! empty( $alt ) ) {
720 + $alt = $e->getAttribute('alt');
721 + if (!empty($alt)) {
701 722 $dateParts[] = $alt;
702 723 }
703 - } elseif ( $e->tagName == 'data' ) {
724 + }
725 + elseif ($e->tagName == 'data') {
704 726 // Use @value, otherwise innertext
705 - $value = $e->hasAttribute( 'value' ) ? $e->getAttribute( 'value' ) : unicodeTrim( $e->nodeValue );
706 - if ( ! empty( $value ) ) {
727 + $value = $e->hasAttribute('value') ? $e->getAttribute('value') : unicodeTrim($e->nodeValue);
728 + if (!empty($value)) {
707 729 $dateParts[] = $value;
708 730 }
709 - } elseif ( $e->tagName == 'abbr' ) {
731 + }
732 + elseif ($e->tagName == 'abbr') {
710 733 // Use @title, otherwise innertext
711 - $title = $e->hasAttribute( 'title' ) ? $e->getAttribute( 'title' ) : unicodeTrim( $e->nodeValue );
712 - if ( ! empty( $title ) ) {
734 + $title = $e->hasAttribute('title') ? $e->getAttribute('title') : unicodeTrim($e->nodeValue);
735 + if (!empty($title)) {
713 736 $dateParts[] = $title;
714 737 }
715 - } elseif ( $e->tagName == 'del' or $e->tagName == 'ins' or $e->tagName == 'time' ) {
738 + }
739 + elseif ($e->tagName == 'del' or $e->tagName == 'ins' or $e->tagName == 'time') {
716 740 // Use @datetime if available, otherwise innertext
717 - $dtAttr = ( $e->hasAttribute( 'datetime' ) ) ? $e->getAttribute( 'datetime' ) : unicodeTrim( $e->nodeValue );
718 - if ( ! empty( $dtAttr ) ) {
741 + $dtAttr = ($e->hasAttribute('datetime')) ? $e->getAttribute('datetime') : unicodeTrim($e->nodeValue);
742 + if (!empty($dtAttr)) {
719 743 $dateParts[] = $dtAttr;
720 744 }
721 - } else {
722 - if ( ! empty( $e->nodeValue ) ) {
723 - $dateParts[] = unicodeTrim( $e->nodeValue );
745 + }
746 + else {
747 + if (!empty($e->nodeValue)) {
748 + $dateParts[] = unicodeTrim($e->nodeValue);
724 749 }
725 750 }
726 751 }
727 752
@@ -728,116 +753,122 @@
728 753 // Look through dateParts
729 754 $datePart = '';
730 755 $timePart = '';
731 756 $timezonePart = '';
732 - foreach ( $dateParts as $part ) {
757 + foreach ($dateParts as $part) {
733 758 // Is this part a full ISO8601 datetime?
734 - if ( preg_match( '/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?(Z|[+-]\d{2}:?\d{2})?$/', $part ) ) {
759 + if (preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?(Z|[+-]\d{2}:?\d{2})?$/', $part)) {
735 760 // Break completely, we’ve got our value.
736 761 $dtValue = $part;
737 762 break;
738 763 } else {
739 764 // Is the current part a valid time(+TZ?) AND no other time representation has been found?
740 - if ( ( preg_match( '/^\d{1,2}:\d{2}(:\d{2})?(Z|[+-]\d{1,2}:?\d{2})?$/', $part ) or preg_match( '/^\d{1,2}(:\d{2})?(:\d{2})?[ap]\.?m\.?$/i', $part ) ) and empty( $timePart ) ) {
765 + if ((preg_match('/^\d{1,2}:\d{2}(:\d{2})?(Z|[+-]\d{1,2}:?\d{2})?$/', $part) or preg_match('/^\d{1,2}(:\d{2})?(:\d{2})?[ap]\.?m\.?$/i', $part)) and empty($timePart)) {
741 766 $timePart = $part;
742 767
743 - $timezoneOffset = normalizeTimezoneOffset( $timePart );
744 - if ( ! $impliedTimezone && $timezoneOffset ) {
768 + $timezoneOffset = normalizeTimezoneOffset($timePart);
769 + if (!$impliedTimezone && $timezoneOffset) {
745 770 $impliedTimezone = $timezoneOffset;
746 771 }
747 - // Is the current part a valid date AND no other date representation has been found?
748 - } elseif ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $part ) and empty( $datePart ) ) {
772 + // Is the current part a valid date AND no other date representation has been found?
773 + } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $part) and empty($datePart)) {
749 774 $datePart = $part;
750 - // Is the current part a valid ordinal date AND no other date representation has been found?
751 - } elseif ( preg_match( '/^\d{4}-\d{3}$/', $part ) and empty( $datePart ) ) {
752 - $datePart = normalizeOrdinalDate( $part );
753 - // Is the current part a valid timezone offset AND no other timezone part has been found?
754 - } elseif ( preg_match( '/^(Z|[+-]\d{1,2}:?(\d{2})?)$/', $part ) and empty( $timezonePart ) ) {
775 + // Is the current part a valid ordinal date AND no other date representation has been found?
776 + } elseif (preg_match('/^\d{4}-\d{3}$/', $part) and empty($datePart)) {
777 + $datePart = normalizeOrdinalDate($part);
778 + // Is the current part a valid timezone offset AND no other timezone part has been found?
779 + } elseif (preg_match('/^(Z|[+-]\d{1,2}:?(\d{2})?)$/', $part) and empty($timezonePart)) {
755 780 $timezonePart = $part;
756 781
757 - $timezoneOffset = normalizeTimezoneOffset( $timezonePart );
758 - if ( ! $impliedTimezone && $timezoneOffset ) {
782 + $timezoneOffset = normalizeTimezoneOffset($timezonePart);
783 + if (!$impliedTimezone && $timezoneOffset) {
759 784 $impliedTimezone = $timezoneOffset;
760 785 }
761 - // Current part already represented by other VCP parts; do nothing with it
786 + // Current part already represented by other VCP parts; do nothing with it
762 787 } else {
763 788 continue;
764 789 }
765 790
766 - if ( ! empty( $datePart ) && ! in_array( $datePart, $dates ) ) {
791 + if ( !empty($datePart) && !in_array($datePart, $dates) ) {
767 792 $dates[] = $datePart;
768 793 }
769 794
770 - if ( ! empty( $timezonePart ) && ! empty( $timePart ) ) {
795 + if (!empty($timezonePart) && !empty($timePart)) {
771 796 $timePart .= $timezonePart;
772 797 }
773 798
774 799 $dtValue = '';
775 800
776 - if ( empty( $datePart ) && ! empty( $timePart ) ) {
777 - $timePart = convertTimeFormat( $timePart );
778 - $dtValue = unicodeTrim( $timePart );
779 - } elseif ( ! empty( $datePart ) && empty( $timePart ) ) {
780 - $dtValue = rtrim( $datePart, 'T' );
781 - } else {
782 - $timePart = convertTimeFormat( $timePart );
783 - $dtValue = rtrim( $datePart, 'T' ) . ' ' . unicodeTrim( $timePart );
801 + if ( empty($datePart) && !empty($timePart) ) {
802 + $timePart = convertTimeFormat($timePart);
803 + $dtValue = unicodeTrim($timePart);
784 804 }
805 + else if ( !empty($datePart) && empty($timePart) ) {
806 + $dtValue = rtrim($datePart, 'T');
807 + }
808 + else {
809 + $timePart = convertTimeFormat($timePart);
810 + $dtValue = rtrim($datePart, 'T') . ' ' . unicodeTrim($timePart);
811 + }
785 812 }
786 813 }
787 814 } else {
788 815 // Not using value-class (phew).
789 - if ( $dt->tagName == 'img' or $dt->tagName == 'area' ) {
816 + if ($dt->tagName == 'img' or $dt->tagName == 'area') {
790 817 // Use @alt
791 818 // Is it an entire dt?
792 - $alt = $dt->getAttribute( 'alt' );
793 - if ( ! empty( $alt ) ) {
819 + $alt = $dt->getAttribute('alt');
820 + if (!empty($alt)) {
794 821 $dtValue = $alt;
795 822 }
796 - } elseif ( in_array( $dt->tagName, array( 'data' ) ) ) {
823 + } elseif (in_array($dt->tagName, array('data'))) {
797 824 // Use @value, otherwise innertext
798 825 // Is it an entire dt?
799 - $value = $dt->getAttribute( 'value' );
800 - if ( ! empty( $value ) ) {
826 + $value = $dt->getAttribute('value');
827 + if (!empty($value)) {
801 828 $dtValue = $value;
802 - } else {
803 - $dtValue = $this->textContent( $dt );
804 829 }
805 - } elseif ( $dt->tagName == 'abbr' ) {
830 + else {
831 + $dtValue = $this->textContent($dt);
832 + }
833 + } elseif ($dt->tagName == 'abbr') {
806 834 // Use @title, otherwise innertext
807 835 // Is it an entire dt?
808 - $title = $dt->getAttribute( 'title' );
809 - if ( ! empty( $title ) ) {
836 + $title = $dt->getAttribute('title');
837 + if (!empty($title)) {
810 838 $dtValue = $title;
811 - } else {
812 - $dtValue = $this->textContent( $dt );
813 839 }
814 - } elseif ( $dt->tagName == 'del' or $dt->tagName == 'ins' or $dt->tagName == 'time' ) {
840 + else {
841 + $dtValue = $this->textContent($dt);
842 + }
843 + } elseif ($dt->tagName == 'del' or $dt->tagName == 'ins' or $dt->tagName == 'time') {
815 844 // Use @datetime if available, otherwise innertext
816 845 // Is it an entire dt?
817 - $dtAttr = $dt->getAttribute( 'datetime' );
818 - if ( ! empty( $dtAttr ) ) {
846 + $dtAttr = $dt->getAttribute('datetime');
847 + if (!empty($dtAttr)) {
819 848 $dtValue = $dtAttr;
820 - } else {
821 - $dtValue = $this->textContent( $dt );
822 849 }
850 + else {
851 + $dtValue = $this->textContent($dt);
852 + }
853 +
823 854 } else {
824 - $dtValue = $this->textContent( $dt );
855 + $dtValue = $this->textContent($dt);
825 856 }
826 857
827 858 // if the dtValue is not just YYYY-MM-DD
828 - if ( ! preg_match( '/^(\d{4}-\d{2}-\d{2})$/', $dtValue ) ) {
859 + if (!preg_match('/^(\d{4}-\d{2}-\d{2})$/', $dtValue)) {
829 860 // no implied timezone set and dtValue has a TZ offset, use un-normalized TZ offset
830 - preg_match( '/Z|[+-]\d{1,2}:?(\d{2})?$/i', $dtValue, $matches );
831 - if ( ! $impliedTimezone && ! empty( $matches[0] ) ) {
861 + preg_match('/Z|[+-]\d{1,2}:?(\d{2})?$/i', $dtValue, $matches);
862 + if (!$impliedTimezone && !empty($matches[0])) {
832 863 $impliedTimezone = $matches[0];
833 864 }
834 865 }
835 866
836 - $dtValue = unicodeTrim( $dtValue );
867 + $dtValue = unicodeTrim($dtValue);
837 868
838 869 // Store the date part so that we can use it when assembling the final timestamp if the next one is missing a date part
839 - if ( preg_match( '/(\d{4}-\d{2}-\d{2})/', $dtValue, $matches ) ) {
870 + if (preg_match('/(\d{4}-\d{2}-\d{2})/', $dtValue, $matches)) {
840 871 $dates[] = $matches[0];
841 872 }
842 873 }
843 874
@@ -844,16 +875,16 @@
844 875 /**
845 876 * if $dtValue is only a time and there are recently parsed dates,
846 877 * form the full date-time using the most recently parsed dt- value
847 878 */
848 - if ( ( preg_match( '/^\d{1,2}:\d{2}(:\d{2})?(Z|[+-]\d{2}:?\d{2}?)?$/', $dtValue ) or preg_match( '/^\d{1,2}(:\d{2})?(:\d{2})?[ap]\.?m\.?$/i', $dtValue ) ) && ! empty( $dates ) ) {
849 - $timezoneOffset = normalizeTimezoneOffset( $dtValue );
850 - if ( ! $impliedTimezone && $timezoneOffset ) {
879 + if ((preg_match('/^\d{1,2}:\d{2}(:\d{2})?(Z|[+-]\d{2}:?\d{2}?)?$/', $dtValue) or preg_match('/^\d{1,2}(:\d{2})?(:\d{2})?[ap]\.?m\.?$/i', $dtValue)) && !empty($dates)) {
880 + $timezoneOffset = normalizeTimezoneOffset($dtValue);
881 + if (!$impliedTimezone && $timezoneOffset) {
851 882 $impliedTimezone = $timezoneOffset;
852 883 }
853 884
854 - $dtValue = convertTimeFormat( $dtValue );
855 - $dtValue = end( $dates ) . ' ' . unicodeTrim( $dtValue );
885 + $dtValue = convertTimeFormat($dtValue);
886 + $dtValue = end($dates) . ' ' . unicodeTrim($dtValue);
856 887 }
857 888
858 889 return $dtValue;
859 890 }
@@ -858,48 +889,48 @@
858 889 return $dtValue;
859 890 }
860 891
861 892 /**
862 - * Given the root element of some embedded markup, return a string representing that markup
893 + * Given the root element of some embedded markup, return a string representing that markup
863 894 *
864 - * @param DOMElement $e The element to parse
865 - * @return string $e’s innerHTML
895 + * @param DOMElement $e The element to parse
896 + * @return string $e’s innerHTML
866 897 *
867 898 * @todo need to mark this element as e- parsed so it doesn’t get parsed as it’s parent’s e-* too
868 899 */
869 - public function parseE( \DOMElement $e ) {
870 - $classTitle = $this->parseValueClassTitle( $e );
900 + public function parseE(\DOMElement $e) {
901 + $classTitle = $this->parseValueClassTitle($e);
871 902
872 - if ( $classTitle !== null ) {
903 + if ($classTitle !== null)
873 904 return $classTitle;
874 - }
875 905
876 906 // Expand relative URLs within children of this element
877 907 // TODO: as it is this is not relative to only children, make this .// and rerun tests
878 - $this->resolveChildUrls( $e );
908 + $this->resolveChildUrls($e);
879 909
880 910 // Temporarily move all descendants into a separate DocumentFragment.
881 911 // This way we can DOMDocument::saveHTML on the entire collection at once.
882 912 // Running DOMDocument::saveHTML per node may add whitespace that isn't in source.
883 913 // See https://stackoverflow.com/q/38317903
884 - $innerNodes = $e->ownerDocument->createDocumentFragment();
885 - while ( $e->hasChildNodes() ) {
886 - $innerNodes->appendChild( $e->firstChild );
914 + if ($innerNodes = $e->ownerDocument->createDocumentFragment()) {
915 + while ($e->hasChildNodes()) {
916 + $innerNodes->appendChild($e->firstChild);
917 + }
918 + $html = $e->ownerDocument->saveHtml($innerNodes);
919 + // Put the nodes back in place.
920 + if ($innerNodes->hasChildNodes()) {
921 + $e->appendChild($innerNodes);
922 + }
887 923 }
888 - $html = $e->ownerDocument->saveHtml( $innerNodes );
889 - // Put the nodes back in place.
890 - if ( $innerNodes->hasChildNodes() ) {
891 - $e->appendChild( $innerNodes );
892 - }
893 924
894 925 $return = array(
895 - 'html' => unicodeTrim( $html ),
896 - 'value' => $this->textContent( $e ),
926 + 'html' => unicodeTrim($html),
927 + 'value' => $this->textContent($e),
897 928 );
898 929
899 - if ( $this->lang ) {
930 + if($this->lang) {
900 931 // Language
901 - if ( $html_lang = $this->language( $e ) ) {
932 + if ( $html_lang = $this->language($e) ) {
902 933 $return['lang'] = $html_lang;
903 934 }
904 935 }
905 936
@@ -905,11 +936,11 @@
905 936
906 937 return $return;
907 938 }
908 939
909 - private function removeTags( \DOMElement &$e, $tagName ) {
910 - while ( ( $r = $e->getElementsByTagName( $tagName ) ) && $r->length ) {
911 - $r->item( 0 )->parentNode->removeChild( $r->item( 0 ) );
940 + private function removeTags(\DOMElement &$e, $tagName) {
941 + while(($r = $e->getElementsByTagName($tagName)) && $r->length) {
942 + $r->item(0)->parentNode->removeChild($r->item(0));
912 943 }
913 944 }
914 945
915 946 /**
@@ -915,22 +946,22 @@
915 946 /**
916 947 * Recursively parse microformats
917 948 *
918 949 * @param DOMElement $e The element to parse
919 - * @param bool $is_backcompat Whether using backcompat parsing or not
920 - * @param bool $has_nested_mf Whether this microformat has a nested microformat
950 + * @param bool $is_backcompat Whether using backcompat parsing or not
951 + * @param bool $has_nested_mf Whether this microformat has a nested microformat
921 952 * @return array A representation of the values contained within microformat $e
922 953 */
923 - public function parseH( \DOMElement $e, $is_backcompat = false, $has_nested_mf = false ) {
954 + public function parseH(\DOMElement $e, $is_backcompat = false, $has_nested_mf = false) {
924 955 // If it’s already been parsed (e.g. is a child mf), skip
925 - if ( $this->parsed->contains( $e ) ) {
956 + if ($this->parsed->offsetExists($e)) {
926 957 return null;
927 958 }
928 959
929 960 // Get current µf name
930 - $mfTypes = mfNamesFromElement( $e, 'h-' );
961 + $mfTypes = mfNamesFromElement($e, 'h-');
931 962
932 - if ( ! $mfTypes ) {
963 + if (!$mfTypes) {
933 964 return null;
934 965 }
935 966
936 967 // Initalise var to store the representation in
@@ -939,132 +970,134 @@
939 970 $dates = array();
940 971 $prefixes = array();
941 972 $impliedTimezone = null;
942 973
943 - if ( $e->tagName == 'area' ) {
944 - $coords = $e->getAttribute( 'coords' );
945 - $shape = $e->getAttribute( 'shape' );
974 + if($e->tagName == 'area') {
975 + $coords = $e->getAttribute('coords');
976 + $shape = $e->getAttribute('shape');
946 977 }
947 978
948 979 // Handle p-*
949 - foreach ( $this->xpath->query( './/*[contains(concat(" ", @class) ," p-")]', $e ) as $p ) {
980 + foreach ($this->xpath->query('.//*[contains(concat(" ", normalize-space(@class)) ," p-")]', $e) as $p) {
950 981 // element is already parsed
951 - if ( $this->isElementParsed( $p, 'p' ) ) {
982 + if ($this->isElementParsed($p, 'p')) {
952 983 continue;
953 - // backcompat parsing and element was not upgraded; skip it
954 - } elseif ( $is_backcompat && empty( $this->upgraded[ $p ] ) ) {
955 - $this->elementPrefixParsed( $p, 'p' );
984 + // backcompat parsing and element was not upgraded; skip it
985 + } else if ( $is_backcompat && empty($this->upgraded[$p]) ) {
986 + $this->elementPrefixParsed($p, 'p');
956 987 continue;
957 988 }
958 989
959 990 $prefixes[] = 'p-';
960 - $pValue = $this->parseP( $p );
991 + $pValue = $this->parseP($p);
961 992
962 993 // Add the value to the array for it’s p- properties
963 - foreach ( mfNamesFromElement( $p, 'p-' ) as $propName ) {
964 - if ( ! empty( $propName ) ) {
965 - $return[ $propName ][] = $pValue;
994 + foreach (mfNamesFromElement($p, 'p-') as $propName) {
995 + if (!empty($propName)) {
996 + $return[$propName][] = $pValue;
966 997 }
967 998 }
968 999
969 1000 // Make sure this sub-mf won’t get parsed as a top level mf
970 - $this->elementPrefixParsed( $p, 'p' );
1001 + $this->elementPrefixParsed($p, 'p');
971 1002 }
972 1003
973 1004 // Handle u-*
974 - foreach ( $this->xpath->query( './/*[contains(concat(" ", @class)," u-")]', $e ) as $u ) {
1005 + foreach ($this->xpath->query('.//*[contains(concat(" ", normalize-space(@class))," u-")]', $e) as $u) {
975 1006 // element is already parsed
976 - if ( $this->isElementParsed( $u, 'u' ) ) {
1007 + if ($this->isElementParsed($u, 'u')) {
977 1008 continue;
978 - // backcompat parsing and element was not upgraded; skip it
979 - } elseif ( $is_backcompat && empty( $this->upgraded[ $u ] ) ) {
980 - $this->elementPrefixParsed( $u, 'u' );
1009 + // backcompat parsing and element was not upgraded; skip it
1010 + } else if ( $is_backcompat && empty($this->upgraded[$u]) ) {
1011 + $this->elementPrefixParsed($u, 'u');
981 1012 continue;
982 1013 }
983 1014
984 1015 $prefixes[] = 'u-';
985 - $uValue = $this->parseU( $u );
1016 + $uValue = $this->parseU($u);
986 1017
987 1018 // Add the value to the array for it’s property types
988 - foreach ( mfNamesFromElement( $u, 'u-' ) as $propName ) {
989 - $return[ $propName ][] = $uValue;
1019 + foreach (mfNamesFromElement($u, 'u-') as $propName) {
1020 + $return[$propName][] = $uValue;
990 1021 }
991 1022
992 1023 // Make sure this sub-mf won’t get parsed as a top level mf
993 - $this->elementPrefixParsed( $u, 'u' );
1024 + $this->elementPrefixParsed($u, 'u');
994 1025 }
995 1026
996 1027 $temp_dates = array();
997 1028
998 1029 // Handle dt-*
999 - foreach ( $this->xpath->query( './/*[contains(concat(" ", @class), " dt-")]', $e ) as $dt ) {
1030 + foreach ($this->xpath->query('.//*[contains(concat(" ", normalize-space(@class)), " dt-")]', $e) as $dt) {
1000 1031 // element is already parsed
1001 - if ( $this->isElementParsed( $dt, 'dt' ) ) {
1032 + if ($this->isElementParsed($dt, 'dt')) {
1002 1033 continue;
1003 - // backcompat parsing and element was not upgraded; skip it
1004 - } elseif ( $is_backcompat && empty( $this->upgraded[ $dt ] ) ) {
1005 - $this->elementPrefixParsed( $dt, 'dt' );
1034 + // backcompat parsing and element was not upgraded; skip it
1035 + } else if ( $is_backcompat && empty($this->upgraded[$dt]) ) {
1036 + $this->elementPrefixParsed($dt, 'dt');
1006 1037 continue;
1007 1038 }
1008 1039
1009 1040 $prefixes[] = 'dt-';
1010 - $dtValue = $this->parseDT( $dt, $dates, $impliedTimezone );
1041 + $dtValue = $this->parseDT($dt, $dates, $impliedTimezone);
1011 1042
1012 - if ( $dtValue ) {
1043 + if ($dtValue) {
1013 1044 // Add the value to the array for dt- properties
1014 - foreach ( mfNamesFromElement( $dt, 'dt-' ) as $propName ) {
1015 - $temp_dates[ $propName ][] = $dtValue;
1045 + foreach (mfNamesFromElement($dt, 'dt-') as $propName) {
1046 + $temp_dates[$propName][] = $dtValue;
1016 1047 }
1017 1048 }
1018 1049 // Make sure this sub-mf won’t get parsed as a top level mf
1019 - $this->elementPrefixParsed( $dt, 'dt' );
1050 + $this->elementPrefixParsed($dt, 'dt');
1020 1051 }
1021 1052
1022 - foreach ( $temp_dates as $propName => $data ) {
1053 + foreach ($temp_dates as $propName => $data) {
1023 1054 foreach ( $data as $dtValue ) {
1024 1055 // var_dump(preg_match('/[+-]\d{2}(\d{2})?$/i', $dtValue));
1025 - if ( $impliedTimezone && preg_match( '/(Z|[+-]\d{2}:?(\d{2})?)$/i', $dtValue, $matches ) == 0 ) {
1056 + if ( $impliedTimezone && preg_match('/(Z|[+-]\d{2}:?(\d{2})?)$/i', $dtValue, $matches) == 0 ) {
1026 1057 $dtValue .= $impliedTimezone;
1027 1058 }
1028 1059
1029 - $return[ $propName ][] = $dtValue;
1060 + $return[$propName][] = $dtValue;
1030 1061 }
1031 1062 }
1032 1063
1033 1064 // Handle e-*
1034 - foreach ( $this->xpath->query( './/*[contains(concat(" ", @class)," e-")]', $e ) as $em ) {
1065 + foreach ($this->xpath->query('.//*[contains(concat(" ", normalize-space(@class))," e-")]', $e) as $em) {
1035 1066 // element is already parsed
1036 - if ( $this->isElementParsed( $em, 'e' ) ) {
1067 + if ($this->isElementParsed($em, 'e')) {
1037 1068 continue;
1038 - // backcompat parsing and element was not upgraded; skip it
1039 - } elseif ( $is_backcompat && empty( $this->upgraded[ $em ] ) ) {
1040 - $this->elementPrefixParsed( $em, 'e' );
1069 + // backcompat parsing and element was not upgraded; skip it
1070 + } else if ( $is_backcompat && empty($this->upgraded[$em]) ) {
1071 + $this->elementPrefixParsed($em, 'e');
1041 1072 continue;
1042 1073 }
1043 1074
1044 1075 $prefixes[] = 'e-';
1045 - $eValue = $this->parseE( $em );
1076 + $eValue = $this->parseE($em);
1046 1077
1047 - if ( $eValue ) {
1078 + if ($eValue) {
1048 1079 // Add the value to the array for e- properties
1049 - foreach ( mfNamesFromElement( $em, 'e-' ) as $propName ) {
1050 - $return[ $propName ][] = $eValue;
1080 + foreach (mfNamesFromElement($em, 'e-') as $propName) {
1081 + $return[$propName][] = $eValue;
1051 1082 }
1052 1083 }
1053 1084 // Make sure this sub-mf won’t get parsed as a top level mf
1054 - $this->elementPrefixParsed( $em, 'e' );
1085 + $this->elementPrefixParsed($em, 'e');
1055 1086 }
1056 1087
1057 1088 // Do we need to imply a name property?
1058 1089 // if no explicit "name" property, and no other p-* or e-* properties, and no nested microformats,
1059 - if ( ! array_key_exists( 'name', $return ) && ! in_array( 'p-', $prefixes ) && ! in_array( 'e-', $prefixes ) && ! $has_nested_mf && ! $is_backcompat ) {
1090 + if (!array_key_exists('name', $return) && !in_array('p-', $prefixes)
1091 + && !in_array('e-', $prefixes) && !$has_nested_mf
1092 + && !$is_backcompat && empty($this->upgraded[$e])) {
1060 1093 $name = false;
1061 1094 // img.h-x[alt] or area.h-x[alt]
1062 - if ( ( $e->tagName === 'img' || $e->tagName === 'area' ) && $e->hasAttribute( 'alt' ) ) {
1063 - $name = $e->getAttribute( 'alt' );
1064 - // abbr.h-x[title]
1065 - } elseif ( $e->tagName === 'abbr' && $e->hasAttribute( 'title' ) ) {
1066 - $name = $e->getAttribute( 'title' );
1095 + if (($e->tagName === 'img' || $e->tagName === 'area') && $e->hasAttribute('alt')) {
1096 + $name = $e->getAttribute('alt');
1097 + // abbr.h-x[title]
1098 + } elseif ($e->tagName === 'abbr' && $e->hasAttribute('title')) {
1099 + $name = $e->getAttribute('title');
1067 1100 } else {
1068 1101 $xpaths = array(
1069 1102 // .h-x>img:only-child[alt]:not([alt=""]):not[.h-*]
1070 1103 './img[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and @alt and string-length(@alt) != 0]',
@@ -1076,35 +1109,33 @@
1076 1109 './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(*) = 1]/img[not(contains(concat(" ", @class), " h-")) and @alt and string-length(@alt) != 0]',
1077 1110 // .h-x>:only-child:not[.h-*]>area:only-child[alt]:not([alt=""]):not[.h-*]
1078 1111 './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(*) = 1]/area[not(contains(concat(" ", @class), " h-")) and @alt and string-length(@alt) != 0]',
1079 1112 // .h-x>:only-child:not[.h-*]>abbr:only-child[title]:not([title=""]):not[.h-*]
1080 - './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(*) = 1]/abbr[not(contains(concat(" ", @class), " h-")) and @title and string-length(@title) != 0]',
1113 + './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(*) = 1]/abbr[not(contains(concat(" ", @class), " h-")) and @title and string-length(@title) != 0]'
1081 1114 );
1082 - foreach ( $xpaths as $xpath ) {
1083 - $nameElement = $this->xpath->query( $xpath, $e );
1084 - if ( $nameElement !== false && $nameElement->length === 1 ) {
1085 - $nameElement = $nameElement->item( 0 );
1086 - if ( $nameElement->tagName === 'img' || $nameElement->tagName === 'area' ) {
1087 - $name = $nameElement->getAttribute( 'alt' );
1115 + foreach ($xpaths as $xpath) {
1116 + $nameElement = $this->xpath->query($xpath, $e);
1117 + if ($nameElement !== false && $nameElement->length === 1) {
1118 + $nameElement = $nameElement->item(0);
1119 + if ($nameElement->tagName === 'img' || $nameElement->tagName === 'area') {
1120 + $name = $nameElement->getAttribute('alt');
1088 1121 } else {
1089 - $name = $nameElement->getAttribute( 'title' );
1122 + $name = $nameElement->getAttribute('title');
1090 1123 }
1091 1124 break;
1092 1125 }
1093 1126 }
1094 1127 }
1095 - if ( $name === false ) {
1096 - $name = $this->textContent( $e, true );
1128 + if ($name === false) {
1129 + $name = $this->textContent($e, true);
1097 1130 }
1098 - $return['name'][] = unicodeTrim( $name );
1131 + $return['name'][] = unicodeTrim($name);
1099 1132 }
1100 1133
1101 1134 // Check for u-photo
1102 - if ( ! array_key_exists( 'photo', $return ) && ! $is_backcompat ) {
1103 -
1104 - $photo = $this->parseImpliedPhoto( $e );
1105 -
1106 - if ( $photo !== false ) {
1135 + if (!array_key_exists('photo', $return) && !in_array('u-', $prefixes) && !$has_nested_mf && !$is_backcompat) {
1136 + $photo = $this->parseImpliedPhoto($e);
1137 + if ($photo !== false) {
1107 1138 $return['photo'][] = $photo;
1108 1139 }
1109 1140 }
1110 1141
@@ -1109,12 +1140,12 @@
1109 1140 }
1110 1141
1111 1142 // Do we need to imply a url property?
1112 1143 // if no explicit "url" property, and no other explicit u-* properties, and no nested microformats
1113 - if ( ! array_key_exists( 'url', $return ) && ! in_array( 'u-', $prefixes ) && ! $has_nested_mf && ! $is_backcompat ) {
1144 + if (!array_key_exists('url', $return) && !in_array('u-', $prefixes) && !$has_nested_mf && !$is_backcompat) {
1114 1145 // a.h-x[href] or area.h-x[href]
1115 - if ( ( $e->tagName === 'a' || $e->tagName === 'area' ) && $e->hasAttribute( 'href' ) ) {
1116 - $return['url'][] = $this->resolveUrl( $e->getAttribute( 'href' ) );
1146 + if (($e->tagName === 'a' || $e->tagName === 'area') && $e->hasAttribute('href')) {
1147 + $return['url'][] = $this->resolveUrl($e->getAttribute('href'));
1117 1148 } else {
1118 1149 $xpaths = array(
1119 1150 // .h-x>a[href]:only-of-type:not[.h-*]
1120 1151 './a[not(contains(concat(" ", @class), " h-")) and count(../a) = 1 and @href]',
@@ -1122,14 +1153,14 @@
1122 1153 './area[not(contains(concat(" ", @class), " h-")) and count(../area) = 1 and @href]',
1123 1154 // .h-x>:only-child:not[.h-*]>a[href]:only-of-type:not[.h-*]
1124 1155 './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(a) = 1]/a[not(contains(concat(" ", @class), " h-")) and @href]',
1125 1156 // .h-x>:only-child:not[.h-*]>area[href]:only-of-type:not[.h-*]
1126 - './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(area) = 1]/area[not(contains(concat(" ", @class), " h-")) and @href]',
1157 + './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(area) = 1]/area[not(contains(concat(" ", @class), " h-")) and @href]'
1127 1158 );
1128 - foreach ( $xpaths as $xpath ) {
1129 - $url = $this->xpath->query( $xpath, $e );
1130 - if ( $url !== false && $url->length === 1 ) {
1131 - $return['url'][] = $this->resolveUrl( $url->item( 0 )->getAttribute( 'href' ) );
1159 + foreach ($xpaths as $xpath) {
1160 + $url = $this->xpath->query($xpath, $e);
1161 + if ($url !== false && $url->length === 1) {
1162 + $return['url'][] = $this->resolveUrl($url->item(0)->getAttribute('href'));
1132 1163 break;
1133 1164 }
1134 1165 }
1135 1166 }
@@ -1135,39 +1166,43 @@
1135 1166 }
1136 1167 }
1137 1168
1138 1169 // Make sure things are unique and in alphabetical order
1139 - $mfTypes = array_unique( $mfTypes );
1140 - sort( $mfTypes );
1170 + $mfTypes = array_unique($mfTypes);
1171 + sort($mfTypes);
1141 1172
1142 1173 // Properties should be an object when JSON serialised
1143 - if ( empty( $return ) and $this->jsonMode ) {
1174 + if (empty($return) and $this->jsonMode) {
1144 1175 $return = new stdClass();
1145 1176 }
1146 1177
1147 1178 // Phew. Return the final result.
1148 1179 $parsed = array(
1149 - 'type' => $mfTypes,
1150 - 'properties' => $return,
1180 + 'type' => $mfTypes,
1181 + 'properties' => $return
1151 1182 );
1152 1183
1153 - if ( $this->lang ) {
1184 + if(trim($e->getAttribute('id')) !== '') {
1185 + $parsed['id'] = trim($e->getAttribute("id"));
1186 + }
1187 +
1188 + if($this->lang) {
1154 1189 // Language
1155 - if ( $html_lang = $this->language( $e ) ) {
1190 + if ( $html_lang = $this->language($e) ) {
1156 1191 $parsed['lang'] = $html_lang;
1157 1192 }
1158 1193 }
1159 1194
1160 - if ( ! empty( $shape ) ) {
1195 + if (!empty($shape)) {
1161 1196 $parsed['shape'] = $shape;
1162 1197 }
1163 1198
1164 - if ( ! empty( $coords ) ) {
1199 + if (!empty($coords)) {
1165 1200 $parsed['coords'] = $coords;
1166 1201 }
1167 1202
1168 - if ( ! empty( $children ) ) {
1169 - $parsed['children'] = array_values( array_filter( $children ) );
1203 + if (!empty($children)) {
1204 + $parsed['children'] = array_values(array_filter($children));
1170 1205 }
1171 1206 return $parsed;
1172 1207 }
1173 1208
@@ -1173,18 +1208,18 @@
1173 1208
1174 1209 /**
1175 1210 * @see http://microformats.org/wiki/microformats2-parsing#parsing_for_implied_properties
1176 1211 */
1177 - public function parseImpliedPhoto( \DOMElement $e ) {
1212 + public function parseImpliedPhoto(\DOMElement $e) {
1178 1213
1179 1214 // img.h-x[src]
1180 - if ( $e->tagName == 'img' ) {
1181 - return $this->resolveUrl( $e->getAttribute( 'src' ) );
1215 + if ($e->tagName == 'img') {
1216 + return $this->resolveUrl($this->parseImg($e));
1182 1217 }
1183 1218
1184 1219 // object.h-x[data]
1185 - if ( $e->tagName == 'object' && $e->hasAttribute( 'data' ) ) {
1186 - return $this->resolveUrl( $e->getAttribute( 'data' ) );
1220 + if ($e->tagName == 'object' && $e->hasAttribute('data')) {
1221 + return $this->resolveUrl($e->getAttribute('data'));
1187 1222 }
1188 1223
1189 1224 $xpaths = array(
1190 1225 // .h-x>img[src]:only-of-type:not[.h-*]
@@ -1196,17 +1231,18 @@
1196 1231 // .h-x>:only-child:not[.h-*]>object[data]:only-of-type:not[.h-*]
1197 1232 './*[not(contains(concat(" ", @class), " h-")) and count(../*) = 1 and count(object) = 1]/object[not(contains(concat(" ", @class), " h-")) and @data]',
1198 1233 );
1199 1234
1200 - foreach ( $xpaths as $path ) {
1201 - $els = $this->xpath->query( $path, $e );
1235 + foreach ($xpaths as $path) {
1236 + $els = $this->xpath->query($path, $e);
1202 1237
1203 - if ( $els !== false && $els->length === 1 ) {
1204 - $el = $els->item( 0 );
1205 - if ( $el->tagName == 'img' ) {
1206 - return $this->resolveUrl( $el->getAttribute( 'src' ) );
1207 - } elseif ( $el->tagName == 'object' ) {
1208 - return $this->resolveUrl( $el->getAttribute( 'data' ) );
1238 + if ($els !== false && $els->length === 1) {
1239 + $el = $els->item(0);
1240 + if ($el->tagName == 'img') {
1241 + $return = $this->parseImg($el);
1242 + return $this->resolveUrl($return);
1243 + } else if ($el->tagName == 'object') {
1244 + return $this->resolveUrl($el->getAttribute('data'));
1209 1245 }
1210 1246 }
1211 1247 }
1212 1248
@@ -1221,9 +1257,8 @@
1221 1257 * For $rels and $rel_urls, if they are empty and $this->jsonMode = true, they will be returned as stdClass,
1222 1258 * optimizing for JSON serialization. Otherwise they will be returned as an empty array.
1223 1259 * Note that $alternates is deprecated in the microformats spec in favor of $rel_urls. $alternates only appears
1224 1260 * in parsed results if $this->enableAlternates = true.
1225 - *
1226 1261 * @return array|stdClass
1227 1262 */
1228 1263 public function parseRelsAndAlternates() {
1229 1264 $rels = array();
@@ -1230,93 +1265,93 @@
1230 1265 $rel_urls = array();
1231 1266 $alternates = array();
1232 1267
1233 1268 // Iterate through all a, area and link elements with rel attributes
1234 - foreach ( $this->xpath->query( '//a[@rel and @href] | //link[@rel and @href] | //area[@rel and @href]' ) as $hyperlink ) {
1269 + foreach ($this->xpath->query('//a[@rel and @href] | //link[@rel and @href] | //area[@rel and @href]') as $hyperlink) {
1235 1270 // Parse the set of rels for the current link
1236 - $linkRels = array_unique( array_filter( preg_split( '/[\t\n\f\r ]/', $hyperlink->getAttribute( 'rel' ) ) ) );
1237 - if ( count( $linkRels ) === 0 ) {
1271 + $linkRels = array_unique(array_filter(preg_split('/[\t\n\f\r ]/', $hyperlink->getAttribute('rel'))));
1272 + if (count($linkRels) === 0) {
1238 1273 continue;
1239 1274 }
1240 1275
1241 1276 // Resolve the href
1242 - $href = $this->resolveUrl( $hyperlink->getAttribute( 'href' ) );
1277 + $href = $this->resolveUrl($hyperlink->getAttribute('href'));
1243 1278
1244 1279 $rel_attributes = array();
1245 1280
1246 - if ( $hyperlink->hasAttribute( 'media' ) ) {
1247 - $rel_attributes['media'] = $hyperlink->getAttribute( 'media' );
1281 + if ($hyperlink->hasAttribute('media')) {
1282 + $rel_attributes['media'] = $hyperlink->getAttribute('media');
1248 1283 }
1249 1284
1250 - if ( $hyperlink->hasAttribute( 'hreflang' ) ) {
1251 - $rel_attributes['hreflang'] = $hyperlink->getAttribute( 'hreflang' );
1285 + if ($hyperlink->hasAttribute('hreflang')) {
1286 + $rel_attributes['hreflang'] = $hyperlink->getAttribute('hreflang');
1252 1287 }
1253 1288
1254 - if ( $hyperlink->hasAttribute( 'title' ) ) {
1255 - $rel_attributes['title'] = $hyperlink->getAttribute( 'title' );
1289 + if ($hyperlink->hasAttribute('title')) {
1290 + $rel_attributes['title'] = $hyperlink->getAttribute('title');
1256 1291 }
1257 1292
1258 - if ( $hyperlink->hasAttribute( 'type' ) ) {
1259 - $rel_attributes['type'] = $hyperlink->getAttribute( 'type' );
1293 + if ($hyperlink->hasAttribute('type')) {
1294 + $rel_attributes['type'] = $hyperlink->getAttribute('type');
1260 1295 }
1261 1296
1262 - if ( strlen( $hyperlink->textContent ) > 0 ) {
1297 + if (strlen($hyperlink->textContent) > 0) {
1263 1298 $rel_attributes['text'] = $hyperlink->textContent;
1264 1299 }
1265 1300
1266 - if ( $this->enableAlternates ) {
1301 + if ($this->enableAlternates) {
1267 1302 // If 'alternate' in rels, create 'alternates' structure, append
1268 - if ( in_array( 'alternate', $linkRels ) ) {
1303 + if (in_array('alternate', $linkRels)) {
1269 1304 $alternates[] = array_merge(
1270 1305 $rel_attributes,
1271 1306 array(
1272 1307 'url' => $href,
1273 - 'rel' => implode( ' ', array_diff( $linkRels, array( 'alternate' ) ) ),
1308 + 'rel' => implode(' ', array_diff($linkRels, array('alternate')))
1274 1309 )
1275 1310 );
1276 1311 }
1277 1312 }
1278 1313
1279 - foreach ( $linkRels as $rel ) {
1280 - if ( ! array_key_exists( $rel, $rels ) ) {
1281 - $rels[ $rel ] = array( $href );
1282 - } elseif ( ! in_array( $href, $rels[ $rel ] ) ) {
1283 - $rels[ $rel ][] = $href;
1314 + foreach ($linkRels as $rel) {
1315 + if (!array_key_exists($rel, $rels)) {
1316 + $rels[$rel] = array($href);
1317 + } elseif (!in_array($href, $rels[$rel])) {
1318 + $rels[$rel][] = $href;
1284 1319 }
1285 1320 }
1286 1321
1287 - if ( ! array_key_exists( $href, $rel_urls ) ) {
1288 - $rel_urls[ $href ] = array( 'rels' => array() );
1322 + if (!array_key_exists($href, $rel_urls)) {
1323 + $rel_urls[$href] = array('rels' => array());
1289 1324 }
1290 1325
1291 1326 // Add the attributes collected only if they were not already set
1292 - $rel_urls[ $href ] = array_merge(
1327 + $rel_urls[$href] = array_merge(
1293 1328 $rel_attributes,
1294 - $rel_urls[ $href ]
1329 + $rel_urls[$href]
1295 1330 );
1296 1331
1297 1332 // Merge current rels with those already set
1298 - $rel_urls[ $href ]['rels'] = array_merge(
1299 - $rel_urls[ $href ]['rels'],
1333 + $rel_urls[$href]['rels'] = array_merge(
1334 + $rel_urls[$href]['rels'],
1300 1335 $linkRels
1301 1336 );
1302 1337 }
1303 1338
1304 1339 // Alphabetically sort the rels arrays after removing duplicates
1305 - foreach ( $rel_urls as $href => $object ) {
1306 - $rel_urls[ $href ]['rels'] = array_unique( $rel_urls[ $href ]['rels'] );
1307 - sort( $rel_urls[ $href ]['rels'] );
1340 + foreach ($rel_urls as $href => $object) {
1341 + $rel_urls[$href]['rels'] = array_unique($rel_urls[$href]['rels']);
1342 + sort($rel_urls[$href]['rels']);
1308 1343 }
1309 1344
1310 - if ( empty( $rels ) and $this->jsonMode ) {
1345 + if (empty($rels) and $this->jsonMode) {
1311 1346 $rels = new stdClass();
1312 1347 }
1313 1348
1314 - if ( empty( $rel_urls ) and $this->jsonMode ) {
1349 + if (empty($rel_urls) and $this->jsonMode) {
1315 1350 $rel_urls = new stdClass();
1316 1351 }
1317 1352
1318 - return array( $rels, $rel_urls, $alternates );
1353 + return array($rels, $rel_urls, $alternates);
1319 1354 }
1320 1355
1321 1356 /**
1322 1357 * Find rel=tag elements that don't have class=category and have an href.
@@ -1322,27 +1357,26 @@
1322 1357 * Find rel=tag elements that don't have class=category and have an href.
1323 1358 * For each element, get the last non-empty URL segment. Append a <data>
1324 1359 * element with that value as the category. Uses the mf1 class 'category'
1325 1360 * which will then be upgraded to p-category during backcompat.
1326 - *
1327 1361 * @param DOMElement $el
1328 1362 */
1329 - public function upgradeRelTagToCategory( DOMElement $el ) {
1330 - $rel_tag = $this->xpath->query( './/a[contains(concat(" ",normalize-space(@rel)," ")," tag ") and not(contains(concat(" ", normalize-space(@class), " "), " category ")) and @href]', $el );
1363 + public function upgradeRelTagToCategory(DOMElement $el) {
1364 + $rel_tag = $this->xpath->query('.//a[contains(concat(" ",normalize-space(@rel)," ")," tag ") and not(contains(concat(" ", normalize-space(@class), " "), " category ")) and @href]', $el);
1331 1365
1332 1366 if ( $rel_tag->length ) {
1333 1367 foreach ( $rel_tag as $tempEl ) {
1334 - $path = trim( parse_url( $tempEl->getAttribute( 'href' ), PHP_URL_PATH ), ' /' );
1335 - $segments = explode( '/', $path );
1336 - $value = array_pop( $segments );
1368 + $path = trim(parse_url($tempEl->getAttribute('href'), PHP_URL_PATH), ' /');
1369 + $segments = explode('/', $path);
1370 + $value = array_pop($segments);
1337 1371
1338 - // build the <data> element
1339 - $dataEl = $tempEl->ownerDocument->createElement( 'data' );
1340 - $dataEl->setAttribute( 'class', 'category' );
1341 - $dataEl->setAttribute( 'value', $value );
1372 + # build the <data> element
1373 + $dataEl = $tempEl->ownerDocument->createElement('data');
1374 + $dataEl->setAttribute('class', 'category');
1375 + $dataEl->setAttribute('value', $value);
1342 1376
1343 - // append as child of input element. this should ensure added element does get parsed inside e-*
1344 - $el->appendChild( $dataEl );
1377 + # append as child of input element. this should ensure added element does get parsed inside e-*
1378 + $el->appendChild($dataEl);
1345 1379 }
1346 1380 }
1347 1381 }
1348 1382
@@ -1347,30 +1381,35 @@
1347 1381 }
1348 1382
1349 1383 /**
1350 1384 * Kicks off the parsing routine
1351 - *
1352 - * @param bool $convertClassic whether to do backcompat parsing on microformats1. Defaults to true.
1385 + * @param bool $convertClassic whether to do backcompat parsing on microformats1. Defaults to true.
1353 1386 * @param DOMElement $context optionally specify an element from which to parse microformats
1354 1387 * @return array An array containing all the microformats found in the current document
1355 1388 */
1356 - public function parse( $convertClassic = true, DOMElement $context = null ) {
1389 + public function parse($convertClassic = true, ?DOMElement $context = null) {
1357 1390 $this->convertClassic = $convertClassic;
1358 - $mfs = $this->parse_recursive( $context );
1391 + $mfs = $this->parse_recursive($context);
1359 1392
1360 1393 // Parse rels
1361 1394 list($rels, $rel_urls, $alternates) = $this->parseRelsAndAlternates();
1362 1395
1363 1396 $top = array(
1364 - 'items' => array_values( array_filter( $mfs ) ),
1365 - 'rels' => $rels,
1397 + 'items' => array_values(array_filter($mfs)),
1398 + 'rels' => $rels,
1366 1399 'rel-urls' => $rel_urls,
1367 1400 );
1368 1401
1369 - if ( $this->enableAlternates && count( $alternates ) ) {
1402 + if ($this->enableAlternates && count($alternates)) {
1370 1403 $top['alternates'] = $alternates;
1371 1404 }
1372 1405
1406 + $top['title'] = '';
1407 + $x_title = $this->xpath->query( '//title' );
1408 + if ( $x_title && $x_title->item( 0 ) ) {
1409 + $top['title'] = $x_title->item( 0 )->textContent;
1410 + }
1411 +
1373 1412 return $top;
1374 1413 }
1375 1414
1376 1415
@@ -1376,78 +1415,82 @@
1376 1415
1377 1416 /**
1378 1417 * Parse microformats recursively
1379 1418 * Keeps track of whether inside a backcompat root or not
1380 - *
1381 1419 * @param DOMElement $context: node to start with
1382 - * @param int $depth: recursion depth
1420 + * @param int $depth: recursion depth
1383 1421 * @return array
1384 1422 */
1385 - public function parse_recursive( DOMElement $context = null, $depth = 0 ) {
1423 + public function parse_recursive(?DOMElement $context = null, $depth = 0) {
1424 + if ( $depth > 2 ) {
1425 + return array();
1426 + }
1427 +
1386 1428 $mfs = array();
1387 - $mfElements = $this->getRootMF( $context );
1429 + $mfElements = $this->getRootMF($context);
1388 1430
1389 - foreach ( $mfElements as $node ) {
1390 - $is_backcompat = ! $this->hasRootMf2( $node );
1431 + foreach ($mfElements as $node) {
1432 + $is_backcompat = !$this->hasRootMf2($node);
1391 1433
1392 - if ( $this->convertClassic && $is_backcompat ) {
1393 - $this->backcompat( $node );
1434 + if ($this->convertClassic && $is_backcompat) {
1435 + $this->backcompat($node);
1394 1436 }
1395 1437
1396 - $recurse = $this->parse_recursive( $node, $depth + 1 );
1438 + $recurse = $this->parse_recursive($node, $depth + 1);
1397 1439
1398 1440 // set bool flag for nested mf
1399 - $has_nested_mf = ( $recurse );
1441 + $has_nested_mf = (bool) $recurse;
1400 1442
1401 1443 // parse for root mf
1402 - $result = $this->parseH( $node, $is_backcompat, $has_nested_mf );
1444 + $result = $this->parseH($node, $is_backcompat, $has_nested_mf);
1403 1445
1404 1446 // TODO: Determine if clearing this is required?
1405 - $this->elementPrefixParsed( $node, 'h' );
1406 - $this->elementPrefixParsed( $node, 'p' );
1407 - $this->elementPrefixParsed( $node, 'u' );
1408 - $this->elementPrefixParsed( $node, 'dt' );
1409 - $this->elementPrefixParsed( $node, 'e' );
1447 + $this->elementPrefixParsed($node, 'h');
1448 + $this->elementPrefixParsed($node, 'p');
1449 + $this->elementPrefixParsed($node, 'u');
1450 + $this->elementPrefixParsed($node, 'dt');
1451 + $this->elementPrefixParsed($node, 'e');
1410 1452
1411 1453 // parseH returned a parsed result
1412 - if ( $result ) {
1454 + if ($result) {
1413 1455
1414 1456 // merge recursive results into current results
1415 - if ( $recurse ) {
1416 - $result = array_merge_recursive( $result, $recurse );
1457 + if ($recurse) {
1458 + $result = array_merge_recursive($result, $recurse);
1417 1459 }
1418 1460
1419 1461 // currently a nested mf; check if node is an mf property of parent
1420 - if ( $depth > 0 ) {
1421 - $temp_properties = nestedMfPropertyNamesFromElement( $node );
1462 + if ($depth > 0) {
1463 + $temp_properties = nestedMfPropertyNamesFromElement($node);
1422 1464
1423 1465 // properties found; set up parsed result in 'properties'
1424 - if ( ! empty( $temp_properties ) ) {
1466 + if (!empty($temp_properties)) {
1425 1467
1426 - foreach ( $temp_properties as $property => $prefixes ) {
1468 + foreach ($temp_properties as $property => $prefixes) {
1427 1469 // Note: handling microformat nesting under multiple conflicting prefixes is not currently specified by the mf2 parsing spec.
1428 1470 $prefixSpecificResult = $result;
1429 - if ( in_array( 'p-', $prefixes ) ) {
1430 - $prefixSpecificResult['value'] = ( ! is_array( $prefixSpecificResult['properties'] ) || empty( $prefixSpecificResult['properties']['name'][0] ) ) ? $this->parseP( $node ) : $prefixSpecificResult['properties']['name'][0];
1431 - } elseif ( in_array( 'e-', $prefixes ) ) {
1432 - $eParsedResult = $this->parseE( $node );
1471 + if (in_array('p-', $prefixes)) {
1472 + $prefixSpecificResult['value'] = (!is_array($prefixSpecificResult['properties']) || empty($prefixSpecificResult['properties']['name'][0])) ? $this->parseP($node) : $prefixSpecificResult['properties']['name'][0];
1473 + } elseif (in_array('e-', $prefixes)) {
1474 + $eParsedResult = $this->parseE($node);
1433 1475 $prefixSpecificResult['html'] = $eParsedResult['html'];
1434 1476 $prefixSpecificResult['value'] = $eParsedResult['value'];
1435 - } elseif ( in_array( 'u-', $prefixes ) ) {
1436 - $prefixSpecificResult['value'] = ( ! is_array( $result['properties'] ) || empty( $result['properties']['url'] ) ) ? $this->parseU( $node ) : reset( $result['properties']['url'] );
1437 - } elseif ( in_array( 'dt-', $prefixes ) ) {
1438 - $parsed_property = $this->parseDT( $node );
1439 - $prefixSpecificResult['value'] = ( $parsed_property ) ? $parsed_property : '';
1477 + } elseif (in_array('u-', $prefixes)) {
1478 + $prefixSpecificResult['value'] = (!is_array($result['properties']) || empty($result['properties']['url'])) ? $this->parseU($node) : reset($result['properties']['url']);
1479 + } elseif (in_array('dt-', $prefixes)) {
1480 + $parsed_property = $this->parseDT($node);
1481 + $prefixSpecificResult['value'] = ($parsed_property) ? $parsed_property : '';
1440 1482 }
1483 + $prefixSpecificResult['value'] = is_array($prefixSpecificResult['value']) ? $prefixSpecificResult['value']['value'] : $prefixSpecificResult['value'];
1441 1484
1442 - $mfs['properties'][ $property ][] = $prefixSpecificResult;
1485 + $mfs['properties'][$property][] = $prefixSpecificResult;
1443 1486 }
1444 1487
1445 - // otherwise, set up in 'children'
1488 + // otherwise, set up in 'children'
1446 1489 } else {
1447 1490 $mfs['children'][] = $result;
1448 1491 }
1449 - // otherwise, top-level mf
1492 + // otherwise, top-level mf
1450 1493 } else {
1451 1494 $mfs[] = $result;
1452 1495 }
1453 1496 }
@@ -1468,35 +1511,29 @@
1468 1511 * If an element with the ID is not found, an empty skeleton mf2 array structure
1469 1512 * will be returned.
1470 1513 *
1471 1514 * @param string $id
1472 - * @param bool $htmlSafe = false whether or not to HTML-encode angle brackets in non e-* properties
1515 + * @param bool $htmlSafe = false whether or not to HTML-encode angle brackets in non e-* properties
1473 1516 * @return array
1474 1517 */
1475 - public function parseFromId( $id, $convertClassic = true ) {
1476 - $matches = $this->xpath->query( "//*[@id='{$id}']" );
1518 + public function parseFromId($id, $convertClassic=true) {
1519 + $matches = $this->xpath->query("//*[@id='{$id}']");
1477 1520
1478 - if ( empty( $matches ) ) {
1479 - return array(
1480 - 'items' => array(),
1481 - 'rels' => array(),
1482 - 'alternates' => array(),
1483 - );
1484 - }
1521 + if (empty($matches))
1522 + return array('items' => array(), 'rels' => array(), 'alternates' => array());
1485 1523
1486 - return $this->parse( $convertClassic, $matches->item( 0 ) );
1524 + return $this->parse($convertClassic, $matches->item(0));
1487 1525 }
1488 1526
1489 1527 /**
1490 1528 * Get the root microformat elements
1491 - *
1492 1529 * @param DOMElement $context
1493 1530 * @return DOMNodeList
1494 1531 */
1495 - public function getRootMF( DOMElement $context = null ) {
1532 + public function getRootMF(?DOMElement $context = null) {
1496 1533 // start with mf2 root class name xpath
1497 1534 $xpaths = array(
1498 - 'contains(concat(" ",normalize-space(@class)), " h-")',
1535 + '(php:function("\\Friends\\Mf2\\classHasMf2RootClassname", normalize-space(@class)))'
1499 1536 );
1500 1537
1501 1538 // add mf1 root class names
1502 1539 foreach ( $this->classicRootMap as $old => $new ) {
@@ -1503,13 +1540,13 @@
1503 1540 $xpaths[] = '( contains(concat(" ",normalize-space(@class), " "), " ' . $old . ' ") )';
1504 1541 }
1505 1542
1506 1543 // final xpath with OR
1507 - $xpath = '//*[' . implode( ' or ', $xpaths ) . ']';
1544 + $xpath = '//*[' . implode(' or ', $xpaths) . ']';
1508 1545
1509 - $mfElements = ( null === $context )
1510 - ? $this->xpath->query( $xpath )
1511 - : $this->xpath->query( '.' . $xpath, $context );
1546 + $mfElements = (null === $context)
1547 + ? $this->xpath->query($xpath)
1548 + : $this->xpath->query('.' . $xpath, $context);
1512 1549
1513 1550 return $mfElements;
1514 1551 }
1515 1552
@@ -1515,120 +1552,173 @@
1515 1552
1516 1553 /**
1517 1554 * Apply the backcompat algorithm to upgrade mf1 classes to mf2.
1518 1555 * This method is called recursively.
1519 - *
1520 1556 * @param DOMElement $el
1521 - * @param string $context
1522 - * @param bool $isParentMf2
1557 + * @param string $context
1558 + * @param bool $isParentMf2
1523 1559 * @see http://microformats.org/wiki/microformats2-parsing#algorithm
1524 1560 */
1525 - public function backcompat( DOMElement $el, $context = '', $isParentMf2 = false ) {
1561 + public function backcompat(DOMElement $el, $context = '', $isParentMf2 = false) {
1526 1562
1527 1563 if ( $context ) {
1528 - $mf1Classes = array( $context );
1564 + $mf1Classes = array($context);
1529 1565 } else {
1530 - $class = str_replace( array( "\t", "\n" ), ' ', $el->getAttribute( 'class' ) );
1531 - $classes = array_filter( explode( ' ', $class ) );
1532 - $mf1Classes = array_intersect( $classes, array_keys( $this->classicRootMap ) );
1566 + $class = str_replace(array("\t", "\n"), ' ', $el->getAttribute('class'));
1567 + $classes = array_filter(explode(' ', $class));
1568 + $mf1Classes = array_intersect($classes, array_keys($this->classicRootMap));
1533 1569 }
1534 1570
1535 - $elHasMf2 = $this->hasRootMf2( $el );
1571 + $elHasMf2 = $this->hasRootMf2($el);
1536 1572
1537 - foreach ( $mf1Classes as $classname ) {
1573 + foreach ($mf1Classes as $classname) {
1538 1574 // special handling for specific properties
1539 - switch ( $classname ) {
1575 + switch ( $classname )
1576 + {
1540 1577 case 'hentry':
1541 - $this->upgradeRelTagToCategory( $el );
1578 + $this->upgradeRelTagToCategory($el);
1542 1579
1543 - $rel_bookmark = $this->xpath->query( './/a[contains(concat(" ",normalize-space(@rel)," ")," bookmark ") and @href]', $el );
1580 + $rel_bookmark = $this->xpath->query('.//a[contains(concat(" ",normalize-space(@rel)," ")," bookmark ") and @href]', $el);
1544 1581
1545 1582 if ( $rel_bookmark->length ) {
1546 1583 foreach ( $rel_bookmark as $tempEl ) {
1547 - $this->addMfClasses( $tempEl, 'u-url' );
1548 - $this->addUpgraded( $tempEl, array( 'bookmark' ) );
1584 + $this->addMfClasses($tempEl, 'u-url');
1585 + $this->addUpgraded($tempEl, array('bookmark'));
1549 1586 }
1550 1587 }
1551 - break;
1588 + break;
1552 1589
1590 + case 'hfeed':
1591 + $this->upgradeRelTagToCategory($el);
1592 + break;
1593 +
1594 + case 'hproduct':
1595 + $review_and_hreview_aggregate = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " review ") and contains(concat(" ", normalize-space(@class), " "), " hreview-aggregate ")]', $el);
1596 +
1597 + if ( $review_and_hreview_aggregate->length ) {
1598 + foreach ( $review_and_hreview_aggregate as $tempEl ) {
1599 + if ( !$this->hasRootMf2($tempEl) ) {
1600 + $this->backcompat($tempEl, 'hreview-aggregate');
1601 + $this->addMfClasses($tempEl, 'p-review h-review-aggregate');
1602 + $this->addUpgraded($tempEl, array('review hreview-aggregate'));
1603 + }
1604 + }
1605 + }
1606 +
1607 + $review_and_hreview = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " review ") and contains(concat(" ", normalize-space(@class), " "), " hreview ")]', $el);
1608 +
1609 + if ( $review_and_hreview->length ) {
1610 + foreach ( $review_and_hreview as $tempEl ) {
1611 + if ( !$this->hasRootMf2($tempEl) ) {
1612 + $this->backcompat($tempEl, 'hreview');
1613 + $this->addMfClasses($tempEl, 'p-review h-review');
1614 + $this->addUpgraded($tempEl, array('review hreview'));
1615 + }
1616 + }
1617 + }
1618 +
1619 + break;
1620 +
1621 + case 'hreview-aggregate':
1553 1622 case 'hreview':
1554 - $item_and_vcard = $this->xpath->query( './/*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " vcard ")]', $el );
1623 + $item_and_vcard = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " vcard ")]', $el);
1555 1624
1556 1625 if ( $item_and_vcard->length ) {
1557 1626 foreach ( $item_and_vcard as $tempEl ) {
1558 - if ( ! $this->hasRootMf2( $tempEl ) ) {
1559 - $this->backcompat( $tempEl, 'vcard' );
1560 - $this->addMfClasses( $tempEl, 'p-item h-card' );
1561 - $this->addUpgraded( $tempEl, array( 'item', 'vcard' ) );
1627 + if ( !$this->hasRootMf2($tempEl) ) {
1628 + $this->backcompat($tempEl, 'vcard');
1629 + $this->addMfClasses($tempEl, 'p-item h-card');
1630 + $this->addUpgraded($tempEl, array('item', 'vcard'));
1562 1631 }
1563 1632 }
1564 1633 }
1565 1634
1566 - $item_and_vevent = $this->xpath->query( './/*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " vevent ")]', $el );
1635 + $item_and_vevent = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " vevent ")]', $el);
1567 1636
1568 1637 if ( $item_and_vevent->length ) {
1569 1638 foreach ( $item_and_vevent as $tempEl ) {
1570 - if ( ! $this->hasRootMf2( $tempEl ) ) {
1571 - $this->addMfClasses( $tempEl, 'p-item h-event' );
1572 - $this->backcompat( $tempEl, 'vevent' );
1573 - $this->addUpgraded( $tempEl, array( 'item', 'vevent' ) );
1639 + if ( !$this->hasRootMf2($tempEl) ) {
1640 + $this->addMfClasses($tempEl, 'p-item h-event');
1641 + $this->backcompat($tempEl, 'vevent');
1642 + $this->addUpgraded($tempEl, array('item', 'vevent'));
1574 1643 }
1575 1644 }
1576 1645 }
1577 1646
1578 - $item_and_hproduct = $this->xpath->query( './/*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " hproduct ")]', $el );
1647 + $item_and_hproduct = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " hproduct ")]', $el);
1579 1648
1580 1649 if ( $item_and_hproduct->length ) {
1581 1650 foreach ( $item_and_hproduct as $tempEl ) {
1582 - if ( ! $this->hasRootMf2( $tempEl ) ) {
1583 - $this->addMfClasses( $tempEl, 'p-item h-product' );
1584 - $this->backcompat( $tempEl, 'vevent' );
1585 - $this->addUpgraded( $tempEl, array( 'item', 'hproduct' ) );
1651 + if ( !$this->hasRootMf2($tempEl) ) {
1652 + $this->addMfClasses($tempEl, 'p-item h-product');
1653 + $this->backcompat($tempEl, 'hproduct');
1654 + $this->addUpgraded($tempEl, array('item', 'hproduct'));
1586 1655 }
1587 1656 }
1588 1657 }
1589 1658
1590 - $this->upgradeRelTagToCategory( $el );
1591 - break;
1659 + $rel_self_bookmark = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@rel), " "), " self ") and contains(concat(" ", normalize-space(@rel), " "), " bookmark ")]', $el);
1592 1660
1661 + if ( $rel_self_bookmark->length ) {
1662 + foreach ( $rel_self_bookmark as $tempEl ) {
1663 + $this->addMfClasses($tempEl, 'u-url');
1664 + $this->addUpgraded($tempEl, array('self', 'bookmark'));
1665 + }
1666 + }
1667 +
1668 + $reviewer_nodes = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " reviewer ")]', $el);
1669 +
1670 + if ( $reviewer_nodes->length ) {
1671 + foreach ( $reviewer_nodes as $tempEl ) {
1672 + if ( !$this->hasRootMf2($tempEl) ) {
1673 + $this->addMfClasses($tempEl, 'p-author h-card');
1674 + }
1675 + }
1676 + }
1677 +
1678 + $this->upgradeRelTagToCategory($el);
1679 + break;
1680 +
1593 1681 case 'vevent':
1594 - $location = $this->xpath->query( './/*[contains(concat(" ", normalize-space(@class), " "), " location ")]', $el );
1682 + $location_and_vcard = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " location ") and contains(concat(" ", normalize-space(@class), " "), " vcard ")]', $el);
1595 1683
1596 - if ( $location->length ) {
1597 - foreach ( $location as $tempEl ) {
1598 - if ( ! $this->hasRootMf2( $tempEl ) ) {
1599 - $this->addMfClasses( $tempEl, 'h-card' );
1600 - $this->backcompat( $tempEl, 'vcard' );
1684 + if ( $location_and_vcard->length ) {
1685 + foreach ( $location_and_vcard as $tempEl ) {
1686 + if ( !$this->hasRootMf2($tempEl) ) {
1687 + $this->addMfClasses($tempEl, 'p-location h-card');
1688 + $this->backcompat($tempEl, 'vcard');
1689 + $this->addUpgraded($tempEl, array('location', 'vcard'));
1601 1690 }
1602 1691 }
1603 1692 }
1604 - break;
1693 + break;
1605 1694 }
1606 1695
1607 1696 // root class has mf1 properties to be upgraded
1608 - if ( isset( $this->classicPropertyMap[ $classname ] ) ) {
1697 + if ( isset($this->classicPropertyMap[$classname]) ) {
1609 1698 // loop through each property of the mf1 root
1610 - foreach ( $this->classicPropertyMap[ $classname ] as $property => $data ) {
1611 - $propertyElements = $this->xpath->query( './/*[contains(concat(" ", normalize-space(@class), " "), " ' . $property . ' ")]', $el );
1699 + foreach ( $this->classicPropertyMap[$classname] as $property => $data ) {
1700 + $propertyElements = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " ' . $property . ' ")]', $el);
1612 1701
1613 1702 // loop through each element with the property
1614 1703 foreach ( $propertyElements as $propertyEl ) {
1615 - $hasRootMf2 = $this->hasRootMf2( $propertyEl );
1704 + $hasRootMf2 = $this->hasRootMf2($propertyEl);
1616 1705
1617 1706 // if the element has not been upgraded and we're not inside an mf2 root, recurse
1618 - if ( ! $this->isElementUpgraded( $propertyEl, $property ) && ! $isParentMf2 ) {
1619 - $temp_context = ( isset( $data['context'] ) ) ? $data['context'] : null;
1620 - $this->backcompat( $propertyEl, $temp_context, $hasRootMf2 );
1621 - $this->addMfClasses( $propertyEl, $data['replace'] );
1707 + if ( !$this->isElementUpgraded($propertyEl, $property) && !$isParentMf2 )
1708 + {
1709 + $temp_context = ( isset($data['context']) ) ? $data['context'] : null;
1710 + $this->backcompat($propertyEl, $temp_context, $hasRootMf2);
1711 + $this->addMfClasses($propertyEl, $data['replace']);
1622 1712 }
1623 1713
1624 - $this->addUpgraded( $propertyEl, $property );
1714 + $this->addUpgraded($propertyEl, $property);
1625 1715 }
1626 1716 }
1627 1717 }
1628 1718
1629 - if ( empty( $context ) && isset( $this->classicRootMap[ $classname ] ) && ! $elHasMf2 ) {
1630 - $this->addMfClasses( $el, $this->classicRootMap[ $classname ] );
1719 + if ( empty($context) && isset($this->classicRootMap[$classname]) && !$elHasMf2 ) {
1720 + $this->addMfClasses($el, $this->classicRootMap[$classname]);
1631 1721 }
1632 1722 }
1633 1723
1634 1724 return;
@@ -1635,22 +1725,21 @@
1635 1725 }
1636 1726
1637 1727 /**
1638 1728 * Add element + property as upgraded during backcompat
1639 - *
1640 - * @param DOMElement $el
1729 + * @param DOMElement $el
1641 1730 * @param string|array $property
1642 1731 */
1643 - public function addUpgraded( DOMElement $el, $property ) {
1644 - if ( ! is_array( $property ) ) {
1645 - $property = array( $property );
1732 + public function addUpgraded(DOMElement $el, $property) {
1733 + if ( !is_array($property) ) {
1734 + $property = array($property);
1646 1735 }
1647 1736
1648 1737 // add element to list of upgraded elements
1649 - if ( ! $this->upgraded->contains( $el ) ) {
1650 - $this->upgraded->attach( $el, $property );
1738 + if ( !$this->upgraded->offsetExists($el) ) {
1739 + $this->upgraded->offsetSet($el, $property);
1651 1740 } else {
1652 - $this->upgraded[ $el ] = array_merge( $this->upgraded[ $el ], $property );
1741 + $this->upgraded[$el] = array_merge($this->upgraded[$el], $property);
1653 1742 }
1654 1743 }
1655 1744
1656 1745 /**
@@ -1655,39 +1744,31 @@
1655 1744
1656 1745 /**
1657 1746 * Add the provided classes to an element.
1658 1747 * Does not add duplicate if class name already exists.
1659 - *
1660 1748 * @param DOMElement $el
1661 - * @param string $classes
1749 + * @param string $classes
1662 1750 */
1663 - public function addMfClasses( DOMElement $el, $classes ) {
1664 - $existingClasses = str_replace( array( "\t", "\n" ), ' ', $el->getAttribute( 'class' ) );
1665 - $existingClasses = array_filter( explode( ' ', $existingClasses ) );
1751 + public function addMfClasses(DOMElement $el, $classes) {
1752 + $existingClasses = str_replace(array("\t", "\n"), ' ', $el->getAttribute('class'));
1753 + $existingClasses = array_filter(explode(' ', $existingClasses));
1666 1754
1667 - $addClasses = array_diff( explode( ' ', $classes ), $existingClasses );
1755 + $addClasses = array_diff(explode(' ', $classes), $existingClasses);
1668 1756
1669 1757 if ( $addClasses ) {
1670 - $el->setAttribute( 'class', $el->getAttribute( 'class' ) . ' ' . implode( ' ', $addClasses ) );
1758 + $el->setAttribute('class', $el->getAttribute('class') . ' ' . implode(' ', $addClasses));
1671 1759 }
1672 1760 }
1673 1761
1674 1762 /**
1675 1763 * Check an element for mf2 h-* class, typically to determine if backcompat should be used
1676 - *
1677 1764 * @param DOMElement $el
1678 1765 */
1679 - public function hasRootMf2( \DOMElement $el ) {
1680 - $class = str_replace( array( "\t", "\n" ), ' ', $el->getAttribute( 'class' ) );
1681 - $classes = array_filter( explode( ' ', $class ) );
1766 + public function hasRootMf2(\DOMElement $el) {
1767 + $class = str_replace(array("\t", "\n"), ' ', $el->getAttribute('class'));
1682 1768
1683 - foreach ( $classes as $classname ) {
1684 - if ( strpos( $classname, 'h-' ) === 0 ) {
1685 - return true;
1686 - }
1687 - }
1688 -
1689 - return false;
1769 + // Check for valid mf2 root classnames, not just any classname with a h- prefix.
1770 + return count(mfNamesFromClass($class, 'h-')) > 0;
1690 1771 }
1691 1772
1692 1773 /**
1693 1774 * Convert Legacy Classnames
@@ -1698,22 +1779,22 @@
1698 1779 * @return Parser $this
1699 1780 */
1700 1781 public function convertLegacy() {
1701 1782 $doc = $this->doc;
1702 - $xp = new DOMXPath( $doc );
1783 + $xp = new DOMXPath($doc);
1703 1784
1704 1785 // replace all roots
1705 - foreach ( $this->classicRootMap as $old => $new ) {
1706 - foreach ( $xp->query( '//*[contains(concat(" ", @class, " "), " ' . $old . ' ") and not(contains(concat(" ", @class, " "), " ' . $new . ' "))]' ) as $el ) {
1707 - $el->setAttribute( 'class', $el->getAttribute( 'class' ) . ' ' . $new );
1786 + foreach ($this->classicRootMap as $old => $new) {
1787 + foreach ($xp->query('//*[contains(concat(" ", normalize-space(@class), " "), " ' . $old . ' ") and not(contains(concat(" ", normalize-space(@class), " "), " ' . $new . ' "))]') as $el) {
1788 + $el->setAttribute('class', $el->getAttribute('class') . ' ' . $new);
1708 1789 }
1709 1790 }
1710 1791
1711 - foreach ( $this->classicPropertyMap as $oldRoot => $properties ) {
1712 - $newRoot = $this->classicRootMap[ $oldRoot ];
1713 - foreach ( $properties as $old => $data ) {
1714 - foreach ( $xp->query( '//*[contains(concat(" ", @class, " "), " ' . $oldRoot . ' ")]//*[contains(concat(" ", @class, " "), " ' . $old . ' ") and not(contains(concat(" ", @class, " "), " ' . $data['replace'] . ' "))]' ) as $el ) {
1715 - $el->setAttribute( 'class', $el->getAttribute( 'class' ) . ' ' . $data['replace'] );
1792 + foreach ($this->classicPropertyMap as $oldRoot => $properties) {
1793 + $newRoot = $this->classicRootMap[$oldRoot];
1794 + foreach ($properties as $old => $data) {
1795 + foreach ($xp->query('//*[contains(concat(" ", normalize-space(@class), " "), " ' . $oldRoot . ' ")]//*[contains(concat(" ", normalize-space(@class), " "), " ' . $old . ' ") and not(contains(concat(" ", normalize-space(@class), " "), " ' . $data['replace'] . ' "))]') as $el) {
1796 + $el->setAttribute('class', $el->getAttribute('class') . ' ' . $data['replace']);
1716 1797 }
1717 1798 }
1718 1799 }
1719 1800
@@ -1725,228 +1806,241 @@
1725 1806 *
1726 1807 * Runs an XPath query over the current document. Works in exactly the same
1727 1808 * way as DOMXPath::query.
1728 1809 *
1729 - * @param string $expression
1810 + * @param string $expression
1730 1811 * @param DOMNode $context
1731 1812 * @return DOMNodeList
1732 1813 */
1733 - public function query( $expression, $context = null ) {
1734 - return $this->xpath->query( $expression, $context );
1814 + public function query($expression, $context = null) {
1815 + return $this->xpath->query($expression, $context);
1735 1816 }
1736 1817
1737 1818 /**
1738 1819 * Classic Root Classname map
1739 - *
1740 1820 * @var array
1741 1821 */
1742 1822 public $classicRootMap = array(
1743 - 'vcard' => 'h-card',
1744 - 'hfeed' => 'h-feed',
1745 - 'hentry' => 'h-entry',
1746 - 'hrecipe' => 'h-recipe',
1747 - 'hresume' => 'h-resume',
1748 - 'vevent' => 'h-event',
1749 - 'hreview' => 'h-review',
1823 + 'vcard' => 'h-card',
1824 + 'hfeed' => 'h-feed',
1825 + 'hentry' => 'h-entry',
1826 + 'hrecipe' => 'h-recipe',
1827 + 'hresume' => 'h-resume',
1828 + 'vevent' => 'h-event',
1829 + 'hreview' => 'h-review',
1830 + 'hreview-aggregate' => 'h-review-aggregate',
1750 1831 'hproduct' => 'h-product',
1751 - 'adr' => 'h-adr',
1832 + 'adr' => 'h-adr',
1833 + 'geo' => 'h-geo'
1752 1834 );
1753 1835
1754 1836 /**
1755 1837 * Mapping of mf1 properties to mf2 and the context they're parsed with
1756 - *
1757 1838 * @var array
1758 1839 */
1759 1840 public $classicPropertyMap = array(
1760 - 'vcard' => array(
1761 - 'fn' => array(
1762 - 'replace' => 'p-name',
1841 + 'vcard' => array(
1842 + 'fn' => array(
1843 + 'replace' => 'p-name'
1763 1844 ),
1764 - 'honorific-prefix' => array(
1765 - 'replace' => 'p-honorific-prefix',
1845 + 'honorific-prefix' => array(
1846 + 'replace' => 'p-honorific-prefix'
1766 1847 ),
1767 - 'given-name' => array(
1768 - 'replace' => 'p-given-name',
1848 + 'given-name' => array(
1849 + 'replace' => 'p-given-name'
1769 1850 ),
1770 - 'additional-name' => array(
1771 - 'replace' => 'p-additional-name',
1851 + 'additional-name' => array(
1852 + 'replace' => 'p-additional-name'
1772 1853 ),
1773 - 'family-name' => array(
1774 - 'replace' => 'p-family-name',
1854 + 'family-name' => array(
1855 + 'replace' => 'p-family-name'
1775 1856 ),
1776 - 'honorific-suffix' => array(
1777 - 'replace' => 'p-honorific-suffix',
1857 + 'honorific-suffix' => array(
1858 + 'replace' => 'p-honorific-suffix'
1778 1859 ),
1779 - 'nickname' => array(
1780 - 'replace' => 'p-nickname',
1860 + 'nickname' => array(
1861 + 'replace' => 'p-nickname'
1781 1862 ),
1782 - 'email' => array(
1783 - 'replace' => 'u-email',
1863 + 'email' => array(
1864 + 'replace' => 'u-email'
1784 1865 ),
1785 - 'logo' => array(
1786 - 'replace' => 'u-logo',
1866 + 'logo' => array(
1867 + 'replace' => 'u-logo'
1787 1868 ),
1788 - 'photo' => array(
1789 - 'replace' => 'u-photo',
1869 + 'photo' => array(
1870 + 'replace' => 'u-photo'
1790 1871 ),
1791 - 'url' => array(
1792 - 'replace' => 'u-url',
1872 + 'url' => array(
1873 + 'replace' => 'u-url'
1793 1874 ),
1794 - 'uid' => array(
1795 - 'replace' => 'u-uid',
1875 + 'uid' => array(
1876 + 'replace' => 'u-uid'
1796 1877 ),
1797 - 'category' => array(
1798 - 'replace' => 'p-category',
1878 + 'category' => array(
1879 + 'replace' => 'p-category'
1799 1880 ),
1800 - 'adr' => array(
1881 + 'adr' => array(
1801 1882 'replace' => 'p-adr',
1802 1883 ),
1803 - 'extended-address' => array(
1804 - 'replace' => 'p-extended-address',
1884 + 'extended-address' => array(
1885 + 'replace' => 'p-extended-address'
1805 1886 ),
1806 - 'street-address' => array(
1807 - 'replace' => 'p-street-address',
1887 + 'street-address' => array(
1888 + 'replace' => 'p-street-address'
1808 1889 ),
1809 - 'locality' => array(
1810 - 'replace' => 'p-locality',
1890 + 'locality' => array(
1891 + 'replace' => 'p-locality'
1811 1892 ),
1812 - 'region' => array(
1813 - 'replace' => 'p-region',
1893 + 'region' => array(
1894 + 'replace' => 'p-region'
1814 1895 ),
1815 - 'postal-code' => array(
1816 - 'replace' => 'p-postal-code',
1896 + 'postal-code' => array(
1897 + 'replace' => 'p-postal-code'
1817 1898 ),
1818 - 'country-name' => array(
1819 - 'replace' => 'p-country-name',
1899 + 'country-name' => array(
1900 + 'replace' => 'p-country-name'
1820 1901 ),
1821 - 'label' => array(
1822 - 'replace' => 'p-label',
1902 + 'label' => array(
1903 + 'replace' => 'p-label'
1823 1904 ),
1824 - 'geo' => array(
1905 + 'geo' => array(
1825 1906 'replace' => 'p-geo h-geo',
1907 + 'context' => 'geo'
1826 1908 ),
1827 - 'latitude' => array(
1828 - 'replace' => 'p-latitude',
1909 + 'latitude' => array(
1910 + 'replace' => 'p-latitude'
1829 1911 ),
1830 - 'longitude' => array(
1831 - 'replace' => 'p-longitude',
1912 + 'longitude' => array(
1913 + 'replace' => 'p-longitude'
1832 1914 ),
1833 - 'tel' => array(
1834 - 'replace' => 'p-tel',
1915 + 'tel' => array(
1916 + 'replace' => 'p-tel'
1835 1917 ),
1836 - 'note' => array(
1837 - 'replace' => 'p-note',
1918 + 'note' => array(
1919 + 'replace' => 'p-note'
1838 1920 ),
1839 - 'bday' => array(
1840 - 'replace' => 'dt-bday',
1921 + 'bday' => array(
1922 + 'replace' => 'dt-bday'
1841 1923 ),
1842 - 'key' => array(
1843 - 'replace' => 'u-key',
1924 + 'key' => array(
1925 + 'replace' => 'u-key'
1844 1926 ),
1845 - 'org' => array(
1846 - 'replace' => 'p-org',
1927 + 'org' => array(
1928 + 'replace' => 'p-org'
1847 1929 ),
1848 1930 'organization-name' => array(
1849 - 'replace' => 'p-organization-name',
1931 + 'replace' => 'p-organization-name'
1850 1932 ),
1851 1933 'organization-unit' => array(
1852 - 'replace' => 'p-organization-unit',
1934 + 'replace' => 'p-organization-unit'
1853 1935 ),
1854 - 'title' => array(
1855 - 'replace' => 'p-job-title',
1936 + 'title' => array(
1937 + 'replace' => 'p-job-title'
1856 1938 ),
1857 - 'role' => array(
1858 - 'replace' => 'p-role',
1939 + 'role' => array(
1940 + 'replace' => 'p-role'
1859 1941 ),
1860 - 'tz' => array(
1861 - 'replace' => 'p-tz',
1942 + 'tz' => array(
1943 + 'replace' => 'p-tz'
1862 1944 ),
1863 - 'rev' => array(
1864 - 'replace' => 'dt-rev',
1945 + 'rev' => array(
1946 + 'replace' => 'dt-rev'
1865 1947 ),
1866 1948 ),
1867 - 'hfeed' => array(
1868 - // nothing currently
1949 + 'hfeed' => array(
1950 + 'author' => array(
1951 + 'replace' => 'p-author h-card',
1952 + 'context' => 'vcard'
1953 + ),
1954 + 'url' => array(
1955 + 'replace' => 'u-url'
1956 + ),
1957 + 'photo' => array(
1958 + 'replace' => 'u-photo'
1959 + ),
1960 + 'category' => array(
1961 + 'replace' => 'p-category'
1962 + ),
1869 1963 ),
1870 - 'hentry' => array(
1871 - 'entry-title' => array(
1872 - 'replace' => 'p-name',
1964 + 'hentry' => array(
1965 + 'entry-title' => array(
1966 + 'replace' => 'p-name'
1873 1967 ),
1874 1968 'entry-summary' => array(
1875 - 'replace' => 'p-summary',
1969 + 'replace' => 'p-summary'
1876 1970 ),
1877 1971 'entry-content' => array(
1878 - 'replace' => 'e-content',
1972 + 'replace' => 'e-content'
1879 1973 ),
1880 - 'published' => array(
1881 - 'replace' => 'dt-published',
1974 + 'published' => array(
1975 + 'replace' => 'dt-published'
1882 1976 ),
1883 - 'updated' => array(
1884 - 'replace' => 'dt-updated',
1977 + 'updated' => array(
1978 + 'replace' => 'dt-updated'
1885 1979 ),
1886 - 'author' => array(
1980 + 'author' => array(
1887 1981 'replace' => 'p-author h-card',
1888 1982 'context' => 'vcard',
1889 1983 ),
1890 - 'category' => array(
1891 - 'replace' => 'p-category',
1984 + 'category' => array(
1985 + 'replace' => 'p-category'
1892 1986 ),
1893 1987 ),
1894 - 'hrecipe' => array(
1895 - 'fn' => array(
1896 - 'replace' => 'p-name',
1988 + 'hrecipe' => array(
1989 + 'fn' => array(
1990 + 'replace' => 'p-name'
1897 1991 ),
1898 - 'ingredient' => array(
1899 - 'replace' => 'p-ingredient',
1992 + 'ingredient' => array(
1993 + 'replace' => 'p-ingredient'
1900 1994 /**
1901 1995 * TODO: hRecipe 'value' and 'type' child mf not parsing correctly currently.
1902 1996 * Per http://microformats.org/wiki/hRecipe#Property_details, they're experimental.
1903 1997 */
1904 1998 ),
1905 - 'yield' => array(
1906 - 'replace' => 'p-yield',
1999 + 'yield' => array(
2000 + 'replace' => 'p-yield'
1907 2001 ),
1908 - 'instructions' => array(
1909 - 'replace' => 'e-instructions',
2002 + 'instructions' => array(
2003 + 'replace' => 'e-instructions'
1910 2004 ),
1911 - 'duration' => array(
1912 - 'replace' => 'dt-duration',
2005 + 'duration' => array(
2006 + 'replace' => 'dt-duration'
1913 2007 ),
1914 - 'photo' => array(
1915 - 'replace' => 'u-photo',
2008 + 'photo' => array(
2009 + 'replace' => 'u-photo'
1916 2010 ),
1917 - 'summary' => array(
1918 - 'replace' => 'p-summary',
2011 + 'summary' => array(
2012 + 'replace' => 'p-summary'
1919 2013 ),
1920 - 'author' => array(
2014 + 'author' => array(
1921 2015 'replace' => 'p-author h-card',
1922 2016 'context' => 'vcard',
1923 2017 ),
1924 - 'nutrition' => array(
1925 - 'replace' => 'p-nutrition',
2018 + 'nutrition' => array(
2019 + 'replace' => 'p-nutrition'
1926 2020 ),
1927 - 'category' => array(
1928 - 'replace' => 'p-category',
2021 + 'category' => array(
2022 + 'replace' => 'p-category'
1929 2023 ),
1930 2024 ),
1931 - 'hresume' => array(
1932 - 'summary' => array(
1933 - 'replace' => 'p-summary',
2025 + 'hresume' => array(
2026 + 'summary' => array(
2027 + 'replace' => 'p-summary'
1934 2028 ),
1935 - 'contact' => array(
2029 + 'contact' => array(
1936 2030 'replace' => 'p-contact h-card',
1937 2031 'context' => 'vcard',
1938 2032 ),
1939 - 'education' => array(
2033 + 'education' => array(
1940 2034 'replace' => 'p-education h-event',
1941 2035 'context' => 'vevent',
1942 2036 ),
1943 - 'experience' => array(
2037 + 'experience' => array(
1944 2038 'replace' => 'p-experience h-event',
1945 2039 'context' => 'vevent',
1946 2040 ),
1947 - 'skill' => array(
1948 - 'replace' => 'p-skill',
2041 + 'skill' => array(
2042 + 'replace' => 'p-skill'
1949 2043 ),
1950 2044 'affiliation' => array(
1951 2045 'replace' => 'p-affiliation h-card',
1952 2046 'context' => 'vcard',
@@ -1951,250 +2045,273 @@
1951 2045 'replace' => 'p-affiliation h-card',
1952 2046 'context' => 'vcard',
1953 2047 ),
1954 2048 ),
1955 - 'vevent' => array(
1956 - 'summary' => array(
1957 - 'replace' => 'p-name',
2049 + 'vevent' => array(
2050 + 'summary' => array(
2051 + 'replace' => 'p-name'
1958 2052 ),
1959 - 'dtstart' => array(
1960 - 'replace' => 'dt-start',
2053 + 'dtstart' => array(
2054 + 'replace' => 'dt-start'
1961 2055 ),
1962 - 'dtend' => array(
1963 - 'replace' => 'dt-end',
2056 + 'dtend' => array(
2057 + 'replace' => 'dt-end'
1964 2058 ),
1965 - 'duration' => array(
1966 - 'replace' => 'dt-duration',
2059 + 'duration' => array(
2060 + 'replace' => 'dt-duration'
1967 2061 ),
1968 2062 'description' => array(
1969 - 'replace' => 'p-description',
2063 + 'replace' => 'p-description'
1970 2064 ),
1971 - 'url' => array(
1972 - 'replace' => 'u-url',
2065 + 'url' => array(
2066 + 'replace' => 'u-url'
1973 2067 ),
1974 - 'category' => array(
1975 - 'replace' => 'p-category',
2068 + 'category' => array(
2069 + 'replace' => 'p-category'
1976 2070 ),
1977 - 'location' => array(
1978 - 'replace' => 'h-card',
1979 - 'context' => 'vcard',
2071 + 'location' => array(
2072 + 'replace' => 'p-location',
1980 2073 ),
1981 - 'geo' => array(
1982 - 'replace' => 'p-location h-geo',
2074 + 'geo' => array(
2075 + 'replace' => 'p-location h-geo'
1983 2076 ),
2077 + 'attendee' => array(
2078 + 'replace' => 'p-attendee h-card',
2079 + 'context' => 'vcard'
2080 + )
1984 2081 ),
1985 - 'hreview' => array(
1986 - 'summary' => array(
1987 - 'replace' => 'p-name',
2082 + 'hreview' => array(
2083 + 'summary' => array(
2084 + 'replace' => 'p-name'
1988 2085 ),
1989 - // fn: see item.fn below
1990 - // photo: see item.photo below
1991 - // url: see item.url below
1992 - 'item' => array(
2086 + # fn: see item.fn below
2087 + # photo: see item.photo below
2088 + # url: see item.url below
2089 + 'item' => array(
1993 2090 'replace' => 'p-item h-item',
1994 - 'context' => 'item',
2091 + 'context' => 'item'
1995 2092 ),
1996 - 'reviewer' => array(
1997 - 'replace' => 'p-author h-card',
1998 - 'context' => 'vcard',
2093 + # reviewer: see backcompat()
2094 + 'dtreviewed' => array(
2095 + 'replace' => 'dt-published'
1999 2096 ),
2000 - 'dtreviewed' => array(
2001 - 'replace' => 'dt-published',
2097 + 'rating' => array(
2098 + 'replace' => 'p-rating'
2002 2099 ),
2003 - 'rating' => array(
2004 - 'replace' => 'p-rating',
2100 + 'best' => array(
2101 + 'replace' => 'p-best'
2005 2102 ),
2006 - 'best' => array(
2007 - 'replace' => 'p-best',
2103 + 'worst' => array(
2104 + 'replace' => 'p-worst'
2008 2105 ),
2009 - 'worst' => array(
2010 - 'replace' => 'p-worst',
2106 + 'description' => array(
2107 + 'replace' => 'e-content'
2011 2108 ),
2012 - 'description' => array(
2013 - 'replace' => 'e-content',
2109 + 'category' => array(
2110 + 'replace' => 'p-category'
2014 2111 ),
2015 - 'category' => array(
2016 - 'replace' => 'p-category',
2112 + ),
2113 + 'hreview-aggregate' => array(
2114 + 'summary' => array(
2115 + 'replace' => 'p-name'
2017 2116 ),
2117 + # fn: see item.fn below
2118 + # photo: see item.photo below
2119 + # url: see item.url below
2120 + 'item' => array(
2121 + 'replace' => 'p-item h-item',
2122 + 'context' => 'item'
2123 + ),
2124 + 'rating' => array(
2125 + 'replace' => 'p-rating'
2126 + ),
2127 + 'best' => array(
2128 + 'replace' => 'p-best'
2129 + ),
2130 + 'worst' => array(
2131 + 'replace' => 'p-worst'
2132 + ),
2133 + 'average' => array(
2134 + 'replace' => 'p-average'
2135 + ),
2136 + 'count' => array(
2137 + 'replace' => 'p-count'
2138 + ),
2139 + 'votes' => array(
2140 + 'replace' => 'p-votes'
2141 + ),
2018 2142 ),
2019 2143 'hproduct' => array(
2020 - 'fn' => array(
2144 + 'fn' => array(
2021 2145 'replace' => 'p-name',
2022 2146 ),
2023 - 'photo' => array(
2147 + 'photo' => array(
2024 2148 'replace' => 'u-photo',
2025 2149 ),
2026 - 'brand' => array(
2150 + 'brand' => array(
2027 2151 'replace' => 'p-brand',
2028 2152 ),
2029 - 'category' => array(
2153 + 'category' => array(
2030 2154 'replace' => 'p-category',
2031 2155 ),
2032 2156 'description' => array(
2033 2157 'replace' => 'p-description',
2034 2158 ),
2035 - 'identifier' => array(
2159 + 'identifier' => array(
2036 2160 'replace' => 'u-identifier',
2037 2161 ),
2038 - 'url' => array(
2162 + 'url' => array(
2039 2163 'replace' => 'u-url',
2040 2164 ),
2041 - 'review' => array(
2042 - 'replace' => 'p-review h-review',
2165 + // review is handled in the special processing section to allow for 'review hreview-aggregate'
2166 + 'price' => array(
2167 + 'replace' => 'p-price'
2043 2168 ),
2044 - 'price' => array(
2045 - 'replace' => 'p-price',
2046 - ),
2047 2169 ),
2048 - 'item' => array(
2049 - 'fn' => array(
2050 - 'replace' => 'p-name',
2170 + 'item' => array(
2171 + 'fn' => array(
2172 + 'replace' => 'p-name'
2051 2173 ),
2052 - 'url' => array(
2053 - 'replace' => 'u-url',
2174 + 'url' => array(
2175 + 'replace' => 'u-url'
2054 2176 ),
2055 2177 'photo' => array(
2056 - 'replace' => 'u-photo',
2178 + 'replace' => 'u-photo'
2057 2179 ),
2058 2180 ),
2059 - 'adr' => array(
2060 - 'post-office-box' => array(
2061 - 'replace' => 'p-post-office-box',
2181 + 'adr' => array(
2182 + 'post-office-box' => array(
2183 + 'replace' => 'p-post-office-box'
2062 2184 ),
2063 2185 'extended-address' => array(
2064 - 'replace' => 'p-extended-address',
2186 + 'replace' => 'p-extended-address'
2065 2187 ),
2066 - 'street-address' => array(
2067 - 'replace' => 'p-street-address',
2188 + 'street-address' => array(
2189 + 'replace' => 'p-street-address'
2068 2190 ),
2069 - 'locality' => array(
2070 - 'replace' => 'p-locality',
2191 + 'locality' => array(
2192 + 'replace' => 'p-locality'
2071 2193 ),
2072 - 'region' => array(
2073 - 'replace' => 'p-region',
2194 + 'region' => array(
2195 + 'replace' => 'p-region'
2074 2196 ),
2075 - 'postal-code' => array(
2076 - 'replace' => 'p-postal-code',
2197 + 'postal-code' => array(
2198 + 'replace' => 'p-postal-code'
2077 2199 ),
2078 - 'country-name' => array(
2079 - 'replace' => 'p-country-name',
2200 + 'country-name' => array(
2201 + 'replace' => 'p-country-name'
2080 2202 ),
2081 2203 ),
2082 - 'geo' => array(
2083 - 'latitude' => array(
2084 - 'replace' => 'p-latitude',
2204 + 'geo' => array(
2205 + 'latitude' => array(
2206 + 'replace' => 'p-latitude'
2085 2207 ),
2086 2208 'longitude' => array(
2087 - 'replace' => 'p-longitude',
2209 + 'replace' => 'p-longitude'
2088 2210 ),
2089 2211 ),
2090 2212 );
2091 2213 }
2092 2214
2093 -function parseUriToComponents( $uri ) {
2215 +function parseUriToComponents($uri) {
2094 2216 $result = array(
2095 - 'scheme' => null,
2217 + 'scheme' => null,
2096 2218 'authority' => null,
2097 - 'path' => null,
2098 - 'query' => null,
2099 - 'fragment' => null,
2219 + 'path' => null,
2220 + 'query' => null,
2221 + 'fragment' => null
2100 2222 );
2101 2223
2102 - $u = parse_url( $uri );
2224 + if ( ! is_string( $uri ) ) {
2225 + return $result;
2226 + }
2227 +
2228 + $u = parse_url($uri);
2103 2229 if ( ! $u ) {
2104 2230 return $result;
2105 2231 }
2106 2232
2107 - if ( array_key_exists( 'scheme', $u ) ) {
2233 + if(array_key_exists('scheme', $u))
2108 2234 $result['scheme'] = $u['scheme'];
2109 - }
2110 2235
2111 - if ( array_key_exists( 'host', $u ) ) {
2112 - if ( array_key_exists( 'user', $u ) ) {
2236 + if(array_key_exists('host', $u)) {
2237 + if(array_key_exists('user', $u))
2113 2238 $result['authority'] = $u['user'];
2114 - }
2115 - if ( array_key_exists( 'pass', $u ) ) {
2239 + if(array_key_exists('pass', $u))
2116 2240 $result['authority'] .= ':' . $u['pass'];
2117 - }
2118 - if ( array_key_exists( 'user', $u ) || array_key_exists( 'pass', $u ) ) {
2241 + if(array_key_exists('user', $u) || array_key_exists('pass', $u))
2119 2242 $result['authority'] .= '@';
2120 - }
2121 2243 $result['authority'] .= $u['host'];
2122 - if ( array_key_exists( 'port', $u ) ) {
2244 + if(array_key_exists('port', $u))
2123 2245 $result['authority'] .= ':' . $u['port'];
2124 - }
2125 2246 }
2126 2247
2127 - if ( array_key_exists( 'path', $u ) ) {
2248 + if(array_key_exists('path', $u))
2128 2249 $result['path'] = $u['path'];
2129 - }
2130 2250
2131 - if ( array_key_exists( 'query', $u ) ) {
2251 + if(array_key_exists('query', $u))
2132 2252 $result['query'] = $u['query'];
2133 - }
2134 2253
2135 - if ( array_key_exists( 'fragment', $u ) ) {
2254 + if(array_key_exists('fragment', $u))
2136 2255 $result['fragment'] = $u['fragment'];
2137 - }
2138 2256
2139 2257 return $result;
2140 2258 }
2141 2259
2142 -function resolveUrl( $baseURI, $referenceURI ) {
2260 +function resolveUrl($baseURI, $referenceURI) {
2143 2261 $target = array(
2144 - 'scheme' => null,
2262 + 'scheme' => null,
2145 2263 'authority' => null,
2146 - 'path' => null,
2147 - 'query' => null,
2148 - 'fragment' => null,
2264 + 'path' => null,
2265 + 'query' => null,
2266 + 'fragment' => null
2149 2267 );
2150 2268
2151 - // 5.2.1 Pre-parse the Base URI
2152 - // The base URI (Base) is established according to the procedure of
2153 - // Section 5.1 and parsed into the five main components described in
2154 - // Section 3
2155 - $base = parseUriToComponents( $baseURI );
2269 + # 5.2.1 Pre-parse the Base URI
2270 + # The base URI (Base) is established according to the procedure of
2271 + # Section 5.1 and parsed into the five main components described in
2272 + # Section 3
2273 + $base = parseUriToComponents($baseURI);
2156 2274
2157 - // If base path is blank (http://example.com) then set it to /
2158 - // (I can't tell if this is actually in the RFC or not, but seems like it makes sense)
2159 - if ( $base['path'] == null ) {
2275 + # If base path is blank (http://example.com) then set it to /
2276 + # (I can't tell if this is actually in the RFC or not, but seems like it makes sense)
2277 + if($base['path'] == null)
2160 2278 $base['path'] = '/';
2161 - }
2162 2279
2163 - // 5.2.2. Transform References
2280 + # 5.2.2. Transform References
2164 2281
2165 - // The URI reference is parsed into the five URI components
2166 - // (R.scheme, R.authority, R.path, R.query, R.fragment) = parse(R);
2167 - $reference = parseUriToComponents( $referenceURI );
2282 + # The URI reference is parsed into the five URI components
2283 + # (R.scheme, R.authority, R.path, R.query, R.fragment) = parse(R);
2284 + $reference = parseUriToComponents($referenceURI);
2168 2285
2169 - // A non-strict parser may ignore a scheme in the reference
2170 - // if it is identical to the base URI's scheme.
2171 - // TODO
2286 + # A non-strict parser may ignore a scheme in the reference
2287 + # if it is identical to the base URI's scheme.
2288 + # TODO
2172 2289
2173 - if ( $reference['scheme'] ) {
2290 + if($reference['scheme']) {
2174 2291 $target['scheme'] = $reference['scheme'];
2175 2292 $target['authority'] = $reference['authority'];
2176 - $target['path'] = removeDotSegments( $reference['path'] );
2293 + $target['path'] = removeDotSegments($reference['path']);
2177 2294 $target['query'] = $reference['query'];
2178 2295 } else {
2179 - if ( $reference['authority'] ) {
2296 + if($reference['authority']) {
2180 2297 $target['authority'] = $reference['authority'];
2181 - $target['path'] = removeDotSegments( $reference['path'] );
2298 + $target['path'] = removeDotSegments($reference['path']);
2182 2299 $target['query'] = $reference['query'];
2183 2300 } else {
2184 - if ( $reference['path'] == '' ) {
2301 + if($reference['path'] == '') {
2185 2302 $target['path'] = $base['path'];
2186 - if ( $reference['query'] ) {
2303 + if($reference['query']) {
2187 2304 $target['query'] = $reference['query'];
2188 2305 } else {
2189 2306 $target['query'] = $base['query'];
2190 2307 }
2191 2308 } else {
2192 - if ( substr( $reference['path'], 0, 1 ) == '/' ) {
2193 - $target['path'] = removeDotSegments( $reference['path'] );
2309 + if(substr($reference['path'], 0, 1) == '/') {
2310 + $target['path'] = removeDotSegments($reference['path']);
2194 2311 } else {
2195 - $target['path'] = mergePaths( $base, $reference );
2196 - $target['path'] = removeDotSegments( $target['path'] );
2312 + $target['path'] = mergePaths($base, $reference);
2313 + $target['path'] = removeDotSegments($target['path']);
2197 2314 }
2198 2315 $target['query'] = $reference['query'];
2199 2316 }
2200 2317 $target['authority'] = $base['authority'];
@@ -2202,46 +2319,46 @@
2202 2319 $target['scheme'] = $base['scheme'];
2203 2320 }
2204 2321 $target['fragment'] = $reference['fragment'];
2205 2322
2206 - // 5.3 Component Recomposition
2323 + # 5.3 Component Recomposition
2207 2324 $result = '';
2208 - if ( $target['scheme'] ) {
2325 + if($target['scheme']) {
2209 2326 $result .= $target['scheme'] . ':';
2210 2327 }
2211 - if ( $target['authority'] ) {
2328 + if($target['authority']) {
2212 2329 $result .= '//' . $target['authority'];
2213 2330 }
2214 2331 $result .= $target['path'];
2215 - if ( $target['query'] ) {
2332 + if($target['query']) {
2216 2333 $result .= '?' . $target['query'];
2217 2334 }
2218 - if ( $target['fragment'] ) {
2335 + if($target['fragment']) {
2219 2336 $result .= '#' . $target['fragment'];
2220 - } elseif ( $referenceURI == '#' ) {
2337 + } elseif($referenceURI == '#') {
2221 2338 $result .= '#';
2222 2339 }
2223 2340 return $result;
2224 2341 }
2225 2342
2226 -// 5.2.3 Merge Paths
2227 -function mergePaths( $base, $reference ) {
2228 - // If the base URI has a defined authority component and an empty
2229 - // path,
2230 - if ( $base['authority'] && $base['path'] == null ) {
2231 - // then return a string consisting of "/" concatenated with the
2232 - // reference's path; otherwise,
2343 +# 5.2.3 Merge Paths
2344 +function mergePaths($base, $reference) {
2345 + # If the base URI has a defined authority component and an empty
2346 + # path,
2347 + if($base['authority'] && $base['path'] == null) {
2348 + # then return a string consisting of "/" concatenated with the
2349 + # reference's path; otherwise,
2233 2350 $merged = '/' . $reference['path'];
2234 2351 } else {
2235 - if ( ( $pos = strrpos( $base['path'], '/' ) ) !== false ) {
2236 - // return a string consisting of the reference's path component
2237 - // appended to all but the last segment of the base URI's path (i.e.,
2238 - // excluding any characters after the right-most "/" in the base URI
2239 - // path,
2240 - $merged = substr( $base['path'], 0, $pos + 1 ) . $reference['path'];
2352 + if(($pos=strrpos($base['path'], '/')) !== false) {
2353 + # return a string consisting of the reference's path component
2354 + # appended to all but the last segment of the base URI's path (i.e.,
2355 + # excluding any characters after the right-most "/" in the base URI
2356 + # path,
2357 + $merged = substr($base['path'], 0, $pos + 1) . $reference['path'];
2241 2358 } else {
2242 - // or excluding the entire base URI path if it does not contain
2243 - // any "/" characters).
2359 + # or excluding the entire base URI path if it does not contain
2360 + # any "/" characters).
2244 2361 $merged = $base['path'];
2245 2362 }
2246 2363 }
2247 2364 return $merged;
@@ -2246,101 +2363,101 @@
2246 2363 }
2247 2364 return $merged;
2248 2365 }
2249 2366
2250 -// 5.2.4.A Remove leading ../ or ./
2251 -function removeLeadingDotSlash( &$input ) {
2252 - if ( substr( $input, 0, 3 ) == '../' ) {
2253 - $input = substr( $input, 3 );
2254 - } elseif ( substr( $input, 0, 2 ) == './' ) {
2255 - $input = substr( $input, 2 );
2367 +# 5.2.4.A Remove leading ../ or ./
2368 +function removeLeadingDotSlash(&$input) {
2369 + if(substr($input, 0, 3) == '../') {
2370 + $input = substr($input, 3);
2371 + } elseif(substr($input, 0, 2) == './') {
2372 + $input = substr($input, 2);
2256 2373 }
2257 2374 }
2258 2375
2259 -// 5.2.4.B Replace leading /. with /
2260 -function removeLeadingSlashDot( &$input ) {
2261 - if ( substr( $input, 0, 3 ) == '/./' ) {
2262 - $input = '/' . substr( $input, 3 );
2376 +# 5.2.4.B Replace leading /. with /
2377 +function removeLeadingSlashDot(&$input) {
2378 + if(substr($input, 0, 3) == '/./') {
2379 + $input = '/' . substr($input, 3);
2263 2380 } else {
2264 - $input = '/' . substr( $input, 2 );
2381 + $input = '/' . substr($input, 2);
2265 2382 }
2266 2383 }
2267 2384
2268 -// 5.2.4.C Given leading /../ remove component from output buffer
2269 -function removeOneDirLevel( &$input, &$output ) {
2270 - if ( substr( $input, 0, 4 ) == '/../' ) {
2271 - $input = '/' . substr( $input, 4 );
2385 +# 5.2.4.C Given leading /../ remove component from output buffer
2386 +function removeOneDirLevel(&$input, &$output) {
2387 + if(substr($input, 0, 4) == '/../') {
2388 + $input = '/' . substr($input, 4);
2272 2389 } else {
2273 - $input = '/' . substr( $input, 3 );
2390 + $input = '/' . substr($input, 3);
2274 2391 }
2275 - $output = substr( $output, 0, strrpos( $output, '/' ) );
2392 + $output = substr($output, 0, strrpos($output, '/'));
2276 2393 }
2277 2394
2278 -// 5.2.4.D Remove . and .. if it's the only thing in the input
2279 -function removeLoneDotDot( &$input ) {
2280 - if ( $input == '.' ) {
2281 - $input = substr( $input, 1 );
2395 +# 5.2.4.D Remove . and .. if it's the only thing in the input
2396 +function removeLoneDotDot(&$input) {
2397 + if($input == '.') {
2398 + $input = substr($input, 1);
2282 2399 } else {
2283 - $input = substr( $input, 2 );
2400 + $input = substr($input, 2);
2284 2401 }
2285 2402 }
2286 2403
2287 -// 5.2.4.E Move one segment from input to output
2288 -function moveOneSegmentFromInput( &$input, &$output ) {
2289 - if ( substr( $input, 0, 1 ) != '/' ) {
2290 - $pos = strpos( $input, '/' );
2404 +# 5.2.4.E Move one segment from input to output
2405 +function moveOneSegmentFromInput(&$input, &$output) {
2406 + if(substr($input, 0, 1) != '/') {
2407 + $pos = strpos($input, '/');
2291 2408 } else {
2292 - $pos = strpos( $input, '/', 1 );
2409 + $pos = strpos($input, '/', 1);
2293 2410 }
2294 2411
2295 - if ( $pos === false ) {
2412 + if($pos === false) {
2296 2413 $output .= $input;
2297 2414 $input = '';
2298 2415 } else {
2299 - $output .= substr( $input, 0, $pos );
2300 - $input = substr( $input, $pos );
2416 + $output .= substr($input, 0, $pos);
2417 + $input = substr($input, $pos);
2301 2418 }
2302 2419 }
2303 2420
2304 -// 5.2.4 Remove Dot Segments
2305 -function removeDotSegments( $path ) {
2306 - // 1. The input buffer is initialized with the now-appended path
2307 - // components and the output buffer is initialized to the empty
2308 - // string.
2421 +# 5.2.4 Remove Dot Segments
2422 +function removeDotSegments($path) {
2423 + # 1. The input buffer is initialized with the now-appended path
2424 + # components and the output buffer is initialized to the empty
2425 + # string.
2309 2426 $input = $path;
2310 2427 $output = '';
2311 2428
2312 2429 $step = 0;
2313 2430
2314 - // 2. While the input buffer is not empty, loop as follows:
2315 - while ( $input ) {
2431 + # 2. While the input buffer is not empty, loop as follows:
2432 + while($input) {
2316 2433 $step++;
2317 2434
2318 - if ( substr( $input, 0, 3 ) == '../' || substr( $input, 0, 2 ) == './' ) {
2319 - // A. If the input buffer begins with a prefix of "../" or "./",
2320 - // then remove that prefix from the input buffer; otherwise,
2321 - removeLeadingDotSlash( $input );
2322 - } elseif ( substr( $input, 0, 3 ) == '/./' || $input == '/.' ) {
2323 - // B. if the input buffer begins with a prefix of "/./" or "/.",
2324 - // where "." is a complete path segment, then replace that
2325 - // prefix with "/" in the input buffer; otherwise,
2326 - removeLeadingSlashDot( $input );
2327 - } elseif ( substr( $input, 0, 4 ) == '/../' || $input == '/..' ) {
2328 - // C. if the input buffer begins with a prefix of "/../" or "/..",
2329 - // where ".." is a complete path segment, then replace that
2330 - // prefix with "/" in the input buffer and remove the last
2331 - // segment and its preceding "/" (if any) from the output
2332 - // buffer; otherwise,
2333 - removeOneDirLevel( $input, $output );
2334 - } elseif ( $input == '.' || $input == '..' ) {
2335 - // D. if the input buffer consists only of "." or "..", then remove
2336 - // that from the input buffer; otherwise,
2337 - removeLoneDotDot( $input );
2435 + if(substr($input, 0, 3) == '../' || substr($input, 0, 2) == './') {
2436 + # A. If the input buffer begins with a prefix of "../" or "./",
2437 + # then remove that prefix from the input buffer; otherwise,
2438 + removeLeadingDotSlash($input);
2439 + } elseif(substr($input, 0, 3) == '/./' || $input == '/.') {
2440 + # B. if the input buffer begins with a prefix of "/./" or "/.",
2441 + # where "." is a complete path segment, then replace that
2442 + # prefix with "/" in the input buffer; otherwise,
2443 + removeLeadingSlashDot($input);
2444 + } elseif(substr($input, 0, 4) == '/../' || $input == '/..') {
2445 + # C. if the input buffer begins with a prefix of "/../" or "/..",
2446 + # where ".." is a complete path segment, then replace that
2447 + # prefix with "/" in the input buffer and remove the last
2448 + # segment and its preceding "/" (if any) from the output
2449 + # buffer; otherwise,
2450 + removeOneDirLevel($input, $output);
2451 + } elseif($input == '.' || $input == '..') {
2452 + # D. if the input buffer consists only of "." or "..", then remove
2453 + # that from the input buffer; otherwise,
2454 + removeLoneDotDot($input);
2338 2455 } else {
2339 - // E. move the first path segment in the input buffer to the end of
2340 - // the output buffer and any subsequent characters up to, but not including,
2341 - // the next "/" character or the end of the input buffer
2342 - moveOneSegmentFromInput( $input, $output );
2456 + # E. move the first path segment in the input buffer to the end of
2457 + # the output buffer and any subsequent characters up to, but not including,
2458 + # the next "/" character or the end of the input buffer
2459 + moveOneSegmentFromInput($input, $output);
2343 2460 }
2344 2461 }
2345 2462
2346 2463 return $output;