a13n
Build AI agents and run them on cloud.
xml2pdf: generate PDFs from declarative XML. Documentation for the XML tags, attributes, command line tool and Go library.
Developer toolsGo · HTMLGitHub @sandeepkumarmishra354OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Generate PDFs from declarative XML: text, images, tables, watermarks, headers and footers, with variables, JSON data and loops. A single static Go binary and library.
Taken from the repository README
Loading the full README.
Read the full README ↗Generate PDFs from a declarative XML description. You describe pages, text, images, tables, watermarks, headers and footers in XML, and xml2pdf lays them out (wrapping text, breaking pages, repeating table headers) and writes a PDF.
${name}. <for-each> repeats content once for each element of a data array.Documentation: https://sandeepkumarmishra354.github.io/xml-to-pdf/ · Try it in your browser: Playground (sources in docs-src/, rebuilt with make docs)
<document title="Hello" page-size="A4" margin="20mm">
<constants><const name="who" value="World"/></constants>
<page>
<text size="24" bold="true">Hello, ${who}!</text>
</page>
</document>
Linux and macOS (one line):
curl -fsSL https://raw.githubusercontent.com/sandeepkumarmishra354/xml-to-pdf/main/install.sh | sh
The script downloads the right binary for your system from the latest release, verifies its SHA-256 checksum, and installs it to /usr/local/bin. If that directory isn't writable, it uses sudo or falls back to ~/.local/bin. Set XML2PDF_VERSION=0.1.0 to pin a version, or XML2PDF_INSTALL_DIR=~/bin to choose the directory. Installing this way also avoids the macOS "could not verify" warning, because macOS only flags files downloaded through a browser.
Windows, or manual download: get the archive for your platform from the latest release, unpack it, and put xml2pdf on your PATH. On macOS, a binary downloaded through a browser is blocked the first time it runs; clear the flag with xattr -d com.apple.quarantine xml2pdf.
With Go (1.24+):
go install github.com/sandeepkumarmishra354/xml-to-pdf/cmd/xml2pdf@latest
From source:
make build # bin/xml2pdf (static)
make dist # cross-compiled binaries for linux/darwin/windows × amd64/arm64 in dist/
make test # run the test suite
make examples # render examples/*.xml into bin/
xml2pdf invoice.xml # writes invoice.pdf
xml2pdf invoice.xml -o out.pdf --data data.json # values from a JSON file
xml2pdf invoice.xml --json '{"customer":{"name":"Globex"}}' # inline JSON
some-api | xml2pdf invoice.xml --data - # JSON from stdin
xml2pdf invoice.xml --data base.json --json '{"status":"PAID"}' # merged, later wins
xml2pdf invoice.xml --set status=PAID --set n=42 # override single values
cat doc.xml | xml2pdf - -o - > doc.pdf # stdin / stdout
| Flag | Meaning |
|---|---|
-o FILE | Output file. It defaults to the input name with .pdf. Use - for stdout. |
--data FILE | A JSON file containing an object. Its values are available as ${key}, ${a.b} or ${list.0.c}, and its arrays work with <for-each>. Use - to read the JSON from stdin. |
--json JSON | The same as --data, but takes the JSON object inline, e.g. --json '{"name":"Alice"}'. |
--base-dir DIR | The directory that relative image paths resolve against. It defaults to the input file's directory, or the working directory when reading from stdin. |
--set k=v | Sets a variable. It can be repeated and takes priority over --data, --json and constants. |
--version | Prints the version. |
--data and --json can be repeated and mixed. They are deep-merged from left to right: nested objects combine key by key, and anything else (strings, numbers, arrays) is replaced by the later value. So --data invoice.json --json '{"customer":{"name":"Initech"}}' changes only the customer's name and keeps the rest of the file.
import "github.com/sandeepkumarmishra354/xml-to-pdf/pkg/xml2pdf"
data, _ := xml2pdf.LoadData("data.json")
err := xml2pdf.RenderFile("invoice.xml", "invoice.pdf", xml2pdf.Options{
Data: data,
Vars: map[string]string{"status": "PAID"},
})
// or stream: xml2pdf.Render(r io.Reader, w io.Writer, opts)
Lengths are in points (1/72 inch) by default. You can also use mm, cm, in and pt suffixes (20mm, 1.5in). Many attributes accept %:
x is a percentage of the page width and y of the page height.Values are looked up in this order, and the first match wins:
--set key=value on the command line (or Options.Vars)--data and --json (or Options.Data); nested keys use dots, e.g. ${customer.name} or ${items.0.price}<constants> in the documentVariables expand in every attribute and in every piece of text. Constants can reference other variables. An undefined variable is an error that reports its line number. Write $${ to output a literal ${.
<constants>
<const name="company" value="Acme Corp"/>
<const name="footer">© ${year} ${company}</const> <!-- value may also be the element text -->
</constants>
<for-each><for-each> repeats its content once for each element of an array from the data. It's handy for table rows:
<table widths="0.5in,4*,1*">
<row header="true"><cell>#</cell><cell>Item</cell><cell>Price</cell></row>
<for-each items="${items}" var="item" number="n">
<row><cell>${n}</cell><cell>${item.name}</cell><cell>${item.price}</cell></row>
</for-each>
</table>
| Attribute | Meaning |
|---|---|
items | The array to loop over: a variable path such as items, ${items} or ${order.lines}. It's required. |
var | The name for the current element (default item). Use ${item} for a scalar element, or ${item.field} for one field of an object. |
index | Optional name for the 0-based position, e.g. index="i" gives ${i}. |
number | Optional name for the 1-based position, e.g. number="n" gives ${n}. |
Loop variables shadow every other variable with the same name. An empty array produces nothing.
A loop can sit anywhere: around table rows, blocks inside a page, inline text, or whole <page> elements (one page per record, for mail-merge). Loops can be nested:
<for-each items="orders" var="order">
<text style="h2">Order ${order.id}</text>
<for-each items="order.lines" var="line"><text>${line.qty} × ${line.sku}</text></for-each>
</for-each>
When you use the Go library, Options.Data can also hold Go slices and maps, such as []map[string]any, not only decoded JSON.
Content flows from top to bottom inside the page margins and continues on new pages as needed. Paragraphs break between lines and tables break between rows; a table's header rows repeat on each new page. Give any block an x and/or y to position it absolutely, measured from the page's top-left corner. Absolute blocks don't affect the flow.
Each <page> element starts a new physical page, and its overflow pages share its settings.
Text properties cascade from document to page to block to inline span:
| Attribute | Values | Inherited |
|---|---|---|
style | one or more named styles, e.g. style="h1 muted" | – |
font | helvetica (also arial, sans), times (serif), courier (monospace), zapfdingbats | yes |
size / font-size | font size, e.g. 11 | yes |
bold, italic, underline | true / false | yes |
color | #rgb, #rrggbb, rgb(r,g,b) or a name (red, gray, navy, …) | yes |
align | left, center, right, justify | yes |
line-height | a multiple of the font size (default 1.2) | yes |
margin-top, margin-bottom | space above and below a block | no |
bg | background color (rows and cells) | no |
Named styles are declared in <resources>. A style can extends another one, and the attributes written on the element itself win over the named style.
<document>The root element. Its attributes are title, author, subject, keywords (PDF metadata), page-size (A3, A4, A5, Letter, Legal, Tabloid, or WxH such as 100mmx150mm), orientation (portrait or landscape), margin (1–4 values, CSS order: top right bottom left; default 0.5in), and any style attribute.
Its children are <constants>, <resources>, <watermark>, <header>, <footer> and <page>.
<resources><resources>
<image id="logo" src="images/logo.png"/> <!-- PNG, JPEG or GIF; path relative to the XML file -->
<style id="h1" size="20" bold="true" color="#1f4e9c" margin-bottom="6"/>
<style id="h2" extends="h1" size="14"/>
</resources>
<page>Optional overrides: page-size, orientation, margin. Use header="false", footer="false" or watermark="false" to hide those on this page and its overflow pages. Style attributes cascade to the content.
Its children are any blocks. Loose text placed directly inside a page becomes a paragraph.
<text> (or <p>) is a paragraph. Whitespace is collapsed and lines wrap automatically. Inline markup: <b>/<strong>, <i>/<em>, <u>, <span …style attributes…>, <br/>.
<text align="justify" margin-bottom="8">Plain, <b>bold</b>, <span color="red" size="14">big red</span><br/>new line</text>
<text x="120mm" y="20mm" width="70mm" align="right">Absolutely placed</text>
<img> (or <image>) takes ref="id" or src="file.png". Set width and/or height; if you set only one, the aspect ratio is kept. Images never grow beyond the available space. align places the image horizontally, and x/y positions it absolutely.
<table>
<table widths="3*,1*,80,20%" border="0.5" border-color="#bbb" padding="4 6"
header-bg="#eee" stripe="#f7f7f7" repeat-header="true">
<row header="true"><cell>Item</cell><cell align="right">Qty</cell>…</row>
<row><cell colspan="2">…</cell><cell valign="middle">…</cell>…</row>
</table>
| Attribute | Meaning |
|---|---|
widths | Column widths. N* takes a share of the remaining space, a plain length is a fixed width, and % is a percentage of the table width. Without widths, all columns are equal. |
width | Table width (default 100%). |
border, border-color | Cell border width (default 0.5; 0 means none) and color. |
padding | Cell padding, 1–4 values (default 4). |
header-bg, stripe | Background of header rows, and of every other body row. |
repeat-header | Repeat header rows after a page break (default true). |
<row> takes header="true" (bold, and repeated across pages), height (minimum height), bg, and style attributes. <cell> takes colspan, valign (top, middle or bottom), bg, and style attributes. A cell can hold inline text or blocks (<text>, <img>, even a nested <table>). Rows are never split across pages.
<spacer height="10mm"/> adds vertical space. <page-break/> starts a new page.
<header> / <footer>Content repeated on each page, inside the top or bottom margin box. The body content area shrinks to make room.
{page} and {pages} in their text become the current page number and the total page count.height sets a fixed height; by default the height is measured from the content.gap is the space between the band and the body (default 6).pages selects which pages show the band.<footer pages="all"><text align="center" size="9">Page {page} of {pages}</text></footer>
<header pages="2-"><text align="right">Annual report</text></header>
<watermark>A text or image watermark. You can declare several.
<watermark text="DRAFT" opacity="0.15" rotate="45" size="80" color="red"/>
<watermark image="logo" width="60%" opacity="0.06" layer="back" pages="2-"/>
| Attribute | Meaning |
|---|---|
text / image / src | The content: text, an image resource id, or an image file. |
opacity | 0–1 (default 0.15). |
rotate | Degrees counter-clockwise (default 45). |
layer | front (default) draws over the content; back draws beneath it. |
x, y | Center point (default: page center). |
width, height | Image size (default: 50% of the page width). |
pages | Page selector (see below). |
| style attributes | font, size (default 60), bold (default true), color (default gray). |
The pages attribute on header, footer and watermark is a comma-separated list of all, first, last, odd, even, 3, 2-5 or 2- (page 2 onwards). Page numbers count physical pages in the output.
<if>) yet.cmd/xml2pdf CLI
pkg/xml2pdf public Go API
internal/parser XML → node tree (keeps mixed content and line numbers)
internal/vars ${...} resolution
internal/model typed document model + validation
internal/style colors, fonts, named styles and the cascade
internal/units length parsing
internal/layout text wrapping, tables, pagination → drawing ops
internal/render drawing ops → PDF (go-pdf/fpdf)
examples/ invoice.xml (+ invoice.json), report.xml
Releases are built by GoReleaser in GitHub Actions. To publish one, push a version tag:
git tag v0.1.0
git push origin v0.1.0
The release workflow runs the tests, cross-compiles the binaries, and publishes a GitHub Release with archives, checksums and a changelog. Run make snapshot to try the release build locally; it needs goreleaser installed and writes to dist/.
MIT: free for personal and commercial use.