#!/usr/bin/python3
# vim:se tw=0 sts=4 ts=4 et ai:
"""
Copyright © 2024 Osamu Aoki

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
"""
import sys
import argparse
import collections
import xml.etree.ElementTree as ET

# You must install python3-polib package
import polib


#######################################################################
# PO data XML analyzer class
#######################################################################
class XMLPOEntry:
    def __init__(self, **kwargs):
        self.linenum = kwargs.get("linenum", 0)
        self.tcomment = kwargs.get("tcomment", "")
        self.msgid = kwargs.get("msgid", "")
        self.msgstr = kwargs.get("msgstr", "")
        self.skip_fuzzy = kwargs.get("skip_fuzzy", True)
        self.skip_obsolete = kwargs.get("skip_obsolete", True)
        self.mask_entity = kwargs.get("mask_entity", True)
        self.mask_namespace = kwargs.get("mask_namespace", True)
        self.check_skip_tag_match = kwargs.get("check_skip_tag_match", True)
        self.xmsgid = self.msgid
        self.xmsgstr = self.msgstr
        if self.mask_entity:
            self.xmsgid = self.xmsgid.replace("&", "_")
            self.xmsgstr = self.xmsgstr.replace("&", "_")
        if self.mask_namespace:
            self.xmsgid = self.xmsgid.replace("xl:href=", "xl_href=")
            self.xmsgstr = self.xmsgstr.replace("xl:href=", "xl_href=")
        # msgid xml analyze
        self.xml_msgid_err = None
        xml_msgid = ET.fromstring("<msg></msg>")
        try:
            xml_msgid = ET.fromstring("<msg>" + self.xmsgid + "</msg>")
        except ET.ParseError as err:
            # look for error position
            col = max(err.position[1] - len("<msg>"), 0)
            self.xml_msgid_err = polib.escape(self.msgid[:col]) + '"<<< ERROR'
        except Exception as err:
            self.xml_msgid_err = "{} error: {}".format(type(err), err)
        self.xml_msgid_tags = collections.Counter(
            [element.tag for element in xml_msgid.iter()]
        )
        del self.xml_msgid_tags["msg"]
        # msgstr xml analyz
        self.xml_msgstr_err = None
        xml_msgstr = ET.fromstring("<msg></msg>")
        try:
            xml_msgstr = ET.fromstring("<msg>" + self.xmsgstr + "</msg>")
        except ET.ParseError as err:
            # look for error position
            col = max(err.position[1] - len("<msg>"), 0)
            self.xml_msgstr_err = polib.escape(self.msgstr[:col]) + '"<<< ERROR'
        except Exception as err:
            self.xml_msgstr_err = "{} error: {}".format(type(err), err)
        self.xml_msgstr_tags = collections.Counter(
            [element.tag for element in xml_msgstr.iter()]
        )
        del self.xml_msgstr_tags["msg"]
        return

    def is_unmatched_xml(self):
        if self.msgstr == "":
            # ignore not-yet-translated data
            return False
        elif self.xml_msgid_err is not None:
            # ignore non-valid XML in msgid
            return False
        elif self.xml_msgstr_err is not None:
            # ignore non-valid XML in msgstr
            return False
        elif "skip-tag-match" in self.tcomment and self.check_skip_tag_match:
            # ignore data with "skip-tag-match" in its translator comment
            return False
        elif self.xml_msgid_tags == self.xml_msgstr_tags:
            return False
        else:
            return True
        #

    def print_error(self):
        if self.xml_msgid_err is not None:
            print("E: msgid XML error at {}".format(self.linenum))
            print("   {}".format(self.xml_msgid_err))
        if self.xml_msgstr_err is not None:
            print("E: msgstr XML error at {}".format(self.linenum))
            print("   {}".format(self.xml_msgstr_err))
        if self.xml_msgid_err is not None or self.xml_msgstr_err is not None:
            print('   msgid "{}"'.format(polib.escape(self.msgid)))
            print('   msgstr "{}"'.format(polib.escape(self.msgstr)))
            print()

    def print_unmatched_tags(self):
        # unmatchd tags
        if self.is_unmatched_xml() is True:
            print("W: unmatched XML tag at {}".format(self.linenum))
            print("   msgid_tags  = {}".format(self.xml_msgid_tags))
            print("   msgstr_tags = {}".format(self.xml_msgstr_tags))
            print('   msgid "{}"'.format(polib.escape(self.msgid)))
            print('   msgstr "{}"'.format(polib.escape(self.msgstr)))
            print()
        return


#######################################################################
# main: parse command line parser
#######################################################################
def main():
    parser = argparse.ArgumentParser(
        description="""\
analyzer for po-file

copyright 2024 Osamu Aoki <osamu@debian.org>
license: MIT
"""
    )
    parser.add_argument(
        "-f",
        "--include-fuzzy",
        action="store_true",
        default=False,
        help="force to include fuzzy PO entries",
    )
    parser.add_argument(
        "-o",
        "--include-obsolete",
        action="store_true",
        default=False,
        help="force to include obsolete PO entries",
    )
    parser.add_argument(
        "-t",
        "--ignore-skip-tag-match",
        action="store_true",
        default=False,
        help="force to ignore skip-tag-match in translator comment",
    )
    parser.add_argument(
        "-e",
        "--expose-entity",
        action="store_true",
        default=False,
        help="force to disable masking of '&' by '_' (expose entity)",
    )
    parser.add_argument(
        "-n",
        "--expose-namespace",
        action="store_true",
        default=False,
        help="force to disable masking of 'xl:href=' by 'xl_href=' (expose namespace)",
    )
    parser.add_argument("pofile", help="po file to be analyzed")
    #######################################################################
    # generate argument parser instance
    #######################################################################
    args = parser.parse_args()
    #######################################################################
    print("I: Process pofile='{}'".format(args.pofile))
    print()
    try:
        po = polib.pofile(args.pofile)
    except Exception as err:
        print("{} error: {} for PO file='{}'".format(type(err), err, args.pofile))
        sys.exit(1)
    error_count = 0
    warn_count = 0
    for entry in po:
        if entry.fuzzy and not args.include_fuzzy:
            continue
        if entry.obsolete and not args.include_obsolete:
            continue
        xmlentry = XMLPOEntry(
            linenum=entry.linenum,
            tcomment=entry.tcomment,
            fuzzy=entry.fuzzy,
            obsolete=entry.obsolete,
            msgid=entry.msgid,
            msgstr=entry.msgstr,
            mask_entity=not args.expose_entity,
            mask_namespace=not args.expose_namespace,
            check_skip_tag_match=not args.ignore_skip_tag_match,
        )
        xmlentry.print_error()
        xmlentry.print_unmatched_tags()
        if (
            xmlentry.xml_msgid_err is not None
            or xmlentry.xml_msgstr_err is not None
        ):
            error_count += 1
        if xmlentry.is_unmatched_xml() is True:
            warn_count += 1
    print("E: XML markup error counts  = {}".format(error_count))
    print("W: XML unmatched tag counts = {}".format(warn_count))
    sys.exit(error_count + warn_count)


#######################################################################
# Test code
#######################################################################
if __name__ == "__main__":
    main()
# vim:set sw=4 sts=4:
