chiark / gitweb /
documentation: verify HTML escaping for both the doxygen and python theme.
authorVladimír Vondruš <mosra@centrum.cz>
Tue, 17 Sep 2024 21:00:52 +0000 (23:00 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Wed, 25 Sep 2024 19:30:33 +0000 (21:30 +0200)
It's a mess and needs to be cleaned up, especially given that I now need
to have non-HTML output from the Python generator, so it's good to have
it actually regression checked.

This actually revealed one superfluous unescape in the Python theme
(which was likely a leftover from when that code was copypasted from the
Doxygen theme), and a Doxygen bug where it doesn't unescape image alt
text. Then m.css doesn't correctly escape the alt text either, which
makes the bug cancel out and everything is alright.

Oh and there's also a bug where if special HTML chars are used in
filenames, it breaks the Doxygen XML output. I should probably make a PR
for those two.

28 files changed:
documentation/doxygen.py
documentation/python.py
documentation/test_doxygen/__init__.py
documentation/test_doxygen/contents_html_escape/Doxyfile [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/Fi&le.h [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/Fi_6le_8h.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/annotated.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/files.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/page-1820.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/page.dox [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/page.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/pages.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/structClass.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/structSub_3_01char_00_01T_01_4.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/structSub_3_01char_00_01T_01_4_1_1Nested.html [new file with mode: 0644]
documentation/test_doxygen/contents_html_escape/tiny.png [new symlink]
documentation/test_doxygen/contents_typography/indexpage_1817.xml [new file with mode: 0644]
documentation/test_doxygen/test_contents.py
documentation/test_python/CMakeLists.txt
documentation/test_python/content_html_escape/content_html_escape.Class.html [new file with mode: 0644]
documentation/test_python/content_html_escape/content_html_escape.html [new file with mode: 0644]
documentation/test_python/content_html_escape/content_html_escape.pybind.html [new file with mode: 0644]
documentation/test_python/content_html_escape/content_html_escape/__init__.py [new file with mode: 0644]
documentation/test_python/content_html_escape/content_html_escape/pybind.cpp [new file with mode: 0644]
documentation/test_python/content_html_escape/page.html [new file with mode: 0644]
documentation/test_python/content_html_escape/page.rst [new file with mode: 0644]
documentation/test_python/content_html_escape/pages.html [new file with mode: 0644]
documentation/test_python/test_content.py

index 8502d5e93298ab9f851f48f86df4fe9b2acfae35..3bb13b86b15f543d2b9be7c3ab7d52d2f7140847 100755 (executable)
@@ -1130,6 +1130,9 @@ def parse_desc_internal(state: State, element: ET.Element, immediate_parent: ET.
                 # The alt text can apparently be specified only with the HTML
                 # <img> tag, not with @image. It's also present only since
                 # 1.9.1(?).
+                # TODO Doxygen seems to be double-escaping this, which
+                #   ultimately means we cannot escape this ourselves as it'd be
+                #   wrong. See test_contents.HtmlEscape for a repro case.
                 alt = i.attrib.get('alt', 'Image')
 
                 caption = i.text
@@ -2640,25 +2643,26 @@ def postprocess_state(state: State):
             # Fill breadcrumb with leaf names and URLs
             include = []
             for i in reversed(path_reverse):
-                include += [state.compounds[i].leaf_name]
+                # TODO the escaping / unescaping is a mess, fix that
+                include += [html.unescape(state.compounds[i].leaf_name)]
 
             state.includes['/'.join(include)] = compound.id
 
     # Resolve navbar links that are just an ID
-    def resolve_link(html, title, url, id):
-        if not html and not title and not url:
+    def resolve_link(html_, title, url, id):
+        if not html_ and not title and not url:
             assert id in state.compounds, "Navbar references {} which wasn't found".format(id)
             found = state.compounds[id]
             title, url = found.name, found.url
-        return html, title, url, id
+        return html_, title, url, id
     for var in 'LINKS_NAVBAR1', 'LINKS_NAVBAR2':
         links = []
-        for html, title, url, id, sub in state.config[var]:
-            html, title, url, id = resolve_link(html, title, url, id)
+        for html_, title, url, id, sub in state.config[var]:
+            html_, title, url, id = resolve_link(html_, title, url, id)
             sublinks = []
             for i in sub:
                 sublinks += [resolve_link(*i)]
-            links += [(html, title, url, id, sublinks)]
+            links += [(html_, title, url, id, sublinks)]
         state.config[var] = links
 
 def build_search_data(state: State, merge_subtrees=True, add_lookahead_barriers=True, merge_prefixes=True) -> bytearray:
index 6c8274c0f937151ffec2d61ccf7130606e75a020..1df447ea395be508f75f258f8c6309cc84c240b5 100755 (executable)
@@ -2497,16 +2497,16 @@ def build_search_data(state: State, merge_subtrees=True, add_lookahead_barriers=
             # chars which should fit the full function name in the list in most
             # cases, yet be still long enough to be able to distinguish
             # particular overloads.
-            # TODO: the suffix_length has to be calculated on UTF-8 and I
-            # am (un)escaping a lot back and forth here -- needs to be
-            # cleaned up
+            # TODO: the suffix_length has to be calculated on UTF-8
             params = ', '.join(result.params)
+            assert is_html_safe(params) # this is not C++, so no <>&
             if len(params) > 49:
                 params = params[:48] + '…'
             name_with_args += '(' + params + ')'
             suffix_length += len(params.encode('utf-8')) + 2
 
         complete_name = joiner.join(result.prefix + [name_with_args])
+        # TODO needs escaping once page names are exposed to search
         assert is_html_safe(complete_name) # this is not C++, so no <>&
         index = map.add(complete_name, result.url, suffix_length=suffix_length, flags=result.flags)
 
@@ -2525,7 +2525,9 @@ def build_search_data(state: State, merge_subtrees=True, add_lookahead_barriers=
                 if name:
                     lookahead_barriers += [len(name)]
                     name += joiner
-                name += html.unescape(j)
+                # TODO needs escaping once page names are exposed to search
+                assert is_html_safe(j) # this is not C++, so no <>&
+                name += j
             trie.insert(name.lower(), index, lookahead_barriers=lookahead_barriers if add_lookahead_barriers else [])
 
             # Add functions the second time with () appended, referencing
index a238d9b5abe84e3b10d512cffb7ab97092b8f6ca..36865fd0fd6b68fa04b43fef0de841537918a973 100644 (file)
@@ -82,8 +82,10 @@ class BaseTestCase(unittest.TestCase):
         if os.path.exists(os.path.join(self.path, 'html')): shutil.rmtree(os.path.join(self.path, 'html'))
 
     def run_doxygen(self, templates=default_templates, wildcard=default_wildcard, index_pages=default_index_pages, config={}):
-        state = State({**copy.deepcopy(default_config), **config})
+        state = State(copy.deepcopy(default_config))
         parse_doxyfile(state, os.path.join(self.path, self.doxyfile))
+        # Make the supplied config values overwrite what's in the Doxyfile
+        state.config = {**state.config, **config}
         run(state, templates=templates, wildcard=wildcard, index_pages=index_pages, sort_globbed_files=True)
 
     def actual_expected_contents(self, actual, expected = None):
diff --git a/documentation/test_doxygen/contents_html_escape/Doxyfile b/documentation/test_doxygen/contents_html_escape/Doxyfile
new file mode 100644 (file)
index 0000000..a511c1f
--- /dev/null
@@ -0,0 +1,19 @@
+INPUT                   = page.dox Fi&le.h
+IMAGE_PATH              = .
+QUIET                   = YES
+GENERATE_HTML           = NO
+GENERATE_LATEX          = NO
+GENERATE_XML            = YES
+XML_PROGRAMLISTING      = NO
+CASE_SENSE_NAMES        = YES
+
+# Needed to make the @struct <file> <name> recognized by m.css, otherwise it's
+# ignored as the directory structure gets lost with empty STRIP_FROM_INC_PATH
+STRIP_FROM_INC_PATH = .
+
+##! M_PAGE_FINE_PRINT   =
+##! M_THEME_COLOR       =
+##! M_FAVICON           =
+##! M_LINKS_NAVBAR1     = files
+##! M_LINKS_NAVBAR2     = pages annotated
+##! M_SEARCH_DISABLED   = YES
diff --git a/documentation/test_doxygen/contents_html_escape/Fi&le.h b/documentation/test_doxygen/contents_html_escape/Fi&le.h
new file mode 100644 (file)
index 0000000..4f67459
--- /dev/null
@@ -0,0 +1,29 @@
+/** @file
+ * @brief The file path should be escaped, in the file list also
+ */
+
+/**
+@brief The class include name as well as derived class reference should be escaped
+*/
+template<class T> struct Class {
+    /** @brief Function */
+    void suffixShouldBeEscaped(const Type<char>::ShouldBeEscaped& = "default value <&> should be escaped") &&;
+
+    /** @brief Enum */
+    enum Enum {
+        Value = Initializer<char&>::ShouldBeEscaped
+    };
+};
+
+template<class, class> struct Sub;
+
+template<class T> struct Sub<char, T>: Class<T> {
+    /** @brief The outer class name should be escaped, in the class list as well */
+    struct Nested {};
+};
+
+/** @struct Sub<char, T> Fi&le.h FakeFi&le.h
+ * @brief The class name as well as base class reference should be escaped, in the class list as well; the faked include should be escaped here too */
+
+/** @brief Function specialization */
+template<> void functionShouldHaveSpecializedNameEscaped<char&>();
diff --git a/documentation/test_doxygen/contents_html_escape/Fi_6le_8h.html b/documentation/test_doxygen/contents_html_escape/Fi_6le_8h.html
new file mode 100644 (file)
index 0000000..2a961c8
--- /dev/null
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Fi&amp;le.h file | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Fi&amp;le.h <span class="m-thin">file</span>
+        </h1>
+        <p>The file path should be escaped, in the file list also.</p>
+        <nav class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#nested-classes">Classes</a></li>
+                <li><a href="#func-members">Functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </nav>
+        <section id="nested-classes">
+          <h2><a href="#nested-classes">Classes</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <div class="m-doc-template">template&lt;class T&gt;</div>
+              struct <a href="structClass.html" class="m-doc">Class</a>
+            </dt>
+            <dd>The class include name as well as derived class reference should be escaped.</dd>
+            <dt>
+              <div class="m-doc-template">template&lt;class T&gt;</div>
+              struct <a href="structSub_3_01char_00_01T_01_4.html" class="m-doc">Sub&lt;char, T&gt;</a>
+            </dt>
+            <dd>The class name as well as base class reference should be escaped, in the class list as well; the faked include should be escaped here too.</dd>
+            <dt>
+              struct <a href="structSub_3_01char_00_01T_01_4_1_1Nested.html" class="m-doc">Sub&lt;char, T&gt;::Nested</a>
+            </dt>
+            <dd>The outer class name should be escaped, in the class list as well.</dd>
+          </dl>
+        </section>
+        <section id="func-members">
+          <h2><a href="#func-members">Functions</a></h2>
+          <dl class="m-doc">
+            <dt id="a3b5d61927252197070e8d998f643a2b2">
+              <div class="m-doc-template">template&lt;&gt;</div>
+              <span class="m-doc-wrap-bumper">void <a href="#a3b5d61927252197070e8d998f643a2b2" class="m-doc-self">functionShouldHaveSpecializedNameEscaped&lt;char&amp;&gt;</a>(</span><span class="m-doc-wrap">)</span>
+            </dt>
+            <dd>Function specialization.</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/annotated.html b/documentation/test_doxygen/contents_html_escape/annotated.html
new file mode 100644 (file)
index 0000000..a82c6f7
--- /dev/null
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html" id="m-navbar-current">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>Classes</h1>
+        <ul class="m-doc">
+          <li>struct <a href="structClass.html" class="m-doc">Class</a> <span class="m-doc">The class include name as well as derived class reference should be escaped.</span></li>
+          <li class="m-doc-collapsible collapsed">
+            <a href="#" onclick="return toggle(this)">struct</a> <a href="structSub_3_01char_00_01T_01_4.html" class="m-doc">Sub&lt;char, T&gt;</a> <span class="m-doc">The class name as well as base class reference should be escaped, in the class list as well; the faked include should be escaped here too.</span>
+            <ul class="m-doc">
+              <li>struct <a href="structSub_3_01char_00_01T_01_4_1_1Nested.html" class="m-doc">Nested</a> <span class="m-doc">The outer class name should be escaped, in the class list as well.</span></li>
+            </ul>
+          </li>
+        </ul>
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+                'm-doc-expansible' : 'm-doc-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of
+           directly in markup so disabling it doesn't harm usability. The list
+           is somehow regenerated on every iteration and shrinks as I change
+           the classes. It's not documented anywhere and I'm not sure if this
+           is the same across browsers, so I am going backwards in that list to
+           be sure. */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-doc-expansible';
+        </script>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/files.html b/documentation/test_doxygen/contents_html_escape/files.html
new file mode 100644 (file)
index 0000000..8d66fad
--- /dev/null
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html" id="m-navbar-current">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>Files</h1>
+        <ul class="m-doc">
+          <li>file <a href="Fi_6le_8h.html" class="m-doc">Fi&amp;le.h</a> <span class="m-doc">The file path should be escaped, in the file list also.</span></li>
+        </ul>
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+                'm-doc-expansible' : 'm-doc-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of
+           directly in markup so disabling it doesn't harm usability. The list
+           is somehow regenerated on every iteration and shrinks as I change
+           the classes. It's not documented anywhere and I'm not sure if this
+           is the same across browsers, so I am going backwards in that list to
+           be sure. */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-doc-expansible';
+        </script>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/page-1820.html b/documentation/test_doxygen/contents_html_escape/page-1820.html
new file mode 100644 (file)
index 0000000..e4b4bdc
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Page &lt;&amp;&gt; title &lt;&amp;&gt; should be escaped, in the page list also | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Page &lt;&amp;&gt; title &lt;&amp;&gt; should be escaped, in the page list also
+        </h1>
+<section id="section"><h2><a href="#section">Section &lt;&amp;&gt; name should be escaped</a></h2><p>The &lt;&amp;&gt; first text before any markup should be escaped. <strong>Yes</strong> The &lt;&amp;&gt; last text after any markup should be escaped.</p><section id="autotoc_md0"><h3><a href="#autotoc_md0">Section level 2 &lt;&amp;&gt; should be escaped</a></h3><section id="autotoc_md1"><h4><a href="#autotoc_md1">Section level 3 &lt;&amp;&gt; should be escaped</a></h4><section id="autotoc_md2"><h5><a href="#autotoc_md2">Section level 4 &lt;&amp;&gt; should be escaped</a></h5></section><section id="autotoc_md3"><h5><a href="#autotoc_md3">5 Section level 5 &lt;&amp;&gt; should be escaped</a></h5><pre>Stuff in a code &lt;&amp;&gt; block &lt;&amp;&gt; should be escaped
+</pre><aside class="m-note m-default"><h4>Paragraph &lt;&amp;&gt; title should be escaped</h4><p>Yes</p></aside><aside class="m-note m-info"><h4>Note</h4><p>Yes</p></aside><p>Text right after a note &lt;&amp;&gt; should be escaped</p><blockquote><p>Text in a blockquote &lt;&amp;&gt; should be escaped</p></blockquote><aside class="m-note m-default"><h4>Id</h4><p>some strange RCS &lt;&amp;&gt; content should be escaped</p></aside><figure class="m-figure"><img src="tiny.png" alt="Image" /><figcaption>Image &lt;&amp;&gt; title should be escaped</figcaption></figure><img class="m-image" src="tiny.png" alt="Image" /><p><a href="https://mcss.mosra.cz/?url&lt;&amp;&gt;should-be-escaped">Yes</a></p></section></section></section></section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/page.dox b/documentation/test_doxygen/contents_html_escape/page.dox
new file mode 100644 (file)
index 0000000..113cd8b
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+@page page Page &lt;&amp;&gt; title &lt;&amp;&gt; should be escaped, in the page list also
+
+@section section Section &lt;&amp;&gt; name should be escaped
+
+The &lt;&amp;&gt; first text before any markup should be escaped. <b>Yes</b>
+The &lt;&amp;&gt; last text after any markup should be escaped.
+
+## Section level 2 &lt;&amp;&gt; should be escaped
+
+### Section level 3 &lt;&amp;&gt; should be escaped
+
+#### Section level 4 &lt;&amp;&gt; should be escaped
+
+####5 Section level 5 &lt;&amp;&gt; should be escaped
+
+    Stuff in a code <&> block <&> should be escaped
+
+@par Paragraph &lt;&amp;&gt; title should be escaped
+    Yes
+
+@note Yes
+
+Text right after a note &lt;&amp;&gt; should be escaped
+
+> Text in a blockquote &lt;&amp;&gt; should be escaped
+
+$Id: some strange RCS &lt;&amp;&gt; content should be escaped $
+
+@image html tiny.png "Image &lt;&amp;&gt; title should be escaped"
+
+<img src="tiny.png" alt="Image &lt;&amp;&gt; alt should be escaped, but only because Doxygen forgets to unescape when parsing and so m.css doesn't escape again"/>
+
+<a href="https://mcss.mosra.cz/?url&lt;&amp;&gt;should-be-escaped">Yes</a>
+*/
diff --git a/documentation/test_doxygen/contents_html_escape/page.html b/documentation/test_doxygen/contents_html_escape/page.html
new file mode 100644 (file)
index 0000000..8e3cfa2
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Page &lt;&amp;&gt; title &lt;&amp;&gt; should be escaped, in the page list also | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Page &lt;&amp;&gt; title &lt;&amp;&gt; should be escaped, in the page list also
+        </h1>
+<section id="section"><h2><a href="#section">Section &lt;&amp;&gt; name should be escaped</a></h2><p>The &lt;&amp;&gt; first text before any markup should be escaped. <strong>Yes</strong> The &lt;&amp;&gt; last text after any markup should be escaped.</p><section id="autotoc_md0"><h3><a href="#autotoc_md0">Section level 2 &lt;&amp;&gt; should be escaped</a></h3><section id="autotoc_md1"><h4><a href="#autotoc_md1">Section level 3 &lt;&amp;&gt; should be escaped</a></h4><section id="autotoc_md2"><h5><a href="#autotoc_md2">Section level 4 &lt;&amp;&gt; should be escaped</a></h5></section><section id="autotoc_md3"><h5><a href="#autotoc_md3">5 Section level 5 &lt;&amp;&gt; should be escaped</a></h5><pre>Stuff in a code &lt;&amp;&gt; block &lt;&amp;&gt; should be escaped
+</pre><aside class="m-note m-default"><h4>Paragraph &lt;&amp;&gt; title should be escaped</h4><p>Yes</p></aside><aside class="m-note m-info"><h4>Note</h4><p>Yes</p></aside><p>Text right after a note &lt;&amp;&gt; should be escaped</p><blockquote><p>Text in a blockquote &lt;&amp;&gt; should be escaped</p></blockquote><aside class="m-note m-default"><h4>Id</h4><p>some strange RCS &lt;&amp;&gt; content should be escaped</p></aside><figure class="m-figure"><img src="tiny.png" alt="Image" /><figcaption>Image &lt;&amp;&gt; title should be escaped</figcaption></figure><img class="m-image" src="tiny.png" alt="Image &lt;&amp;&gt; alt should be escaped, but only because Doxygen forgets to unescape when parsing and so m.css doesn't escape again" /><p><a href="https://mcss.mosra.cz/?url&lt;&amp;&gt;should-be-escaped">Yes</a></p></section></section></section></section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/pages.html b/documentation/test_doxygen/contents_html_escape/pages.html
new file mode 100644 (file)
index 0000000..c328f23
--- /dev/null
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html" id="m-navbar-current">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>Pages</h1>
+        <ul class="m-doc">
+          <li><a href="page.html" class="m-doc">Page &lt;&amp;&gt; title &lt;&amp;&gt; should be escaped, in the page list also</a> <span class="m-doc"></span></li>
+        </ul>
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+                'm-doc-expansible' : 'm-doc-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of
+           directly in markup so disabling it doesn't harm usability. The list
+           is somehow regenerated on every iteration and shrinks as I change
+           the classes. It's not documented anywhere and I'm not sure if this
+           is the same across browsers, so I am going backwards in that list to
+           be sure. */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-doc-expansible';
+        </script>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/structClass.html b/documentation/test_doxygen/contents_html_escape/structClass.html
new file mode 100644 (file)
index 0000000..c492ef9
--- /dev/null
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Class struct | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <div class="m-doc-include m-code m-inverted m-right-m m-text-right"><span class="cp">#include</span> <a class="cpf" href="Fi_6le_8h.html">&lt;Fi&amp;le.h&gt;</a></div>
+          <div class="m-doc-template">template&lt;class T&gt;</div>
+          Class <span class="m-thin">struct</span>
+        </h1>
+        <p>The class include name as well as derived class reference should be escaped.</p>
+        <nav class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#derived-classes">Derived classes</a></li>
+                <li><a href="#pub-types">Public types</a></li>
+                <li><a href="#pub-methods">Public functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </nav>
+        <section id="derived-classes">
+          <h2><a href="#derived-classes">Derived classes</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <div class="m-doc-template">template&lt;class T&gt;</div>
+              struct <a href="structSub_3_01char_00_01T_01_4.html" class="m-doc">Sub&lt;char, T&gt;</a>
+            </dt>
+            <dd>The class name as well as base class reference should be escaped, in the class list as well; the faked include should be escaped here too.</dd>
+          </dl>
+        </section>
+        <section id="pub-types">
+          <h2><a href="#pub-types">Public types</a></h2>
+          <dl class="m-doc">
+            <dt id="abc566500394204b1aff6106bb4559e18">
+              <span class="m-doc-wrap-bumper">enum <a href="#abc566500394204b1aff6106bb4559e18" class="m-doc-self">Enum</a> { </span><span class="m-doc-wrap"><a href="#abc566500394204b1aff6106bb4559e18a1f46b75c9c2abcdd838e6ff824a60db9" class="m-doc">Value</a> = Initializer&lt;char&amp;&gt;::ShouldBeEscaped }</span>
+            </dt>
+            <dd>Enum.</dd>
+          </dl>
+        </section>
+        <section id="pub-methods">
+          <h2><a href="#pub-methods">Public functions</a></h2>
+          <dl class="m-doc">
+            <dt id="a0dbbef222ebfc3607c4ad7283ec260c3">
+              <span class="m-doc-wrap-bumper">void <a href="#a0dbbef222ebfc3607c4ad7283ec260c3" class="m-doc-self">suffixShouldBeEscaped</a>(</span><span class="m-doc-wrap">const Type&lt;char&gt;::ShouldBeEscaped&amp; = &quot;default value &lt;&amp;&gt; should be escaped&quot;) &amp;&amp;</span>
+            </dt>
+            <dd>Function.</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/structSub_3_01char_00_01T_01_4.html b/documentation/test_doxygen/contents_html_escape/structSub_3_01char_00_01T_01_4.html
new file mode 100644 (file)
index 0000000..849c147
--- /dev/null
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Sub&lt;char, T&gt; struct | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <div class="m-doc-include m-code m-inverted m-right-m m-text-right"><span class="cp">#include</span> <a class="cpf" href="Fi_6le_8h.html">&lt;FakeFi&amp;le.h&gt;</a></div>
+          <div class="m-doc-template">template&lt;class T&gt;</div>
+          Sub&lt;char, T&gt; <span class="m-thin">struct</span>
+        </h1>
+        <p>The class name as well as base class reference should be escaped, in the class list as well; the faked include should be escaped here too.</p>
+        <nav class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#base-classes">Base classes</a></li>
+                <li><a href="#pub-types">Public types</a></li>
+              </ul>
+            </li>
+          </ul>
+        </nav>
+        <section id="base-classes">
+          <h2><a href="#base-classes">Base classes</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <div class="m-doc-template">template&lt;class T&gt;</div>
+              struct <a href="structClass.html" class="m-doc">Class&lt;T&gt;</a>
+            </dt>
+            <dd>The class include name as well as derived class reference should be escaped.</dd>
+          </dl>
+        </section>
+        <section id="pub-types">
+          <h2><a href="#pub-types">Public types</a></h2>
+          <dl class="m-doc">
+            <dt>
+              struct <a href="structSub_3_01char_00_01T_01_4_1_1Nested.html" class="m-doc">Nested</a>
+            </dt>
+            <dd>The outer class name should be escaped, in the class list as well.</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/structSub_3_01char_00_01T_01_4_1_1Nested.html b/documentation/test_doxygen/contents_html_escape/structSub_3_01char_00_01T_01_4_1_1Nested.html
new file mode 100644 (file)
index 0000000..a95dec4
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Sub&lt;char, T&gt;::Nested struct | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-6 m-col-m-none">
+            <li><a href="files.html">Files</a></li>
+          </ol>
+          <ol class="m-col-t-6 m-col-m-none" start="2">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="annotated.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="structSub_3_01char_00_01T_01_4.html">Sub&lt;char, T&gt;</a>::<wbr/></span>Nested <span class="m-thin">struct</span>
+          <div class="m-doc-include m-code m-inverted m-text-right"><span class="cp">#include</span> <a class="cpf" href="Fi_6le_8h.html">&lt;Fi&amp;le.h&gt;</a></div>
+        </h1>
+        <p>The outer class name should be escaped, in the class list as well.</p>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_doxygen/contents_html_escape/tiny.png b/documentation/test_doxygen/contents_html_escape/tiny.png
new file mode 120000 (symlink)
index 0000000..79abd3c
--- /dev/null
@@ -0,0 +1 @@
+../../../plugins/m/test/images/tiny.png
\ No newline at end of file
diff --git a/documentation/test_doxygen/contents_typography/indexpage_1817.xml b/documentation/test_doxygen/contents_typography/indexpage_1817.xml
new file mode 100644 (file)
index 0000000..e448710
--- /dev/null
@@ -0,0 +1,52 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="1.8.18">
+  <compounddef id="indexpage" kind="page">
+    <compoundname>index</compoundname>
+    <title>My Project</title>
+    <briefdescription>
+    </briefdescription>
+    <detaileddescription>
+<para><blockquote><para>A blockquote. </para>
+</blockquote><verbatim>Preformatted text.
+</verbatim></para>
+<para>Paragraph <linebreak/>
+ with <linebreak/>
+ explicit <linebreak/>
+ line <linebreak/>
+ breaks.</para>
+<sect1 id="index_1section">
+<title>Page section</title>
+<para><preformatted>Differently
+  preformatted
+text.</preformatted></para>
+<para><itemizedlist>
+<listitem><para>Unordered</para>
+</listitem><listitem><para>list</para>
+</listitem><listitem><para>of<itemizedlist>
+<listitem><para>nested</para>
+</listitem><listitem><para>items</para>
+</listitem></itemizedlist>
+</para>
+</listitem><listitem><para>and back</para>
+</listitem></itemizedlist>
+<orderedlist>
+<listitem><para>Ordered</para>
+</listitem><listitem><para>list</para>
+</listitem><listitem><para>of<orderedlist>
+<listitem><para>nested</para>
+</listitem><listitem><para>items</para>
+</listitem></orderedlist>
+</para>
+</listitem><listitem><para>and back</para>
+</listitem></orderedlist>
+</para>
+<para><anchor id="index_1an-anchor"/> This is a <computeroutput>typewriter text</computeroutput>, <emphasis>emphasis</emphasis>, <bold>bold</bold>. <emphasis>Emphasis with <computeroutput>typewriter</computeroutput> and <bold>bold</bold> nested.</emphasis> <ulink url="http://google.com">http://google.com</ulink> and <ulink url="http://google.com">URL</ulink>. <small>Small <emphasis>text</emphasis>.</small> En-dash <ndash/> and em-dash <mdash/>. Reference to a <ref refid="index_1section" kindref="member">Page section</ref>. Named reference with special characters in title: <ref refid="index_1section" kindref="member"><raquo/> Warnings <laquo/></ref>. Reference with escaped characters in<nonbreakablespace/>title: <ref refid="index_1an-anchor" kindref="member">&lt;anchor&gt;</ref>.</para>
+<para>2<superscript>nd</superscript> is L<subscript><infin/></subscript> <forall/> <nabla/> <pi/> <real/> <imaginary/> This costs no $, <euro/>, <pound/>, <yen/> or <curren/>.</para>
+<para>Empty elements: <emphasis></emphasis> <computeroutput></computeroutput> <bold></bold> <preformatted></preformatted> <small></small></para>
+<para><hruler/>
+</para>
+<para>Above is a horizontal line. </para>
+</sect1>
+    </detaileddescription>
+  </compounddef>
+</doxygen>
index 06dc2f1f370027ffc0146f0c7f4f35c639c6f5f1..1c067d433e7506bea53dca4709df41b5dd24959c 100644 (file)
@@ -33,6 +33,9 @@ import unittest
 
 from hashlib import sha1
 
+from doxygen import EntryType
+from _search import pretty_print, searchdata_filename
+
 from . import BaseTestCase, IntegrationTestCase, doxygen_version, parse_version
 
 def dot_version():
@@ -625,3 +628,112 @@ class Blockquote(IntegrationTestCase):
             self.assertEqual(*self.actual_expected_contents('index.html'))
         else:
             self.assertEqual(*self.actual_expected_contents('index.html', 'index-pygments29.html'))
+
+class HtmlEscape(IntegrationTestCase):
+    def setUp(self):
+        IntegrationTestCase.setUp(self)
+
+        # Doxygen does *almost* a good job of escaping everything, except the
+        # bit in <includes>. Patch that up.
+        for i in [
+            'structClass.xml',
+            'structSub_3_01char_00_01T_01_4.xml',
+            'structSub_3_01char_00_01T_01_4_1_1Nested.xml',
+            # These two are broken only in 1.8.16 and older, the second one
+            # isn't actually used for anything but still produces an error log
+            # if not patched.
+            'Fi_6le_8h.xml',
+            'structSub.xml'
+        ]:
+            with open(os.path.join(self.path, 'xml', i), 'r+') as f:
+                contents = f.read()
+                f.seek(0)
+                f.truncate(0)
+                f.write(contents.replace('Fi&le.h', 'Fi&amp;le.h'))
+
+    def test(self):
+        self.run_doxygen(wildcard='*.xml')
+
+        # Page title escaping, content escaping
+        self.assertEqual(*self.actual_expected_contents('pages.html'))
+
+        # Versions before 1.9.1(?) don't have the alt attribute preserved for
+        # <img>
+        if parse_version(doxygen_version()) >= parse_version("1.9.1"):
+            self.assertEqual(*self.actual_expected_contents('page.html'))
+        else:
+            self.assertEqual(*self.actual_expected_contents('page.html', 'page-1820.html'))
+
+        # Filename escaping
+        self.assertEqual(*self.actual_expected_contents('files.html'))
+        self.assertEqual(*self.actual_expected_contents('Fi_6le_8h.html'))
+
+        # Class name escaping; include, symbol and value escaping
+        self.assertEqual(*self.actual_expected_contents('annotated.html'))
+        self.assertEqual(*self.actual_expected_contents('structClass.html'))
+        self.assertEqual(*self.actual_expected_contents('structSub_3_01char_00_01T_01_4.html'))
+        self.assertEqual(*self.actual_expected_contents('structSub_3_01char_00_01T_01_4_1_1Nested.html'))
+
+    def test_search(self):
+        # Re-run everything with search enabled, the search data shouldn't be
+        # escaped. Not done as part of above as it'd unnecessarily inflate the
+        # size of compared files with the search icon and popup.
+        self.run_doxygen(index_pages=[], wildcard='*.xml', config={
+            'SEARCH_DISABLED': False,
+            'SEARCH_DOWNLOAD_BINARY': True
+        })
+
+        with open(os.path.join(self.path, 'html', searchdata_filename.format(search_filename_prefix='searchdata')), 'rb') as f:
+            serialized = f.read()
+            search_data_pretty = pretty_print(serialized, entryTypeClass=EntryType)[0]
+        # print(search_data_pretty)
+        self.assertEqual(search_data_pretty, """
+8 symbols
+fi&le.h [2]
+||     :$
+||      :functionshouldhavespecializednameescaped<char&> [0]
+||                                                      ($
+||                                                       ) [1]
+|unctionshouldhavespecializednameescaped<char&> [0]
+||                                             ($
+||                                              ) [1]
+page <&> title <&> should be escaped, in the page list also [3]
+class [7]
+|    :$
+|     :enum [4]
+|      suffixshouldbeescaped [5]
+|      |                    ($
+|      |                     ) [6]
+enum [4]
+suffixshouldbeescaped [5]
+| |                  ($
+| |                   ) [6]
+| b<char, t> [8]
+| |         :$
+| |          :nested [9]
+nested [9]
+0: ::functionShouldHaveSpecializedNameEscaped<char&>() [prefix=2[:14], suffix_length=2, type=FUNC] -> #a3b5d61927252197070e8d998f643a2b2
+1:  [prefix=0[:48], type=FUNC] ->
+2: Fi&le.h [type=FILE] -> Fi_6le_8h.html
+3: Page <&> title <&> should be escaped, in the page list also [type=PAGE] -> page.html
+4: ::Enum [prefix=7[:16], type=ENUM] -> #abc566500394204b1aff6106bb4559e18
+5: ::suffixShouldBeEscaped(const Type<char>::ShouldBeEscaped&) && [prefix=7[:16], suffix_length=39, type=FUNC] -> #a0dbbef222ebfc3607c4ad7283ec260c3
+6:  [prefix=5[:50], suffix_length=37, type=FUNC] ->
+7: Class [type=STRUCT] -> structClass.html
+8: Sub<char, T> [type=STRUCT] -> structSub_3_01char_00_01T_01_4.html
+9: ::Nested [prefix=8[:30], type=STRUCT] -> _1_1Nested.html
+(EntryType.PAGE, CssClass.SUCCESS, 'page'),
+(EntryType.NAMESPACE, CssClass.PRIMARY, 'namespace'),
+(EntryType.GROUP, CssClass.SUCCESS, 'group'),
+(EntryType.CLASS, CssClass.PRIMARY, 'class'),
+(EntryType.STRUCT, CssClass.PRIMARY, 'struct'),
+(EntryType.UNION, CssClass.PRIMARY, 'union'),
+(EntryType.TYPEDEF, CssClass.PRIMARY, 'typedef'),
+(EntryType.DIR, CssClass.WARNING, 'dir'),
+(EntryType.FILE, CssClass.WARNING, 'file'),
+(EntryType.FUNC, CssClass.INFO, 'func'),
+(EntryType.DEFINE, CssClass.INFO, 'define'),
+(EntryType.ENUM, CssClass.PRIMARY, 'enum'),
+(EntryType.ENUM_VALUE, CssClass.DEFAULT, 'enum val'),
+(EntryType.VAR, CssClass.DEFAULT, 'var')
+""".strip())
index 2c06800898060fbc1d72a18f7e376464ec30ed7c..bb7457abbfa62aa28fd66d1e9125e57eadeec387 100644 (file)
@@ -33,6 +33,12 @@ foreach(target pybind_signatures pybind_enums pybind_external_overload_docs pybi
     set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${target})
 endforeach()
 
+# Need a special location for this one
+pybind11_add_module(pybind_content_html_escape content_html_escape/content_html_escape/pybind.cpp)
+set_target_properties(pybind_content_html_escape PROPERTIES
+    OUTPUT_NAME pybind
+    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/content_html_escape/content_html_escape)
+
 # Need a special location for this one
 pybind11_add_module(pybind_inspect_create_intersphinx inspect_create_intersphinx/inspect_create_intersphinx/pybind.cpp)
 set_target_properties(pybind_inspect_create_intersphinx PROPERTIES
diff --git a/documentation/test_python/content_html_escape/content_html_escape.Class.html b/documentation/test_python/content_html_escape/content_html_escape.Class.html
new file mode 100644 (file)
index 0000000..4d7e0b7
--- /dev/null
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>content_html_escape.Class | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="content_html_escape.html">content_html_escape</a>.<wbr/></span>Class <span class="m-thin">class</span>
+        </h1>
+        <nav class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#enums">Enums</a></li>
+                <li><a href="#classmethods">Class methods</a></li>
+                <li><a href="#staticmethods">Static methods</a></li>
+                <li><a href="#methods">Methods</a></li>
+                <li><a href="#dunder-methods">Special methods</a></li>
+                <li><a href="#data">Data</a></li>
+              </ul>
+            </li>
+          </ul>
+        </nav>
+        <section id="enums">
+          <h2><a href="#enums">Enums</a></h2>
+          <dl class="m-doc">
+            <dt id="ClassEnum">
+              <span class="m-doc-wrap-bumper">class <a href="#ClassEnum" class="m-doc-self">ClassEnum</a>(enum.Enum): </span><span class="m-doc-wrap"><a href="#ClassEnum-VALUE_THAT_SHOULD_BE_ESCAPED" class="m-doc-self" id="ClassEnum-VALUE_THAT_SHOULD_BE_ESCAPED">VALUE_THAT_SHOULD_BE_ESCAPED</a> = &#x27;&lt;&amp;&gt;&#x27;</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="classmethods">
+          <h2><a href="#classmethods">Class methods</a></h2>
+          <dl class="m-doc">
+            <dt id="classmethod">
+              <span class="m-doc-wrap-bumper">def <a href="#classmethod" class="m-doc-self">classmethod</a>(</span><span class="m-doc-wrap">default_string_that_should_be_escaped = &#x27;&lt;&amp;&gt;&#x27;)</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="staticmethods">
+          <h2><a href="#staticmethods">Static methods</a></h2>
+          <dl class="m-doc">
+            <dt id="staticmethod">
+              <span class="m-doc-wrap-bumper">def <a href="#staticmethod" class="m-doc-self">staticmethod</a>(</span><span class="m-doc-wrap">default_string_that_should_be_escaped = &#x27;&lt;&amp;&gt;&#x27;)</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="methods">
+          <h2><a href="#methods">Methods</a></h2>
+          <dl class="m-doc">
+            <dt id="method">
+              <span class="m-doc-wrap-bumper">def <a href="#method" class="m-doc-self">method</a>(</span><span class="m-doc-wrap">self,
+              default_string_that_should_be_escaped = &#x27;&lt;&amp;&gt;&#x27;)</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="dunder-methods">
+          <h2><a href="#dunder-methods">Special methods</a></h2>
+          <dl class="m-doc">
+            <dt id="__dunder_method__">
+              <span class="m-doc-wrap-bumper">def <a href="#__dunder_method__" class="m-doc-self">__dunder_method__</a>(</span><span class="m-doc-wrap">self,
+              default_string_that_should_be_escaped = &#x27;&lt;&amp;&gt;&#x27;)</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt id="DATA_THAT_SHOULD_BE_ESCAPED">
+              <a href="#DATA_THAT_SHOULD_BE_ESCAPED" class="m-doc-self">DATA_THAT_SHOULD_BE_ESCAPED</a> = &#x27;&lt;&amp;&gt;&#x27;
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/content_html_escape/content_html_escape.html b/documentation/test_python/content_html_escape/content_html_escape.html
new file mode 100644 (file)
index 0000000..b85681f
--- /dev/null
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>content_html_escape | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          content_html_escape <span class="m-thin">module</span>
+        </h1>
+        <p>Summary that should be &lt;&amp;&gt; escaped &lt;&amp;&gt;</p>
+        <nav class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#packages">Modules</a></li>
+                <li><a href="#classes">Classes</a></li>
+                <li><a href="#enums">Enums</a></li>
+                <li><a href="#functions">Functions</a></li>
+                <li><a href="#data">Data</a></li>
+              </ul>
+            </li>
+          </ul>
+        </nav>
+<p>Details that *aren&#x27;t* rST-processed and thus should only be &lt;&amp;&gt; escaped &lt;&amp;&gt;.</p>
+        <section id="namespaces">
+          <h2><a href="#namespaces">Modules</a></h2>
+          <dl class="m-doc">
+            <dt>module <a href="content_html_escape.pybind.html" class="m-doc">pybind</a></dt>
+            <dd>pybind11 html escaping</dd>
+          </dl>
+        </section>
+        <section id="classes">
+          <h2><a href="#classes">Classes</a></h2>
+          <dl class="m-doc">
+            <dt>class <a href="content_html_escape.Class.html" class="m-doc">Class</a></dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="enums">
+          <h2><a href="#enums">Enums</a></h2>
+          <dl class="m-doc">
+            <dt id="Enum">
+              <span class="m-doc-wrap-bumper">class <a href="#Enum" class="m-doc-self">Enum</a>(enum.Enum): </span><span class="m-doc-wrap"><a href="#Enum-VALUE_THAT_SHOULD_BE_ESCAPED" class="m-doc-self" id="Enum-VALUE_THAT_SHOULD_BE_ESCAPED">VALUE_THAT_SHOULD_BE_ESCAPED</a> = &#x27;&lt;&amp;&gt;&#x27;</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="functions">
+          <h2><a href="#functions">Functions</a></h2>
+          <dl class="m-doc">
+            <dt id="function">
+              <span class="m-doc-wrap-bumper">def <a href="#function" class="m-doc-self">function</a>(</span><span class="m-doc-wrap">default_string_that_should_be_escaped = &#x27;&lt;&amp;&gt;&#x27;,
+              default_function_that_should_be_escaped = &lt;function &lt;lambda&gt;&gt;)</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt id="DATA_THAT_SHOULD_BE_ESCAPED">
+              <a href="#DATA_THAT_SHOULD_BE_ESCAPED" class="m-doc-self">DATA_THAT_SHOULD_BE_ESCAPED</a> = &#x27;&lt;&amp;&gt;&#x27;
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/content_html_escape/content_html_escape.pybind.html b/documentation/test_python/content_html_escape/content_html_escape.pybind.html
new file mode 100644 (file)
index 0000000..12f2977
--- /dev/null
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>content_html_escape.pybind | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="content_html_escape.html">content_html_escape</a>.<wbr/></span>pybind <span class="m-thin">module</span>
+        </h1>
+        <p>pybind11 html escaping</p>
+        <nav class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#functions">Functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </nav>
+        <section id="functions">
+          <h2><a href="#functions">Functions</a></h2>
+          <dl class="m-doc">
+            <dt id="default_value_should_be_escaped">
+              <span class="m-doc-wrap-bumper">def <a href="#default_value_should_be_escaped" class="m-doc-self">default_value_should_be_escaped</a>(</span><span class="m-doc-wrap">string: str = &#x27;&lt;&amp;&gt;&#x27;) -&gt; int</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/content_html_escape/content_html_escape/__init__.py b/documentation/test_python/content_html_escape/content_html_escape/__init__.py
new file mode 100644 (file)
index 0000000..dba885e
--- /dev/null
@@ -0,0 +1,39 @@
+"""
+Summary that should be <&> escaped <&>
+
+Details that *aren't* rST-processed and thus should only be <&> escaped <&>.
+"""
+
+import enum
+
+from . import pybind
+
+# TODO page names can be escaped!
+
+class Class:
+    class ClassEnum(enum.Enum):
+        VALUE_THAT_SHOULD_BE_ESCAPED = "<&>"
+
+    @staticmethod
+    def staticmethod(default_string_that_should_be_escaped = "<&>"):
+        pass
+
+    @classmethod
+    def classmethod(cls, default_string_that_should_be_escaped = "<&>"):
+        pass
+
+    def __dunder_method__(self, default_string_that_should_be_escaped = "<&>"):
+        pass
+
+    def method(self, default_string_that_should_be_escaped = "<&>"):
+        pass
+
+    DATA_THAT_SHOULD_BE_ESCAPED = "<&>"
+
+class Enum(enum.Enum):
+    VALUE_THAT_SHOULD_BE_ESCAPED = "<&>"
+
+def function(default_string_that_should_be_escaped = "<&>", default_function_that_should_be_escaped = lambda a: a):
+    pass
+
+DATA_THAT_SHOULD_BE_ESCAPED = "<&>"
diff --git a/documentation/test_python/content_html_escape/content_html_escape/pybind.cpp b/documentation/test_python/content_html_escape/content_html_escape/pybind.cpp
new file mode 100644 (file)
index 0000000..9c6df58
--- /dev/null
@@ -0,0 +1,11 @@
+#include <pybind11/pybind11.h>
+
+namespace py = pybind11;
+
+int defaultValueShouldBeEscaped(const char*) { return 0; }
+
+PYBIND11_MODULE(pybind, m) {
+    m.doc() = "pybind11 html escaping";
+
+    m.def("default_value_should_be_escaped", defaultValueShouldBeEscaped, py::arg("string") = "<&>");
+}
diff --git a/documentation/test_python/content_html_escape/page.html b/documentation/test_python/content_html_escape/page.html
new file mode 100644 (file)
index 0000000..d3c6196
--- /dev/null
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Page title that should be &lt;&amp;&gt; escaped &lt;&amp;&gt;, and also in the page tree | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="pages.html">Pages</a></li>
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Page title that should be &lt;&amp;&gt; escaped &lt;&amp;&gt;, and also in the page tree
+        </h1>
+<p>Page content that &lt;&amp;&gt; <em>is</em> rST-processed &lt;&amp;&gt; and thus gets escaped by docutils.</p>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/content_html_escape/page.rst b/documentation/test_python/content_html_escape/page.rst
new file mode 100644 (file)
index 0000000..22a8734
--- /dev/null
@@ -0,0 +1,4 @@
+Page title that should be <&> escaped <&>, and also in the page tree
+####################################################################
+
+Page content that <&> *is* rST-processed <&> and thus gets escaped by docutils.
diff --git a/documentation/test_python/content_html_escape/pages.html b/documentation/test_python/content_html_escape/pages.html
new file mode 100644 (file)
index 0000000..e52b682
--- /dev/null
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="pages.html" id="m-navbar-current">Pages</a></li>
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>Pages</h1>
+        <ul class="m-doc">
+          <li><a href="page.html" class="m-doc">Page title that should be &lt;&amp;&gt; escaped &lt;&amp;&gt;, and also in the page tree</a> <span class="m-doc"></span></li>
+        </ul>
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+                'm-doc-expansible' : 'm-doc-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of
+           directly in markup so disabling it doesn't harm usability. The list
+           is somehow regenerated on every iteration and shrinks as I change
+           the classes. It's not documented anywhere and I'm not sure if this
+           is the same across browsers, so I am going backwards in that list to
+           be sure. */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-doc-expansible';
+        </script>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index 0076e762f5b0d760c582954f3a6838bdec1b7f62..cdc2bccca3fe97f2e3e5db1168627f9b7fd87a90 100644 (file)
 #
 
 import os
+import unittest
 
 from . import BaseInspectTestCase
 
+from _search import pretty_print, searchdata_filename
+from python import EntryType
+
 class Content(BaseInspectTestCase):
     def test(self):
         self.run_python({
@@ -52,3 +56,42 @@ class ParseDocstrings(BaseInspectTestCase):
         })
         self.assertEqual(*self.actual_expected_contents('content_parse_docstrings.html'))
         self.assertEqual(*self.actual_expected_contents('content_parse_docstrings.Class.html'))
+
+class HtmlEscape(BaseInspectTestCase):
+    def test(self):
+        self.run_python({
+            'INPUT_PAGES': ['page.rst'],
+            'PYBIND11_COMPATIBILITY': True,
+            'LINKS_NAVBAR1': [
+                ('Pages', 'pages', []),
+                ('Modules', 'modules', [])],
+        })
+
+        # Page title escaping
+        self.assertEqual(*self.actual_expected_contents('page.html'))
+        self.assertEqual(*self.actual_expected_contents('pages.html'))
+
+        # Value escaping
+        self.assertEqual(*self.actual_expected_contents('content_html_escape.html'))
+        self.assertEqual(*self.actual_expected_contents('content_html_escape.Class.html'))
+        self.assertEqual(*self.actual_expected_contents('content_html_escape.pybind.html'))
+
+    @unittest.skip("Page names are currently not exposed to search and there's nothing else that would require escaping, nothing to test")
+    def test_search(self):
+        # Re-run everything with search enabled, the search data shouldn't be
+        # escaped. Not done as part of above as it'd unnecessarily inflate the
+        # size of compared files with the search icon and popup.
+        self.run_python({
+            'INPUT_PAGES': ['page.rst'],
+            'PYBIND11_COMPATIBILITY': True,
+            'SEARCH_DISABLED': False,
+            'SEARCH_DOWNLOAD_BINARY': True
+        })
+
+        with open(os.path.join(self.path, 'output', searchdata_filename.format(search_filename_prefix='searchdata')), 'rb') as f:
+            serialized = f.read()
+            search_data_pretty = pretty_print(serialized, entryTypeClass=EntryType)[0]
+        # print(search_data_pretty)
+        self.assertEqual(search_data_pretty, """
+TODO
+""".strip())