|
| 1 | +# -*- coding: UTF-8 -*- |
| 2 | +"""Base128-Latin1 Codec - base128-latin1 content encoding. |
| 3 | +
|
| 4 | +This Base128 encoding, unlike the "base128" codec (relying on the ASCII charset, hence holding control characters), uses |
| 5 | + a set of 128 printable characters (digits, letters and Latin-1 characters). The data is taken by |
| 6 | + chunks of 7 bytes, each chunk being split into 8 groups of 7 bits written with 1 character each ; the bits of a last |
| 7 | + incomplete chunk are split the same way, those of its last group being right-aligned. A last character gives the length |
| 8 | + of the last chunk (0 when it is complete). Whitespace is ignored when decoding. |
| 9 | +
|
| 10 | +This codec: |
| 11 | +- en/decodes strings from str to str |
| 12 | +- en/decodes strings from bytes to bytes |
| 13 | +- decodes file content to str (read) |
| 14 | +- encodes file content from str to bytes (write) |
| 15 | +
|
| 16 | +Reference: https://github.com/rpuntaie/base128 |
| 17 | +""" |
| 18 | +from ._base import main |
| 19 | +from ..__common__ import * |
| 20 | + |
| 21 | + |
| 22 | +__examples__ = { |
| 23 | + 'enc(base128-latin1|base128_latin1|base-128-latin-1)': { |
| 24 | + '': "0", '\x00' * 7: "000000000", '\xff' * 7: "ÿ" * 8 + "0", '\x01': "011", '\xff': "ÿ11", |
| 25 | + 'Hello World!': "aPjÃây·ÕtÚÊÃX15", |
| 26 | + }, |
| 27 | + 'dec(base128-latin1)': { |
| 28 | + 'aPjÃây·ÕtÚÊÃX15': "Hello World!", '011': "\x01", '0': "", '': None, '7': None, '1': None, '01': None, |
| 29 | + '0111': None, '021': None, '01!1': None, '0000000000': None, '0000000001': None, |
| 30 | + }, |
| 31 | + 'enc-dec(base128-latin1)': ["\xff", "\xff\xfe\xfd\xfc\xfb\xfa", "\xff\xfe\xfd\xfc\xfb\xfa\xf9", |
| 32 | + "@random{512,1024}"], |
| 33 | +} |
| 34 | +__guess__ = ["base128-latin1"] |
| 35 | + |
| 36 | + |
| 37 | +B128 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzµ¶" \ |
| 38 | + "·¼½¾ÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÑÒÓÔÕÖרÙÚÛÜÝÞßáâãäåæçèéêëìíîïñòóôõö÷øùúûüýþÿ" |
| 39 | + |
| 40 | + |
| 41 | +def base128_latin1_encode(text, errors="strict"): |
| 42 | + t, r = b(text), [] |
| 43 | + for i in range(0, len(t), 7): |
| 44 | + chunk = t[i:i+7] |
| 45 | + n, v = 8 * len(chunk), int.from_bytes(chunk, "big") |
| 46 | + # groups of 7 bits from the most significant ones, the last group of an incomplete chunk being right-aligned |
| 47 | + for k in range(n - 7, -7, -7): |
| 48 | + r.append(B128[v >> k & 0x7f if k >= 0 else v & (1 << (k + 7)) - 1]) |
| 49 | + r.append(B128[len(t) % 7]) |
| 50 | + return (s := "".join(r)), len(s) |
| 51 | + |
| 52 | + |
| 53 | +def base128_latin1_decode(text, errors="strict"): |
| 54 | + _h, text, r = handle_error("base128-latin1", errors, decode=True), "".join(ensure_str(text).split()), b"" |
| 55 | + # a wrong character cannot be replaced in a chunk, hence it is dropped |
| 56 | + t = "".join(c if c in B128 else _h(c, i) and "" for i, c in enumerate(text)) |
| 57 | + if len(t) == 0 or (m := B128.index(t[-1])) > 6: |
| 58 | + return b(_h(t[-1:], len(t) - 1)), len(text) |
| 59 | + # the last character gives the length of the last chunk of data (m bytes, encoded with ceil(8m/7) characters) |
| 60 | + t = t[:-1] |
| 61 | + if m and len(t) == 0: |
| 62 | + return b(_h(text, 0)), len(text) |
| 63 | + for i in range(0, len(t), 8): |
| 64 | + chunk, n = t[i:i+8], m if m and i + 8 >= len(t) else 7 |
| 65 | + groups = [B128.index(c) for c in chunk] |
| 66 | + # the last group of an incomplete chunk only holds the remaining bits, right-aligned |
| 67 | + rbits = 8 * n - 7 * (len(chunk) - 1) |
| 68 | + if len(chunk) != (8 if n == 7 else -(-8 * m // 7)) or groups[-1] >> rbits: |
| 69 | + r += b(_h(chunk, i, r)) |
| 70 | + continue |
| 71 | + v = 0 |
| 72 | + for g in groups[:-1]: |
| 73 | + v = v << 7 | g |
| 74 | + r += (v << rbits | groups[-1]).to_bytes(n, "big") |
| 75 | + return r, len(r) |
| 76 | + |
| 77 | + |
| 78 | +add("base128-latin1", base128_latin1_encode, base128_latin1_decode, r"^base[-_]?128[-_]latin[-_]?1$", |
| 79 | + expansion_factor=8 / 7) |
| 80 | +main128latin1 = main(128, "<https://github.com/rpuntaie/base128>", "latin1", inv=False, swap=False) |
0 commit comments