diff --git a/lib/ex_doc/formatter/epub.ex b/lib/ex_doc/formatter/epub.ex
index 6c14b693f..5d4111b72 100644
--- a/lib/ex_doc/formatter/epub.ex
+++ b/lib/ex_doc/formatter/epub.ex
@@ -50,6 +50,23 @@ defmodule ExDoc.Formatter.EPUB do
Path.relative_to_cwd(epub)
end
+ @doc """
+ Helper that replaces anchor names and links that could potentially cause problems on EPUB documents
+
+ This helper replaces all the `&` with `&` found in anchors like
+ `Kernel.xhtml#&&/2` or `
...
`
+
+ These anchor names cause a fatal error while EPUB readers parse the files,
+ resulting in truncated content.
+
+ For more details, see: https://github.com/elixir-lang/ex_doc/issues/1851
+ """
+ def fix_anchors(content) do
+ content
+ |> String.replace(~r{id="&+/\d+[^"]*}, &String.replace(&1, "&", "&"))
+ |> String.replace(~r{href="[^#"]*#&+/\d+[^"]*}, &String.replace(&1, "&", "&"))
+ end
+
defp normalize_config(config) do
output =
config.output
@@ -63,7 +80,11 @@ defmodule ExDoc.Formatter.EPUB do
for {_title, extras} <- config.extras do
Enum.each(extras, fn %{id: id, title: title, title_content: title_content, content: content} ->
output = "#{config.output}/OEBPS/#{id}.xhtml"
- html = Templates.extra_template(config, title, title_content, content)
+
+ html =
+ config
+ |> Templates.extra_template(title, title_content, content)
+ |> fix_anchors()
if File.regular?(output) do
ExDoc.Utils.warn("file #{Path.relative_to_cwd(output)} already exists", [])
@@ -157,7 +178,11 @@ defmodule ExDoc.Formatter.EPUB do
end
defp generate_module_page(module_node, config) do
- content = Templates.module_page(config, module_node)
+ content =
+ config
+ |> Templates.module_page(module_node)
+ |> fix_anchors()
+
File.write("#{config.output}/OEBPS/#{module_node.id}.xhtml", content)
end
diff --git a/lib/ex_doc/formatter/epub/templates/nav_template.eex b/lib/ex_doc/formatter/epub/templates/nav_template.eex
index cc1bfa691..4d041b22a 100644
--- a/lib/ex_doc/formatter/epub/templates/nav_template.eex
+++ b/lib/ex_doc/formatter/epub/templates/nav_template.eex
@@ -5,15 +5,11 @@
<%= nav_grouped_item_template config.extras %>
<%= unless Enum.empty?(nodes.modules) do %>
-
-
- - Modules
-
- <%= nav_grouped_item_template nodes.modules %>
-
-
-
-
+ Modules
+
+ <%= nav_grouped_item_template nodes.modules %>
+
+
<% end %>
<%= nav_item_template "Mix Tasks", nodes.tasks %>
diff --git a/test/ex_doc/formatter/epub_test.exs b/test/ex_doc/formatter/epub_test.exs
index c1f02225c..eeb8681c9 100644
--- a/test/ex_doc/formatter/epub_test.exs
+++ b/test/ex_doc/formatter/epub_test.exs
@@ -151,6 +151,9 @@ defmodule ExDoc.Formatter.EPUBTest do
assert content =~
~r{TypesAndSpecs.Sub
}
+ assert content =~
+ ~r{&&/2
}
+
content = File.read!(tmp_dir <> "/epub/OEBPS/nav.xhtml")
assert content =~ ~r{README}
end
@@ -248,4 +251,19 @@ defmodule ExDoc.Formatter.EPUBTest do
after
File.rm_rf!("test/tmp/epub_assets")
end
+
+ describe "fix_anchors/1" do
+ test "adapts anchor names to avoid parsing errors from EPUB readers" do
+ for {source, expected} <- [
+ {~S|its documentation|,
+ ~S|its documentation|},
+ {~S|&&/2
|,
+ ~S|&&/2
|},
+ {~S|title
|,
+ ~S|title
|}
+ ] do
+ assert ExDoc.Formatter.EPUB.fix_anchors(source) == expected
+ end
+ end
+ end
end
diff --git a/test/fixtures/README.md b/test/fixtures/README.md
index 6963347fa..ab032b853 100644
--- a/test/fixtures/README.md
+++ b/test/fixtures/README.md
@@ -15,3 +15,7 @@ hello
## more > than
raw content
+
+The following text includes a reference to an anchor that causes problems in EPUB documents.
+
+To remove this anti-pattern, we can replace `&&/2`, `||/2`, and `!/1` by `and/2`, `or/2`, and `not/1` respectively.