Escapers
Escapers for CSV, Markdown, XML, HTML, JSON and URL, each verified against a real parser
Overview
Text that came from somewhere else — a name, a status, a free-text reason — cannot be dropped into a document as it stands. A comma reshapes a CSV row, a pipe splits a Markdown table cell, an unescaped quote ends an HTML attribute early and starts whatever follows it. An escaper takes such a value and returns one that embeds at a specific place in a specific format without changing the document's structure.
There is no general "escaped string": a value is escaped for somewhere. The same name that is safe inside a Markdown code span breaks a CSV row, and the same value quoted for CSV is meaningless inside an XML attribute. So each escaper here names the position it is for and is correct there and nowhere else. Two escapers are never applied to the same value for extra safety; where a document genuinely contains another, they nest once per layer.
Every escaper accepts any value whose str() succeeds — None included —
and returns a string that can be written as UTF-8. Each is verified by
round-tripping a hostile value through a real parser for its format, in the
position a consumer would use it, rather than by reading the specification.
Installation
pip install vcti-escapers
In requirements.txt
vcti-escapers>=1.0.0
In pyproject.toml dependencies
dependencies = [
"vcti-escapers>=1.0.0",
]
Quick Start
from vcti.escapers import csv_field, html_attribute, json_string, xml_text
csv_field("failed, retried") # '"failed, retried"'
csv_field(138.0) # '138.0' — numbers stay bare
xml_text("a & b") # 'a & b'
html_attribute('" onclick="') # '" onclick="'
json_string("</script>") # '"\\u003c/script\\u003e"'
Every escaper takes any value whose str() succeeds, None included. What
absence looks like is the target's decision, so each escaper documents its
own answer.
csv_field(None) # '' — an empty field
json_string(None) # '""' — an empty JSON string, since null is not one
The escapers
They form one flat pool, grouped here by format for finding rather than because a format owns them.
| Format | Escaper | For |
|---|---|---|
| Markdown | markdown_code |
An inline code span in prose — content shown literally, not obeyed |
| Markdown | markdown_table_cell |
A code span in a GFM table cell, where | must also be escaped |
| CSV | csv_field |
One RFC 4180 field; text quoted, numbers bare |
| XML | xml_text |
Character data between tags |
| XML | xml_attribute |
Inside an attribute (caller supplies the quotes) |
| HTML | html_text |
Ordinary text content between tags |
| HTML | html_attribute |
Inside a quoted ordinary attribute (caller supplies the quotes) |
| JSON | json_string |
A string literal, quotes included, safe inside <script> |
| URL | url_path_segment |
One path segment — a slash stays inside it |
| URL | url_query_value |
One query parameter value, form-encoded |
A format appears more than once because a format needs different escaping in different positions — that distinction is the point, and picking the wrong one is the mistake this package exists to prevent. Read each escaper's docstring before first use; several carry limits that matter.
Two worth knowing up front:
html_attributeis for ordinary attributes. An event handler (onclick), a URL attribute (href,src) orstyleneeds more than escaping — a perfectly escapedjavascript:URL still runs.- Escapers do not chain for extra safety. They nest, once per layer, when a document genuinely contains another — see docs/patterns.md.
Using them with a template engine
Escapers are plain functions, so they register wherever a template engine takes callables. Nothing here depends on a template engine or knows one exists.
from vcti.escapers import csv_field, markdown_code
filters = {"csv_field": csv_field, "markdown_code": markdown_code}
# Jinja2: environment.filters.update(filters)
Registered under their own names, they read the same in a template as in
Python — {{ item.name | csv_field }}.
Dependencies
None.
Documentation
| If you want to… | Read |
|---|---|
| See practical, real-world usage | docs/patterns.md |
| Understand the architecture and design decisions | docs/design.md |
| Navigate and understand the source | docs/source-guide.md |
| Add an escaper | docs/extending.md |
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vcti_escapers-1.0.0.tar.gz.
File metadata
- Download URL: vcti_escapers-1.0.0.tar.gz
- Upload date:
- Size: 31.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d9e371eb1f5f6f0fec175afdb42a4f104e32ab3a5255b50b1736daf3159008b
|
|
| MD5 |
534a3b466b6db4c79e4401a266cfe6d5
|
|
| BLAKE2b-256 |
0a186fb5d37c289756ae7276e1d9628f2d410f158468da70d1d975da1d53674b
|
Provenance
The following attestation bundles were made for vcti_escapers-1.0.0.tar.gz:
Publisher:
release.yml on vcollab/vcti-python-escapers
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vcti_escapers-1.0.0.tar.gz -
Subject digest:
0d9e371eb1f5f6f0fec175afdb42a4f104e32ab3a5255b50b1736daf3159008b - Sigstore transparency entry: 2718770443
- Sigstore integration time:
-
Permalink:
vcollab/vcti-python-escapers@d85dd30a863a3dea7684f6e9ee49b2bb2b09cfbb -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/vcollab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d85dd30a863a3dea7684f6e9ee49b2bb2b09cfbb -
Trigger Event:
push
-
Statement type:
File details
Details for the file vcti_escapers-1.0.0-py3-none-any.whl.
File metadata
- Download URL: vcti_escapers-1.0.0-py3-none-any.whl
- Upload date:
- Size: 19.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07e700474b73a51ec6f84f2859c175f98f7836ae149759dc11e88b679591b97c
|
|
| MD5 |
48883045ade2ccdf3cae1509bd8c70c7
|
|
| BLAKE2b-256 |
3936ba760373b0927c56c4d88cf15006a03f5576669f954c463f3dcd5888a9c1
|
Provenance
The following attestation bundles were made for vcti_escapers-1.0.0-py3-none-any.whl:
Publisher:
release.yml on vcollab/vcti-python-escapers
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vcti_escapers-1.0.0-py3-none-any.whl -
Subject digest:
07e700474b73a51ec6f84f2859c175f98f7836ae149759dc11e88b679591b97c - Sigstore transparency entry: 2718770924
- Sigstore integration time:
-
Permalink:
vcollab/vcti-python-escapers@d85dd30a863a3dea7684f6e9ee49b2bb2b09cfbb -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/vcollab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d85dd30a863a3dea7684f6e9ee49b2bb2b09cfbb -
Trigger Event:
push
-
Statement type: