Skip to content

Commit 824b3b0

Browse files
authored
Merge pull request #19 from KEINOS/feat-user-custom-toc-tag
Feat #17 user custom toc tag. Close #17
2 parents 32a29df + a494d0e commit 824b3b0

13 files changed

+574
-54
lines changed

Extension.php

Lines changed: 108 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ParsedownToC extends DynamicParent
4747
* Constants.
4848
* ------------------------------------------------------------------------
4949
*/
50-
const version = '1.1.1'; // Version is available since v1.1.0
50+
const version = '1.1.2'; // Version is available since v1.1.0
5151
const VERSION_PARSEDOWN_REQUIRED = '1.7';
5252
const TAG_TOC_DEFAULT = '[toc]';
5353
const ID_ATTRIBUTE_DEFAULT = 'toc';
@@ -124,15 +124,19 @@ protected function blockHeader($Line)
124124
}
125125

126126
/**
127-
* Parses the given markdown string to an HTML string.
128-
* It's an alias of the parent method: \Parsedown::text()
127+
* Parses the given markdown string to an HTML string but it leaves the ToC
128+
* tag as is. It's an alias of the parent method "\DynamicParent::text()".
129129
*
130130
* @param string $text Markdown string to be parsed.
131131
* @return string Parsed HTML string.
132132
*/
133133
public function body($text)
134134
{
135-
return DynamicParent::text($text);
135+
$text = $this->encodeTagToHash($text); // Escapes ToC tag temporary
136+
$html = DynamicParent::text($text); // Parses the markdown text
137+
$html = $this->decodeTagFromHash($html); // Unescape the ToC tag
138+
139+
return $html;
136140
}
137141

