From c77bce12e53418c2457f17ce1e34238f7baa448d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 15 Aug 2015 11:48:39 +0200 Subject: [PATCH] Mf2 extlib update from https://github.com/indieweb/php-mf2/ --- extlib/Mf2/Parser.php | 380 +++++++++++++++++++++++++----------------- 1 file changed, 227 insertions(+), 153 deletions(-) diff --git a/extlib/Mf2/Parser.php b/extlib/Mf2/Parser.php index 27805f2324..b8a954f2c8 100644 --- a/extlib/Mf2/Parser.php +++ b/extlib/Mf2/Parser.php @@ -13,17 +13,17 @@ use stdClass; /** * Parse Microformats2 - * + * * Functional shortcut for the commonest cases of parsing microformats2 from HTML. - * + * * Example usage: - * + * * use Mf2; * $output = Mf2\parse('Barnaby Walters'); * echo json_encode($output, JSON_PRETTY_PRINT); - * + * * Produces: - * + * * { * "items": [ * { @@ -35,7 +35,7 @@ use stdClass; * ], * "rels": {} * } - * + * * @param string|DOMDocument $input The HTML string or DOMDocument object to parse * @param string $url The URL the input document was found at, for relative URL resolution * @param bool $convertClassic whether or not to convert classic microformats @@ -84,7 +84,7 @@ function fetch($url, $convertClassic = true, &$curlInfo=null) { /** * Unicode to HTML Entities * @param string $input String containing characters to convert into HTML entities - * @return string + * @return string */ function unicodeToHtmlEntities($input) { return mb_convert_encoding($input, 'HTML-ENTITIES', mb_detect_encoding($input)); @@ -92,10 +92,10 @@ function unicodeToHtmlEntities($input) { /** * Collapse Whitespace - * + * * Collapses any sequences of whitespace within a string into a single space * character. - * + * * @deprecated since v0.2.3 * @param string $str * @return string @@ -113,10 +113,10 @@ function unicodeTrim($str) { /** * Microformat Name From Class string - * - * Given the value of @class, get the relevant mf classnames (e.g. h-card, + * + * Given the value of @class, get the relevant mf classnames (e.g. h-card, * p-name). - * + * * @param string $class A space delimited list of classnames * @param string $prefix The prefix to look for * @return string|array The prefixed name of the first microfomats class found or false @@ -127,9 +127,9 @@ function mfNamesFromClass($class, $prefix='h-') { $matches = array(); foreach ($classes as $classname) { - $compare_classname = strtolower(' ' . $classname); - $compare_prefix = strtolower(' ' . $prefix); - if (stristr($compare_classname, $compare_prefix) !== false && ($compare_classname != $compare_prefix)) { + $compare_classname = ' ' . $classname; + $compare_prefix = ' ' . $prefix; + if (strstr($compare_classname, $compare_prefix) !== false && ($compare_classname != $compare_prefix)) { $matches[] = ($prefix === 'h-') ? $classname : substr($classname, strlen($prefix)); } } @@ -139,10 +139,10 @@ function mfNamesFromClass($class, $prefix='h-') { /** * Get Nested µf Property Name From Class - * - * Returns all the p-, u-, dt- or e- prefixed classnames it finds in a + * + * Returns all the p-, u-, dt- or e- prefixed classnames it finds in a * space-separated string. - * + * * @param string $class * @return array */ @@ -153,19 +153,24 @@ function nestedMfPropertyNamesFromClass($class) { $class = str_replace(array(' ', ' ', "\n"), ' ', $class); foreach (explode(' ', $class) as $classname) { foreach ($prefixes as $prefix) { - $compare_classname = strtolower(' ' . $classname); - if (stristr($compare_classname, $prefix) && ($compare_classname != $prefix)) { - $propertyNames = array_merge($propertyNames, mfNamesFromClass($classname, ltrim($prefix))); + // Check if $classname is a valid property classname for $prefix. + if (mb_substr($classname, 0, mb_strlen($prefix)) == $prefix && $classname != $prefix) { + $propertyName = mb_substr($classname, mb_strlen($prefix)); + $propertyNames[$propertyName][] = $prefix; } } } + + foreach ($propertyNames as $property => $prefixes) { + $propertyNames[$property] = array_unique($prefixes); + } return $propertyNames; } /** * Wraps mfNamesFromClass to handle an element as input (common) - * + * * @param DOMElement $e The element to get the classname for * @param string $prefix The prefix to look for * @return mixed See return value of mf2\Parser::mfNameFromClass() @@ -192,28 +197,27 @@ function convertTimeFormat($time) { $hh = $mm = $ss = ''; preg_match('/(\d{1,2}):?(\d{2})?:?(\d{2})?(a\.?m\.?|p\.?m\.?)?/i', $time, $matches); - // if no am/pm specified + // If no am/pm is specified: if (empty($matches[4])) { return $time; - } - // else am/pm specified - else { + } else { + // Otherwise, am/pm is specified. $meridiem = strtolower(str_replace('.', '', $matches[4])); - // hours + // Hours. $hh = $matches[1]; - // add 12 to the pm hours + // Add 12 to hours if pm applies. if ($meridiem == 'pm' && ($hh < 12)) { $hh += 12; } $hh = str_pad($hh, 2, '0', STR_PAD_LEFT); - // minutes + // Minutes. $mm = (empty($matches[2]) ) ? '00' : $matches[2]; - // seconds, only if supplied + // Seconds, only if supplied. if (!empty($matches[3])) { $ss = $matches[3]; } @@ -229,11 +233,11 @@ function convertTimeFormat($time) { /** * Microformats2 Parser - * + * * A class which holds state for parsing microformats2 from HTML. - * + * * Example usage: - * + * * use Mf2; * $parser = new Mf2\Parser('

