Rtf2txt

#!/usr/bin/env python3
import sys
import re
import codecs
SKIP_DESTINATIONS = {
    "fonttbl", "colortbl", "stylesheet", "info", "pict",
    "object", "objdata", "header", "headerl", "headerr",
    "footer", "footerl", "footerr", "filetbl", "listtable",
    "listoverridetable", "generator", "xmlnstbl", "datastore",
    "themedata", "colorschememapping", "latentstyles",
    "rsidtbl", "revtbl", "annotation", "atnauthor",
    "atnid", "atnref", "atntime", "aftncn", "aftnsep",
    "aftnsepc", "ftncn", "ftnsep", "ftnsepc",
    "object", "nonshppict", "shppict", "do", "formfield",
}
SPECIAL_CHARS = {
    "\\": "\\",
    "{": "{",
    "}": "}",
    "~": "\u00a0",      
    "_": "\u2011",       
    "-": "",            
}

SPECIAL_WORDS = {
    "emdash": "\u2014",
    "endash": "\u2013",
    "emspace": "\u2003",
    "enspace": "\u2002",
    "qmspace": "\u2005",
    "bullet": "\u2022",
    "lquote": "\u2018",
    "rquote": "\u2019",
    "ldblquote": "\u201c",
    "rdblquote": "\u201d",
    "zwj": "\u200d",
    "zwnj": "\u200c",
}


def convert(data):
    text = data.decode("latin-1")
    state = {
        "skip": False,
        "uc": 1,
        "codepage": "cp1252",
        "ignorable": False,
    }
    stack = []

    output = []
    i = 0
    n = len(text)

    def emit(s):
        if not state["skip"]:
            output.append(s)

    def decode_byte(value):
        try:
            return bytes([value]).decode(
                state["codepage"], errors="replace"
            )
        except LookupError:
            return bytes([value]).decode("cp1252", errors="replace")

    def decode_raw(s):
        """Decode raw RTF text bytes using the current code page."""
        if state["skip"]:
            return

        raw = bytes(ord(c) for c in s)
        try:
            output.append(
                raw.decode(state["codepage"], errors="replace")
            )
        except LookupError:
            output.append(raw.decode("cp1252", errors="replace"))

    while i < n:
        c = text[i]

        if c == "{":
            stack.append(state.copy())
            state = state.copy()
            state["ignorable"] = False
            i += 1
            continue

        if c == "}":
            if stack:
                state = stack.pop()
            i += 1
            continue

        if c != "\\":
            if c == "\r" or c == "\n":
                i += 1
                continue

            if c == "\t":
                emit("\t")
                i += 1
                continue

            decode_raw(c)
            i += 1
            continue

        i += 1

        if i >= n:
            break

        c = text[i]

        if c == "'":
            if i + 2 < n:
                try:
                    value = int(text[i + 1:i + 3], 16)
                    emit(decode_byte(value))
                    i += 3
                    continue
                except ValueError:
                    pass

            i += 1
            continue

        if not c.isalpha():
            i += 1

            if c == "*":
                state["ignorable"] = True
                continue

            if c in SPECIAL_CHARS:
                emit(SPECIAL_CHARS[c])
                continue

            if c == "~":
                emit("\u00a0")
                continue

            if c == "\n" or c == "\r":
                continue

            if c == ":":
                continue

            if c == "|":
                emit("|")
                continue

            if c == "\\":
                emit("\\")
                continue

            continue

        start = i
        while i < n and text[i].isalpha():
            i += 1

        word = text[start:i]

        sign = 1
        if i < n and text[i] == "-":
            sign = -1
            i += 1

        num_start = i
        while i < n and text[i].isdigit():
            i += 1

        arg = None
        if i > num_start:
            arg = sign * int(text[num_start:i])

        if i < n and text[i] == " ":
            i += 1

        if state["ignorable"]:
            state["ignorable"] = False
            state["skip"] = True
            continue

        if word in SKIP_DESTINATIONS:
            state["skip"] = True
            continue

        if word == "ansicpg" and arg is not None:
            state["codepage"] = f"cp{arg}"
            continue

        if word == "uc" and arg is not None:
            state["uc"] = max(0, arg)
            continue

        if word == "u" and arg is not None:
            value = arg if arg >= 0 else arg + 65536
            emit(chr(value))

            fallback = state["uc"]
            while fallback > 0 and i < n:
                if text[i] == "\\":
                    if i + 1 < n and text[i + 1] == "'":
                        i = min(i + 4, n)
                    elif i + 1 < n and not text[i + 1].isalpha():
                        i += 2
                    else:
                        break
                elif text[i] in "{}":
                    break
                else:
                    i += 1

                fallback -= 1

            continue

        if word in ("par", "row"):
            emit("\n")
            continue

        if word == "line":
            emit("\n")
            continue

        if word == "tab":
            emit("\t")
            continue

        if word == "page":
            emit("\n")
            continue

        if word == "sect":
            emit("\n")
            continue

        if word in SPECIAL_WORDS:
            emit(SPECIAL_WORDS[word])
            continue

        if word == "emdash":
            emit("\u2014")
            continue

        if word == "enspace":
            emit("\u2002")
            continue

        if word == "ansi":
            state["codepage"] = "cp1252"
            continue

        if word == "mac":
            state["codepage"] = "mac_roman"
            continue

        if word == "pc":
            state["codepage"] = "cp437"
            continue

        if word == "pca":
            state["codepage"] = "cp850"
            continue

    result = "".join(output)
    result = result.replace("\r\n", "\n").replace("\r", "\n")

    result = re.sub(r"[ \t]+\n", "\n", result)

    result = re.sub(r"\n{4,}", "\n\n\n", result)

    return result


def main():
    data = sys.stdin.buffer.read()
    result = convert(data)
    sys.stdout.write(result)


if __name__ == "__main__":
    main()
#!/bin/bash
find . -type f -iname '*.rtf' -print0 |
while IFS= read -r -d  file; do
    rtf2txt < "$file" > "${file%.*}.txt"
done
Unknown's avatar

About Linuxgal

Need a spiritual home? Consider joining us at Mary Queen of the Universe Latter-day Buddhislamic Free Will Christian UFO Synagogue of Vishnu
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply