From ad8b6f94c4a35d6bd7706c27fa4868570c2d30ab Mon Sep 17 00:00:00 2001
From: cassapi
Date: Mon, 12 Feb 2024 18:22:28 +0100
Subject: [PATCH] fix: review the document title extraction & fix tests
---
.phpmd.baseline.xml | 4 +-
demo/.htaccess | 2 +-
doc/Development-HOWTO.md | 22 +--
src/MarkdownExtended/Parser.php | 10 ++
src/MarkdownExtended/Util/Helper.php | 19 +++
.../Console/UsageTest.php | 157 ++++++++++++++----
.../MarkdownExtendedTests/ConsoleTestCase.php | 3 +
.../MarkdownExtendedTests/ParserTestCase.php | 80 ++++++++-
tests/test-meta.md | 6 +-
9 files changed, 252 insertions(+), 51 deletions(-)
diff --git a/.phpmd.baseline.xml b/.phpmd.baseline.xml
index 5a110862..50a7fdc1 100644
--- a/.phpmd.baseline.xml
+++ b/.phpmd.baseline.xml
@@ -100,10 +100,12 @@
+
+
@@ -113,8 +115,8 @@
-
+
diff --git a/demo/.htaccess b/demo/.htaccess
index 9ec7c94f..6df79c95 100644
--- a/demo/.htaccess
+++ b/demo/.htaccess
@@ -35,6 +35,6 @@ AddType text/html .md
# Treat '.md' files by the Markdown handler
# CAUTION - this requires to know exactly where the CGI is ...
AddHandler MarkDown .md
-Action MarkDown /cgi-scripts/mde_apacheHandler.sh virtual
+Action MarkDown /demo/cgi-scripts/mde_apacheHandler.sh virtual
# Endfile
diff --git a/doc/Development-HOWTO.md b/doc/Development-HOWTO.md
index 2a95ec8b..7fe04365 100644
--- a/doc/Development-HOWTO.md
+++ b/doc/Development-HOWTO.md
@@ -7,7 +7,7 @@ This documentation file will introduce you the "dev-cycle" of PHP MarkdownExtend
First of all, you may do the following two things:
- read the *How to contribute* section below to learn about forking, working and pulling,
-- from your fork of the repository, switch to the `dev` branch: this is where the dev things are done.
+- from your fork of the repository, switch to the `develop` branch: this is where the dev things are done.
### How to contribute ?
@@ -36,11 +36,11 @@ comment the request with your vision of the thing or your experience.
### Full installation of a fork
To prepare a development version of PHP MarkdownExtended, clone your fork of the repository and
-put it on the "dev" branch:
+put it on the "develop" branch:
git clone http://github.com//markdown-extended.git
cd markdown-extended
- git checkout dev
+ git checkout develop
Then you can create your own branch with the name of your feature:
@@ -64,18 +64,18 @@ and pulling new commits:
git remote add upstream http://github.com/e-picas/markdown-extended.git
// get last original remote commits
- git checkout dev
- git pull upstream dev
+ git checkout develop
+ git pull upstream develop
### Development life-cycle
-As said above, all development MUST be done on the `dev` branch of the repository. Doing so we
+As said above, all development MUST be done on the `develop` branch of the repository. Doing so we
can commit our development features to let users using a clone test and improve them.
When the work gets a stable stage, it seems to be time to build and publish a new release. This
is done by creating a tag named like `vX.Y.Z[-status]` from the "master" branch after having
-merged the "dev" one in. Please see the [Semantic Versioning](http://semver.org/) work by
+merged the "develop" one in. Please see the [Semantic Versioning](http://semver.org/) work by
Tom Preston-Werner for more info about the release version name construction rules.
@@ -177,10 +177,10 @@ Note that the package is integrated in [Code Climate](https://codeclimate.com/gi
To make a new release of the package, you must follow these steps:
-1. merge the "dev" branch into "master"
+1. merge the "develop" branch into "master"
git checkout master
- git merge --no-ff --no-commit dev
+ git merge --no-ff --no-commit develop
2. fix code standards errors:
@@ -212,9 +212,9 @@ To make a new release of the package, you must follow these steps:
rm -f bin/markdown-extended.phar && mv markdown-extended.phar bin/
git commit -a -m "new X.Y.Z-STATE phar"
-9. merge "master" into "dev":
+9. merge "master" into "develop":
- git checkout dev
+ git checkout develop
git merge --no-ff master
Finally, don't forget to push all changes to `origin` and to make a release page
diff --git a/src/MarkdownExtended/Parser.php b/src/MarkdownExtended/Parser.php
index 86ff0e99..dc70a5b1 100644
--- a/src/MarkdownExtended/Parser.php
+++ b/src/MarkdownExtended/Parser.php
@@ -140,6 +140,16 @@ public function transform($content, $name = null, $primary = true)
// actually parse content
$content = $this->parseContent($content);
+ // guess the title if it is NOT empty
+ // @TODO - Try to make it better extracting directly from the MD source
+ // something strange is here: \MarkdownExtended\Grammar\Filter\Header::_setContentTitle()
+ if (strtolower($this->getKernel()->getConfig('output_format')) == 'html') {
+ $titles = Helper::getTextBetweenTags($content->getBody(), 'h1');
+ if (isset($titles[0])) {
+ $content->setTitle($titles[0]);
+ }
+ }
+
// force template if needed
$tpl = $this->getKernel()->getConfig('template');
if (!is_null($tpl) && $tpl === 'auto') {
diff --git a/src/MarkdownExtended/Util/Helper.php b/src/MarkdownExtended/Util/Helper.php
index 3392aac7..a9e3f3b7 100644
--- a/src/MarkdownExtended/Util/Helper.php
+++ b/src/MarkdownExtended/Util/Helper.php
@@ -156,6 +156,25 @@ public static function isSingleLine($str = '')
return (bool) (false === strpos($str, PHP_EOL));
}
+ /**
+ * Extract all contents of a specific HTML tag from a content
+ *
+ * @param string $string The HTML string to search in
+ * @param string $tagname The tagname to extract
+ *
+ * @return array
+ */
+ public static function getTextBetweenTags($string, $tagname)
+ {
+ $d = new \DOMDocument();
+ $d->loadHTML($string);
+ $return = [];
+ foreach($d->getElementsByTagName($tagname) as $item) {
+ $return[] = $item->textContent;
+ }
+ return $return;
+ }
+
// --------------
// Regular expressions
// --------------
diff --git a/tests/MarkdownExtendedTests/Console/UsageTest.php b/tests/MarkdownExtendedTests/Console/UsageTest.php
index 7eee7b59..cf73b231 100644
--- a/tests/MarkdownExtendedTests/Console/UsageTest.php
+++ b/tests/MarkdownExtendedTests/Console/UsageTest.php
@@ -25,8 +25,6 @@ class UsageTest extends ConsoleTestCase
*/
public function testSimpleStringTemplate()
{
- $res1 = $this->runCommand($this->getBaseCmd().' --template "'.ParserTestCase::MD_STRING.'"');
- $res2 = $this->runCommand($this->getBaseCmd().' -t "'.ParserTestCase::MD_STRING.'"');
$line = ParserTestCase::PARSED_STRING;
$html = $this->stripWhitespaceAndNewLines(
<<runCommand($this->getBaseCmd().' --template "'.ParserTestCase::MD_STRING.'"');
// status with long option
$this->assertEquals(
$res1['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with "--template" long option (status)'
);
// stdout with long option
@@ -56,10 +55,11 @@ public function testSimpleStringTemplate()
'[console] test of the CLI on a simple string with "--template" long option'
);
+ $res2 = $this->runCommand($this->getBaseCmd().' -t "'.ParserTestCase::MD_STRING.'"');
// status with short option
$this->assertEquals(
$res2['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with "-t" short option (status)'
);
// stdout with short option
@@ -90,7 +90,7 @@ public function testSimpleStringCustomTemplate()
$this->assertEquals(
$res['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with "--template=test-template" long option (status)'
);
$this->assertEquals(
@@ -111,7 +111,7 @@ public function testSimpleStringCustomTemplateError()
// status NOT 0
$this->assertNotEquals(
$res['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with a wrong template path (status NOT 0)'
);
// stderr not empty
@@ -143,7 +143,7 @@ public function testSimpleStringAsJson()
// status with long option and no equal sign
$this->assertEquals(
$res1['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with "--response json" long option (status)'
);
// stdout with long option and no equal sign
@@ -156,7 +156,7 @@ public function testSimpleStringAsJson()
// status with long option and equal sign
$this->assertEquals(
$res2['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with "--response=json" long option (status)'
);
// stdout with long option and equal sign
@@ -169,7 +169,7 @@ public function testSimpleStringAsJson()
// status with short option and no equal sign
$this->assertEquals(
$res3['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with "-r json" short option (status)'
);
// stdout with short option and no equal sign
@@ -182,7 +182,7 @@ public function testSimpleStringAsJson()
// status with short option and equal sign
$this->assertEquals(
$res4['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a simple string with "-r=json" short option (status)'
);
// stdout with short option and equal sign
@@ -203,13 +203,13 @@ public function testTemplateAuto()
$file = $this->getPath([$this->getBasePath(), 'tests', 'test.md']);
$file_meta = $this->getPath([$this->getBasePath(), 'tests', 'test-meta.md']);
$body = $this->stripWhitespaceAndNewLines($this->getFileExpectedBody_test());
- $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContent_test());
+ $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContentLong_test());
// full content without metadata
$res1 = $this->runCommand($this->getBaseCmd().' '.$file);
$this->assertEquals(
$res1['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with no metadata and automatic templating (status)'
);
$this->assertEquals(
@@ -222,7 +222,7 @@ public function testTemplateAuto()
$res2 = $this->runCommand($this->getBaseCmd().' '.$file_meta);
$this->assertEquals(
$res2['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with metadata and automatic templating (status)'
);
$this->assertEquals(
@@ -237,7 +237,7 @@ public function testTemplateAuto()
*
* @runInSeparateProcess
*/
- public function testExtract()
+ public function testExtractFullContent()
{
$file = $this->getPath([$this->getBasePath(), 'tests', 'test-meta.md']);
$meta = [
@@ -248,14 +248,15 @@ public function testExtract()
foreach ($meta as $var => $val) {
$meta_str .= $var.': '.$val.PHP_EOL;
}
- $body = $this->stripWhitespaceAndNewLines($this->getFileExpectedBody_test());
- $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContent_test());
+ $title = $this->stripWhitespaceAndNewLines($this->getFileExpectedTitleLong_test());
+ $body = $this->stripWhitespaceAndNewLines($this->getFileExpectedBodyLong_test());
+ $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContentLong_test());
// full content
$res1 = $this->runCommand($this->getBaseCmd().' '.$file);
$this->assertEquals(
$res1['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with metadata (status)'
);
$this->assertEquals(
@@ -263,12 +264,33 @@ public function testExtract()
$html,
'[console] test of the CLI on a file with metadata'
);
+ }
+
+ /**
+ * Test a call on a file with metadata and a custom template
+ *
+ * @runInSeparateProcess
+ */
+ public function testExtractMetadata()
+ {
+ $file = $this->getPath([$this->getBasePath(), 'tests', 'test-meta.md']);
+ $meta = [
+ 'meta1' => 'a value for meta 1',
+ 'meta2' => 'another value for meta 2',
+ ];
+ $meta_str = '';
+ foreach ($meta as $var => $val) {
+ $meta_str .= $var.': '.$val.PHP_EOL;
+ }
+ $title = $this->stripWhitespaceAndNewLines($this->getFileExpectedTitleLong_test());
+ $body = $this->stripWhitespaceAndNewLines($this->getFileExpectedBodyLong_test());
+ $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContentLong_test());
// extraction of metadata
$res2 = $this->runCommand($this->getBaseCmd().' -e '.$file);
$this->assertEquals(
$res2['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with metadata extraction without argument (short option "-e") (status)'
);
$this->assertStringEndsWith(
@@ -279,7 +301,7 @@ public function testExtract()
$res3 = $this->runCommand($this->getBaseCmd().' --extract '.$file);
$this->assertEquals(
$res3['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with metadata extraction without argument (long option "--extract") (status)'
);
$this->assertStringEndsWith(
@@ -287,12 +309,33 @@ public function testExtract()
trim($res3['stdout']),
'[console] test of the CLI on a file with metadata extraction without argument (long option "--extract")'
);
+ }
+
+ /**
+ * Test a call on a file with metadata and a custom template
+ *
+ * @runInSeparateProcess
+ */
+ public function testExtractSingleMetadata()
+ {
+ $file = $this->getPath([$this->getBasePath(), 'tests', 'test-meta.md']);
+ $meta = [
+ 'meta1' => 'a value for meta 1',
+ 'meta2' => 'another value for meta 2',
+ ];
+ $meta_str = '';
+ foreach ($meta as $var => $val) {
+ $meta_str .= $var.': '.$val.PHP_EOL;
+ }
+ $title = $this->stripWhitespaceAndNewLines($this->getFileExpectedTitleLong_test());
+ $body = $this->stripWhitespaceAndNewLines($this->getFileExpectedBodyLong_test());
+ $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContentLong_test());
// extraction of a single metadata
$res4 = $this->runCommand($this->getBaseCmd().' -e=meta1 '.$file);
$this->assertEquals(
$res4['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with one single metadata extraction (short option "-e=meta1") (status)'
);
$this->assertEquals(
@@ -303,7 +346,7 @@ public function testExtract()
$res5 = $this->runCommand($this->getBaseCmd().' --extract=meta1 '.$file);
$this->assertEquals(
$res5['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with one single metadata extraction (long option "--extract=meta1") (status)'
);
$this->assertEquals(
@@ -311,12 +354,33 @@ public function testExtract()
$meta['meta1'],
'[console] test of the CLI on a file with one single metadata extraction (long option "--extract=meta1")'
);
+ }
+
+ /**
+ * Test a call on a file with metadata and a custom template
+ *
+ * @runInSeparateProcess
+ */
+ public function testExtractBody()
+ {
+ $file = $this->getPath([$this->getBasePath(), 'tests', 'test-meta.md']);
+ $meta = [
+ 'meta1' => 'a value for meta 1',
+ 'meta2' => 'another value for meta 2',
+ ];
+ $meta_str = '';
+ foreach ($meta as $var => $val) {
+ $meta_str .= $var.': '.$val.PHP_EOL;
+ }
+ $title = $this->stripWhitespaceAndNewLines($this->getFileExpectedTitleLong_test());
+ $body = $this->stripWhitespaceAndNewLines($this->getFileExpectedBodyLong_test());
+ $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContentLong_test());
// extraction of the body
$res6 = $this->runCommand($this->getBaseCmd().' -e=body '.$file);
$this->assertEquals(
$res6['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with body extraction (short option "-e=body") (status)'
);
$this->assertEquals(
@@ -327,7 +391,7 @@ public function testExtract()
$res7 = $this->runCommand($this->getBaseCmd().' --extract=body '.$file);
$this->assertEquals(
$res7['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with body extraction (short option "--extract=body") (status)'
);
$this->assertEquals(
@@ -337,6 +401,41 @@ public function testExtract()
);
}
+ /**
+ * Test a call on a file with metadata and a custom template
+ *
+ * @runInSeparateProcess
+ */
+ public function testExtractTitle()
+ {
+ $file = $this->getPath([$this->getBasePath(), 'tests', 'test-meta.md']);
+ $title = $this->stripWhitespaceAndNewLines($this->getFileExpectedTitleLong_test());
+
+ // extraction of the title
+ $res8 = $this->runCommand($this->getBaseCmd().' -e=title '.$file);
+ $this->assertEquals(
+ $res8['status'],
+ ConsoleTestCase::STATUS_OK,
+ '[console] test of the CLI on a file with body extraction (short option "-e=title") (status)'
+ );
+ $this->assertEquals(
+ $this->stripWhitespaceAndNewLines($res8['stdout']),
+ $title,
+ '[console] test of the CLI on a file with body extraction (short option "-e=title")'
+ );
+ $res9 = $this->runCommand($this->getBaseCmd().' --extract=title '.$file);
+ $this->assertEquals(
+ $res9['status'],
+ ConsoleTestCase::STATUS_OK,
+ '[console] test of the CLI on a file with body extraction (short option "--extract=title") (status)'
+ );
+ $this->assertEquals(
+ $this->stripWhitespaceAndNewLines($res9['stdout']),
+ $title,
+ '[console] test of the CLI on a file with body extraction (short option "--extract=title")'
+ );
+ }
+
/**
* Test a call on a file with metadata and output generation
*
@@ -348,14 +447,14 @@ public function testOutput()
$file = $this->getPath([$this->getBasePath(), 'tests', 'test-meta.md']);
$output = $this->getPath([$this->getBasePath(), 'tmp', 'test-output-%s.html']);
- $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContent_test());
+ $html = $this->stripWhitespaceAndNewLines($this->getFileExpectedContentLong_test());
// short option
$output1 = sprintf($output, '1');
$res1 = $this->runCommand($this->getBaseCmd().' -o '.$output1.' '.$file);
$this->assertEquals(
$res1['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with short option output generation (status)'
);
$this->assertEquals(
@@ -374,7 +473,7 @@ public function testOutput()
$res2 = $this->runCommand($this->getBaseCmd().' --output '.$output2.' '.$file);
$this->assertEquals(
$res2['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with long option output generation (status)'
);
$this->assertEquals(
@@ -408,7 +507,7 @@ public function testOutputBackup()
$res1 = $this->runCommand($this->getBaseCmd().' -o '.$output.' '.$file);
$this->assertEquals(
$res1['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with short option output generation N (status)'
);
$this->assertEquals(
@@ -425,7 +524,7 @@ public function testOutputBackup()
$res2 = $this->runCommand($this->getBaseCmd().' -o '.$output.' '.$file);
$this->assertEquals(
$res2['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with short option output generation N+1 (status)'
);
$this->assertEquals(
@@ -450,7 +549,7 @@ public function testOutputBackup()
$res3 = $this->runCommand($this->getBaseCmd().' --force -o '.$output.' '.$file);
$this->assertEquals(
$res3['status'],
- '0',
+ ConsoleTestCase::STATUS_OK,
'[console] test of the CLI on a file with short option output generation N+2 and the force option (status)'
);
$this->assertEquals(
diff --git a/tests/MarkdownExtendedTests/ConsoleTestCase.php b/tests/MarkdownExtendedTests/ConsoleTestCase.php
index 85cd9440..2f1085e2 100644
--- a/tests/MarkdownExtendedTests/ConsoleTestCase.php
+++ b/tests/MarkdownExtendedTests/ConsoleTestCase.php
@@ -13,6 +13,9 @@
class ConsoleTestCase extends ParserTestCase
{
+ const STATUS_OK = 0;
+ const STATUS_ERROR = 1;
+
/**
* Gets the basic 'php bin/markdown-extended' string
*
diff --git a/tests/MarkdownExtendedTests/ParserTestCase.php b/tests/MarkdownExtendedTests/ParserTestCase.php
index 0099e68c..5b4d99da 100644
--- a/tests/MarkdownExtendedTests/ParserTestCase.php
+++ b/tests/MarkdownExtendedTests/ParserTestCase.php
@@ -28,14 +28,6 @@ public function getTestFilepath()
return $this->getResourcePath('test.md');
}
- /**
- * Validate class methods
- public function testCreate()
- {
- }
- */
-
-
/**
* Get the tests test file parsed full content
*
@@ -83,4 +75,76 @@ public function getFileExpectedBody_test()
perferendis doloribus asperiores repellat.
EOF;
}
+
+
+ /**
+ * Get the tests test file path
+ *
+ * @return string
+ */
+ public function getTestFilepathLong()
+ {
+ return $this->getResourcePath('test-2.md');
+ }
+
+ /**
+ * Get the tests test file parsed full content
+ *
+ * @return string
+ */
+ public function getFileExpectedContentLong_test()
+ {
+ $body = $this->getFileExpectedBodyLong_test();
+ return <<
+
+
+
+ Document title
+
+
+
+
+{$body}
+
+
+EOF;
+ }
+
+ /**
+ * Get the tests test.md file parsed body
+ *
+ * @return string
+ */
+ public function getFileExpectedBodyLong_test()
+ {
+ return <<Document title
+At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium
+voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi.
+
+ Sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt
+ mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et
+ expedita distinctio.
+
+Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id
+quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.[^1]
+Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet
+ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic
+tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut
+perferendis doloribus asperiores repellat.
+EOF;
+ }
+
+ /**
+ * Get the tests test.md file parsed title
+ *
+ * @return string
+ */
+ public function getFileExpectedTitleLong_test()
+ {
+ return "Document title";
+ }
+
+
}
diff --git a/tests/test-meta.md b/tests/test-meta.md
index a0a593eb..a94fd868 100644
--- a/tests/test-meta.md
+++ b/tests/test-meta.md
@@ -1,6 +1,8 @@
meta1: a value for meta 1
meta2: another value for meta 2
+# Document title
+
At vero eos et accusamus et **iusto odio dignissimos ducimus qui blanditiis** praesentium
voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi.
@@ -9,8 +11,10 @@ voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi.
expedita distinctio.
Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id
-quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.
+quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.[^1]
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet
ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic
tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut
perferendis doloribus asperiores repellat.
+
+[^1]: The footnote content...