view lxmldump.py @ 20:f274504eafd0

Use Python argparse module instead of custom self-rolled argument parser.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 11 May 2021 17:13:38 +0300
parents 7c6eb57798bd
children 7ef08e05a5bf
line wrap: on
line source

#!/usr/bin/python3 -B
# coding=utf-8
###
### lxmldump - Dump ISO/FDIS 1951 XML file data
### Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
### (C) Copyright 2021 Tecnic Software productions (TNSP)
###
### Python 3.7+ required!
###
import sys
import signal
import re
from pathlib import Path
import xml.etree.ElementTree as xmlET
import unicodedata
import argparse

assert sys.version_info >= (3, 7)


###
### Default settings
###
pkk_str_fmap = {
    "Fragment" : ["<", ">"],
}


pkk_debug_list = [
    "ahas",
    "ahavakkaine",
    "ahavakala",
    "ahavakoittuo",
    "ahvaliha",
    "aloilleh",
    "hanjahtoakseh",
    "akkalisto",
]


###
### Misc. helper functions, etc
###
def pkk_cleanup():
    return 0


## Print string to stdout using normalized Unicode if enabled
def pkk_print(smsg):
    try:
        if pkk_cfg.normalize:
            sys.stdout.write(unicodedata.normalize("NFC", smsg))
        else:
            sys.stdout.write(smsg)

    except (BrokenPipeError, IOError) as e:
        sys.stderr.close()


## Print string with indentation
def pkk_printi(indent, smsg):
    pkk_print(("    " * indent) + smsg)


## Check value against current verbosity level
def pkk_verbosity(lvl):
    return pkk_cfg.verbosity >= lvl


## Fatal error handler
def pkk_fatal(smsg):
    print(u"ERROR: "+ smsg)
    sys.exit(1)


## Handler for SIGINT signals
def pkk_signal_handler(signal, frame):
    pkk_cleanup()
    print(u"\nQuitting due to SIGINT / Ctrl+C!")
    sys.exit(1)


## Clean string by removing tabs and newlines
def pkk_str_clean(mstr):
    return re.sub(r'[\n\r\t]', '', mstr)


## Format "Ptr" node as text
def pkk_ptr_to_text(pnode):
    return "<PTR:{}>{}</PTR>".format(
        pnode.attrib["{http://www.w3.org/TR/xlink}href"],
        ("".join(pnode.itertext())).strip())


## Get text inside a given node
def pkk_node_to_text(lnode):
    stmp = ""
    for pnode in lnode.iter():
        if pnode.tag == "Ptr":
            stmp += pkk_ptr_to_text(pnode)
        else:
            if isinstance(pnode.text, str):
                ptext = pkk_str_clean(pnode.text).strip()
                if pkk_cfg.annotate and isinstance(pnode.tag, str) and pnode.tag in pkk_str_fmap:
                    stmp += pkk_str_fmap[pnode.tag][0] + ptext + pkk_str_fmap[pnode.tag][1]
                else:
                    stmp += ptext

            if isinstance(pnode.tail, str):
                stmp += pkk_str_clean(pnode.tail)

    return stmp.strip()


## Simple recursive dump starting at given node
def pkk_dump_recursive(indent, lnode):
    if lnode.tag in ["Example"]:
        stmp = pkk_node_to_text(lnode)
        pkk_printi(indent, "{} \"{}\"\n".format(lnode.tag, stmp))
    else:
        if isinstance(lnode.text, str):
            stmp = pkk_str_clean(lnode.text).strip()
            if stmp != "":
                stmp = " \""+ stmp +"\""
        else:
            stmp = ""

        if len(lnode.attrib) > 0:
            atmp = " "+ str(lnode.attrib)
        else:
            atmp = ""

        pkk_printi(indent, "{}{}{}\n".format(lnode.tag, atmp, stmp))
        for qnode in lnode.findall("./*"):
            pkk_dump_recursive(indent + 1, qnode)


## Output item(s) under given node with given format string
def pkk_output_subs_fmt(indent, dnode, dsub, dname, dfmt):
    for qnode in dnode.findall(dsub):
        pkk_printi(indent, dfmt.format(dname, pkk_node_to_text(qnode)))

## Output item(s) under given node with a prefixed name string
def pkk_output_subs_prefix(indent, dnode, dsub, dname):
    pkk_output_subs_fmt(indent, dnode, dsub, dname, "{0} \"{1}\"\n")


