Skip to content

Commit 08a864f

Browse files
authored
Merge pull request #859 from PHPCSStandards/feature/generators-improve-anchor-links
Generators/HTML: improve anchor links
2 parents 82a9ec2 + 462ff5f commit 08a864f

File tree

38 files changed

+667
-79
lines changed

38 files changed

+667
-79
lines changed

src/Generators/HTML.php

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ class HTML extends Generator
5353
margin-top: 50px;
5454
}
5555
56+
h2 a.sniffanchor,
57+
h2 a.sniffanchor {
58+
color: #006C95;
59+
opacity: 0;
60+
padding: 0 3px;
61+
text-decoration: none;
62+
font-weight: bold;
63+
}
64+
h2:hover a.sniffanchor,
65+
h2:focus a.sniffanchor {
66+
opacity: 1;
67+
}
68+
5669
.code-comparison {
5770
width: 100%;
5871
}
@@ -100,6 +113,13 @@ class HTML extends Generator
100113
}
101114
</style>';
102115

116+
/**
117+
* List of seen slugified anchors to ensure uniqueness.
118+
*
119+
* @var array<string, true>
120+
*/
121+
private $seenAnchors = [];
122+
103123

104124
/**
105125
* Generates the documentation for a standard.
@@ -119,6 +139,10 @@ public function generate()
119139
$content = ob_get_contents();
120140
ob_end_clean();
121141

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+
122146
if (trim($content) !== '') {
123147
echo $this->getFormattedHeader();
124148
echo $this->getFormattedToc();
@@ -215,7 +239,7 @@ protected function getFormattedToc()
215239
$doc->load($file);
216240
$documentation = $doc->getElementsByTagName('documentation')->item(0);
217241
$title = $this->getTitle($documentation);
218-
$output .= sprintf($listItemTemplate, str_replace(' ', '-', $title), $title);
242+
$output .= sprintf($listItemTemplate, $this->titleToAnchor($title), $title);
219243
}
220244

221245
$output .= ' </ul>'.PHP_EOL;
@@ -290,14 +314,46 @@ public function processSniff(DOMNode $doc)
290314

291315
if (trim($content) !== '') {
292316
$title = $this->getTitle($doc);
293-
echo ' <a name="'.str_replace(' ', '-', $title).'" />'.PHP_EOL;
294-
echo ' <h2>'.$title.'</h2>'.PHP_EOL;
317+
printf(
318+
' <h2 id="%1$s">%2$s<a class="sniffanchor" href="#%1$s"> &sect; </a></h2>'.PHP_EOL,
319+
$this->titleToAnchor($title),
320+
$title
321+
);
295322
echo $content;
296323
}
297324

298325
}//end processSniff()
299326

300327

328+
/**
329+
* Transform a title to a string which can be used as an HTML anchor.
330+
*
331+
* @param string $title The title.
332+
*
333+
* @since 3.12.0
334+
*
335+
* @return string
336+
*/
337+
private function titleToAnchor($title)
338+
{
339+
// Slugify the text.
340+
$title = strtolower($title);
341+
$title = preg_replace('`[^a-z0-9\._-]`', '-', $title);
342+
343+
if (isset($this->seenAnchors[$title]) === true) {
344+
// Try to find a unique anchor for this title.
345+
for ($i = 2; (isset($this->seenAnchors[$title.'-'.$i]) === true); $i++);
346+
$title .= '-'.$i;
347+
}
348+
349+
// Add to "seen" list.
350+
$this->seenAnchors[$title] = true;
351+
352+
return $title;
353+
354+
}//end titleToAnchor()
355+
356+
301357
/**
302358
* Print a text block found in a standard.
303359
*
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0"?>
2+
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="GeneratorTest" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/PHPCSStandards/PHP_CodeSniffer/master/phpcs.xsd">
3+
4+
<config name="installed_paths" value="./tests/Core/Generators/Fixtures/"/>
5+
6+
<rule ref="StandardWithDocs.Content.DocumentationTitleToAnchorSlug1"/>
7+
<rule ref="StandardWithDocs.Content.DocumentationTitleToAnchorSlug2"/>
8+
<rule ref="StandardWithDocs.Content.DocumentationTitleToAnchorSlug3"/>
9+
10+
</ruleset>

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonBlankLines.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="Code-Comparison,-blank-lines" />
79-
<h2>Code Comparison, blank lines</h2>
91+
<h2 id="code-comparison--blank-lines">Code Comparison, blank lines<a class="sniffanchor" href="#code-comparison--blank-lines"> &sect; </a></h2>
8092
<p class="text">This is a standard block.</p>
8193
<table class="code-comparison">
8294
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonBlockLength.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="Code-Comparison,-block-length" />
79-
<h2>Code Comparison, block length</h2>
91+
<h2 id="code-comparison--block-length">Code Comparison, block length<a class="sniffanchor" href="#code-comparison--block-length"> &sect; </a></h2>
8092
<p class="text">This is a standard block.</p>
8193
<table class="code-comparison">
8294
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonEncoding.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="Code-Comparison,-char-encoding" />
79-
<h2>Code Comparison, char encoding</h2>
91+
<h2 id="code-comparison--char-encoding">Code Comparison, char encoding<a class="sniffanchor" href="#code-comparison--char-encoding"> &sect; </a></h2>
8092
<p class="text">This is a standard block.</p>
8193
<table class="code-comparison">
8294
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeComparisonLineLength.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="Code-Comparison,-line-length" />
79-
<h2>Code Comparison, line length</h2>
91+
<h2 id="code-comparison--line-length">Code Comparison, line length<a class="sniffanchor" href="#code-comparison--line-length"> &sect; </a></h2>
8092
<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/>
8193
Ref: squizlabs/PHP_CodeSniffer#2522</p>
8294
<table class="code-comparison">

tests/Core/Generators/Expectations/ExpectedOutputCodeTitleLineWrapping.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="Code-Title,-line-wrapping" />
79-
<h2>Code Title, line wrapping</h2>
91+
<h2 id="code-title--line-wrapping">Code Title, line wrapping<a class="sniffanchor" href="#code-title--line-wrapping"> &sect; </a></h2>
8092
<p class="text">This is a standard block.</p>
8193
<table class="code-comparison">
8294
<tr>

tests/Core/Generators/Expectations/ExpectedOutputCodeTitleWhitespace.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="Code-Title,-whitespace-handling" />
79-
<h2>Code Title, whitespace handling</h2>
91+
<h2 id="code-title--whitespace-handling">Code Title, whitespace handling<a class="sniffanchor" href="#code-title--whitespace-handling"> &sect; </a></h2>
8092
<p class="text">This is a standard block.</p>
8193
<table class="code-comparison">
8294
<tr>

tests/Core/Generators/Expectations/ExpectedOutputDocumentationTitleCase.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="lowercase-title" />
79-
<h2>lowercase title</h2>
91+
<h2 id="lowercase-title">lowercase title<a class="sniffanchor" href="#lowercase-title"> &sect; </a></h2>
8092
<p class="text">This is a standard block.</p>
8193
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
8294
</body>

tests/Core/Generators/Expectations/ExpectedOutputDocumentationTitleLength.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="This-is-a-very-very-very-very-very-very-very-very-very-very-very-long-title" />
79-
<h2>This is a very very very very very very very very very very very long title</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>
8092
<p class="text">This is a standard block.</p>
8193
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
8294
</body>

tests/Core/Generators/Expectations/ExpectedOutputDocumentationTitlePCREFallback.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
margin-top: 50px;
2727
}
2828

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+
2942
.code-comparison {
3043
width: 100%;
3144
}
@@ -75,8 +88,7 @@
7588
</head>
7689
<body>
7790
<h1>GeneratorTest Coding Standards</h1>
78-
<a name="Documentation-Title-PCRE-Fallback" />
79-
<h2>Documentation Title PCRE Fallback</h2>
91+
<h2 id="documentation-title-pcre-fallback">Documentation Title PCRE Fallback<a class="sniffanchor" href="#documentation-title-pcre-fallback"> &sect; </a></h2>
8092
<p class="text">Testing the document title can get determined from the sniff name if missing.</p>
8193
<p class="text">This file name contains an acronym on purpose to test the word splitting.</p>
8294
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>

0 commit comments

Comments
 (0)