Barnaby Walters

'); * $output = $parser->parse(); @@ -244,18 +248,18 @@ class Parser { /** @var DOMXPath object which can be used to query over any fragment*/ public $xpath; - + /** @var DOMDocument */ public $doc; - + /** @var SplObjectStorage */ protected $parsed; - + public $jsonMode; /** * Constructor - * + * * @param DOMDocument|string $input The data to parse. A string of HTML or a DOMDocument * @param string $url The URL of the parsed document, for relative URL resolution * @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. @@ -271,20 +275,20 @@ class Parser { $doc = new DOMDocument(); @$doc->loadHTML(''); } - + $this->xpath = new DOMXPath($doc); - + $baseurl = $url; foreach ($this->xpath->query('//base[@href]') as $base) { $baseElementUrl = $base->getAttribute('href'); - + if (parse_url($baseElementUrl, PHP_URL_SCHEME) === null) { /* The base element URL is relative to the document URL. * * :/ * * Perhaps the author was high? */ - + $baseurl = resolveUrl($url, $baseElementUrl); } else { $baseurl = $baseElementUrl; @@ -296,31 +300,31 @@ class Parser { foreach ($this->xpath->query('//template') as $templateEl) { $templateEl->parentNode->removeChild($templateEl); } - + $this->baseurl = $baseurl; $this->doc = $doc; $this->parsed = new SplObjectStorage(); $this->jsonMode = $jsonMode; } - + private function elementPrefixParsed(\DOMElement $e, $prefix) { if (!$this->parsed->contains($e)) $this->parsed->attach($e, array()); - + $prefixes = $this->parsed[$e]; $prefixes[] = $prefix; $this->parsed[$e] = $prefixes; } - + private function isElementParsed(\DOMElement $e, $prefix) { if (!$this->parsed->contains($e)) return false; - + $prefixes = $this->parsed[$e]; - + if (!in_array($prefix, $prefixes)) return false; - + return true; } @@ -352,72 +356,72 @@ class Parser { // TODO: figure out if this has problems with sms: and geo: URLs public function resolveUrl($url) { - // If the URL is seriously malformed it’s probably beyond the scope of this + // If the URL is seriously malformed it’s probably beyond the scope of this // parser to try to do anything with it. if (parse_url($url) === false) return $url; - + $scheme = parse_url($url, PHP_URL_SCHEME); - + if (empty($scheme) and !empty($this->baseurl)) { return resolveUrl($this->baseurl, $url); } else { return $url; } } - + // Parsing Functions - + /** - * Parse value-class/value-title on an element, joining with $separator if + * Parse value-class/value-title on an element, joining with $separator if * there are multiple. - * + * * @param \DOMElement $e * @param string $separator = '' if multiple value-title elements, join with this string * @return string|null the parsed value or null if value-class or -title aren’t in use */ public function parseValueClassTitle(\DOMElement $e, $separator = '') { $valueClassElements = $this->xpath->query('./*[contains(concat(" ", @class, " "), " value ")]', $e); - + if ($valueClassElements->length !== 0) { // Process value-class stuff $val = ''; foreach ($valueClassElements as $el) { $val .= $this->textContent($el); } - + return unicodeTrim($val); } - + $valueTitleElements = $this->xpath->query('./*[contains(concat(" ", @class, " "), " value-title ")]', $e); - + if ($valueTitleElements->length !== 0) { // Process value-title stuff $val = ''; foreach ($valueTitleElements as $el) { $val .= $el->getAttribute('title'); } - + return unicodeTrim($val); } - + // No value-title or -class in this element return null; } - + /** * Given an element with class="p-*", get it’s value - * + * * @param DOMElement $p The element to parse * @return string The plaintext value of $p, dependant on type * @todo Make this adhere to value-class */ public function parseP(\DOMElement $p) { $classTitle = $this->parseValueClassTitle($p, ' '); - + if ($classTitle !== null) return $classTitle; - + if ($p->tagName == 'img' and $p->getAttribute('alt') !== '') { $pValue = $p->getAttribute('alt'); } elseif ($p->tagName == 'area' and $p->getAttribute('alt') !== '') { @@ -429,13 +433,13 @@ class Parser { } else { $pValue = unicodeTrim($this->textContent($p)); } - + return $pValue; } /** * Given an element with class="u-*", get the value of the URL - * + * * @param DOMElement $u The element to parse * @return string The plaintext value of $u, dependant on type * @todo make this adhere to value-class @@ -443,18 +447,18 @@ class Parser { public function parseU(\DOMElement $u) { if (($u->tagName == 'a' or $u->tagName == 'area') and $u->getAttribute('href') !== null) { $uValue = $u->getAttribute('href'); - } elseif ($u->tagName == 'img' and $u->getAttribute('src') !== null) { + } elseif (in_array($u->tagName, array('img', 'audio', 'video', 'source')) and $u->getAttribute('src') !== null) { $uValue = $u->getAttribute('src'); } elseif ($u->tagName == 'object' and $u->getAttribute('data') !== null) { $uValue = $u->getAttribute('data'); } - + if (isset($uValue)) { return $this->resolveUrl($uValue); } - + $classTitle = $this->parseValueClassTitle($u); - + if ($classTitle !== null) { return $classTitle; } elseif ($u->tagName == 'abbr' and $u->getAttribute('title') !== null) { @@ -468,7 +472,7 @@ class Parser { /** * Given an element with class="dt-*", get the value of the datetime as a php date object - * + * * @param DOMElement $dt The element to parse * @param array $dates Array of dates processed so far * @return string The datetime string found @@ -477,11 +481,11 @@ class Parser { // Check for value-class pattern $valueClassChildren = $this->xpath->query('./*[contains(concat(" ", @class, " "), " value ") or contains(concat(" ", @class, " "), " value-title ")]', $dt); $dtValue = false; - + if ($valueClassChildren->length > 0) { // They’re using value-class $dateParts = array(); - + foreach ($valueClassChildren as $e) { if (strstr(' ' . $e->getAttribute('class') . ' ', ' value-title ')) { $title = $e->getAttribute('title'); @@ -591,16 +595,16 @@ class Parser { $dtValue = $dt->nodeValue; } - if ( preg_match('/(\d{4}-\d{2}-\d{2})/', $dtValue, $matches) ) { + if (preg_match('/(\d{4}-\d{2}-\d{2})/', $dtValue, $matches)) { $dates[] = $matches[0]; } } /** - * if $dtValue is only a time and there are recently parsed dates, - * form the full date-time using the most recnetly parsed dt- value + * if $dtValue is only a time and there are recently parsed dates, + * form the full date-time using the most recently parsed dt- value */ - if ( (preg_match('/^\d{1,2}:\d{1,2}(Z?[+|-]\d{2}:?\d{2})?/', $dtValue) or preg_match('/^\d{1,2}[a|p]m/', $dtValue)) && !empty($dates) ) { + if ((preg_match('/^\d{1,2}:\d{1,2}(Z?[+|-]\d{2}:?\d{2})?/', $dtValue) or preg_match('/^\d{1,2}[a|p]m/', $dtValue)) && !empty($dates)) { $dtValue = convertTimeFormat($dtValue); $dtValue = end($dates) . 'T' . unicodeTrim($dtValue, 'T'); } @@ -613,15 +617,15 @@ class Parser { * * @param DOMElement $e The element to parse * @return string $e’s innerHTML - * + * * @todo need to mark this element as e- parsed so it doesn’t get parsed as it’s parent’s e-* too */ public function parseE(\DOMElement $e) { $classTitle = $this->parseValueClassTitle($e); - + if ($classTitle !== null) return $classTitle; - + // Expand relative URLs within children of this element // TODO: as it is this is not relative to only children, make this .// and rerun tests $this->resolveChildUrls($e); @@ -630,7 +634,7 @@ class Parser { foreach ($e->childNodes as $node) { $html .= $node->C14N(); } - + return array( 'html' => $html, 'value' => unicodeTrim($this->textContent($e)) @@ -639,7 +643,7 @@ class Parser { /** * Recursively parse microformats - * + * * @param DOMElement $e The element to parse * @return array A representation of the values contained within microformat $e */ @@ -660,26 +664,39 @@ class Parser { foreach ($this->xpath->query('.//*[contains(concat(" ", @class)," h-")]', $e) as $subMF) { // Parse $result = $this->parseH($subMF); - + // If result was already parsed, skip it if (null === $result) continue; + // In most cases, the value attribute of the nested microformat should be the p- parsed value of the elemnt. + // The only times this is different is when the microformat is nested under certain prefixes, which are handled below. $result['value'] = $this->parseP($subMF); // Does this µf have any property names other than h-*? $properties = nestedMfPropertyNamesFromElement($subMF); - + if (!empty($properties)) { // Yes! It’s a nested property µf - foreach ($properties as $property) { - $return[$property][] = $result; + foreach ($properties as $property => $prefixes) { + // Note: handling microformat nesting under multiple conflicting prefixes is not currently specified by the mf2 parsing spec. + $prefixSpecificResult = $result; + if (in_array('p-', $prefixes)) { + $prefixSpecificResult['value'] = $prefixSpecificResult['properties']['name'][0]; + } elseif (in_array('e-', $prefixes)) { + $eParsedResult = $this->parseE($subMF); + $prefixSpecificResult['html'] = $eParsedResult['html']; + $prefixSpecificResult['value'] = $eParsedResult['value']; + } elseif (in_array('u-', $prefixes)) { + $prefixSpecificResult['value'] = $this->parseU($subMF); + } + $return[$property][] = $prefixSpecificResult; } } else { // No, it’s a child µf $children[] = $result; } - + // Make sure this sub-mf won’t get parsed as a µf or property // TODO: Determine if clearing this is required? $this->elementPrefixParsed($subMF, 'h'); @@ -689,19 +706,24 @@ class Parser { $this->elementPrefixParsed($subMF, 'e'); } + if($e->tagName == 'area') { + $coords = $e->getAttribute('coords'); + $shape = $e->getAttribute('shape'); + } + // Handle p-* foreach ($this->xpath->query('.//*[contains(concat(" ", @class) ," p-")]', $e) as $p) { if ($this->isElementParsed($p, 'p')) continue; $pValue = $this->parseP($p); - + // Add the value to the array for it’s p- properties foreach (mfNamesFromElement($p, 'p-') as $propName) { if (!empty($propName)) $return[$propName][] = $pValue; } - + // Make sure this sub-mf won’t get parsed as a top level mf $this->elementPrefixParsed($p, 'p'); } @@ -710,32 +732,32 @@ class Parser { foreach ($this->xpath->query('.//*[contains(concat(" ", @class)," u-")]', $e) as $u) { if ($this->isElementParsed($u, 'u')) continue; - + $uValue = $this->parseU($u); - + // Add the value to the array for it’s property types foreach (mfNamesFromElement($u, 'u-') as $propName) { $return[$propName][] = $uValue; } - + // Make sure this sub-mf won’t get parsed as a top level mf $this->elementPrefixParsed($u, 'u'); } - + // Handle dt-* foreach ($this->xpath->query('.//*[contains(concat(" ", @class), " dt-")]', $e) as $dt) { if ($this->isElementParsed($dt, 'dt')) continue; - + $dtValue = $this->parseDT($dt, $dates); - + if ($dtValue) { // Add the value to the array for dt- properties foreach (mfNamesFromElement($dt, 'dt-') as $propName) { $return[$propName][] = $dtValue; } } - + // Make sure this sub-mf won’t get parsed as a top level mf $this->elementPrefixParsed($dt, 'dt'); } @@ -762,22 +784,43 @@ class Parser { if (!array_key_exists('name', $return)) { try { // Look for img @alt - if ($e->tagName == 'img' and $e->getAttribute('alt') != '') + if (($e->tagName == 'img' or $e->tagName == 'area') and $e->getAttribute('alt') != '') throw new Exception($e->getAttribute('alt')); - + if ($e->tagName == 'abbr' and $e->hasAttribute('title')) throw new Exception($e->getAttribute('title')); - + // Look for nested img @alt foreach ($this->xpath->query('./img[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { - if ($em->getAttribute('alt') != '') + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { throw new Exception($em->getAttribute('alt')); + } + } + + // Look for nested area @alt + foreach ($this->xpath->query('./area[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { + throw new Exception($em->getAttribute('alt')); + } } + // Look for double nested img @alt foreach ($this->xpath->query('./*[count(preceding-sibling::*)+count(following-sibling::*)=0]/img[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { - if ($em->getAttribute('alt') != '') + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { + throw new Exception($em->getAttribute('alt')); + } + } + + // Look for double nested img @alt + foreach ($this->xpath->query('./*[count(preceding-sibling::*)+count(following-sibling::*)=0]/area[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { throw new Exception($em->getAttribute('alt')); + } } throw new Exception($e->nodeValue); @@ -812,36 +855,58 @@ class Parser { // Check for u-url if (!array_key_exists('url', $return)) { // Look for img @src - if ($e->tagName == 'a') + if ($e->tagName == 'a' or $e->tagName == 'area') $url = $e->getAttribute('href'); - - // Look for nested img @src + + // Look for nested a @href foreach ($this->xpath->query('./a[count(preceding-sibling::a)+count(following-sibling::a)=0]', $e) as $em) { - $url = $em->getAttribute('href'); - break; + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames)) { + $url = $em->getAttribute('href'); + break; + } } - + + // Look for nested area @src + foreach ($this->xpath->query('./area[count(preceding-sibling::area)+count(following-sibling::area)=0]', $e) as $em) { + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames)) { + $url = $em->getAttribute('href'); + break; + } + } + if (!empty($url)) $return['url'][] = $this->resolveUrl($url); } // Make sure things are in alphabetical order sort($mfTypes); - + // Phew. Return the final result. $parsed = array( 'type' => $mfTypes, 'properties' => $return ); - if (!empty($children)) + + if (!empty($shape)) { + $parsed['shape'] = $shape; + } + + if (!empty($coords)) { + $parsed['coords'] = $coords; + } + + if (!empty($children)) { $parsed['children'] = array_values(array_filter($children)); + } return $parsed; } - + /** * Parse Rels and Alternatives - * - * Returns [$rels, $alternatives]. If the $rels value is to be empty, i.e. there are no links on the page + * + * Returns [$rels, $alternatives]. If the $rels value is to be empty, i.e. there are no links on the page * with a rel value *not* containing `alternate`, then the type of $rels depends on $this->jsonMode. If set * to true, it will be a stdClass instance, optimising for JSON serialisation. Otherwise (the default case), * it will be an empty array. @@ -849,18 +914,18 @@ class Parser { public function parseRelsAndAlternates() { $rels = array(); $alternates = array(); - + // Iterate through all a, area and link elements with rel attributes foreach ($this->xpath->query('//*[@rel and @href]') as $hyperlink) { if ($hyperlink->getAttribute('rel') == '') continue; - + // Resolve the href $href = $this->resolveUrl($hyperlink->getAttribute('href')); - + // Split up the rel into space-separated values $linkRels = array_filter(explode(' ', $hyperlink->getAttribute('rel'))); - + // If alternate in rels, create alternate structure, append if (in_array('alternate', $linkRels)) { $alt = array( @@ -869,10 +934,19 @@ class Parser { ); if ($hyperlink->hasAttribute('media')) $alt['media'] = $hyperlink->getAttribute('media'); - + if ($hyperlink->hasAttribute('hreflang')) $alt['hreflang'] = $hyperlink->getAttribute('hreflang'); - + + if ($hyperlink->hasAttribute('title')) + $alt['title'] = $hyperlink->getAttribute('title'); + + if ($hyperlink->hasAttribute('type')) + $alt['type'] = $hyperlink->getAttribute('type'); + + if ($hyperlink->nodeValue) + $alt['text'] = $hyperlink->nodeValue; + $alternates[] = $alt; } else { foreach ($linkRels as $rel) { @@ -880,38 +954,38 @@ class Parser { } } } - + if (empty($rels) and $this->jsonMode) { $rels = new stdClass(); } - + return array($rels, $alternates); } - + /** * Kicks off the parsing routine - * + * * If `$htmlSafe` is set, any angle brackets in the results from non e-* properties * will be HTML-encoded, bringing all output to the same level of encoding. - * + * * If a DOMElement is set as the $context, only descendants of that element will * be parsed for microformats. - * + * * @param bool $htmlSafe whether or not to html-encode non e-* properties. Defaults to false * @param DOMElement $context optionally an element from which to parse microformats * @return array An array containing all the µfs found in the current document */ public function parse($convertClassic = true, DOMElement $context = null) { $mfs = array(); - + if ($convertClassic) { $this->convertLegacy(); } - + $mfElements = null === $context ? $this->xpath->query('//*[contains(concat(" ", @class), " h-")]') : $this->xpath->query('.//*[contains(concat(" ", @class), " h-")]', $context); - + // Parser microformats foreach ($mfElements as $node) { // For each microformat @@ -920,64 +994,64 @@ class Parser { // Add the value to the array for this property type $mfs[] = $result; } - + // Parse rels list($rels, $alternates) = $this->parseRelsAndAlternates(); - + $top = array( 'items' => array_values(array_filter($mfs)), 'rels' => $rels ); - + if (count($alternates)) $top['alternates'] = $alternates; - + return $top; } - + /** * Parse From ID - * + * * Given an ID, parse all microformats which are children of the element with * that ID. - * + * * Note that rel values are still document-wide. - * - * If an element with the ID is not found, an empty skeleton mf2 array structure + * + * If an element with the ID is not found, an empty skeleton mf2 array structure * will be returned. - * + * * @param string $id * @param bool $htmlSafe = false whether or not to HTML-encode angle brackets in non e-* properties * @return array */ public function parseFromId($id, $convertClassic=true) { $matches = $this->xpath->query("//*[@id='{$id}']"); - + if (empty($matches)) return array('items' => array(), 'rels' => array(), 'alternates' => array()); - + return $this->parse($convertClassic, $matches->item(0)); } /** * Convert Legacy Classnames - * + * * Adds microformats2 classnames into a document containing only legacy * semantic classnames. - * + * * @return Parser $this */ public function convertLegacy() { $doc = $this->doc; $xp = new DOMXPath($doc); - + // replace all roots foreach ($this->classicRootMap as $old => $new) { foreach ($xp->query('//*[contains(concat(" ", @class, " "), " ' . $old . ' ") and not(contains(concat(" ", @class, " "), " ' . $new . ' "))]') as $el) { $el->setAttribute('class', $el->getAttribute('class') . ' ' . $new); } } - + foreach ($this->classicPropertyMap as $oldRoot => $properties) { $newRoot = $this->classicRootMap[$oldRoot]; foreach ($properties as $old => $new) { @@ -986,16 +1060,16 @@ class Parser { } } } - + return $this; } - + /** * XPath Query - * + * * Runs an XPath query over the current document. Works in exactly the same * way as DOMXPath::query. - * + * * @param string $expression * @param DOMNode $context * @return DOMNodeList @@ -1003,7 +1077,7 @@ class Parser { public function query($expression, $context = null) { return $this->xpath->query($expression, $context); } - + /** * Classic Root Classname map */ @@ -1013,11 +1087,11 @@ class Parser { 'hentry' => 'h-entry', 'hrecipe' => 'h-recipe', 'hresume' => 'h-resume', - 'hevent' => 'h-event', + 'vevent' => 'h-event', 'hreview' => 'h-review', 'hproduct' => 'h-product' ); - + public $classicPropertyMap = array( 'vcard' => array( 'fn' => 'p-name', @@ -1084,7 +1158,7 @@ class Parser { 'skill' => 'p-skill', 'affiliation' => 'p-affiliation h-card', ), - 'hevent' => array( + 'vevent' => array( 'dtstart' => 'dt-start', 'dtend' => 'dt-end', 'duration' => 'dt-duration', @@ -1246,7 +1320,7 @@ function resolveUrl($baseURI, $referenceURI) { # 5.2.3 Merge Paths function mergePaths($base, $reference) { # If the base URI has a defined authority component and an empty - # path, + # path, if($base['authority'] && $base['path'] == null) { # then return a string consisting of "/" concatenated with the # reference's path; otherwise, -- 2.39.5