138142
/**
@@ -167,7 +171,8 @@ public function contentsList($type_return = 'string')
167171
}
168172

169173
/**
170-
* Generates link-able anchor from the text.
174+
* Generates an anchor text that are link-able even the heading is not in
175+
* ASCII.
171176
*
172177
* @param string $text
173178
* @return string
@@ -177,6 +182,53 @@ protected function createAnchorID($text)
177182
return urlencode($this->fetchText($text));
178183
}
179184

185+
/**
186+
* Decodes the hashed ToC tag to an original tag and replaces.
187+
*
188+
* This is used to avoid parsing user defined ToC tag which includes "_" in
189+
* their tag such as "[[_toc_]]". Unless it will be parsed as:
190+
* "<p>[[<em>TOC</em>]]</p>"
191+
*
192+
* @param string $text
193+
* @return string
194+
*/
195+
protected function decodeTagFromHash($text)
196+
{
197+
$salt = $this->getSalt();
198+
$tag_origin = $this->getTagToC();
199+
$tag_hashed = hash('sha256', $salt . $tag_origin);
200+
201+
if (strpos($text, $tag_hashed) === false) {
202+
return $text;
203+
}
204+
205+
return str_replace($tag_hashed, $tag_origin, $text);
206+
}
207+
208+
/**
209+
* Encodes the ToC tag to a hashed tag and replace.
210+
*
211+
* This is used to avoid parsing user defined ToC tag which includes "_" in
212+
* their tag such as "[[_toc_]]". Unless it will be parsed as:
213+
* "<p>[[<em>TOC</em>]]</p>"
214+
*
215+
* @param string $text
216+
* @return string
217+
*/
218+
protected function encodeTagToHash($text)
219+
{
220+
$salt = $this->getSalt();
221+
$tag_origin = $this->getTagToC();
222+
223+
if (strpos($text, $tag_origin) === false) {
224+
return $text;
225+
}
226+
227+
$tag_hashed = hash('sha256', $salt . $tag_origin);
228+
229+
return str_replace($tag_origin, $tag_hashed, $text);
230+
}
231+
180232
/**
181233
* Get only the text from a markdown string.
182234
* It parses to HTML once then trims the tags to get the text.
@@ -204,35 +256,33 @@ protected function getIdAttributeToC()
204256
}
205257

206258
/**
207-
* Gets the markdown tag for ToC.
259+
* Unique string to use as a salt value.
208260
*
209261
* @return string
210262
*/
211-
protected function getTagToC()
263+
protected function getSalt()
212264
{
213-
if (isset($this->tag_toc) && ! empty($this->tag_toc)) {
214-
return $this->tag_toc;
265+
static $salt;
266+
if (isset($salt)) {
267+
return $salt;
215268
}
216269

217-
return self::TAG_TOC_DEFAULT;
270+
$salt = hash('md5', time());
271+
return $salt;
218272
}
219273

220274
/**
221-
* Replaces the "[toc]" tag (by default) to the parsed ToC list.
275+
* Gets the markdown tag for ToC.
222276
*
223-
* @param string $html Parsed HTML string
224-
* @return string Parsed HTML string with parsed ToC.
277+
* @return string
225278
*/
226-
protected function replaceTagToC($html)
279+
protected function getTagToC()
227280
{
228-
$toc = $this->contentsList();
229-
$tag_toc = $this->getTagToC();
230-
$id_toc = $this->getIdAttributeToC();
231-
232-
$needle = '<p>' . $tag_toc . '</p>';
233-
$replace = "<div id=\"${id_toc}\">${toc}</div>";
281+
if (isset($this->tag_toc) && ! empty($this->tag_toc)) {
282+
return $this->tag_toc;
283+
}
234284

235-
return str_replace($needle, $replace, $html);
285+
return self::TAG_TOC_DEFAULT;
236286
}
237287

238288
/**
@@ -297,6 +347,29 @@ protected function setContentsListAsString(array $Content)
297347
protected $contentsListString = '';
298348
protected $firstHeadLevel = 0;
299349

350+
/**
351+
* Sets the user defined ToC markdown tag.
352+
*
353+
* @param string $tag
354+
* @return void
355+
*/
356+
public function setTagToc($tag)
357+
{
358+
$tag = trim($tag);
359+
if (self::escape($tag) === $tag) {
360+
// Set ToC tag if it's safe
361+
$this->tag_toc = $tag;
362+
} else {
363+
// Do nothing but log
364+
error_log(
365+
'Malformed ToC user tag given.'
366+
. ' At: ' . __FUNCTION__ . '() '
367+
. ' in Line:' . __LINE__ . ' (Using default ToC tag)'
368+
);
369+
}
370+
}
371+
protected $tag_toc='';
372+
300373
/**
301374
* Parses markdown string to HTML and also the "[toc]" tag as well.
302375
* It overrides the parent method: \Parsedown::text().
@@ -306,13 +379,22 @@ protected function setContentsListAsString(array $Content)
306379
*/
307380
public function text($text)
308381
{
309-
$body = $this->body($text);
310-
$tag = $this->getTagToC();
382+
// Parses the markdown text except the ToC tag. This also searches
383+
// the list of contents and available to get from "contentsList()"
384+
// method.
385+
$html = $this->body($text);
311386

312-
if (strpos($text, $tag) === false) {
313-
return $body;
387+
$tag_origin = $this->getTagToC();
388+
389+
if (strpos($text, $tag_origin) === false) {
390+
return $html;
314391
}
315392

316-
return $this->replaceTagToC($body);
393+
$toc_data = $this->contentsList();
394+
$toc_id = $this->getIdAttributeToC();
395+
$needle = '<p>' . $tag_origin . '</p>';
396+
$replace = "<div id=\"${toc_id}\">${toc_data}</div>";
397+
398+
return str_replace($needle, $replace, $html);
317399
}
318400
}

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,23 @@ echo $body . PHP_EOL; // Main body
8585
- Main Class: `ParsedownToC()`
8686
- Arguments: none
8787
- Methods:
88-
- `text(string $text)`: Returns the parsed content and `[toc]` tag(s) parsed as well.
88+
- `text(string $text)`:
89+
- Returns the parsed content and `[toc]` tag(s) parsed as well.
8990
- Required argument `$text`: Markdown string to be parsed.
90-
- `body(string $text)`: Returns the parsed content without parsing `[toc]` tag.
91+
- `body(string $text)`:
92+
- Returns the parsed content WITHOUT parsing `[toc]` tag.
9193
- Required argument `$text`: Markdown string to be parsed.
92-
- `toc([string $type_return='string'])`: Returns the ToC, the table of contents, in HTML or JSON.
93-
- Option argument `$type_return`: `string` or `json` can be specified. (`string`=HTML(default), `json`=JSON)
94+
- `toc([string $type_return='string'])`:
95+
- Returns the ToC, the table of contents, in HTML or JSON.
96+
- Option argument:
97+
- `$type_return`:
98+
- `string` or `json` can be specified. `string`=HTML, `json`=JSON.
99+
- Default `string`
94100
- Alias method: `contentsList(string $type_return)`
101+
- `setTagToc(string $tag='[tag]')`:
102+
- Sets user defined ToC markdown tag. Use this method before `text()` or `body()` method if you want to use the ToC tag rather than the "`[toc]`".
103+
- Empty value sets the default ToC tag.
104+
- Available since v1.1.2
95105
- Other Methods:
96106
- All the public methods of `Parsedown` and/or `Parsedown Extend` are available to use.
97107
- Note: As of v1.1.0 the old alias class: `Extension()` is deprecated.
@@ -145,7 +155,7 @@ php -r "copy('https://KEINOS.github.io/parsedown-extension_table-of-contents/Ext
145155

146156
As of Parsedown ToC Extension v1.1.1, you can use the [anchor identifiers](https://michelf.ca/projects/php-markdown/extra/#header-id) for [Parsedown Extra](https://github.com/erusev/parsedown-extra).
147157

148-
With this feature, you can specify the anchor name you like. Useful if the headings are in UTF-8 (not in ASCII) and/or placing "go back" links in page.
158+
With this feature, you can specify the anchor name you like. Useful if the headings are in UTF-8 (not in ASCII) and to make it readable. Such as placing the "go back" links in a page.
149159

150160
```markdown
151161
# SampleHead1 {#self-defined-head1}

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"test": "./tests/run-tests.sh",
3434
"test-docker": "docker run --rm --user root -v $(pwd):/app php:cli /app/tests/run-tests.sh",
3535
"test-docker-php5": "docker run --rm --user root -v $(pwd):/app php:5-cli-alpine /app/tests/run-tests.sh",
36+
"test-docker-php5.3": "docker run --rm --user root -v $(pwd):/app php:5.3.29-cli /app/tests/run-tests.sh",
3637
"test-docker-php8": "docker run --rm --user root -v $(pwd):/app keinos/php8-jit /app/tests/run-tests.sh"
3738
}
3839
}

tests/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ In order to run the tests in PHP5 as well, we decided NOT to use PHPUnit and do
1818
```
1919

2020
```bash
21-
$ # For those who want to run the tests in PHP ^5.6 (Alpine)
21+
$ # For those who want to run the tests in PHP 5.3.26 (Debian)
22+
$ composer test-docker-php5.3
23+
...
24+
```
25+
26+
```bash
27+
$ # For those who want to run the tests in PHP 5.6.40 (Alpine)
2228
$ composer test-docker-php5
2329
...
2430
```

0 commit comments

Comments
 (0)