Skip to content

Commit 20f17d2

Browse files
committed
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
1 parent e7c0cac commit 20f17d2

File tree

7 files changed

+161
-33
lines changed

7 files changed

+161
-33
lines changed

Mf2/Parser.php

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ class Parser {
280280
/** @var boolean Whether to include experimental language parsing in the result */
281281
public $lang = false;
282282

283+
/** @var bool Whether to include alternates object (dropped from spec in favor of rel-urls) */
284+
public $enableAlternates = false;
285+
283286
/**
284287
* Elements upgraded to mf2 during backcompat
285288
* @var SplObjectStorage
@@ -1153,62 +1156,89 @@ public function parseImpliedPhoto(\DOMElement $e) {
11531156
}
11541157

11551158
/**
1156-
* Parse Rels and Alternatives
1159+
* Parse rels and alternates
11571160
*
1158-
* Returns [$rels, $alternatives]. If the $rels value is to be empty, i.e. there are no links on the page
1159-
* with a rel value *not* containing `alternate`, then the type of $rels depends on $this->jsonMode. If set
1160-
* to true, it will be a stdClass instance, optimising for JSON serialisation. Otherwise (the default case),
1161-
* it will be an empty array.
1161+
* Returns [$rels, $rel_urls, $alternates].
1162+
* For $rels and $rel_urls, if they are empty and $this->jsonMode = true, they will be returned as stdClass,
1163+
* optimizing for JSON serialization. Otherwise they will be returned as an empty array.
1164+
* Note that $alternates is deprecated in the microformats spec in favor of $rel_urls. $alternates only appears
1165+
* in parsed results if $this->enableAlternates = true.
1166+
* @return array|stdClass
11621167
*/
11631168
public function parseRelsAndAlternates() {
11641169
$rels = array();
1170+
$rel_urls = array();
11651171
$alternates = array();
11661172

11671173
// Iterate through all a, area and link elements with rel attributes
11681174
foreach ($this->xpath->query('//a[@rel and @href] | //link[@rel and @href] | //area[@rel and @href]') as $hyperlink) {
1169-
if ($hyperlink->getAttribute('rel') == '')
1175+
if ($hyperlink->getAttribute('rel') == '') {
11701176
continue;
1177+
}
11711178

11721179
// Resolve the href
11731180
$href = $this->resolveUrl($hyperlink->getAttribute('href'));
11741181

11751182
// Split up the rel into space-separated values
11761183
$linkRels = array_filter(explode(' ', $hyperlink->getAttribute('rel')));
11771184

1178-
// If alternate in rels, create alternate structure, append
1179-
if (in_array('alternate', $linkRels)) {
1180-
$alt = array(
1181-
'url' => $href,
1182-
'rel' => implode(' ', array_diff($linkRels, array('alternate')))
1183-
);
1184-
if ($hyperlink->hasAttribute('media'))
1185-
$alt['media'] = $hyperlink->getAttribute('media');
1185+
$rel_attributes = array();
11861186

1187-
if ($hyperlink->hasAttribute('hreflang'))
1188-
$alt['hreflang'] = $hyperlink->getAttribute('hreflang');
1187+
if ($hyperlink->hasAttribute('media')) {
1188+
$rel_attributes['media'] = $hyperlink->getAttribute('media');
1189+
}
11891190

1190-
if ($hyperlink->hasAttribute('title'))
1191-
$alt['title'] = $hyperlink->getAttribute('title');
1191+
if ($hyperlink->hasAttribute('hreflang')) {
1192+
$rel_attributes['hreflang'] = $hyperlink->getAttribute('hreflang');
1193+
}
11921194

1193-
if ($hyperlink->hasAttribute('type'))
1194-
$alt['type'] = $hyperlink->getAttribute('type');
1195+
if ($hyperlink->hasAttribute('title')) {
1196+
$rel_attributes['title'] = $hyperlink->getAttribute('title');
1197+
}
11951198

1196-
if ($hyperlink->nodeValue)
1197-
$alt['text'] = $hyperlink->nodeValue;
1199+
if ($hyperlink->hasAttribute('type')) {
1200+
$rel_attributes['type'] = $hyperlink->getAttribute('type');
1201+
}
11981202

1199-
$alternates[] = $alt;
1200-
} else {
1201-
foreach ($linkRels as $rel) {
1202-
$rels[$rel][] = $href;
1203+
if ($hyperlink->nodeValue) {
1204+
$rel_attributes['text'] = $hyperlink->nodeValue;
1205+
}
1206+
1207+
if ($this->enableAlternates) {
1208+
// If 'alternate' in rels, create 'alternates' structure, append
1209+
if (in_array('alternate', $linkRels)) {
1210+
$alternates[] = array_merge(
1211+
$rel_attributes,
1212+
array(
1213+
'url' => $href,
1214+
'rel' => implode(' ', array_diff($linkRels, array('alternate')))
1215+
)
1216+
);
12031217
}
12041218
}
1219+
1220+
foreach ($linkRels as $rel) {
1221+
$rels[$rel][] = $href;
1222+
}
1223+
1224+
if (!in_array($href, $rel_urls)) {
1225+
$rel_urls[$href] = array_merge(
1226+
$rel_attributes,
1227+
array('rels' => $linkRels)
1228+
);
1229+
}
1230+
12051231
}
12061232

12071233
if (empty($rels) and $this->jsonMode) {
12081234
$rels = new stdClass();
12091235
}
12101236

1211-
return array($rels, $alternates);
1237+
if (empty($rel_urls) and $this->jsonMode) {
1238+
$rel_urls = new stdClass();
1239+
}
1240+
1241+
return array($rels, $rel_urls, $alternates);
12121242
}
12131243

12141244
/**
@@ -1239,14 +1269,15 @@ public function parse($convertClassic = true, DOMElement $context = null) {
12391269
}
12401270

12411271
// Parse rels
1242-
list($rels, $alternates) = $this->parseRelsAndAlternates();
1272+
list($rels, $rel_urls, $alternates) = $this->parseRelsAndAlternates();
12431273

12441274
$top = array(
12451275
'items' => array_values(array_filter($mfs)),
1246-
'rels' => $rels
1276+
'rels' => $rels,
1277+
'rel-urls' => $rel_urls,
12471278
);
12481279

1249-
if (count($alternates)) {
1280+
if ($this->enableAlternates && count($alternates)) {
12501281
$top['alternates'] = $alternates;
12511282
}
12521283

tests/Mf2/ClassicMicroformatsTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ public function setUp() {
2424

2525
public function testParsesClassicHcard() {
2626
$input = '<div class="vcard"><span class="fn n">Barnaby Walters</span> is a person.</div>';
27-
$expected = '{"items": [{"type": ["h-card"], "properties": {"name": ["Barnaby Walters"]}}], "rels": {}}';
27+
$expected = '{"items": [{"type": ["h-card"], "properties": {"name": ["Barnaby Walters"]}}], "rels": {}, "rel-urls": {}}';
2828
$parser = new Parser($input, '', true);
2929
$this->assertJsonStringEqualsJsonString(json_encode($parser->parse()), $expected);
3030
}
3131

3232
public function testParsesClassicHEntry() {
3333
$input = '<div class="hentry"><h1 class="entry-title">microformats2 Is Great</h1> <p class="entry-summary">yes yes it is.</p></div>';
34-
$expected = '{"items": [{"type": ["h-entry"], "properties": {"name": ["microformats2 Is Great"], "summary": ["yes yes it is."]}}], "rels": {}}';
34+
$expected = '{"items": [{"type": ["h-entry"], "properties": {"name": ["microformats2 Is Great"], "summary": ["yes yes it is."]}}], "rels": {}, "rel-urls": {}}';
3535
$parser = new Parser($input, '', true);
3636
$this->assertJsonStringEqualsJsonString(json_encode($parser->parse()), $expected);
3737
}

tests/Mf2/CombinedMicroformatsTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function testHEventLocationHCard() {
3737
</div>';
3838
$expected = '{
3939
"rels": {},
40+
"rel-urls": {},
4041
"items": [{
4142
"type": ["h-event"],
4243
"properties": {
@@ -79,6 +80,7 @@ public function testHCardOrgPOrg() {
7980
</div>';
8081
$expected = '{
8182
"rels": {},
83+
"rel-urls": {},
8284
"items": [{
8385
"type": ["h-card"],
8486
"properties": {
@@ -110,6 +112,7 @@ public function testHCardOrgHCard() {
110112
</div>';
111113
$expected = '{
112114
"rels": {},
115+
"rel-urls": {},
113116
"items": [{
114117
"type": ["h-card"],
115118
"properties": {
@@ -148,6 +151,7 @@ public function testHCardPOrgHCardHOrg() {
148151
</div>';
149152
$expected = '{
150153
"rels": {},
154+
"rel-urls": {},
151155
"items": [{
152156
"type": ["h-card"],
153157
"properties": {
@@ -184,6 +188,7 @@ public function testHCardChildHCard() {
184188
</div>';
185189
$expected = '{
186190
"rels": {},
191+
"rel-urls": {},
187192
"items": [{
188193
"type": ["h-card"],
189194
"properties": {
@@ -286,7 +291,8 @@ public function testMicroformatsNestedUnderUPropertyClassnamesDeriveValueFromURL
286291
}]
287292
}
288293
}],
289-
"rels":[]
294+
"rels":[],
295+
"rel-urls": []
290296
}';
291297
$mf = Mf2\parse($input);
292298

tests/Mf2/MicroformatsWikiExamplesTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function testHandlesEmptyStringsCorrectly() {
2929
$input = '';
3030
$expected = '{
3131
"rels": {},
32+
"rel-urls": {},
3233
"items": []
3334
}';
3435

@@ -42,6 +43,7 @@ public function testHandlesNullCorrectly() {
4243
$input = Null;
4344
$expected = '{
4445
"rels": {},
46+
"rel-urls": {},
4547
"items": []
4648
}';
4749

@@ -59,6 +61,7 @@ public function testSimplePersonReference() {
5961
$input = '<span class="h-card">Frances Berriman</span>';
6062
$expected = '{
6163
"rels": {},
64+
"rel-urls": {},
6265
"items": [{
6366
"type": ["h-card"],
6467
"properties": {
@@ -79,6 +82,7 @@ public function testSimpleHyperlinkedPersonReference() {
7982
$input = '<a class="h-card" href="http://benward.me">Ben Ward</a>';
8083
$expected = '{
8184
"rels": {},
85+
"rel-urls": {},
8286
"items": [{
8387
"type": ["h-card"],
8488
"properties": {
@@ -101,6 +105,7 @@ public function testSimplePersonImage() {
101105
// Added root items key
102106
$expected = '{
103107
"rels": {},
108+
"rel-urls": {},
104109
"items": [{
105110
"type": ["h-card"],
106111
"properties": {
@@ -125,6 +130,7 @@ public function testHyperlinkedImageNameAndPhotoProperties() {
125130
// Added root items key
126131
$expected = '{
127132
"rels": {},
133+
"rel-urls": {},
128134
"items": [{
129135
"type": ["h-card"],
130136
"properties": {
@@ -162,6 +168,7 @@ public function testMoreDetailedPerson() {
162168

163169
$expected = '{
164170
"rels": {},
171+
"rel-urls": {},
165172
"items": [{
166173
"type": ["h-card"],
167174
"properties": {

tests/Mf2/ParseImpliedTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public function testMultipleImpliedHCards() {
132132
</a>';
133133
$expected = '{
134134
"rels": {},
135+
"rel-urls": {},
135136
"items": [{
136137
"type": ["h-card"],
137138
"properties": {

tests/Mf2/ParserTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ public function testParsesRelValues() {
164164
public function testParsesRelAlternateValues() {
165165
$input = '<a rel="alternate home" href="http://example.org" hreflang="de", media="screen" type="text/html" title="German Homepage Link">German Homepage</a>';
166166
$parser = new Parser($input);
167+
$parser->enableAlternates = true;
167168
$output = $parser->parse();
168169

169170
$this->assertArrayHasKey('alternates', $output);

tests/Mf2/RelTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,86 @@ public function testRelValueOnBTag() {
9494
$this->assertArrayNotHasKey('webmention', $output['rels']);
9595
}
9696

97+
public function testEnableAlternatesFlagTrue() {
98+
$input = '<a rel="author" href="http://example.com/a">author a</a>
99+
<a rel="author" href="http://example.com/b">author b</a>
100+
<a rel="in-reply-to" href="http://example.com/1">post 1</a>
101+
<a rel="in-reply-to" href="http://example.com/2">post 2</a>
102+
<a rel="alternate home"
103+
href="http://example.com/fr"
104+
media="handheld"
105+
hreflang="fr">French mobile homepage</a>';
106+
$parser = new Parser($input);
107+
$parser->enableAlternates = true;
108+
$output = $parser->parse();
109+
110+
$this->assertArrayHasKey('alternates', $output);
111+
}
112+
113+
public function testEnableAlternatesFlagFalse() {
114+
$input = '<a rel="author" href="http://example.com/a">author a</a>
115+
<a rel="author" href="http://example.com/b">author b</a>
116+
<a rel="in-reply-to" href="http://example.com/1">post 1</a>
117+
<a rel="in-reply-to" href="http://example.com/2">post 2</a>
118+
<a rel="alternate home"
119+
href="http://example.com/fr"
120+
media="handheld"
121+
hreflang="fr">French mobile homepage</a>';
122+
$parser = new Parser($input);
123+
$parser->enableAlternates = false;
124+
$output = $parser->parse();
125+
126+
$this->assertArrayNotHasKey('alternates', $output);
127+
}
128+
129+
/**
130+
* @see https://github.com/indieweb/php-mf2/issues/112
131+
* @see http://microformats.org/wiki/microformats2-parsing#rel_parse_examples
132+
*/
133+
public function testRelURLs() {
134+
$input = '<a rel="author" href="http://example.com/a">author a</a>
135+
<a rel="author" href="http://example.com/b">author b</a>
136+
<a rel="in-reply-to" href="http://example.com/1">post 1</a>
137+
<a rel="in-reply-to" href="http://example.com/2">post 2</a>
138+
<a rel="alternate home"
139+
href="http://example.com/fr"
140+
media="handheld"
141+
hreflang="fr">French mobile homepage</a>
142+
<link rel="alternate" type="application/atom+xml" href="http://example.com/articles.atom" title="Atom Feed" />';
143+
$parser = new Parser($input);
144+
$output = $parser->parse();
145+
146+
$this->assertArrayHasKey('rels', $output);
147+
$this->assertCount(4, $output['rels']);
148+
$this->assertArrayHasKey('author', $output['rels']);
149+
$this->assertArrayHasKey('in-reply-to', $output['rels']);
150+
$this->assertArrayHasKey('alternate', $output['rels']);
151+
$this->assertArrayHasKey('home', $output['rels']);
152+
153+
$this->assertArrayHasKey('rel-urls', $output);
154+
$this->assertCount(6, $output['rel-urls']);
155+
$this->assertArrayHasKey('http://example.com/a', $output['rel-urls']);
156+
$this->assertArrayHasKey('http://example.com/b', $output['rel-urls']);
157+
$this->assertArrayHasKey('http://example.com/1', $output['rel-urls']);
158+
$this->assertArrayHasKey('http://example.com/2', $output['rel-urls']);
159+
$this->assertArrayHasKey('http://example.com/fr', $output['rel-urls']);
160+
$this->assertArrayHasKey('http://example.com/articles.atom', $output['rel-urls']);
161+
162+
$this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/a']);
163+
$this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/a']);
164+
$this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/b']);
165+
$this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/b']);
166+
$this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/1']);
167+
$this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/1']);
168+
$this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/2']);
169+
$this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/2']);
170+
$this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/fr']);
171+
$this->assertArrayHasKey('text', $output['rel-urls']['http://example.com/fr']);
172+
$this->assertArrayHasKey('media', $output['rel-urls']['http://example.com/fr']);
173+
$this->assertArrayHasKey('hreflang', $output['rel-urls']['http://example.com/fr']);
174+
$this->assertArrayHasKey('title', $output['rel-urls']['http://example.com/articles.atom']);
175+
$this->assertArrayHasKey('type', $output['rel-urls']['http://example.com/articles.atom']);
176+
$this->assertArrayHasKey('rels', $output['rel-urls']['http://example.com/articles.atom']);
177+
}
178+
97179
}

0 commit comments

Comments
 (0)