# -*- coding: utf-8 -*-
r"""
tia_extract_strings.py —— 从博图工程二进制数据文件里提取可读文本
用途：TIA 工程（V13+）的程序数据保存在 <项目>\System\PEData.plf 这类二进制容器里，
     直接读是乱码，但其中的块名、变量名、注释等文本串仍是明文存储。
     本脚本扫描 UTF-16LE / UTF-16BE / UTF-8 文本串并统计，用于快速了解工程内容。
     - 中文注释多为 UTF-8 编码，块/指令名多为 ASCII
     - UTF-16LE 段读出的"中文"常是 UTF-16BE 文本的字节镜像，属干扰项
用法：
    python tia_extract_strings.py <PEData.plf 路径>
    python tia_extract_strings.py <文件> --filter 关键字   # 只看含关键字的串
"""
import sys
import re
import collections

ASCII = lambda c: 0x20 <= c <= 0x7E
CJK = lambda c: 0x4E00 <= c <= 0x9FFF
CJK_PUNCT = lambda c: 0x3000 <= c <= 0x303F
FULLWIDTH = lambda c: 0xFF00 <= c <= 0xFFEF


def utf16_runs(data, minlen=3, endian='le'):
    """扫描 UTF-16 文本串，尝试两种字节对齐
    endian: 'le' 小端(\x41\x00) / 'be' 大端(\x00\x41)"""
    out = []
    for offset in (0, 1):
        i = offset
        cur = []
        while i + 1 < len(data):
            if endian == 'le':
                cp = data[i] | (data[i + 1] << 8)
            else:
                cp = (data[i] << 8) | data[i + 1]
            if ASCII(cp) or CJK(cp) or CJK_PUNCT(cp) or FULLWIDTH(cp) or cp == 0x000D:
                cur.append(chr(cp))
                i += 2
            else:
                if len(cur) >= minlen:
                    out.append(''.join(cur))
                cur = []
                i += 2 if len(cur) == 0 else 1
        if len(cur) >= minlen:
            out.append(''.join(cur))
    return out


def utf8_runs(data, minlen=4):
    """扫描 UTF-8 文本串"""
    pat = rb'[\x20-\x7e\xc2-\xf4][\x20-\x7e\x80-\xbf]{' + str(minlen - 1).encode() + rb',}'
    res = []
    for b in re.findall(pat, data):
        try:
            res.append(b.decode('utf-8'))
        except UnicodeDecodeError:
            pass
    return res


def clean(s):
    s = s.strip('\x00\r\n \t')
    return s


def main():
    path = sys.argv[1]
    kw = None
    if '--filter' in sys.argv:
        kw = sys.argv[sys.argv.index('--filter') + 1]

    data = open(path, 'rb').read()
    print(f'文件: {path}   大小: {len(data):,} 字节\n')

    u16le = [clean(s) for s in utf16_runs(data, endian='le')]
    u16be = [clean(s) for s in utf16_runs(data, endian='be')]
    u8 = [clean(s) for s in utf8_runs(data)]

    for name, items in (('UTF-16LE', u16le), ('UTF-16BE', u16be), ('UTF-8', u8)):
        items = [s for s in items if len(s) >= 2]
        if kw:
            items = [s for s in items if kw in s]
        counter = collections.Counter(items)
        print(f'===== {name}：{len(items)} 段，去重 {len(counter)} =====')
        for s, n in counter.most_common(120):
            print(f'{n:>4}x  {s[:120]}')
        print()


if __name__ == '__main__':
    main()
