#!/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
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