<reponame>harisont/conllu-plus-converter
from conllu import *
import argparse

std_fields = ["ID", 
              "FORM", 
              "LEMMA", 
              "UPOS", 
              "XPOS", 
              "FEATS", 
              "HEAD", 
              "DEPREL", 
              "DEPS", 
              "MISC"]

def from_plus(path):
    """
    Given the path to a CoNLL-U Plus file, return the corresponding plain 
    CoNLL-U string.
    """
    with open(path) as f:
        plus_fields = [field.upper() for field in parse_conllu_plus_fields(f)]
        plus_content = f.read()
        plus_sentences = parse(plus_content, plus_fields)

    def from_plus_sentence(plus_sent):
        sent = TokenList()
        sent.metadata = plus_sent.metadata

        # workaround for globals being treated as sentence metadata by conllu
        plus_sent.metadata.pop("global.columns", None)

        def from_plus_token(plus_tok):
            tok = {}
            for field in std_fields:
                if field in plus_fields:
                    tok[field] = plus_tok[field]
                else:
                    tok[field] = "_"
            for field in set(plus_fields) - set(std_fields):
                plus_key = field
                plus_val = plus_tok[field] if field in plus_tok else "_"
                if "MISC" in tok:
                    tok["MISC"] += ("|{}={}".format(plus_key, plus_val))
                else:
                    tok["MISC"] = ("{}={}".format(plus_key, plus_val))
            return tok
        
        for plus_tok in plus_sent:
            sent.append(from_plus_token(plus_tok))
        return sent

    sentences = list(map(from_plus_sentence, plus_sentences))
    return "".join([sent.serialize() for sent in sentences])
        

def to_plus(path, fields):
    with open(path) as f:
        sentences = parse(f.read())
        fields = [field.upper() for field in fields] # fields of conllu+ file
        plus_fields = set(fields) - set(std_fields) # nonstandard fields

        def to_plus_sentence(sent):
            plus_sent = TokenList()
            plus_sent.metadata = sent.metadata

            def to_plus_token(tok):
                plus_tok = {}
                for field in fields:
                    if field == "MISC":
                        # dunno if this actually works, need to test
                        plus_tok[field] = dict(filter(
                            lambda p: p[0] not in plus_fields, 
                            tok["misc"].items()
                            ))
                    elif field in std_fields:
                        # using the conllu library, field names are lowercase
                        # but key names in Key=val pairs are uppercase (damn!)
                        plus_tok[field] = tok[field.lower()]
                    else:
                        # since Key=val pairs are parsed as dictionaries (!)
                        if field in tok["misc"]:
                            plus_tok[field] = tok["misc"][field]  
                        else:
                            plus_tok[field] = "_"
                return plus_tok

            for tok in sent:
                plus_sent.append(to_plus_token(tok))
            return plus_sent

        plus_sentences = list(map(to_plus_sentence, sentences))
        comment = "# global.columns = {}\n".format(
            " ".join([field.upper() for field in fields])
            )
        return "\n".join(
            [comment] + [sent.serialize() for sent in plus_sentences]
            )
    

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Convert between CoNLL-U Plus and plain CoNLL-u files."
        )
    parser.add_argument(
        'mode', 
        metavar='MODE', 
        type=str, 
        help='''either "from_plus" (CoNLL-U Plus -> plain CoNLL-U)
                or "to_plus=FIELDS" (plain CoNNL-U -> CoNLL-U Plus),
                where FIELDS is a comma-separated list of the desired fields 
                for the CoNLL-U Plus (no spaces!).'''
        )
    parser.add_argument(
        'path', 
        metavar='PATH', 
        type=str, 
        help='path to the file to be converted')
    args = parser.parse_args()

    if args.mode == "from_plus":
        print(from_plus(args.path))
    elif args.mode.startswith("to_plus"):
        fields = args.mode.split("=",1)[1].split(",")
        print(to_plus(args.path, fields))

from conllu import *
import argparse

std_fields = ["ID", 
              "FORM", 
              "LEMMA", 
              "UPOS", 
              "XPOS", 
              "FEATS", 
              "HEAD", 
              "DEPREL", 
              "DEPS", 
              "MISC"]