## Output a main "Headword" or "Sense" node under it
def pkk_output_sense(indent, dnode):
    # Search form and definition
    pkk_output_subs_prefix(indent, dnode, "./SearchForm", "srch")
    pkk_output_subs_prefix(indent, dnode, "./Definition", "defn")

    # Examples
    for wnode in dnode.findall("./ExampleBlock/ExampleCtn"):
        sstr = pkk_node_to_text(wnode.find("./Example"))
        lstr = ""

        if pkk_verbosity(1):
            ltmp = []
            for qnode in wnode.findall("./FreeTopic[@type='levikki']/GeographicalUsage"):
                ltmp.append("{} [{}]".format(pkk_node_to_text(qnode), qnode.attrib["class"]))

            if len(ltmp) > 0:
                lstr = " ({})".format(", ".join(ltmp))

        pkk_printi(indent + 1, "{} \"{}\"{}\n".format("exmp", sstr, lstr))


## Output one "DictionaryEntry" node
def pkk_output_node(indent, dnode):

    for wnode in dnode.findall("./HeadwordCtn"):
        # Create list with grammatical attributes (noun, verb, etc.)
        tmpl = []
        for pnode in wnode.findall("./PartOfSpeechCtn/PartOfSpeech"):
            tmpl.append(pnode.attrib["freeValue"])

        for pnode in wnode.findall("./GrammaticalNote"):
            tmpl.append(pkk_node_to_text(pnode))

        # Remove duplicates and sort the list
        tmpl = list(set(tmpl))
        tmpl.sort(reverse=False, key=lambda attr: (attr, len(attr)))

        # Print the headword and attributes if any
        pkk_output_subs_fmt(indent, wnode, "./Headword", "", "\"{1}\"")
        if len(tmpl) > 0:
            pkk_print(" ({})\n".format(" ; ".join(tmpl)))
        else:
            pkk_print("\n")

        # Print main "sense"
        pkk_output_sense(indent + 1, wnode)

        # Print any other "senses"
        index = 1
        for wnode in dnode.findall("./SenseGrp"):
            pkk_printi(indent + 1, "sense #{}\n".format(index))
            pkk_output_sense(indent + 2, wnode)
            index += 1


###
### Main program starts
###
signal.signal(signal.SIGINT, pkk_signal_handler)

optparser = argparse.ArgumentParser(
    description="lxmldump - Dump ISO/FDIS 1951 XML file data",
    usage="%(prog)s [options] <input xml file(s)>",
    epilog="\n\n"
    )

optparser.add_argument("filenames", action="extend", nargs="*",
    type=str, metavar="filename", help="XML filename(s)")

optparser.add_argument("-d", "--dump",
    action="store_const", const=1, default=0,
    dest="mode", help="output as simple dump")

optparser.add_argument("-x", "--xml",
    action="store_const", const=2,
    dest="mode", help="output as XML")

optparser.add_argument("-n", "--normalize",
    action="store_const", const=True, default=False,
    dest="normalize", help="output NFC normalized Unicode")

optparser.add_argument("-a", "--annotate",
    action="store_const", const=True, default=False,
    dest="annotate", help="annotate strings")

optparser.add_argument("-v", "--verbosity",
    type=int, choices=range(0,4), default=3,
    metavar="n",
    dest="verbosity", help='set verbosity level (0-3, default: %(default)s)')

optparser.add_argument("-p", "--debug",
    action="store_const", const=True, default=False,
    dest="debug", help=argparse.SUPPRESS)


### Show help if needed
pkk_cfg = optparser.parse_args()
if len(pkk_cfg.filenames) == 0:
    optparser.print_help()
    sys.exit(0)


### Handle each input file
for filename in pkk_cfg.filenames:
    # Parse XML file into element tree
    try:
        uxml = xmlET.parse(filename)
    except Exception as e:
        pkk_fatal(u"SVG/XML parsing failed: {0}".format(str(e)))

    # Dump output
    try:
        xroot = uxml.getroot()
        for dnode in xroot.findall("./DictionaryEntry"):

            if pkk_cfg.debug and dnode.attrib["identifier"] not in pkk_debug_list:
                continue

            if pkk_cfg.mode == 0:
                try:
                    pkk_output_node(0, dnode)
                except Exception as e:
                    pkk_dump_recursive(0, dnode)
                    print(str(e))
                    sys.exit(0)
            elif pkk_cfg.mode == 1:
                pkk_dump_recursive(0, dnode)
            elif pkk_cfg.mode == 2:
                pkk_print(str(xmlET.tostring(dnode, encoding="utf8")) + "\n")
            else:
                pkk_fatal("Invalid operation mode?")

            print("\n")

    except (BrokenPipeError, IOError) as e:
        sys.stderr.close()
        sys.exit(1)

pkk_cleanup()
sys.exit(0)