Čtení přes ElementTree
import xml.etree.ElementTree as ET
strom = ET.parse('objednavka.xml') # nebo ET.fromstring(text) — vrací rovnou kořen
koren = strom.getroot()
print(koren.get('cislo')) # atribut: 2026-117
print(koren.findtext('zakaznik')) # text prvku: Jana Nováková
for polozka in koren.iter('polozka'): # iter() hledá v celém stromu, findall() jen v cestě
mnozstvi = int(polozka.get('mnozstvi'))
print(f'{polozka.text} × {mnozstvi}')
# Hodnoty jsou vždy řetězce, čísla převeď sám
celkem = sum(float(p.get('cena')) * int(p.get('mnozstvi')) for p in koren.findall('polozka'))
Prvek (Element) se chová jako seznam svých potomků: len(prvek),
prvek[0] i for dite in prvek fungují. Text za koncovou značkou
potomka je v jeho atributu tail, ne v text rodiče.
Pozor na pravdivostní hodnotu: prvek bez potomků je v podmínce nepravdivý,
i když existuje (Python 3.12 na to upozorňuje varováním). Místo if koren.find('zakaznik'): piš
if koren.find('zakaznik') is not None:.
ElementTree, lxml, nebo iterparse?
Všechny ukázky pracují s touto objednávkou:
<?xml version="1.0" encoding="UTF-8"?>
<objednavka cislo="2026-117">
<zakaznik>Jana Nováková</zakaznik>
<polozka kod="HR-01" mnozstvi="2" cena="149.90">Hrnek</polozka>
<polozka kod="TA-07" mnozstvi="1" cena="219.00">Talíř</polozka>
</objednavka> | xml.etree.ElementTree | lxml | iterparse | xml.dom.minidom | |
|---|---|---|---|---|
| Instalace | součást Pythonu | pip install lxml | v ElementTree i lxml | součást Pythonu |
| Paměť | celý dokument | celý dokument | jen rozpracovaný záznam | celý dokument, nejvíc |
| XPath | jen podmnožina v find() | plné XPath 1.0 | po záznamech přes find() | ne |
| Validace XSD, XSLT | ne | ano | — | ne |
| Kdy použít | běžné čtení a zápis bez závislostí | dotazy, validace, transformace, čísla řádků | soubory ve stovkách MB, feedy | starší kód, rychlé toprettyxml() |
API knihovny lxml je s ElementTree kompatibilní: find(), findall(),
get(), text i iter() fungují stejně. Kód napsaný pro
ElementTree proto obvykle stačí přepnout změnou importu a pak využít, co lxml přidává navíc.
Chyby parsování
import xml.etree.ElementTree as ET
try:
koren = ET.fromstring('<objednavka><polozka></objednavka>')
except ET.ParseError as e:
radek, sloupec = e.position
print(f'Řádek {radek}, sloupec {sloupec}: {e}')
# Řádek 1, sloupec 23: mismatched tag: line 1, column 23
# lxml hlásí stejné chyby jako libxml2 (PHP, xmllint)
from lxml import etree
try:
etree.fromstring(b'<objednavka><polozka></objednavka>')
except etree.XMLSyntaxError as e:
print(e.lineno, e.msg)
# 1 Opening and ending tag mismatch: polozka line 1 and objednavka, line 1, column 35
ElementTree používá parser expat, a proto hlásí krátké zprávy jako not well-formed
(invalid token) nebo mismatched tag. Co znamenají a jak je opravit,
najdeš v přehledu chybových hlášek, kde jsou zprávy Pythonu u každé
příčiny.
Kódování a lxml
text = open('feed.xml', encoding='utf-8').read()
etree.fromstring(text)
# ValueError: Unicode strings with encoding declaration are not supported.
# Řešení: předat bajty a nechat parser přečíst kódování z deklarace
with open('feed.xml', 'rb') as f:
koren = etree.fromstring(f.read()) # nebo rovnou etree.parse('feed.xml')
Soubor s XML proto otevírej binárně ('rb') a dekódování nech na parseru.
Parser se řídí deklarací encoding, takže správně načte i feed ve windows-1250.
Vytvoření XML
import xml.etree.ElementTree as ET
koren = ET.Element('objednavka', cislo='2026-118')
ET.SubElement(koren, 'zakaznik').text = 'Petr & syn' # & se zapíše jako &
polozka = ET.SubElement(koren, 'polozka', kod='MI-03', mnozstvi='3')
polozka.text = 'Miska'
strom = ET.ElementTree(koren)
ET.indent(strom) # odsazení (Python 3.9+)
strom.write('export.xml', encoding='utf-8', xml_declaration=True)
ET.tostring(koren, encoding='unicode') # jako str; bez encoding vrací bytes
Řetězce neskládej ručně (f'<a>{hodnota}</a>') — jeden znak
& nebo < v datech udělá z výsledku nevalidní XML. Knihovna
speciální znaky escapuje sama. Hotový feed nebo export si pak můžeš zkontrolovat ve
validátoru.
Serializace XML
Při převodu XML stromu na text (serializaci) knihovna zapíše zvláštní znaky podle pravidel XML, například ampersand jako &. Zápis zpět nemusí zachovat původní prefixy, odsazení, CDATA nebo pořadí atributů; testuj obsah a požadované kódování.
text = ET.tostring(koren, encoding="unicode")
ET.ElementTree(koren).write("vystup.xml", encoding="utf-8", xml_declaration=True) DOM
ElementTree je stromové API, ale neimplementuje celý W3C DOM. Pokud potřebuješ DOM uzly, standardní knihovna nabízí xml.dom.minidom. Pro práci s velkými soubory použij iterparse a průběžně uvolňuj dokončené elementy.
Velké soubory: iterparse
import xml.etree.ElementTree as ET
# Feed se 100 000 produkty po jednom záznamu, bez načtení celého do paměti
kontext = ET.iterparse('feed.xml', events=('start', 'end'))
_, koren = next(kontext) # první událost je začátek kořene
for udalost, prvek in kontext:
if udalost == 'end' and prvek.tag == 'SHOPITEM':
uloz(prvek.findtext('PRODUCTNAME'), float(prvek.findtext('PRICE_VAT')))
koren.clear() # zahodit zpracované záznamy
Samotné prvek.clear() nestačí: prázdné prvky zůstávají zavěšené na kořeni a
paměť u milionu záznamů stejně roste. Proto se na začátku vezme kořen a po každém záznamu se
vyčistí celý. V lxml se totéž dělá odebráním už zpracovaných sourozenců:
# lxml umí filtrovat rovnou podle názvu (i s jmenným prostorem)
for _, prvek in etree.iterparse('zbozi.xml', tag='{http://www.zbozi.cz/ns/offer/1.0}SHOPITEM'):
zpracuj(prvek)
prvek.clear(keep_tail=True)
while prvek.getprevious() is not None: # uvolnit i dřív zpracované sourozence
del prvek.getparent()[0] Hledání: cesty v ElementTree
koren.find('polozka') # první přímý potomek <polozka>, jinak None
koren.findall('.//polozka[@kod="TA-07"]') # podle hodnoty atributu
koren.findall('polozka[1]') # podle pozice (od 1)
koren.findtext('neexistuje', default='') # bez default vrací None
# Tohle ElementTree neumí — jen omezenou podmnožinu XPath:
# koren.findall('polozka[@mnozstvi > 1]') # SyntaxError: invalid predicate find() a findall() berou jen omezenou podmnožinu
XPath: názvy, *, ., ..,
//, test na atribut, na text potomka a na pozici. Porovnání čísel, funkce jako
contains() nebo osy v ní nejsou — pro ně sáhni po lxml.
Plné XPath v lxml
from lxml import etree
strom = etree.parse('objednavka.xml')
# Plné XPath 1.0: porovnání, funkce, výsledkem může být i číslo nebo text
kody = strom.xpath('//polozka[@mnozstvi > 1]/@kod') # ['HR-01']
pocet = strom.xpath('count(//polozka)') # 2.0 (float)
jmeno = strom.xpath('string(/objednavka/zakaznik)') # 'Jana Nováková'
# Proměnné místo skládání řetězců (ochrana před XPath injection)
polozka = strom.xpath('//polozka[@kod = $kod]', kod='HR-01')[0]
print(polozka.sourceline) # číslo řádku v souboru: 4
lxml stojí na knihovně libxml2 a podporuje XPath 1.0. Funkce z novějších
verzí (matches(), string-join()) v něm nejsou; regulární výrazy
nabízí přes rozšíření EXSLT. Výraz si před vložením do kódu vyzkoušej v testeru s volbou
XPath 1.0.
Jmenné prostory
text = '<f:faktura xmlns:f="http://example.cz/faktura"><f:cislo>FV2026-042</f:cislo></f:faktura>'
NS = {'fa': 'http://example.cz/faktura'} # prefix v dotazu nemusí být stejný jako v dokumentu
# ElementTree: slovník prefixů, nebo zápis {uri}jméno
koren = ET.fromstring(text)
koren.findtext('fa:cislo', namespaces=NS) # 'FV2026-042'
koren.findtext('{http://example.cz/faktura}cislo')
koren.tag # '{http://example.cz/faktura}faktura'
# lxml: stejný slovník v xpath()
etree.fromstring(text.encode()).xpath('//fa:cislo/text()', namespaces=NS)
# Výchozí jmenný prostor (xmlns="…" bez prefixu) — ElementTree zná klíč '' pro find*()
koren.findall('polozka', namespaces={'': 'http://example.cz/objednavka'})
Nejčastější chyba: dokument má výchozí jmenný prostor a findall('polozka') nic
nenajde, protože se prvek ve skutečnosti jmenuje
{http://example.cz/objednavka}polozka. V lxml xpath() klíč
'' nefunguje — prefix je potřeba vymyslet a psát //o:polozka. Víc v
kapitole Jmenné prostory.
Validace proti XSD a XSLT
Standardní knihovna validaci neumí, lxml ano. Chyby mají stejné znění jako
xmllint, takže je najdeš i v přehledu
chyb validace.
from lxml import etree
schema = etree.XMLSchema(etree.parse('objednavka.xsd'))
dokument = etree.parse('objednavka.xml')
if not schema.validate(dokument):
for chyba in schema.error_log:
print(f'Řádek {chyba.line}: {chyba.message}')
# nebo schema.assertValid(dokument), které vyhodí DocumentInvalid Stejně jednoduše lxml spustí transformaci XSLT 1.0:
transformace = etree.XSLT(etree.parse('faktura.xsl'))
html = transformace(etree.parse('faktura.xml'))
html.write_output('faktura.html') # respektuje xsl:output Bezpečnost
ElementTree externí entity nenačítá a s aktuální verzí expat odolá i entitním bombám. lxml
od verze 5 rozbaluje jen interní entity, ale načítá DTD a u starších verzí rozbaluje i
externí. Pro XML z cizích zdrojů proto použij knihovnu defusedxml se stejným
API jako standardní moduly, nebo lxml s parserem
etree.XMLParser(resolve_entities=False, no_network=True). Proč na tom záleží,
vysvětluje kapitola o útoku XXE.
Načtení JSON
JSON parser vrací datovou strukturu, ne XML strom. Chyby syntaxe zpracuj jako chyby vstupu; data ověř také strukturálně.
import json
data = json.loads('{"jmeno":"Jana","vek":20}')
print(data["jmeno"]) Vytvoření JSON
Vytvoř hodnotu v paměti a nech její textový zápis na knihovně.
data = {"jmeno": "Jana", "vek": 20, "aktivni": True} Serializace JSON
Neskládej text ručně. Ověř přesnost čísel, reprezentaci dat a chování nečíselných hodnot.
text = json.dumps(data, ensure_ascii=False, allow_nan=False, indent=2) JSON Schema
Standardní knihovna neposkytuje plnou validaci JSON Schema. Vyber knihovnu s podporou požadované verze pravidel JSON Schema z oficiální dokumentace či katalogu. Základy a rozdíly verzí vysvětluje kurz JSON Schema.
Ukázka vyžaduje instalaci uvedeného balíčku. Kontrola format závisí na nastavení knihovny; validace nenahrazuje obchodní pravidla.
from jsonschema import Draft202012Validator
schema = {"type": "object", "properties": {"vek": {"type": "integer"}}, "required": ["vek"]}
Draft202012Validator.check_schema(schema)
Draft202012Validator(schema).validate({"vek": 20}) Dokumentace API
Příklady JSON vycházejí z oficiální dokumentace jazyka. Překlady typů, maximální hloubka a chování při chybě jsou vlastnosti knihovny, ne samotné syntaxe JSON.