Markup stripped, order preserved.
DITA, DocBook, JATS, XBRL filings — the content is real prose buried in tags, entities, and attributes, in document order, not alphabetical or arbitrary order.
the-problem
Markup formats put real content in places a simple tag-stripper doesn't look. HTML entities like ’ or & need resolving back to the character they represent, not left as literal text or double-decoded. CDATA sections hold raw, unescaped content that a naive parser can either skip entirely or fail to close correctly. XML namespaces mean the 'same' tag name can mean two different things in two different parts of a document. And in plenty of real-world XML dialects — DITA and DocBook technical docs, JATS scientific articles, XBRL financial filings — meaningful content lives in attribute values, not just element text, which a text-node-only extractor misses completely. Getting this right means resolving entities, walking the tree in document order (not the order attributes happen to be declared), and knowing which attributes are content versus which are just structural scaffolding.
one-request-solution
txtfetch parses the actual XML tree rather than regex-stripping tags: entities resolve to their real characters, CDATA content is read correctly, and element text comes back in document order with the tag scaffolding removed. For a typed, structured view of the same document — element names and hierarchy preserved rather than flattened to prose — request ?format=json.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@product-catalog.xmlimport os
import requests
with open("product-catalog.xml", "rb") as f:
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
files={"file": f},
)
print(r.json()["extracted_text"])import { readFile } from "node:fs/promises";
const file = new Blob([await readFile("product-catalog.xml")]);
const form = new FormData();
form.append("file", file, "product-catalog.xml");
const res = await fetch("https://api.txtfetch.com/v1/extract", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
body: form,
});
const { extracted_text } = await res.json();
console.log(extracted_text);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
type extractResponse struct {
Status string `json:"status"`
ExtractedText string `json:"extracted_text"`
}
func main() {
f, err := os.Open("product-catalog.xml")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "product-catalog.xml")
if err != nil {
panic(err)
}
if _, err := io.Copy(part, f); err != nil {
panic(err)
}
writer.Close()
req, err := http.NewRequest("POST", "https://api.txtfetch.com/v1/extract", &body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result extractResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.ExtractedText)
}Or skip the download — pass a url parameter and txtfetch fetches the document server-side:
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/feeds/press-release.xml" \
-H "Authorization: Bearer $TXTFETCH_KEY"import os
import requests
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
params={"url": "https://example.com/feeds/press-release.xml"},
)
print(r.json()["extracted_text"])const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/feeds/press-release.xml");
const res = await fetch(endpoint, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
});
const { extracted_text } = await res.json();
console.log(extracted_text);package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
)
type extractResponse struct {
Status string `json:"status"`
ExtractedText string `json:"extracted_text"`
}
func main() {
endpoint, err := url.Parse("https://api.txtfetch.com/v1/extract")
if err != nil {
panic(err)
}
q := endpoint.Query()
q.Set("url", "https://example.com/feeds/press-release.xml")
endpoint.RawQuery = q.Encode()
req, err := http.NewRequest("POST", endpoint.String(), nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result extractResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.ExtractedText)
}{
"status": "success",
"extracted_text": "..."
}formats-covered
.xml.json.yaml.html.xhtml.svg
response-options
Need the element structure, not flattened prose? ?format=json returns a typed element tree instead of one text blob.
faq
- Does extraction resolve HTML/XML entities like & or ’?
- Yes — entities resolve to the character they represent in the extracted text rather than being left as literal escape sequences.
- Is content inside CDATA sections included?
- Yes — CDATA blocks are parsed as the raw content they contain, not skipped or double-escaped.
- What about content stored in XML attributes rather than element text, like in DITA or XBRL files?
- Common technical and financial XML dialects (DITA, DocBook, JATS, XBRL) carry meaningful content in specific attributes, not just element text. txtfetch's XML handling accounts for this rather than only reading text nodes.
- Can I get a structured element tree instead of flattened text?
- Yes — pass ?format=json for a typed, hierarchical view of the document instead of prose-flattened text.
go-further
Web & data
Stop parsing. Start shipping.
Create an account and get an API key in minutes — the free Hobby plan needs no card.
Get started →