Skip to content

Commit 784ef83

Browse files
committed
Generators/HTML: slugify anchor links
... to prevent issues with URL encoding. Note: as this _may_ result in duplicate anchor links, this commit includes a protection against this by adding a numeric suffix to the anchor if a duplicate is detected. Includes a test with a variety of non-ascii chars and duplicate titles. Includes updated test expectations for various other tests.
1 parent 4bc3d2e commit 784ef83

File tree

40 files changed

+301
-42
lines changed

40 files changed

+301
-42
lines changed

src/Generators/HTML.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ class HTML extends Generator
114114
</style>';
115115

116116

117+
/**
118+
* List of seen slugified anchors to ensure uniqueness.
119+
*
120+
* @var array<string, true>
121+
*/
122+
private $seenAnchors = [];
123+
117124
/**
118125
* Generates the documentation for a standard.
119126
*
@@ -132,6 +139,10 @@ public function generate()
132139
$content = ob_get_contents();
133140
ob_end_clean();
134141

142+
// Clear anchor cache after Documentation generation.
143+
// The anchor generation for the TOC anchor links will use the same logic, so should end up with the same unique slugs.
144+
$this->seenAnchors = [];
145+
135146
if (trim($content) !== '') {
136147
echo $this->getFormattedHeader();
137148
echo $this->getFormattedToc();
@@ -325,7 +336,22 @@ public function processSniff(DOMNode $doc)
325336
*/
326337
private function titleToAnchor($title)
327338
{
328-
return str_replace(' ', '-', $title);
339+
// Slugify the text.
340+
$title = strtolower($title);
341+
$title = str_replace(' ', '-', $title);
342+
$title = preg_replace('`[^a-z0-9._-]`', '-', $title);
343+
344+
if (isset($this->seenAnchors[$title]) === false) {
345+
// Add to "seen" list.
346+
$this->seenAnchors[$title] = true;
347+
} else {
348+
// Try to find a unique anchor for this title.
349+
for ($i = 2; (isset($this->seenAnchors[$title.'-'.$i]) === true); $i++);
350+
$title .= '-'.$i;
351+
$this->seenAnchors[$title] = true;
352+
}
353+
354+
return $title;
329355

330356
}//end titleToAnchor()
331357

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonBlankLines.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="Code-Comparison,-blank-lines">Code Comparison, blank lines<a class="sniffanchor" href="#Code-Comparison,-blank-lines"> &sect; </a></h2>
91+
<h2 id="code-comparison--blank-lines">Code Comparison, blank lines<a class="sniffanchor" href="#code-comparison--blank-lines"> &sect; </a></h2>
9292
<p class="text">This is a standard block.</p>
9393
<table class="code-comparison">
9494
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonBlockLength.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="Code-Comparison,-block-length">Code Comparison, block length<a class="sniffanchor" href="#Code-Comparison,-block-length"> &sect; </a></h2>
91+
<h2 id="code-comparison--block-length">Code Comparison, block length<a class="sniffanchor" href="#code-comparison--block-length"> &sect; </a></h2>
9292
<p class="text">This is a standard block.</p>
9393
<table class="code-comparison">
9494
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonEncoding.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="Code-Comparison,-char-encoding">Code Comparison, char encoding<a class="sniffanchor" href="#Code-Comparison,-char-encoding"> &sect; </a></h2>
91+
<h2 id="code-comparison--char-encoding">Code Comparison, char encoding<a class="sniffanchor" href="#code-comparison--char-encoding"> &sect; </a></h2>
9292
<p class="text">This is a standard block.</p>
9393
<table class="code-comparison">
9494
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonLineLength.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="Code-Comparison,-line-length">Code Comparison, line length<a class="sniffanchor" href="#Code-Comparison,-line-length"> &sect; </a></h2>
91+
<h2 id="code-comparison--line-length">Code Comparison, line length<a class="sniffanchor" href="#code-comparison--line-length"> &sect; </a></h2>
9292
<p class="text">Ensure there is no PHP &quot;Warning: str_repeat(): Second argument has to be greater than or equal to 0&quot;.<br/>
9393
Ref: squizlabs/PHP_CodeSniffer#2522</p>
9494
<table class="code-comparison">

tests/Core/Generators/Expectations/ExpectedOutputCodeTitleLineWrapping.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="Code-Title,-line-wrapping">Code Title, line wrapping<a class="sniffanchor" href="#Code-Title,-line-wrapping"> &sect; </a></h2>
91+
<h2 id="code-title--line-wrapping">Code Title, line wrapping<a class="sniffanchor" href="#code-title--line-wrapping"> &sect; </a></h2>
9292
<p class="text">This is a standard block.</p>
9393
<table class="code-comparison">
9494
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeTitleWhitespace.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="Code-Title,-whitespace-handling">Code Title, whitespace handling<a class="sniffanchor" href="#Code-Title,-whitespace-handling"> &sect; </a></h2>
91+
<h2 id="code-title--whitespace-handling">Code Title, whitespace handling<a class="sniffanchor" href="#code-title--whitespace-handling"> &sect; </a></h2>
9292
<p class="text">This is a standard block.</p>
9393
<table class="code-comparison">
9494
<tr>

tests/Core/Generators/Expectations/ExpectedOutputDocumentationTitleLength.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="This-is-a-very-very-very-very-very-very-very-very-very-very-very-long-title">This is a very very very very very very very very very very very long title<a class="sniffanchor" href="#This-is-a-very-very-very-very-very-very-very-very-very-very-very-long-title"> &sect; </a></h2>
91+
<h2 id="this-is-a-very-very-very-very-very-very-very-very-very-very-very-long-title">This is a very very very very very very very very very very very long title<a class="sniffanchor" href="#this-is-a-very-very-very-very-very-very-very-very-very-very-very-long-title"> &sect; </a></h2>
9292
<p class="text">This is a standard block.</p>
9393
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
9494
</body>

tests/Core/Generators/Expectations/ExpectedOutputDocumentationTitlePCREFallback.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
</head>
8989
<body>
9090
<h1>GeneratorTest Coding Standards</h1>
91-
<h2 id="Documentation-Title-PCRE-Fallback">Documentation Title PCRE Fallback<a class="sniffanchor" href="#Documentation-Title-PCRE-Fallback"> &sect; </a></h2>
91+
<h2 id="documentation-title-pcre-fallback">Documentation Title PCRE Fallback<a class="sniffanchor" href="#documentation-title-pcre-fallback"> &sect; </a></h2>
9292
<p class="text">Testing the document title can get determined from the sniff name if missing.</p>
9393
<p class="text">This file name contains an acronym on purpose to test the word splitting.</p>
9494
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<html>
2+
<head>
3+
<title>GeneratorTest Coding Standards</title>
4+
<style>
5+
body {
6+
background-color: #FFFFFF;
7+
font-size: 14px;
8+
font-family: Arial, Helvetica, sans-serif;
9+
color: #000000;
10+
}
11+
12+
h1 {
13+
color: #666666;
14+
font-size: 20px;
15+
font-weight: bold;
16+
margin-top: 0px;
17+
background-color: #E6E7E8;
18+
padding: 20px;
19+
border: 1px solid #BBBBBB;
20+
}
21+
22+
h2 {
23+
color: #00A5E3;
24+
font-size: 16px;
25+
font-weight: normal;
26+
margin-top: 50px;
27+
}
28+
29+
h2 a.sniffanchor,
30+
h2 a.sniffanchor {
31+
color: #006C95;
32+
opacity: 0;
33+
padding: 0 3px;
34+
text-decoration: none;
35+
font-weight: bold;
36+
}
37+
h2:hover a.sniffanchor,
38+
h2:focus a.sniffanchor {
39+
opacity: 1;
40+
}
41+
42+
.code-comparison {
43+
width: 100%;
44+
}
45+
46+
.code-comparison td {
47+
border: 1px solid #CCCCCC;
48+
}
49+
50+
.code-comparison-title, .code-comparison-code {
51+
font-family: Arial, Helvetica, sans-serif;
52+
font-size: 12px;
53+
color: #000000;
54+
vertical-align: top;
55+
padding: 4px;
56+
width: 50%;
57+
background-color: #F1F1F1;
58+
line-height: 15px;
59+
}
60+
61+
.code-comparison-title {
62+
text-align: left;
63+
font-weight: 600;
64+
}
65+
66+
.code-comparison-code {
67+
font-family: Courier;
68+
background-color: #F9F9F9;
69+
}
70+
71+
.code-comparison-highlight {
72+
background-color: #DDF1F7;
73+
border: 1px solid #00A5E3;
74+
line-height: 15px;
75+
}
76+
77+
.tag-line {
78+
text-align: center;
79+
width: 100%;
80+
margin-top: 30px;
81+
font-size: 12px;
82+
}
83+
84+
.tag-line a {
85+
color: #000000;
86+
}
87+
</style>
88+
</head>
89+
<body>
90+
<h1>GeneratorTest Coding Standards</h1>
91+
<h2>Table of Contents</h2>
92+
<ul class="toc">
93+
<li><a href="#url-enc-de-non---sc-----chars">URL enc@de non-àscíï chars</a></li>
94+
<li><a href="#url-enc-de-non---sc-----chars-2">URL enc@de non-àscíï chars</a></li>
95+
<li><a href="#url-enc-de-non---sc-----chars-3">URL enc@de non-àscíï chars</a></li>
96+
</ul>
97+
<h2 id="url-enc-de-non---sc-----chars">URL enc@de non-àscíï chars<a class="sniffanchor" href="#url-enc-de-non---sc-----chars"> &sect; </a></h2>
98+
<p class="text">The documentation title has non-ascii characters, which will be slugified for use in an HTML anchor link.</p>
99+
<h2 id="url-enc-de-non---sc-----chars-2">URL enc@de non-àscíï chars<a class="sniffanchor" href="#url-enc-de-non---sc-----chars-2"> &sect; </a></h2>
100+
<p class="text">The documentation title has non-ascii characters, which will be slugified for use in an HTML anchor link.<br/>
101+
A duplicate anchor link will get a numeric suffix.</p>
102+
<h2 id="url-enc-de-non---sc-----chars-3">URL enc@de non-àscíï chars<a class="sniffanchor" href="#url-enc-de-non---sc-----chars-3"> &sect; </a></h2>
103+
<p class="text">The documentation title has non-ascii characters, which will be slugified for use in an HTML anchor link.<br/>
104+
A duplicate anchor link will get a numeric suffix.</p>
105+
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
106+
</body>
107+
</html>

0 commit comments

Comments
 (0)