1
0
Files
Rihards 3c6990587f Likums-as-Code 0.4.1: Act/LikumiId un Act/Url
likumi.lv identifikators (shēmā jau bija, neobligāts) tagad ir katrā likumā — no kataloga 58 failos
(katram DataVersion ieraksts); tools/convert.py to raksta HTML avotam. Teksta pilnības skaitā
identifikators netiek skaitīts. Vajadzīgs, lai nolikumu atsauces (Nolikumi-as-Code Cite@likumiId)
savienotu ar likumu. validate: 58/58 OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016igpMJBtapmG6vHE4vSF9q
2026-10-24 08:49:12 +00:00

423 lines
22 KiB
Python

"""convert.py — likumi.lv konsolidētā likuma Word (.docx) vai lapa (HTML) → likumi-0.1 XML.
Lieto: python3 convert.py <likums.docx|<likumi_id>.html> <izvade.xml>
HTML lapu un grozījumu lapas lejupielādē: python3 tools/html_source.py fetch <likumi_id> <kešs>
Strukturē pēc Word stiliem un sarakstu numerācijas (nevis ar MI). Neskaidros gadījumus
ieraksta verification JSON (`<izvade>.verify.json`), nevis min.
"""
import sys, os, re, json, hashlib, datetime
from lxml import etree
NS = "urn:pppa:vpk:likumi:0.1"
W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
SUP = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹")
ROMAN = {"I": 1, "V": 5, "X": 10, "L": 50}
MONTHS = {"janvār": 1, "februār": 2, "mart": 3, "aprīl": 4, "maij": 5, "jūnij": 6, "jūlij": 7,
"august": 8, "septembr": 9, "oktobr": 10, "novembr": 11, "decembr": 12}
def roman(s):
v = 0
for i, c in enumerate(s):
x = ROMAN[c]; v += -x if i + 1 < len(s) and ROMAN[s[i + 1]] > x else x
return v
def dmy(s): # 03.04.2026 -> 2026-04-03
d, m, y = s.split("."); return f"{y}-{m}-{d}"
def lvdate(s): # 2000.gada 19.oktobrī
m = re.search(r"(\d{4})\.\s*gada\s+(\d{1,2})\.\s*([a-zēūīāšžčņļķģ]+)", s)
if not m: return None
mon = next(v for k, v in MONTHS.items() if m.group(3).startswith(k))
return f"{m.group(1)}-{mon:02d}-{int(m.group(2)):02d}"
def text_of(p):
"""Paragraph text; superscript digits → Unicode superscript (4¹)."""
if hasattr(p, "_num"): return p.text # html_source.Para
out = []
for r in p.runs:
t = r.text
out.append(t.translate(SUP) if r.font.superscript and t.strip().isdigit() else t)
return re.sub(r"\s+", " ", "".join(out)).strip()
def sup_suffix(n): # "4¹" -> ("4","1")
base = re.match(r"\d+", n).group(0)
rest = n[len(base):].translate(str.maketrans("⁰¹²³⁴⁵⁶⁷⁸⁹", "0123456789"))
return base, rest
def idpart(prefix, n):
b, s = sup_suffix(n)
letter = s[-1].lower() if s[-1:].isalpha() else "" # "644¹⁸A" → art644s18a
s = s.rstrip("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
return f"{prefix}{b}" + (f"s{s}" if s else "") + letter
class Numbering:
def __init__(self, d):
self.num, self.abs = {}, {}
el = d.part.numbering_part.element
for a in el.findall(W + "abstractNum"):
self.abs[a.get(W + "abstractNumId")] = {l.get(W + "ilvl"): int(l.find(W + "start").get(W + "val")) if l.find(W + "start") is not None else 1 for l in a.findall(W + "lvl")}
for n in el.findall(W + "num"):
starts = dict(self.abs.get(n.find(W + "abstractNumId").get(W + "val"), {}))
for o in n.findall(W + "lvlOverride"):
so = o.find(W + "startOverride")
if so is not None: starts[o.get(W + "ilvl")] = int(so.get(W + "val"))
self.num[str(n.get(W + "numId"))] = starts
self.count = {}
def next(self, numId, ilvl):
numId = str(numId)
c = self.count.setdefault(numId, {})
c[ilvl] = c.get(ilvl, self.num.get(numId, {}).get(str(ilvl), 1) - 1) + 1
for k in list(c):
if k > ilvl: del c[k]
return c[ilvl]
def celex(t):
"""CELEX pirmajam ES aktam tekstā: direktīva 2008/114/EK (L) vai kopš 2015 — direktīva/regula/lēmums (ES) 2022/2557.
Veco regulu numurs (EK) Nr. 1049/2001 (numurs/gads) netiek minēts — atstāj bez CELEX."""
for m in re.finditer(r"\((?:ES|ES, Euratom)\)\s*(\d{4})/(\d+)|\b(\d{4})/(\d+)/(?:EK|ES|EEK)\b", t):
y, n = (m.group(1), m.group(2)) if m.group(1) else (m.group(3), m.group(4))
before = t[max(0, m.start() - 60):m.start()].lower()
k = max(("L", before.rfind("direktīv")), ("R", before.rfind("regul")), ("D", before.rfind("lēmum")), key=lambda x: x[1])
kind = k[0] if k[1] >= 0 else ("L" if m.group(3) else None)
if kind: return f"3{y}{kind}{int(n):04d}"
return None
def numpr(p):
if hasattr(p, "_num"): return p._num # html_source.Para: (drukātais numurs, līmenis)
pPr = p._p.pPr
if pPr is None or pPr.numPr is None or pPr.numPr.numId is None: return None
return pPr.numPr.numId.val, (pPr.numPr.ilvl.val if pPr.numPr.ilvl is not None else 0)
NOTE = re.compile(r"^\((Ar grozījumiem|\d{2}\.\d{2}\.\d{4}\.? likuma redakcijā|Pants\b|Daļa\b|Punkts\b|Nodaļ)")
DELETED = re.compile(r"^\((Izslēgt|izslēgt)")
ART = re.compile(r"^(\d+)\.?([⁰¹²³⁴⁵⁶⁷⁸⁹]*[A-Z]?)\.?\s*pants(?:\.|(?=\s|$))\s*(.*)$")
AP_SIG = re.compile(r"^((?:Latvijas Republikas )?Augstākās Padomes (?:priekšsēdētāj|sekretār|priekšsēdētāja vietniek)\S*)\s+(.+)$")
SECT = re.compile(r"^([A-Z])\s+sadaļa\.?\s*(.*)$") # sadaļa virs nodaļām (PPP likums, kapitāla daļu likums)
CHAP = re.compile(r"^([IVXL]+)([⁰¹²³⁴⁵⁶⁷⁸⁹]*|\s[A-C](?=\s))\s+nodaļa\.?\s*(.*)$")
INLINE_PART = re.compile(r"^\((\d+[⁰¹²³⁴⁵⁶⁷⁸⁹]+)\)\s*(.*)$")
INLINE_POINT = re.compile(r"^(\d+[⁰¹²³⁴⁵⁶⁷⁸⁹]*)\)\s*(.*)$")
def E(parent, tag, text=None, **attrs):
e = etree.SubElement(parent, f"{{{NS}}}{tag}", {k: str(v) for k, v in attrs.items() if v is not None})
if text is not None: e.text = text
return e
def convert(path, out):
if path.endswith(".html"):
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import html_source
ps, nb, raw, src_name, issues = html_source.load(path)
src_kind = "likumi.lv konsolidētās redakcijas lapas (HTML)"
pase_adopted = html_source.pase(open(path, encoding="utf-8").read()).get("Pieņemts", "")
else:
import docx
d = docx.Document(path)
ps = [p for p in d.paragraphs]
nb = Numbering(d)
issues = []
raw = open(path, "rb").read()
src_name = path.split("/")[-1]
src_kind = "likumi.lv konsolidētā teksta (Word)"
pase_adopted = ""
# --- galvene (likumi.lv) ---
lines = [text_of(p) for p in ps]
head = " ".join(lines[:4])
issuer = re.search(r"Izdevējs:\s*(\S+)", head).group(1)
typ = re.search(r"Veids:\s*(\S+)", head).group(1)
pub = re.search(r"Latvijas Vēstnesis,\s*([\d/]+)", head)
asof = dmy(re.search(r"Attēlotā redakcija:\s*(\d{2}\.\d{2}\.\d{4})", head).group(1))
i = 4
amend_txt = []
if lines[i].startswith("Grozījumi"):
i += 1
while lines[i] and not lines[i].startswith("Saeima ir pieņēmusi"):
amend_txt.append(lines[i]); i += 1
while not lines[i]: i += 1
formula = None
if lines[i].startswith("Saeima ir pieņēmusi"):
formula = lines[i]; i += 1
title = lines[i]; i += 1
# --- noslēgums (no beigām) ---
tail = {}
j = len(lines) - 1
while j > i:
t = lines[j]
if not t or t.startswith("©"): j -= 1; continue
if t.startswith("Rīgā"): tail["prom"] = t
elif re.match(r"^Valsts prezident\S*\s+vietā$", t): tail["sig_for"] = t
elif re.match(r"^(Valsts prezident|Saeimas priekšsēdētāj)", t): tail["sig"] = t
elif t.startswith("Likums Saeimā pieņemts"): tail["adopt"] = t
elif AP_SIG.match(t): tail.setdefault("ap_sigs", []).insert(0, t)
elif t.startswith("Likums stājas spēkā"): tail["inforce"] = t
else: break
j -= 1
end = j + 1
adopted = lvdate(tail.get("adopt", ""))
if not adopted and pase_adopted:
adopted = dmy(pase_adopted.rstrip("."))
issues.append(f"Pieņemšanas datums ņemts no likumi.lv pases: {pase_adopted}")
adopted = adopted or "1900-01-01"
if adopted == "1900-01-01": issues.append("Pieņemšanas datums nav atrasts")
doc_id = f"lv-lik-{adopted}"
# divi likumi, pieņemti vienā dienā: otrais saņem -2 (… -9); izšķir likumi.yaml pēc likumi_id
lid = os.path.basename(path)[:-5] if path.endswith(".html") else None
if lid and os.path.exists("likumi.yaml"):
import yaml
taken = {e["id"]: str(e.get("likumi_id")) for e in yaml.safe_load(open("likumi.yaml", encoding="utf-8"))}
base, k = doc_id, 1
while doc_id in taken and taken[doc_id] != lid:
k += 1
if k > 9: raise SystemExit(f"Vairāk nekā 9 likumi pieņemti {adopted}")
doc_id = f"{base}-{k}"
if k > 1: issues.append(f"Tajā pašā dienā pieņemts cits likums — ID {doc_id}")
root = etree.Element(f"{{{NS}}}Law", nsmap={None: NS}, id=doc_id, schemaVersion="0.1")
act = E(root, "Act")
E(act, "Title", title); E(act, "Issuer", issuer); E(act, "Type", typ); E(act, "Adopted", adopted)
E(act, "Published", "Latvijas Vēstnesis" + (f", {pub.group(1)}" if pub else ""), issue=pub.group(1) if pub else None)
if "inforce" in tail:
f = lvdate(tail["inforce"])
if f: E(act, "InForce", f)
tv = E(root, "TextVersion", basis="consolidated", asOf=asof)
for m in re.finditer(r"(\d{2}\.\d{2}\.\d{4})\. likums / (.*?) / Stājas spēkā (\d{2}\.\d{2}\.\d{4})\.", " ".join(amend_txt)):
E(tv, "Amendment", f"{m.group(1)}. likums", adopted=dmy(m.group(1)), published=m.group(2).strip(), inForce=dmy(m.group(3)))
src = E(root, "Source")
E(src, "File", src_name); E(src, "SHA256", hashlib.sha256(raw).hexdigest())
E(src, "Retrieved", datetime.date.today().isoformat())
if formula: E(root, "EnactingFormula", formula)
body = E(root, "Body")
# --- korpuss ---
chap = art = part = point = None
container = body
chap_parent = body # sadaļa, ja likumā tādas ir
tp = eu = None
mode = "body"
last = None # pēdējā vienība, kurai pievienot piezīmi
open_note = None
chap_no = 0
pending_chap = None
used = set()
parts_seen = []
def uid(x):
if x in used: issues.append(f"Dubults ID {x}")
used.add(x); return x
def new_art(n, heading):
nonlocal art, part, point, parts_seen
art = E(container, "Article", id=uid(f"{doc_id}.{idpart('art', n)}"), n=n)
if heading: E(art, "Heading", heading)
part = point = None; parts_seen = []
return art
def add_note(t):
nonlocal open_note
tgt = last if last is not None else art
if tgt is None: issues.append(f"Piezīme bez vienības: {t[:60]}"); return
open_note = E(tgt, "Note", t)
for k in range(i, end):
p = ps[k]; t = lines[k]
if not t: continue
# turpinājuma rinda nepabeigtai piezīmei "(Ar grozījumiem ... " bez ")"
if open_note is not None and open_note.text.count("(") > open_note.text.count(")"):
open_note.text += " " + t; continue
open_note = None
st = p.style.name
if pending_chap is not None and st.startswith("Heading 1") and not CHAP.match(t):
E(pending_chap, "Title", t); pending_chap = None; continue
pending_chap = None
ms = SECT.match(t)
if ms and st.startswith("Heading 1"):
chap_parent = sect = E(body, "Chapter", id=uid(f"{doc_id}.s{ms.group(1)}"), n=ms.group(1), kind="division")
if ms.group(2): E(sect, "Title", ms.group(2))
else: pending_chap = sect
container = sect; mode = "body"; art = last = None; continue
if st.startswith("Heading 1") or (CHAP.match(t) and len(t) < 160):
m = CHAP.match(t)
chap_no = roman(m.group(1)) if m else chap_no + 1
if not m: issues.append(f"Nodaļai nav numura virsrakstā, pieņemts {chap_no}: {t[:50]}")
ttl = m.group(3) if m else t
suf = (m.group(2) or "").strip() if m else ""
# iestarpināta nodaļa: II¹ → c2.1 (n 2¹); V A → c5.1 (n 5A); V B → c5.2
sub = (int(suf.translate(str.maketrans("⁰¹²³⁴⁵⁶⁷⁸⁹", "0123456789"))) if suf[:1] in "⁰¹²³⁴⁵⁶⁷⁸⁹" else "ABC".index(suf) + 1) if suf else None
chap = E(chap_parent, "Chapter", id=uid(f"{doc_id}.c{chap_no}" + (f".{sub}" if sub else "")), n=str(chap_no) + suf,
kind="general" if chap_no == 1 and "Vispārīg" in ttl else "substantive")
if ttl: E(chap, "Title", ttl)
else: pending_chap = chap
container = chap; mode = "body"; art = last = None; continue
if t == "Pārejas noteikumi":
tp = E(root, "TransitionalProvisions", id=uid(f"{doc_id}.tp")); E(tp, "Title", t)
mode = "tp"; last = None; continue
if t.startswith("Informatīva atsauce uz Eiropas Savienības"):
eu = E(root, "EuReference", id=uid(f"{doc_id}.eu")); E(eu, "Title", t)
mode = "eu"; last = None; continue
if mode == "eu":
if numpr(p) is None and len(eu.findall(f"{{{NS}}}Item")) == 0:
E(eu, "Lead", t); continue
if numpr(p) is None:
it = eu.findall(f"{{{NS}}}Item")[-1]; it.find(f"{{{NS}}}Text").text += " " + t; continue
n = len(eu.findall(f"{{{NS}}}Item")) + 1
it = E(eu, "Item", id=uid(f"{doc_id}.eu.{n}"))
cx = celex(t)
if cx: it.set("celex", cx)
E(it, "Text", t); continue
if mode == "tp":
np_ = numpr(p)
if np_ and np_[1] == 0:
n = str(nb.next(*np_))
last = point = E(tp, "Point", id=uid(f"{doc_id}.tp.{idpart('p', n)}"), n=n,
kind="deleted" if DELETED.match(t) else "transitional",
status="deleted" if DELETED.match(t) else None)
E(point, "Text", t); continue
m = INLINE_POINT.match(t)
if (np_ or m) and point is not None:
if np_ and last is not None and last.getparent() is not tp and last is not point:
parent = last if INLINE_POINT.match(lines[k - 1] or "") else last.getparent()
else:
parent = point
n = m.group(1) if m and not np_ else str(nb.next(*np_))
body_t = m.group(2) if m and not np_ else t
sp = E(parent, "Point", id=uid(f"{parent.get('id')}.{idpart('p', n)}"), n=n)
E(sp, "Text", body_t); last = sp; continue
if NOTE.match(t): last = point if point is not None else last; add_note(t); continue
if point is not None and point.find(f"{{{NS}}}Text") is not None:
point.find(f"{{{NS}}}Text").text += "\n" + t
issues.append(f"Pārejas noteikumu {point.get('n')}. punkts: nenumurēta rindkopa pievienota punktam"); continue
issues.append(f"Pārejas noteikumos neklasificēta rinda: {t[:70]}"); continue
# --- pants ---
m = ART.match(t)
if m and (st.startswith("Heading 2") or numpr(p) is None) and len(m.group(2)) < 200:
new_art(m.group(1) + m.group(2), m.group(3) or None); last = art; continue
if art is None and chap is not None and (NOTE.match(t) or DELETED.match(t)):
nt = etree.Element(f"{{{NS}}}Note"); nt.text = t
chap.insert(1, nt); open_note = nt; continue
if art is None:
issues.append(f"Teksts ārpus panta: {t[:70]}"); continue
if NOTE.match(t):
last = art; add_note(t); continue
np_ = numpr(p)
mi = INLINE_PART.match(t) if np_ is None else None
if (np_ and np_[1] == 0) or mi:
if mi:
n, txt = mi.group(1), mi.group(2)
else:
v = nb.next(*np_); n = str(v); txt = t
if parts_seen and not getattr(nb, "explicit", False): # Word numerācija "41" pēc "4" = 4¹
prev = parts_seen[-1]
pb = sup_suffix(prev)[0]
if v != int(pb) + 1 and str(v).startswith(pb) and len(str(v)) > len(pb):
n = pb + str(v)[len(pb):].translate(SUP)
issues.append(f"{art.get('n')}. pants: numurs {v} interpretēts kā {n}")
if art.find(f"{{{NS}}}Text") is not None:
issues.append(f"{art.get('n')}. pantā teksts un daļas sajaukti")
dele = bool(DELETED.match(txt))
part = E(art, "Part", id=uid(f"{art.get('id')}.{idpart('d', n)}"), n=n,
kind="deleted" if dele else None, status="deleted" if dele else None)
E(part, "Text", txt); parts_seen.append(n); point = None; last = part; continue
if np_ and np_[1] >= 1:
parent = part if part is not None else art
if parent is art and art.find(f"{{{NS}}}Text") is None:
issues.append(f"{art.get('n')}. pants: punkti bez ievadteksta")
n = str(nb.next(*np_))
if np_[1] >= 2 and point is not None: parent = point
dele = bool(DELETED.match(t))
pt = E(parent, "Point", id=uid(f"{parent.get('id')}.{idpart('p', n)}"), n=n,
kind="deleted" if dele else None, status="deleted" if dele else None)
E(pt, "Text", t)
if np_[1] == 1: point = pt
last = pt; continue
# nenumurēta rindkopa pantā
if art.find(f"{{{NS}}}Part") is None and art.find(f"{{{NS}}}Text") is None:
dele = bool(DELETED.match(t))
E(art, "Text", t)
if dele: art.set("status", "deleted")
last = art; continue
tgt = last if last is not None else art
tx = tgt.find(f"{{{NS}}}Text")
if tx is not None:
tx.text += "\n" + t; issues.append(f"{art.get('n')}. pants: nenumurēta rindkopa pievienota iepriekšējai vienībai")
else:
issues.append(f"Neklasificēta rinda: {t[:70]}")
# piezīmes jābūt pēc teksta/punktiem: sakārtot
for el in root.iter(f"{{{NS}}}Article", f"{{{NS}}}Part", f"{{{NS}}}Point"):
notes = el.findall(f"{{{NS}}}Note")
for nt in notes: el.remove(nt); el.append(nt)
if "inforce" in tail: E(root, "InForceStatement", tail["inforce"])
if "adopt" in tail: E(root, "Adoption", tail["adopt"])
if "prom" in tail: E(root, "Promulgation", tail["prom"])
if "sig" in tail:
role, name = re.match(r"^(Valsts prezident\S*|Saeimas priekšsēdētāj\S*)\s+(.*)$", tail["sig"]).groups()
if "sig_for" in tail: role += f" ({tail['sig_for']})"
E(root, "Signatory", name, role=role)
for t in tail.get("ap_sigs", []):
role, name = AP_SIG.match(t).groups()
E(root, "Signatory", name, role=role)
# pants, kas pieņemts, bet vēl nav spēkā: likumi.lv rāda tikai nosaukumu un piezīmi
for a in root.iter(f"{{{NS}}}Article"):
if a.find(f"{{{NS}}}Text") is None and a.find(f"{{{NS}}}Part") is None:
nts = " ".join(x.text or "" for x in a.findall(f"{{{NS}}}Note"))
if re.search(r"stājas spēkā \d{2}\.\d{2}\.\d{4}\.? un iekļauts likuma redakcijā", nts):
a.set("status", "pending"); issues.append(f"{a.get('n')}. pants vēl nav spēkā — bez teksta (status pending)")
else:
issues.append(f"{a.get('n')}. pantam nav teksta")
# likumi.lv identifikators (HTML avots)
if lid:
act_el = root.find(f"{{{NS}}}Act")
if act_el.find(f"{{{NS}}}LikumiId") is None:
E(act_el, "LikumiId", lid); E(act_el, "Url", f"https://likumi.lv/ta/id/{lid}")
# pielikumi (tikai HTML avots)
ann_chars = 0
for idx, a in enumerate(getattr(nb, "annexes", []), 1):
mn = re.search(r"(\d+)\.?\s*([⁰¹²³⁴⁵⁶⁷⁸⁹]*)\.?\s*pielikums", a["heading"])
an = (mn.group(1) + mn.group(2)) if mn else str(idx)
ann = E(root, "Annex", id=uid(f"{doc_id}.{idpart('ann', an)}"), n=an)
E(ann, "Heading", a["heading"])
if a["title"]: E(ann, "Title", a["title"])
for ln in a["lines"]: E(ann, "Line", ln)
ann_chars += sum(len(re.sub(r"\s", "", x)) for x in [a["heading"], a["title"] or "", *a["lines"]])
# viena ES akta atsauce bez saraksta: ievadteikums ir vienīgais punkts
for eu in root.findall(f"{{{NS}}}EuReference"):
lead = eu.find(f"{{{NS}}}Lead")
if lead is not None and not eu.findall(f"{{{NS}}}Item"):
eu.remove(lead)
it = E(eu, "Item", id=uid(f"{doc_id}.eu.1"))
cx = celex(lead.text)
if cx: it.set("celex", cx)
E(it, "Text", lead.text)
# elementu secība saskaņā ar XSD
order = ["Act", "TextVersion", "Source", "EnactingFormula", "Body", "TransitionalProvisions", "EuReference",
"InForceStatement", "Adoption", "Promulgation", "Signatory", "Annex", "DataVersion"]
kids = list(root)
for c in kids: root.remove(c)
for name in order:
for c in kids:
if etree.QName(c).localname == name: root.append(c)
dv = E(root, "DataVersion", number=1, date=datetime.date.today().isoformat())
E(dv, "Change", f"Pirmā versija: konvertēts no {src_kind} ar tools/convert.py")
etree.ElementTree(root).write(out, encoding="UTF-8", xml_declaration=True, pretty_print=True)
# pilnīguma pārbaude: avota teksta zīmes (bez galvenes) pret XML tekstu
def body_chars(x): # numuri, kas kļūst par atribūtiem (Article@n, Chapter@n), netiek skaitīti
x = re.sub(r"^\d+\.?[⁰¹²³⁴⁵⁶⁷⁸⁹]*[A-Z]?\.?\s*pants(?:\.|(?=\s|$))", "", x)
x = re.sub(r"^[IVXL]+([⁰¹²³⁴⁵⁶⁷⁸⁹]*|\s[A-C])?\s+nodaļa\.?", "", x)
return len(re.sub(r"\s", "", x))
src_chars = sum(body_chars(x) for x in lines[i - 1:end] if x) + ann_chars
xml_chars = sum(len(re.sub(r"\s", "", x)) for x in root.itertext()
if x) - sum(len(re.sub(r"\s", "", e.text or "")) for e in root.iter(f"{{{NS}}}Change", f"{{{NS}}}File", f"{{{NS}}}SHA256", f"{{{NS}}}Retrieved", f"{{{NS}}}Issuer", f"{{{NS}}}Type", f"{{{NS}}}Adopted", f"{{{NS}}}Published", f"{{{NS}}}InForce", f"{{{NS}}}Amendment", f"{{{NS}}}Signatory", f"{{{NS}}}EnactingFormula",
f"{{{NS}}}Adoption", f"{{{NS}}}InForceStatement", f"{{{NS}}}Promulgation",
f"{{{NS}}}LikumiId", f"{{{NS}}}Url")) # noslēgums nav avota skaitā
counts = {k: len(list(root.iter(f"{{{NS}}}{k}"))) for k in ["Chapter", "Article", "Part", "Point", "Note", "Amendment"]}
rep = {"id": doc_id, "counts": counts, "sourceChars": src_chars, "xmlChars": xml_chars, "issues": issues}
json.dump(rep, open(out.replace(".xml", ".verify.json"), "w"), ensure_ascii=False, indent=1)
return rep
if __name__ == "__main__":
print(json.dumps(convert(sys.argv[1], sys.argv[2]), ensure_ascii=False, indent=1))