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 (e.g. {% block %}...{% endblock %}, {% if %}...{% endif %}) enclose a
body and require a matching closing tag. The grammar must know the tag name at parse time in
order to look for the correct {% endXXX %} closing token — this is why paired tags are handled
by named rules or an explicit whitelist rather than a catch-all.
This distinction explains what happens with an unknown paired tag: because the parser has no
rule telling it to look for a closing {% endcustom %}, the opening tag falls through to the
catch-all and is recorded as a dj_unpaired_statement. The closing {% endcustom %} is then
parsed as a second, separate dj_unpaired_statement. See
Unknown paired Django tags under Limitations for the planned fix.
N.B: It's important to note that the {% [tag] %} ... {% end[tag] %} convention is just that,
a convention not a rule of Django syntax. Any proposed fix would need to be lenient on paired
statements that do not follow this convention.
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
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), bare 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 bare start_tag/end_tag alternatives to regular XML content causes
GLR ambiguity: the parser begins treating every ordinary opening tag as a bare
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 bare 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. This is non-trivial — the scanner needs to coordinate with Django block
boundaries — but it avoids the GLR ambiguity that makes the pure-grammar
approach unworkable. The same scanner update would also be a natural place to
address unknown paired Django tags.
Unknown paired Django tags
The grammar handles known built-in paired tags by name (autoescape, block,
filter, for, if, comment, etc.). Any tag not in that list — including
third-party or project-specific paired tags — is parsed as two separate
dj_unpaired_statement nodes rather than a single dj_paired_statement with
a body.
If a fix becomes necessary: the same external scanner approach described
above for XML tags applies here. The scanner would maintain a second 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 the
grammar uses to close the corresponding dj_paired_statement. This removes
the need for a tag whitelist and handles arbitrary custom and third-party tags.
Both stacks could be implemented together in a single scanner update.
License
Metadata
Release files for tree-sitter-xml-django 0.4.2
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.4.2.tar.gz | 78.2 kB | With provenance | Details |
Built distributions (wheels)
Total release size: 496.3 kB
Release files / tree_sitter_xml_django-0.4.2.tar.gz
| Download URL | tree_sitter_xml_django-0.4.2.tar.gz |
|---|---|
| Size | 78.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4ac791cfe28bfac0fde7fb645439366043331709f31d7e72694e6a4997b914a5
|
|
BLAKE2b-256 checksum How to use checksums |
2bad3a943760b7d88129d3f3eb93c616b640a507746bdadfa439939f8a8fd643
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-win_arm64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-win_arm64.whl |
|---|---|
| Size | 39.6 kB |
| Tags | CPython 3.10 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
15bcad9d275945d860a144b25eff20f88a0e473332191f01a06c9e8891d84c88
|
|
BLAKE2b-256 checksum How to use checksums |
498baf78c5cad1653ca6ef10e9deaab09d9803f6a0aae3f1c287f2110883f501
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-win_amd64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 40.3 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
594bb267d3a7249f90c36cdde04f0eeb01b5d20529ce1e19f6dcc87c5a1d020b
|
|
BLAKE2b-256 checksum How to use checksums |
7a4f68c6072209369c9f25417fec218155036e9e235ccc427499a226e5ac6611
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 65.7 kB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
c13cfa653beeb784ebfdaffde65e02fce38054c416077f7a0a615ff949c885a7
|
|
BLAKE2b-256 checksum How to use checksums |
6dcc6b26ab230479929ab431dcfa6ba5fe4c9aa718bf6dac69aa404f723ad8fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-musllinux_1_2_aarch64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 67.8 kB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
2ca790ba499a19f243166a78be4a1fd9707344eb92589b7afec9387f5f832a5d
|
|
BLAKE2b-256 checksum How to use checksums |
3108b92dab9de57bdb470f0163b1b39249458029152c7586e646e0f051c25169
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 63.0 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
be14f91a2e0895b836550df88b7b4a8cf1a04eae50c1b58c479305e92d5cec36
|
|
BLAKE2b-256 checksum How to use checksums |
4ff68391a7b3219f37bb3827fdbd8dc0cb12cba9d92126fabaf960f0f187fd5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 61.9 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 Linux glibc 2.5+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
824985d587872d6dc6df13dfeb847724dc743f54d04d46b41e8153b7446a2006
|
|
BLAKE2b-256 checksum How to use checksums |
e3d6cc97b1caf93047af31700fb0a6995e7e59969aa59dca862ed927d85dd202
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 40.6 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e18d30d0cbb9127883b181c406737bc6a5975b62fe7fbaf80707f5526c627fa2
|
|
BLAKE2b-256 checksum How to use checksums |
fb45798e334f93665beacf26440d8bcf5ea3b80b13ffc64e50807088a8161ef4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logRelease files / tree_sitter_xml_django-0.4.2-cp310-abi3-macosx_10_9_x86_64.whl
| Download URL | tree_sitter_xml_django-0.4.2-cp310-abi3-macosx_10_9_x86_64.whl |
|---|---|
| Size | 39.3 kB |
| Tags | CPython 3.10 abi3 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
ceeb3353a739b4863baa931d40d4ecd8f045fcb2c88b1c7be6949f407e69593d
|
|
BLAKE2b-256 checksum How to use checksums |
a806be75f633063bc25cb9e42560476db842900dfc0af5697d70586092c73d64
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 11, 2026.
Transparency logSecurity for tree-sitter-xml-django 0.4.2
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.4.2.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 Sep 11, 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.