tree-sitter-xml-django
Django-XML template language grammar for tree-sitter.
Supports .xml and .rml files using Django template syntax embedded in XML.
Requirements
- Node.js (for grammar generation)
- Python 3.x (for Python bindings)
- tree-sitter CLI
Usage
Python
import tree_sitter_xml_django as ts_xml_django
from tree_sitter import Language, Parser
language = Language(ts_xml_django.language())
parser = Parser(language)
tree = parser.parse(b"<root>{% if condition %}<child/>{% endif %}</root>")
Contributing
First-time setup
Requires Node.js, uv, and Python 3.10+.
# Install Node.js and Python dependencies
make dev
# Install pre-commit hooks
uvx pre-commit install --install-hooks
make dev runs npm install (tree-sitter CLI) and uv sync (Python dev dependencies) in one step.
Common tasks
| Command | Description |
|---|---|
make generate |
Re-generate src/parser.c from grammar.js |
make test |
Run tree-sitter corpus tests and Python tests |
make package |
Build the Python distribution package |
Releasing a new version
To publish a new version of the package to PyPI:
- Bump the
versionfield inpyproject.toml(follows semver) - Merge the change to
main
CI will detect that the version has no corresponding git tag, publish the package to PyPI, and then push a vX.Y.Z tag to GitHub automatically. No manual tagging or release steps are needed.
If a merge to main does not include a version bump, the publish step is skipped silently.
The external scanner at src/scanner.c
Using the parser requires a binding in your target language that calls the generated file src/parser.c.
For more complex "lookahead" rules, src/scanner.c provides a collection of methods that expose tokens
referenced in parser.c. It is worth noting that parser.c cannot be edited directly without breaking
the synchronisation between grammar.js and parser.c, but scanner.c can be edited so long as the
method names don't change. The CI will only detect differences between the generated parser.c file and
its grammar rules in grammar.js, and from the perspective of the parser, a change to the method logic
doesn't change what it sees - only the method name that it calls.
Paired vs unpaired Django statements
Django template tags fall into two categories, and the grammar treats them very differently.
Unpaired statements (e.g. {% load %}, {% url %}, {% csrf_token %}) stand alone — no
body, no closing tag. Because they require no context about what came before or after, the
grammar handles them with a single catch-all rule (dj_unpaired_statement) that matches any
{% identifier %} not claimed by a more specific rule. The tag name is deliberately not
enumerated; knowing it adds nothing to the parse.
Paired statements with unique rules block, if-elif-else, for are unique Django tags
with specific sequences that are handled separately to regular paired statements.
For all other paired tags (e.g. {% editable %}...{% endeditable %}), the external
scanner maintains a stack of open Django tag names. When it encounters {% endXXX %}, it checks
whether XXX is on the stack and, if so, emits a specialised close token that closes the
corresponding dj_paired_statement. This means arbitrary third-party and project-specific paired
tags are handled automatically without needing to enumerate them in the grammar.
N.B: The {% [tag] %} ... {% end[tag] %} convention is just that — a convention, not a rule of
Django syntax. The scanner is deliberately lenient: a tag that has no matching {% endXXX %} in
the remaining input is still treated as an unpaired statement rather than an error.
Not (yet) implemented
Query files (typically found at /queries/*.scm) are not implemented in this grammar.
The only use case for this library at the time of writing is programmatic
tree manipulation, not editor integration. Implement these if the grammar is ever adopted
for editor use (syntax highlighting, language injection, symbol navigation).
Limitations
Whitespace or newlines before branch/close tags are not preserved as char_data
Leading whitespace (newlines, spaces) immediately before closing paired tags such as
{% elif %}, or {% endXXX %} is consumed by the external scanner with
skip=true as part of establishing the correct GLR parse path. When the whitespace
immediately precedes one of these tags with nothing else on the same line, it does not
appear as a char_data node in the parse tree:
{% if a %}
{% elif b %}...{% endif %}
{# the \n between if-body and {% elif %} is silently dropped #}
{% mytag %}
{% endmytag %}
{# the \n between open and close is silently dropped #}
<para>This is well{% if use_whitespace %} {% elif use_hyphen %}-{% endif %}known fact</para>
{# Resolves to "This is wellknown fact" when use_whitespace=True #}
When there is any other content on the same line before the branch/close tag, normal
char_data nodes are produced for the surrounding whitespace:
{% if a %}
<x/>
{% elif b %}...{% endif %}
{# \n after {% if a %} and \n after <x/> both become char_data nodes #}
This is a known limitation of the GLR path-resolution mechanism in the scanner.
Removing the skip=true whitespace advance causes GLR to commit to the wrong parse
path for certain body content (e.g. <!DOCTYPE> nodes), so the current approach
is retained.
Generic paired tag whose close tag appears only inside a comment body
If the only occurrence of {% endXXX %} in the remaining input sits inside a
{% comment %}…{% endcomment %} block, the scanner will treat it as a valid
close tag, emit _generic_open_tag for XXX, and then never emit the matching
_generic_close_tag (because the comment body is opaque and consumes the
{% endXXX %}). This produces an ERROR node:
{# BUG: produces ERROR — {% endmytag %} inside comment is unreachable #}
{% mytag %}{% comment %}{% endmytag %}{% endcomment %}
The correct fix is to make the lookahead scanner skip {% comment %} blocks, but
this is an uncommon pattern in practice and the added scanner complexity is not
currently justified.
XML tags spanning Django conditional branches
Django templates sometimes use conditional blocks to select between variants of an XML structure, relying on the template engine to produce valid XML at render time even though the static source is not well-formed XML. Any XML tag that opens or closes across a Django branch boundary — in either direction — will produce incorrect parse trees:
{# open before block, close inside branch — ERROR nodes #}
<keepTogether>
{% if necf_state %}
</keepTogether>
{% elif vic_state %}
</keepTogether>
{% endif %}
{# open inside branches, close after block — wrong tree, no ERROR nodes #}
{% if wide %}
<keepTogether>
{% else %}
<keepTogether>
{% endif %}
</keepTogether>
The second case appears to parse without ERROR nodes when the pattern sits at
the document root, but in practice these templates have a wrapping parent
element. Inside XML element content (_node), unpaired end_tag is not allowed,
so the closing tag is silently consumed as the close of the parent element
instead, producing a structurally wrong tree.
Adding unpaired start_tag/end_tag alternatives to regular XML content causes
GLR ambiguity: the parser begins treating every ordinary opening tag as an unpaired
node and orphans its close tag, breaking large amounts of valid XML.
The one exception is when both the mismatched open and close tags land
inside Django statement bodies (e.g. inside a {% for %} or {% block %}
body), because that context already allows unpaired tags:
{% for item in items %}
{% if wide %}<keepTogether>{% else %}<keepTogether>{% endif %}
<para>{{ item }}</para>
</keepTogether> {# inside the for body — parses without error #}
{% endfor %}
If a fix becomes necessary: the principled path is an external scanner
(C code in src/scanner.c) that maintains a stack of open XML element names.
When it encounters a </tag> whose name is not on the stack it emits a
distinct _orphan_end_tag token rather than a normal end_tag, allowing the
grammar to admit it in _node context without competing with legitimate close
tags.
License
Metadata
Release files for tree-sitter-xml-django 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| tree_sitter_xml_django-0.6.0.tar.gz | 66.4 kB | With provenance | Details |
Built distributions (wheels)
Total release size: 481.2 kB
Release files / tree_sitter_xml_django-0.6.0.tar.gz
| Download URL | tree_sitter_xml_django-0.6.0.tar.gz |
|---|---|
| Size | 66.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
26718d9f11df0ce0cb31c4627f7f96b749e90144ade91e107ac323fad1943cb6
|
|
BLAKE2b-256 checksum How to use checksums |
1f90eb3a3e76476a6178c84e3931fea365f1b380e45c17ec3b95b29678e18e77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-win_arm64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-win_arm64.whl |
|---|---|
| Size | 37.7 kB |
| Tags | CPython 3.10 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
cc37945a30ca3eb3d54b5dc627b6a9530d3d32136992001f615ae66161a6f30a
|
|
BLAKE2b-256 checksum How to use checksums |
b7e42a31a18f65965d848077ed1925767434733a64f11f130a241f797ec383a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-win_amd64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 37.7 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
5cd46718e9bb1a0888288ffa9d61fa1a5f5d8f0bdbd359eb9ea37a426c311231
|
|
BLAKE2b-256 checksum How to use checksums |
f7a431446dc7e33da0d9d45af06826ba45799e2f40ce6fe5c3e71e434130d6f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 65.0 kB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
2067ebb52bf37ff78c6ece1eb7655061680e18ebd53c3903c76182b29970902e
|
|
BLAKE2b-256 checksum How to use checksums |
ff7116b913fbbe505af464eb6a2598c398732982459a0dbb25420cdbe4e2d18c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-musllinux_1_2_aarch64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 67.3 kB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
2ed6eec15572e4d1e620ce993809ff223971ec01bf7ef249d246895cef620fa6
|
|
BLAKE2b-256 checksum How to use checksums |
6f5c398f07710d5b8a1381f9b9c292830abed316e8ec2170b23ffc7270ecd610
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 65.0 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
331474ab167c650646c2b320d04c2860d9968efc54595bf1fdb5b6ca42cb80f6
|
|
BLAKE2b-256 checksum How to use checksums |
289662571ed4d651f2a0ba4e9c46137f79d7ec8b03a07cb8d7c37bc53d025f35
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 68.1 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
dc84a12569472d594333c04d840ef5f0df165e15157be0a5fd7f2692cb137b3e
|
|
BLAKE2b-256 checksum How to use checksums |
1a1d4f3fa0bb3f42cb7433282f4f052ea2e252a34c5712769099b6527ef81d3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 37.7 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
cacb34f4565c93c282f43e543ab805f5623039e0547c373c40a169aae07d333c
|
|
BLAKE2b-256 checksum How to use checksums |
9aa062a40a59f362c6388d1432ce81028cf5c3e32609a6ddf5286cf066a5612a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.6.0-cp310-abi3-macosx_10_9_x86_64.whl
| Download URL | tree_sitter_xml_django-0.6.0-cp310-abi3-macosx_10_9_x86_64.whl |
|---|---|
| Size | 36.4 kB |
| Tags | CPython 3.10 abi3 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
dabc5a2e4af15274d4c993832525a73315d7a68e89e5f43803a3b3a5a221805d
|
|
BLAKE2b-256 checksum How to use checksums |
cd81be93590b8a902e5cc9624fd6c431d2c857ba07216c063a9cbb96250b0838
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for tree-sitter-xml-django 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 9 files in this release have been uploaded with provenance metadata.
Attestation for tree_sitter_xml_django-0.6.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.