One of the most common HTML-to-PDF problems is creating dynamic table of contents. The below JavaScript and HTML is an excellent starting point, though it may need to be customized to fit your document. As far as we can tell, this code was originally written by Ateş Göral on StackOverflow back in 2009.
window.onload = function () {
var toc = "";
var level = 0;
document.getElementById("contents").innerHTML =
document.getElementById("contents").innerHTML.replace(/<h([\d])>([^<]+)<\/h([\d])>/gi,
function (str, openLevel, titleText, closeLevel) {
if (openLevel != closeLevel) {
return str;
}
if (openLevel > level) {
toc += (new Array(openLevel - level + 1)).join("<ul>");
} else if (openLevel < level) {
toc += (new Array(level - openLevel + 1)).join("</ul>");
}
level = parseInt(openLevel);
var anchor = titleText.replace(/ /g, "_");
toc += "<li><a href=\"#" + anchor + "\">" + titleText + "</a></li>";
return "<h" + openLevel + "><a name=\"" + anchor + "\">" + titleText + "</a></h" + closeLevel + ">";
}
);
if (level) {
toc += (new Array(level + 1)).join("</ul>");
}
document.getElementById("toc").innerHTML += toc;
};
Let's break this down bit by bit.
Step 1: Required HTML
This code requires HTML with two different sections, #toc
and #content
. The content section should contained nested header elements.
<div id="toc">
<h3>Table of Contents</h3>
</div>
<div id="contents">
<h1>Main Heading (h1)</h1>
<h2>Second level heading (h2)</h2>
<h2>Another heading (h2)</h2>
<h3>Sub heading (h3)</h3>
<h2>One more second level heading (h2)</h2>
</div>
Step 1: Loop Through Header Elements
document.getElementById("contents").innerHTML.replace(/<h([\d])>([^<]+)<\/h([\d])>/gi,
function (str, openLevel, titleText, closeLevel) {
...
}
);
This code uses a regex to find every header element (<h1>
, <h2>
, etc.) in your document.
Step 2: Insert an Anchor
var anchor = titleText.replace(/ /g, "_");
...
return "<h" + openLevel + "><a name=\"" + anchor + "\">" + titleText + "</a></h" + closeLevel + ">";
HTML anchors are used for linking within a document, which is exactly what we want for a table of contents. This code replaces all the existing header elements with new elements containing an anchor link.
This code replaces all your header elements without including any CSS classes or IDs that might have existed on the previous headers. You may need to customize this.
Step 3: Generate a Table of Contents
if (openLevel > level) {
toc += (new Array(openLevel - level + 1)).join("<ul>");
} else if (openLevel < level) {
toc += (new Array(level - openLevel + 1)).join("</ul>");
}
...
toc += "<li><a href=\"#" + anchor + "\">" + titleText + "</a></li>";
...
toc += (new Array(level + 1)).join("</ul>");
The toc
string contains our new, dynamicaly-generated table of contents. Each section title is linked to the new anchors we just inserted and a separate HTML list is made for each level of the table of contents.
And that's it! Pretty nifty code from Ateş that's over a decade over. Depending on the complexity of your document, you may want to customize it with additional CSS or limit how many levels it goes.
Bonus: Add Page Numbers
One of the major advantages of DocRaptor's HTML-to-PDF tool is CSS that's aware of your document—such as the number of pages in your document or the page number of a link target. If you add this CSS, it will add page numbers to your dynamically-generated table of contents:
#toc li a::after {
content: " | Page " target-counter(attr(href), page);
}
To try this entire tutorial, just use these complete examples of DocRaptor's API creating documents with dynamic table of contents:
Code Examples
- Test API Key:
- YOUR_API_KEY_HERE
Using DocRaptor's API
Below are the basics of using DocRaptor's HTTP API. It can easily be used in any programming language, but if we don't have a native agent for your language, please let us know! That'll help us write the best examples and agents.
The API key shown here, YOUR_API_KEY_HERE
, actually works! Use it for testing without creating an account. The only requirement is test mode must be used (test
parameter set to true
).
To make documents, send a POST request to this URL:
https://YOUR_API_KEY_HERE@docraptor.com/docs
Send JSON-encoded API parameters (you'll need to set an HTTP header for Content-Type
as application/json
). These are the only required parameters:
{
"type": "pdf",
"document_content": "Hello World!"
}
Generating the Document with cURL
Here's a completely functional code sample for this tutorial:
cat <<-'PDF_OPTIONS' > pdf_options.json
{
"test": true,
"document_content": "<div id=\"toc\"><h3>Table of Contents</h3></div><div id=\"contents\"><h1>Main Heading (h1)</h1><h2>Second level heading (h2)</h2><h2>Another heading (h2)</h2><h3>Sub heading (h3)</h3><h2>One more second level heading (h2)</h2></div><script>window.onload = function () {var toc = \"\";var level = 0;document.getElementById(\"contents\").innerHTML =document.getElementById(\"contents\").innerHTML.replace(/<h([\\d])>([^<]+)<\\/h([\\d])>/gi,function (str, openLevel, titleText, closeLevel) {if (openLevel != closeLevel) {return str;}if (openLevel > level) {toc += (new Array(openLevel - level + 1)).join(\"<ul>\");} else if (openLevel < level) {toc += (new Array(level - openLevel + 1)).join(\"</ul>\");}level = parseInt(openLevel);var anchor = titleText.replace(/ /g, \"_\");toc += \"<li><a href=\\\"#\" + anchor + \"\\\">\" + titleText + \"</a></li>\";return \"<h\" + openLevel + \"><a name=\\\"\" + anchor + \"\\\">\" + titleText + \"</a></h\" + closeLevel + \">\";});if (level) {toc += (new Array(level + 1)).join(\"</ul>\");}document.getElementById(\"toc\").innerHTML += toc;};</script><style>#toc li a::after {content: \" | Page \" target-counter(attr(href), page);}</style>",
"document_type": "pdf",
"javascript": true
}
PDF_OPTIONS
curl https://YOUR_API_KEY_HERE@docraptor.com/docs \
--fail --silent --show-error \
--header "Content-Type:application/json" \
--data @pdf_options.json \
> table-of-contents.pdf
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.
Frameworks supported
- .NET 4.0 or later
- Windows Phone 7.1 (Mango)
Dependencies
- RestSharp - 105.2.3 or later
- Json.NET - 7.0.0 or later
- JsonSubTypes - 1.2.0 or later
Installing the .NET Agent
With Nuget:
nuget.exe install DocRaptor
With the Package Manager Console:
Install-Package DocRaptor
You can also download the DLL directly from GitHub.
Generating the Document
The API key shown here, YOUR_API_KEY_HERE
, actually works! Use it for testing without creating an account. The only requirement is test mode must be used (test
parameter set to true
).
using DocRaptor.Client;
using DocRaptor.Model;
using DocRaptor.Api;
using System;
using System.IO;
class Example
{
static void Main(string[] args)
{
DocApi docraptor = new DocApi();
docraptor.Configuration.Username = "YOUR_API_KEY_HERE";
try
{
Doc doc = new Doc(
name: "table-of-contents",
test: true, // test documents are free but watermarked
documentContent: System.IO.File.ReadAllText(@"table-of-contents-content.html"),
// documentUrl: "http://docraptor.com/examples/invoice.html", // or use a url
documentType: Doc.DocumentTypeEnum.Pdf,
javascript: true // enables JavaScript processing
);
byte[] create_response = docraptor.CreateDoc(doc);
File.WriteAllBytes("table-of-contents.pdf", create_response);
} catch (DocRaptor.Client.ApiException error) {
Console.Write(error.ErrorContent);
}
}
}
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.
Install the Java Agent
Add the com.docraptor
package to your project. For example, with Maven you'd add the DocRaptor package to your pom.xml
:
<dependency>
<groupId>com.docraptor</groupId>
<artifactId>docraptor</artifactId>
<version>2.0.0</version>
</dependency>
And then install it:
mvn install com.docraptor
Generating the Document
The API key shown here, YOUR_API_KEY_HERE
, actually works! Use it for testing without creating an account. The only requirement is test mode must be used (test
parameter set to true
).
import com.docraptor.*;
import java.io.*;
import java.net.*;
import java.nio.file.*;
public class Sync {
public static void main(String[] args) throws Exception {
DocApi docraptor = new DocApi();
ApiClient client = docraptor.getApiClient();
client.setUsername("YOUR_API_KEY_HERE");
try {
Doc doc = new Doc();
doc.setTest(true); // test documents are free but watermarked
doc.setDocumentContent(
new String(
Files.readAllBytes(Paths.get("table-of-contents-content.html"))
)
);
// doc.setDocumentUrl("http://docraptor.com/examples/invoice.html"); // or use a url
doc.setDocumentType(Doc.DocumentTypeEnum.PDF);
doc.setJavascript(true); // enables JavaScript processing
byte[] createResponse = docraptor.createDoc(doc);
FileOutputStream file = new FileOutputStream(
"table-of-contents.pdf"
);
file.write(createResponse);
file.close();
} catch (ApiException error) {
System.err.println(error);
System.err.println(error.getCode());
System.err.println(error.getMessage());
System.err.println(error.getResponseBody());
}
}
}
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.
The DocRaptor JavaScript Library
The DocRaptor JavaScript library makes it easy to create PDFs with JavaScript. The library does not require jQuery, but you can use jQuery to define your document content. When a PDF is requested, the library constructs a hidden form and submits it to the DocRaptor API. The form is necessary to trigger the browser download window.
This library exposes your API key in your website source code. This code should never be used in a publicly-accessible location, instead try using our referrer-based API or a server-side agent such as PHP or Ruby.
JavaScript Example
<html>
<head>
<script src="https://docraptor.com/docraptor-1.0.0.js"></script>
<script>
var downloadPDFOf = function(selector) {
DocRaptor.createAndDownloadDoc("YOUR_API_KEY_HERE", {
name: "table-of-contents",
test: true, // test documents are free but watermarked
document_content: document.querySelector(selector).innerHTML,
// document_url: "http://docraptor.com/examples/invoice.html", // or use a url
document_type: "pdf",
javascript: true // enables JavaScript processing
});
};
</script>
<style>
@media print {
#pdf-button {
display: none;
}
}
</style>
</head>
<body>
<div id="toc">
<h3>Table of Contents</h3>
</div>
<div id="contents">
<h1>Main Heading (h1)</h1>
<h2>Second level heading (h2)</h2>
<h2>Another heading (h2)</h2>
<h3>Sub heading (h3)</h3>
<h2>One more second level heading (h2)</h2>
</div>
<script>
window.onload = function() {
var toc = "";
var level = 0;
document.getElementById("contents").innerHTML = document
.getElementById("contents")
.innerHTML.replace(/<h([\d])>([^<]+)<\/h([\d])>/gi, function(
str,
openLevel,
titleText,
closeLevel
) {
if (openLevel != closeLevel) {
return str;
}
if (openLevel > level) {
toc += new Array(openLevel - level + 1).join("<ul>");
} else if (openLevel < level) {
toc += new Array(level - openLevel + 1).join("</ul>");
}
level = parseInt(openLevel);
var anchor = titleText.replace(/ /g, "_");
toc += '<li><a href="#' + anchor + '">' + titleText + "</a></li>";
return (
"<h" +
openLevel +
'><a name="' +
anchor +
'">' +
titleText +
"</a></h" +
closeLevel +
">"
);
});
if (level) {
toc += new Array(level + 1).join("</ul>");
}
document.getElementById("toc").innerHTML += toc;
};
</script>
<style>
#toc li a::after {
content: " | Page " target-counter(attr(href), page);
}
</style>
<button id="pdf-button" type="button" onclick="downloadPDFOf('html')">
Download PDF
</button>
</body>
</html>
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.
DocRaptor & Node.js
The best way to use DocRaptor with Node.js is directly through our HTTP API. In this example, we'll use the request module, but you can obviously use any HTTP client you'd like.
Install the Request Module
npm install request
Generating the Document
The API key shown here, YOUR_API_KEY_HERE
, actually works! Use it for testing without creating an account. The only requirement is test mode must be used (test
parameter set to true
).
const request = require("request");
const fs = require("fs");
document_content = `
<div id="toc">
<h3>Table of Contents</h3>
</div>
<div id="contents">
<h1>Main Heading (h1)</h1>
<h2>Second level heading (h2)</h2>
<h2>Another heading (h2)</h2>
<h3>Sub heading (h3)</h3>
<h2>One more second level heading (h2)</h2>
</div>
<script>
window.onload = function () {
var toc = "";
var level = 0;
document.getElementById("contents").innerHTML =
document.getElementById("contents").innerHTML.replace(/<h([\\d])>([^<]+)<\\/h([\\d])>/gi,
function (str, openLevel, titleText, closeLevel) {
if (openLevel != closeLevel) {
return str;
}
if (openLevel > level) {
toc += (new Array(openLevel - level + 1)).join("<ul>");
} else if (openLevel < level) {
toc += (new Array(level - openLevel + 1)).join("</ul>");
}
level = parseInt(openLevel);
var anchor = titleText.replace(/ /g, "_");
toc += "<li><a href=\\"#" + anchor + "\\">" + titleText + "</a></li>";
return "<h" + openLevel + "><a name=\\"" + anchor + "\\">" + titleText + "</a></h" + closeLevel + ">";
}
);
if (level) {
toc += (new Array(level + 1)).join("</ul>");
}
document.getElementById("toc").innerHTML += toc;
};
</script>
<style>
#toc li a::after {
content: " | Page " target-counter(attr(href), page);
}
</style>
`;
config = {
url: "https://docraptor.com/docs",
encoding: null, //IMPORTANT! This produces a binary body response instead of text
headers: {
"Content-Type": "application/json"
},
json: {
user_credentials: "YOUR_API_KEY_HERE",
doc: {
test: true, // test documents are free but watermarked
document_content: document_content,
// document_url: "http://docraptor.com/examples/invoice.html", // or use a url
document_type: "pdf",
javascript: true // enables JavaScript processing
}
}
};
request.post(config, function(error, response, body) {
if (response.statusCode == 200) {
fs.writeFile(
"table-of-contents.pdf",
body,
"binary",
function(writeErr) {
if (writeErr) throw writeErr;
console.log(
"Saved table-of-contents.pdf!"
);
}
);
} else {
console.error("Error converting to PDF!", body.toString());
}
});
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.
Installing the PHP Agent
composer require docraptor/docraptor
Generating the Document
The API key shown here, YOUR_API_KEY_HERE
, actually works! Use it for testing without creating an account. The only requirement is test mode must be used (test
parameter set to true
).
<?php
$docraptor = new DocRaptor\DocApi();
$docraptor->getConfig()->setUsername("YOUR_API_KEY_HERE");
$document_content = <<<DOCUMENT_CONTENT
<div id="toc">
<h3>Table of Contents</h3>
</div>
<div id="contents">
<h1>Main Heading (h1)</h1>
<h2>Second level heading (h2)</h2>
<h2>Another heading (h2)</h2>
<h3>Sub heading (h3)</h3>
<h2>One more second level heading (h2)</h2>
</div>
<script>
window.onload = function () {
var toc = "";
var level = 0;
document.getElementById("contents").innerHTML =
document.getElementById("contents").innerHTML.replace(/<h([\d])>([^<]+)<\/h([\d])>/gi,
function (str, openLevel, titleText, closeLevel) {
if (openLevel != closeLevel) {
return str;
}
if (openLevel > level) {
toc += (new Array(openLevel - level + 1)).join("<ul>");
} else if (openLevel < level) {
toc += (new Array(level - openLevel + 1)).join("</ul>");
}
level = parseInt(openLevel);
var anchor = titleText.replace(/ /g, "_");
toc += "<li><a href=\"#" + anchor + "\">" + titleText + "</a></li>";
return "<h" + openLevel + "><a name=\"" + anchor + "\">" + titleText + "</a></h" + closeLevel + ">";
}
);
if (level) {
toc += (new Array(level + 1)).join("</ul>");
}
document.getElementById("toc").innerHTML += toc;
};
</script>
<style>
#toc li a::after {
content: " | Page " target-counter(attr(href), page);
}
</style>
DOCUMENT_CONTENT;
try {
$doc = new DocRaptor\Doc();
$doc->setTest(true); # test documents are free but watermarked
$doc->setDocumentContent($document_content);
# $doc->setDocumentUrl("http://docraptor.com/examples/invoice.html"); # or use a url
$doc->setDocumentType("pdf");
$doc->setJavascript(true); # enables JavaScript processing
$create_response = $docraptor->createDoc($doc);
$pdf = fopen(
'table-of-contents.pdf',
"wb"
);
fwrite($pdf, $create_response);
fclose($pdf);
} catch (DocRaptor\ApiException $error) {
echo $error . "\n";
echo $error->getMessage() . "\n";
echo $error->getCode() . "\n";
echo $error->getResponseBody() . "\n";
}
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.
Installing the Python Agent
pip install --upgrade docraptor
Generating the Document
The API key shown here, YOUR_API_KEY_HERE
, actually works! Use it for testing without creating an account. The only requirement is test mode must be used (test
parameter set to true
).
import docraptor
doc_api = docraptor.DocApi()
doc_api.api_client.configuration.username = 'YOUR_API_KEY_HERE'
document_content = r"""
<div id="toc">
<h3>Table of Contents</h3>
</div>
<div id="contents">
<h1>Main Heading (h1)</h1>
<h2>Second level heading (h2)</h2>
<h2>Another heading (h2)</h2>
<h3>Sub heading (h3)</h3>
<h2>One more second level heading (h2)</h2>
</div>
<script>
window.onload = function () {
var toc = "";
var level = 0;
document.getElementById("contents").innerHTML =
document.getElementById("contents").innerHTML.replace(/<h([\d])>([^<]+)<\/h([\d])>/gi,
function (str, openLevel, titleText, closeLevel) {
if (openLevel != closeLevel) {
return str;
}
if (openLevel > level) {
toc += (new Array(openLevel - level + 1)).join("<ul>");
} else if (openLevel < level) {
toc += (new Array(level - openLevel + 1)).join("</ul>");
}
level = parseInt(openLevel);
var anchor = titleText.replace(/ /g, "_");
toc += "<li><a href=\"#" + anchor + "\">" + titleText + "</a></li>";
return "<h" + openLevel + "><a name=\"" + anchor + "\">" + titleText + "</a></h" + closeLevel + ">";
}
);
if (level) {
toc += (new Array(level + 1)).join("</ul>");
}
document.getElementById("toc").innerHTML += toc;
};
</script>
<style>
#toc li a::after {
content: " | Page " target-counter(attr(href), page);
}
</style>
"""
try:
response = doc_api.create_doc({
'test': True, # test documents are free but watermarked
'document_content': document_content,
# 'document_url': 'http://docraptor.com/examples/invoice.html', # or use a url
'document_type': 'pdf',
'javascript': True, # enables JavaScript processing
})
with open('table-of-contents.pdf', 'w+b') as f:
binary_formatted_response = bytearray(response)
f.write(binary_formatted_response)
f.close()
except docraptor.rest.ApiException as error:
print(error.status)
print(error.reason)
print(error.body)
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.
Installing the Ruby Agent
Add or require the docraptor
gem to your project or script.
Generating the Document
The API key shown here, YOUR_API_KEY_HERE
, actually works! Use it for testing without creating an account. The only requirement is test mode must be used (test
parameter set to true
).
require "docraptor"
DocRaptor.configure do |config|
config.username = "YOUR_API_KEY_HERE"
end
$docraptor = DocRaptor::DocApi.new
document_content = <<~'DOCUMENT_CONTENT'
<div id="toc">
<h3>Table of Contents</h3>
</div>
<div id="contents">
<h1>Main Heading (h1)</h1>
<h2>Second level heading (h2)</h2>
<h2>Another heading (h2)</h2>
<h3>Sub heading (h3)</h3>
<h2>One more second level heading (h2)</h2>
</div>
<script>
window.onload = function () {
var toc = "";
var level = 0;
document.getElementById("contents").innerHTML =
document.getElementById("contents").innerHTML.replace(/<h([\d])>([^<]+)<\/h([\d])>/gi,
function (str, openLevel, titleText, closeLevel) {
if (openLevel != closeLevel) {
return str;
}
if (openLevel > level) {
toc += (new Array(openLevel - level + 1)).join("<ul>");
} else if (openLevel < level) {
toc += (new Array(level - openLevel + 1)).join("</ul>");
}
level = parseInt(openLevel);
var anchor = titleText.replace(/ /g, "_");
toc += "<li><a href=\"#" + anchor + "\">" + titleText + "</a></li>";
return "<h" + openLevel + "><a name=\"" + anchor + "\">" + titleText + "</a></h" + closeLevel + ">";
}
);
if (level) {
toc += (new Array(level + 1)).join("</ul>");
}
document.getElementById("toc").innerHTML += toc;
};
</script>
<style>
#toc li a::after {
content: " | Page " target-counter(attr(href), page);
}
</style>
DOCUMENT_CONTENT
begin
response = $docraptor.create_doc(
test: true, # test documents are free but watermarked
document_content: document_content,
# document_url: "http://docraptor.com/examples/invoice.html", # or use a url
document_type: "pdf",
javascript: true # enables JavaScript processing
)
File.open("table-of-contents.pdf", "wb") do |file|
file.write(response)
end
rescue DocRaptor::ApiError => error
puts "#{error.class}: #{error.message}"
puts error.code
puts error.response_body
puts error.backtrace[0..3].join("\n")
end
Downloads
Here are all the files you need to follow this tutorial:
Related Tutorials & Documentation
See the complete PDF tutorial list or review the documentation for additional info, such as generating asynchronous documents.