def from_plus(path):
    """
    Given the path to a CoNLL-U Plus file, return the corresponding plain 
    CoNLL-U string.
    """
    with open(path) as f:
        plus_fields = [field.upper() for field in parse_conllu_plus_fields(f)]
        plus_content = f.read()
        plus_sentences = parse(plus_content, plus_fields)

    def from_plus_sentence(plus_sent):
        sent = TokenList()
        sent.metadata = plus_sent.metadata

        # workaround for globals being treated as sentence metadata by conllu
        plus_sent.metadata.pop("global.columns", None)

        def from_plus_token(plus_tok):
            tok = {}
            for field in std_fields:
                if field in plus_fields:
                    tok[field] = plus_tok[field]
                else:
                    tok[field] = "_"
            for field in set(plus_fields) - set(std_fields):
                plus_key = field
                plus_val = plus_tok[field] if field in plus_tok else "_"
                if "MISC" in tok:
                    tok["MISC"] += ("|{}={}".format(plus_key, plus_val))
                else:
                    tok["MISC"] = ("{}={}".format(plus_key, plus_val))
            return tok
        
        for plus_tok in plus_sent:
            sent.append(from_plus_token(plus_tok))
        return sent

    sentences = list(map(from_plus_sentence, plus_sentences))
    return "".join([sent.serialize() for sent in sentences])
        

def to_plus(path, fields):
    with open(path) as f:
        sentences = parse(f.read())
        fields = [field.upper() for field in fields] # fields of conllu+ file
        plus_fields = set(fields) - set(std_fields) # nonstandard fields

        def to_plus_sentence(sent):
            plus_sent = TokenList()
            plus_sent.metadata = sent.metadata

            def to_plus_token(tok):
                plus_tok = {}
                for field in fields:
                    if field == "MISC":
                        # dunno if this actually works, need to test
                        plus_tok[field] = dict(filter(
                            lambda p: p[0] not in plus_fields, 
                            tok["misc"].items()
                            ))
                    elif field in std_fields:
                        # using the conllu library, field names are lowercase
                        # but key names in Key=val pairs are uppercase (damn!)
                        plus_tok[field] = tok[field.lower()]
                    else:
                        # since Key=val pairs are parsed as dictionaries (!)
                        if field in tok["misc"]:
                            plus_tok[field] = tok["misc"][field]  
                        else:
                            plus_tok[field] = "_"
                return plus_tok

            for tok in sent:
                plus_sent.append(to_plus_token(tok))
            return plus_sent

        plus_sentences = list(map(to_plus_sentence, sentences))
        comment = "# global.columns = {}\n".format(
            " ".join([field.upper() for field in fields])
            )
        return "\n".join(
            [comment] + [sent.serialize() for sent in plus_sentences]
            )
    

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Convert between CoNLL-U Plus and plain CoNLL-u files."
        )
    parser.add_argument(
        'mode', 
        metavar='MODE', 
        type=str, 
        help='''either "from_plus" (CoNLL-U Plus -> plain CoNLL-U)
                or "to_plus=FIELDS" (plain CoNNL-U -> CoNLL-U Plus),
                where FIELDS is a comma-separated list of the desired fields 
                for the CoNLL-U Plus (no spaces!).'''
        )
    parser.add_argument(
        'path', 
        metavar='PATH', 
        type=str, 
        help='path to the file to be converted')
    args = parser.parse_args()

    if args.mode == "from_plus":
        print(from_plus(args.path))
    elif args.mode.startswith("to_plus"):
        fields = args.mode.split("=",1)[1].split(",")
        print(to_plus(args.path, fields))
Given the path to a CoNLL-U Plus file, return the corresponding plain      CoNLL-U string. # workaround for globals being treated as sentence metadata by conllu # fields of conllu+ file # nonstandard fields # dunno if this actually works, need to test # using the conllu library, field names are lowercase # but key names in Key=val pairs are uppercase (damn!) # since Key=val pairs are parsed as dictionaries (!) either "from_plus" (CoNLL-U Plus -> plain CoNLL-U)                 or "to_plus=FIELDS" (plain CoNNL-U -> CoNLL-U Plus),                 where FIELDS is a comma-separated list of the desired fields                  for the CoNLL-U Plus (no spaces!).