From 20f17d2cfdd66fa7c948d121a2c64c3bbb47a160 Mon Sep 17 00:00:00 2001 From: Gregor Morrill Date: Sat, 27 May 2017 17:00:37 -0700 Subject: [PATCH] Add rel-urls object - Add test for rel-urls - Remove 'alternates' in favor of 'rel-urls' per spec; still available with flag $enableAlternates - Add tests for $enableAlterates flag - Update existing tests to include expected empty rel-urls object --- Mf2/Parser.php | 91 +++++++++++++++------- tests/Mf2/ClassicMicroformatsTest.php | 4 +- tests/Mf2/CombinedMicroformatsTest.php | 8 +- tests/Mf2/MicroformatsWikiExamplesTest.php | 7 ++ tests/Mf2/ParseImpliedTest.php | 1 + tests/Mf2/ParserTest.php | 1 + tests/Mf2/RelTest.php | 82 +++++++++++++++++++ 7 files changed, 161 insertions(+), 33 deletions(-) diff --git a/Mf2/Parser.php b/Mf2/Parser.php index 39e90c6..22dd026 100644 --- a/Mf2/Parser.php +++ b/Mf2/Parser.php @@ -280,6 +280,9 @@ class Parser { /** @var boolean Whether to include experimental language parsing in the result */ public $lang = false; + /** @var bool Whether to include alternates object (dropped from spec in favor of rel-urls) */ + public $enableAlternates = false; + /** * Elements upgraded to mf2 during backcompat * @var SplObjectStorage @@ -1153,21 +1156,25 @@ public function parseImpliedPhoto(\DOMElement $e) { } /** - * Parse Rels and Alternatives + * Parse rels and alternates * - * 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. + * Returns [$rels, $rel_urls, $alternates]. + * For $rels and $rel_urls, if they are empty and $this->jsonMode = true, they will be returned as stdClass, + * optimizing for JSON serialization. Otherwise they will be returned as an empty array. + * Note that $alternates is deprecated in the microformats spec in favor of $rel_urls. $alternates only appears + * in parsed results if $this->enableAlternates = true. + * @return array|stdClass */ public function parseRelsAndAlternates() { $rels = array(); + $rel_urls = array(); $alternates = array(); // Iterate through all a, area and link elements with rel attributes foreach ($this->xpath->query('//a[@rel and @href] | //link[@rel and @href] | //area[@rel and @href]') as $hyperlink) { - if ($hyperlink->getAttribute('rel') == '') + if ($hyperlink->getAttribute('rel') == '') { continue; + } // Resolve the href $href = $this->resolveUrl($hyperlink->getAttribute('href')); @@ -1175,40 +1182,63 @@ public function parseRelsAndAlternates() { // 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( - 'url' => $href, - 'rel' => implode(' ', array_diff($linkRels, array('alternate'))) - ); - if ($hyperlink->hasAttribute('media')) - $alt['media'] = $hyperlink->getAttribute('media'); + $rel_attributes = array(); - if ($hyperlink->hasAttribute('hreflang')) - $alt['hreflang'] = $hyperlink->getAttribute('hreflang'); + if ($hyperlink->hasAttribute('media')) { + $rel_attributes['media'] = $hyperlink->getAttribute('media'); + } - if ($hyperlink->hasAttribute('title')) - $alt['title'] = $hyperlink->getAttribute('title'); + if ($hyperlink->hasAttribute('hreflang')) { + $rel_attributes['hreflang'] = $hyperlink->getAttribute('hreflang'); + } - if ($hyperlink->hasAttribute('type')) - $alt['type'] = $hyperlink->getAttribute('type'); + if ($hyperlink->hasAttribute('title')) { + $rel_attributes['title'] = $hyperlink->getAttribute('title'); + } - if ($hyperlink->nodeValue) - $alt['text'] = $hyperlink->nodeValue; + if ($hyperlink->hasAttribute('type')) { + $rel_attributes['type'] = $hyperlink->getAttribute('type'); + } - $alternates[] = $alt; - } else { - foreach ($linkRels as $rel) { - $rels[$rel][] = $href; + if ($hyperlink->nodeValue) { + $rel_attributes['text'] = $hyperlink->nodeValue; + } + + if ($this->enableAlternates) { + // If 'alternate' in rels, create 'alternates' structure, append + if (in_array('alternate', $linkRels)) { + $alternates[] = array_merge( + $rel_attributes, + array( + 'url' => $href, + 'rel' => implode(' ', array_diff($linkRels, array('alternate'))) + ) + ); } } + + foreach ($linkRels as $rel) { + $rels[$rel][] = $href; + } + + if (!in_array($href, $rel_urls)) { + $rel_urls[$href] = array_merge( + $rel_attributes, + array('rels' => $linkRels) + ); + } + } if (empty($rels) and $this->jsonMode) { $rels = new stdClass(); } - return array($rels, $alternates); + if (empty($rel_urls) and $this->jsonMode) { + $rel_urls = new stdClass(); + } + + return array($rels, $rel_urls, $alternates); } /** @@ -1239,14 +1269,15 @@ public function parse($convertClassic = true, DOMElement $context = null) { } // Parse rels - list($rels, $alternates) = $this->parseRelsAndAlternates(); + list($rels, $rel_urls, $alternates) = $this->parseRelsAndAlternates(); $top = array( 'items' => array_values(array_filter($mfs)), - 'rels' => $rels + 'rels' => $rels, + 'rel-urls' => $rel_urls, ); - if (count($alternates)) { + if ($this->enableAlternates && count($alternates)) { $top['alternates'] = $alternates; } diff --git a/tests/Mf2/ClassicMicroformatsTest.php b/tests/Mf2/ClassicMicroformatsTest.php index e1c5514..a057d8b 100644 --- a/tests/Mf2/ClassicMicroformatsTest.php +++ b/tests/Mf2/ClassicMicroformatsTest.php @@ -24,14 +24,14 @@ public function setUp() { public function testParsesClassicHcard() { $input = '
Barnaby Walters is a person.
'; - $expected = '{"items": [{"type": ["h-card"], "properties": {"name": ["Barnaby Walters"]}}], "rels": {}}'; + $expected = '{"items": [{"type": ["h-card"], "properties": {"name": ["Barnaby Walters"]}}], "rels": {}, "rel-urls": {}}'; $parser = new Parser($input, '', true); $this->assertJsonStringEqualsJsonString(json_encode($parser->parse()), $expected); } public function testParsesClassicHEntry() { $input = '

microformats2 Is Great

yes yes it is.

'; - $expected = '{"items": [{"type": ["h-entry"], "properties": {"name": ["microformats2 Is Great"], "summary": ["yes yes it is."]}}], "rels": {}}'; + $expected = '{"items": [{"type": ["h-entry"], "properties": {"name": ["microformats2 Is Great"], "summary": ["yes yes it is."]}}], "rels": {}, "rel-urls": {}}'; $parser = new Parser($input, '', true); $this->assertJsonStringEqualsJsonString(json_encode($parser->parse()), $expected); } diff --git a/tests/Mf2/CombinedMicroformatsTest.php b/tests/Mf2/CombinedMicroformatsTest.php index 8525824..76b7af1 100644 --- a/tests/Mf2/CombinedMicroformatsTest.php +++ b/tests/Mf2/CombinedMicroformatsTest.php @@ -37,6 +37,7 @@ public function testHEventLocationHCard() { '; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-event"], "properties": { @@ -79,6 +80,7 @@ public function testHCardOrgPOrg() { '; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -110,6 +112,7 @@ public function testHCardOrgHCard() { '; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -148,6 +151,7 @@ public function testHCardPOrgHCardHOrg() { '; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -184,6 +188,7 @@ public function testHCardChildHCard() { '; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -286,7 +291,8 @@ public function testMicroformatsNestedUnderUPropertyClassnamesDeriveValueFromURL }] } }], - "rels":[] + "rels":[], + "rel-urls": [] }'; $mf = Mf2\parse($input); diff --git a/tests/Mf2/MicroformatsWikiExamplesTest.php b/tests/Mf2/MicroformatsWikiExamplesTest.php index 0432718..07c80bb 100644 --- a/tests/Mf2/MicroformatsWikiExamplesTest.php +++ b/tests/Mf2/MicroformatsWikiExamplesTest.php @@ -29,6 +29,7 @@ public function testHandlesEmptyStringsCorrectly() { $input = ''; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [] }'; @@ -42,6 +43,7 @@ public function testHandlesNullCorrectly() { $input = Null; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [] }'; @@ -59,6 +61,7 @@ public function testSimplePersonReference() { $input = 'Frances Berriman'; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -79,6 +82,7 @@ public function testSimpleHyperlinkedPersonReference() { $input = 'Ben Ward'; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -101,6 +105,7 @@ public function testSimplePersonImage() { // Added root items key $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -125,6 +130,7 @@ public function testHyperlinkedImageNameAndPhotoProperties() { // Added root items key $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { @@ -162,6 +168,7 @@ public function testMoreDetailedPerson() { $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { diff --git a/tests/Mf2/ParseImpliedTest.php b/tests/Mf2/ParseImpliedTest.php index 416aced..ad12b2a 100644 --- a/tests/Mf2/ParseImpliedTest.php +++ b/tests/Mf2/ParseImpliedTest.php @@ -132,6 +132,7 @@ public function testMultipleImpliedHCards() { '; $expected = '{ "rels": {}, + "rel-urls": {}, "items": [{ "type": ["h-card"], "properties": { diff --git a/tests/Mf2/ParserTest.php b/tests/Mf2/ParserTest.php index fa892d7..df9ac32 100644 --- a/tests/Mf2/ParserTest.php +++ b/tests/Mf2/ParserTest.php @@ -164,6 +164,7 @@ public function testParsesRelValues() { public function testParsesRelAlternateValues() { $input = 'German Homepage'; $parser = new Parser($input); + $parser->enableAlternates = true; $output = $parser->parse(); $this->assertArrayHasKey('alternates', $output); diff --git a/tests/Mf2/RelTest.php b/tests/Mf2/RelTest.php index e4ff8ca..55c08f0 100644 --- a/tests/Mf2/RelTest.php +++ b/tests/Mf2/RelTest.php @@ -94,4 +94,86 @@ public function testRelValueOnBTag() { $this->assertArrayNotHasKey('webmention', $output['rels']); } + public function testEnableAlternatesFlagTrue() { + $input = ' + +post 1 +post 2 +French mobile homepage'; + $parser = new Parser($input); + $parser->enableAlternates = true; + $output = $parser->parse(); + + $this->assertArrayHasKey('alternates', $output); + } + + public function testEnableAlternatesFlagFalse() { + $input = ' + +post 1 +post 2 +French mobile homepage'; + $parser = new Parser($input); + $parser->enableAlternates = false; + $output = $parser->parse(); + + $this->assertArrayNotHasKey('alternates', $output); + } + + /** + * @see https://github.com/indieweb/php-mf2/issues/112 + * @see http://microformats.org/wiki/microformats2-parsing#rel_parse_examples + */ + public function testRelURLs() { + $input = ' + +post 1 +post 2 +French mobile homepage +'; + $parser = new Parser($input); + $output = $parser->parse(); + + $this->assertArrayHasKey('rels', $output); + $this->assertCount(4, $output['rels']); + $this->assertArrayHasKey('author', $output['rels']); + $this->assertArrayHasKey('in-reply-to', $output['rels']); + $this->assertArrayHasKey('alternate', $output['rels']); + $this->assertArrayHasKey('home', $output['rels']); + + $this->assertArrayHasKey('rel-urls', $output); + $this->assertCount(6, $output['rel-urls']); + $this->assertArrayHasKey('http://example.com/a', $output['rel-urls']); + $this->assertArrayHasKey('http://example.com/b', $output['rel-urls']); + $this->assertArrayHasKey('http://example.com/1', $output['rel-urls']); + $this->assertArrayHasKey('http://example.com/2', $output['rel-urls']); + $this->assertArrayHasKey('http://example.com/fr', $output['rel-urls']); + $this->assertArrayHasKey('http://example.com/articles.atom', $output['rel-urls']); + + $this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/a']); + $this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/a']); + $this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/b']); + $this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/b']); + $this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/1']); + $this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/1']); + $this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/2']); + $this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/2']); + $this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/fr']); + $this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/fr']); + $this->assertArrayHasKey('media', $output['rel-urls']['http://example.com/fr']); + $this->assertArrayHasKey('hreflang', $output['rel-urls']['http://example.com/fr']); + $this->assertArrayHasKey('title', $output['rel-urls']['http://example.com/articles.atom']); + $this->assertArrayHasKey('type', $output['rel-urls']['http://example.com/articles.atom']); + $this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/articles.atom']); + } + }