README.md
· 954 B · Markdown
Brut
# Редактирование файлов справки синтаксис-помощника 1С
Шаги редактирования
1. Поместите файл `shcntx_ru.hbk` в папку с кодом
2. Запустите `p1_*.py` и `p2_*.py` для извлечения html из hbk-файла
3. Используйте `e5_*.py` для форматирования html в более читаемый вид
4. Для вставки примера кода можно использовать `e4_*.py` (код записан в константу)
5. Уменьшите html файл обратно после редактирования `e6_*.py`
6. Запакуйте всё обратно с помощью `p3_*.py` и `p4_*.py` и замените оригинальный файл
[Код был сгенерирован с помощью ИИ](https://github.com/me-shaon/GLWTPL/blob/master/translations/NSFW_LICENSE_ru-RU)
Редактирование файлов справки синтаксис-помощника 1С
Шаги редактирования
- Поместите файл
shcntx_ru.hbkв папку с кодом - Запустите
p1_*.pyиp2_*.pyдля извлечения html из hbk-файла - Используйте
e5_*.pyдля форматирования html в более читаемый вид - Для вставки примера кода можно использовать
e4_*.py(код записан в константу) - Уменьшите html файл обратно после редактирования
e6_*.py - Запакуйте всё обратно с помощью
p3_*.pyиp4_*.pyи замените оригинальный файл
e1_analyze.py
· 1.3 KiB · Python
Brut
#!/usr/bin/env python3
"""
e1_analyze.py — Анализирует извлечённые потоки: сжатие, тип данных
"""
import os
import re
import zlib
STREAMS_DIR = "hbk_streams"
for fname in sorted(os.listdir(STREAMS_DIR)):
if fname.startswith("_") or fname.endswith(".meta"):
continue
path = os.path.join(STREAMS_DIR, fname)
with open(path, "rb") as f:
raw = f.read()
print(f"\n{'=' * 60}")
print(f" {fname} ({len(raw):,} байт)")
print(f" head: {raw[:32].hex(' ')}")
# Пробуем zlib
try:
dec = zlib.decompress(raw)
print(f" → zlib OK: {len(dec):,} байт распаковано")
print(f" head: {dec[:64]}")
out = os.path.join(STREAMS_DIR, fname + ".deflated")
with open(out, "wb") as f:
f.write(dec)
except zlib.error:
print(f" → не zlib")
# Ищем строки
strings = re.findall(rb"[\x20-\x7e]{8,}", raw[:4096])
if strings:
print(f" ASCII строки (первые 5):")
for s in strings[:5]:
print(f" {s.decode('ascii')}")
utf = re.findall(rb"(?:[\x20-\x7e]\x00){4,}", raw[:4096])
if utf:
print(f" UTF-16LE строки:")
for s in utf[:5]:
print(f" {s.decode('utf-16-le')}")
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | e1_analyze.py — Анализирует извлечённые потоки: сжатие, тип данных |
| 4 | """ |
| 5 | |
| 6 | import os |
| 7 | import re |
| 8 | import zlib |
| 9 | |
| 10 | STREAMS_DIR = "hbk_streams" |
| 11 | |
| 12 | for fname in sorted(os.listdir(STREAMS_DIR)): |
| 13 | if fname.startswith("_") or fname.endswith(".meta"): |
| 14 | continue |
| 15 | |
| 16 | path = os.path.join(STREAMS_DIR, fname) |
| 17 | with open(path, "rb") as f: |
| 18 | raw = f.read() |
| 19 | |
| 20 | print(f"\n{'=' * 60}") |
| 21 | print(f" {fname} ({len(raw):,} байт)") |
| 22 | print(f" head: {raw[:32].hex(' ')}") |
| 23 | |
| 24 | # Пробуем zlib |
| 25 | try: |
| 26 | dec = zlib.decompress(raw) |
| 27 | print(f" → zlib OK: {len(dec):,} байт распаковано") |
| 28 | print(f" head: {dec[:64]}") |
| 29 | out = os.path.join(STREAMS_DIR, fname + ".deflated") |
| 30 | with open(out, "wb") as f: |
| 31 | f.write(dec) |
| 32 | except zlib.error: |
| 33 | print(f" → не zlib") |
| 34 | |
| 35 | # Ищем строки |
| 36 | strings = re.findall(rb"[\x20-\x7e]{8,}", raw[:4096]) |
| 37 | if strings: |
| 38 | print(f" ASCII строки (первые 5):") |
| 39 | for s in strings[:5]: |
| 40 | print(f" {s.decode('ascii')}") |
| 41 | |
| 42 | utf = re.findall(rb"(?:[\x20-\x7e]\x00){4,}", raw[:4096]) |
| 43 | if utf: |
| 44 | print(f" UTF-16LE строки:") |
| 45 | for s in utf[:5]: |
| 46 | print(f" {s.decode('utf-16-le')}") |
| 47 |
e2_explore.py
· 1.2 KiB · Python
Brut
#!/usr/bin/env python3
"""
e2_explore.py — Обзор содержимого FileStorage
"""
import os
CONTENT_DIR = os.path.join("hbk_content", "FileStorage")
html_files = []
other_files = []
for root, dirs, files in os.walk(CONTENT_DIR):
for fname in files:
fpath = os.path.join(root, fname)
relpath = os.path.relpath(fpath, CONTENT_DIR)
size = os.path.getsize(fpath)
if fname.endswith(".html"):
html_files.append((relpath, size))
else:
other_files.append((relpath, size))
print(f"HTML-файлов: {len(html_files)}")
print(f"Других файлов: {len(other_files)}")
print(f"\nПримеры HTML:")
for path, size in sorted(html_files)[:20]:
print(f" {path} ({size:,} Б)")
if other_files:
print(f"\nДругие файлы:")
for path, size in sorted(other_files)[:20]:
print(f" {path} ({size:,} Б)")
# Показать содержимое первого HTML
if html_files:
first = os.path.join(CONTENT_DIR, html_files[0][0])
print(f"\n{'=' * 60}")
print(f"Содержимое: {html_files[0][0]}")
print("=" * 60)
with open(first, "r", encoding="utf-8", errors="replace") as f:
print(f.read()[:2000])
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | e2_explore.py — Обзор содержимого FileStorage |
| 4 | """ |
| 5 | |
| 6 | import os |
| 7 | |
| 8 | CONTENT_DIR = os.path.join("hbk_content", "FileStorage") |
| 9 | |
| 10 | html_files = [] |
| 11 | other_files = [] |
| 12 | |
| 13 | for root, dirs, files in os.walk(CONTENT_DIR): |
| 14 | for fname in files: |
| 15 | fpath = os.path.join(root, fname) |
| 16 | relpath = os.path.relpath(fpath, CONTENT_DIR) |
| 17 | size = os.path.getsize(fpath) |
| 18 | |
| 19 | if fname.endswith(".html"): |
| 20 | html_files.append((relpath, size)) |
| 21 | else: |
| 22 | other_files.append((relpath, size)) |
| 23 | |
| 24 | print(f"HTML-файлов: {len(html_files)}") |
| 25 | print(f"Других файлов: {len(other_files)}") |
| 26 | print(f"\nПримеры HTML:") |
| 27 | for path, size in sorted(html_files)[:20]: |
| 28 | print(f" {path} ({size:,} Б)") |
| 29 | |
| 30 | if other_files: |
| 31 | print(f"\nДругие файлы:") |
| 32 | for path, size in sorted(other_files)[:20]: |
| 33 | print(f" {path} ({size:,} Б)") |
| 34 | |
| 35 | # Показать содержимое первого HTML |
| 36 | if html_files: |
| 37 | first = os.path.join(CONTENT_DIR, html_files[0][0]) |
| 38 | print(f"\n{'=' * 60}") |
| 39 | print(f"Содержимое: {html_files[0][0]}") |
| 40 | print("=" * 60) |
| 41 | with open(first, "r", encoding="utf-8", errors="replace") as f: |
| 42 | print(f.read()[:2000]) |
| 43 |
e3_find_example.py
· 1.1 KiB · Python
Brut
#!/usr/bin/env python3
"""
e3_find_example.py — Найти файлы с примерами кода, чтобы узнать разметку
"""
import os
CONTENT_DIR = os.path.join("hbk_content", "FileStorage")
keywords = [
b"V8SH_example",
b"V8SH_code",
b'class="code"',
b"<pre",
b"Example",
"Пример".encode("utf-8"),
]
for root, dirs, files in os.walk(CONTENT_DIR):
for fname in files:
if not fname.endswith(".html"):
continue
fpath = os.path.join(root, fname)
with open(fpath, "rb") as f:
data = f.read()
for kw in keywords:
if kw in data:
rel = os.path.relpath(fpath, CONTENT_DIR)
# Показать контекст вокруг ключевого слова
idx = data.find(kw)
snippet = data[max(0, idx - 100) : idx + 300]
print(f"\n{'=' * 60}")
print(f"Файл: {rel}")
print(f"Ключ: {kw}")
print(snippet.decode("utf-8", errors="replace"))
break # один пример с файла достаточно
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | e3_find_example.py — Найти файлы с примерами кода, чтобы узнать разметку |
| 4 | """ |
| 5 | |
| 6 | import os |
| 7 | |
| 8 | CONTENT_DIR = os.path.join("hbk_content", "FileStorage") |
| 9 | |
| 10 | keywords = [ |
| 11 | b"V8SH_example", |
| 12 | b"V8SH_code", |
| 13 | b'class="code"', |
| 14 | b"<pre", |
| 15 | b"Example", |
| 16 | "Пример".encode("utf-8"), |
| 17 | ] |
| 18 | |
| 19 | for root, dirs, files in os.walk(CONTENT_DIR): |
| 20 | for fname in files: |
| 21 | if not fname.endswith(".html"): |
| 22 | continue |
| 23 | fpath = os.path.join(root, fname) |
| 24 | with open(fpath, "rb") as f: |
| 25 | data = f.read() |
| 26 | for kw in keywords: |
| 27 | if kw in data: |
| 28 | rel = os.path.relpath(fpath, CONTENT_DIR) |
| 29 | # Показать контекст вокруг ключевого слова |
| 30 | idx = data.find(kw) |
| 31 | snippet = data[max(0, idx - 100) : idx + 300] |
| 32 | print(f"\n{'=' * 60}") |
| 33 | print(f"Файл: {rel}") |
| 34 | print(f"Ключ: {kw}") |
| 35 | print(snippet.decode("utf-8", errors="replace")) |
| 36 | break # один пример с файла достаточно |
| 37 |
e4_code_format.py
· 11 KiB · Python
Brut
#!/usr/bin/env python3
"""
e4_code_format.py — Генератор HTML-таблицы с подсветкой кода 1С
в стиле встроенной справки 1С:Предприятие
Результат копируется в буфер обмена и сохраняется в файл.
"""
# ═══════════════════════════════════════════════════════════════
# ВВЕДИТЕ КОД 1С ЗДЕСЬ:
# ═══════════════════════════════════════════════════════════════
CODE_1C = """\
// Пример чтения JSON
&НаСервере
Процедура ЗагрузкаНаСервере(Путь)
Чтение = Новый ЧтениеJSON;
Чтение.ОткрытьФайл(Путь);
Данные = ПрочитатьJSON(Чтение);
Чтение.Закрыть(); Количество = 0;
Для Каждого Товар Из Данные Цикл
Ссылка = Справочники.ЗагрузкаТоваров.НайтиПоКоду(Товар.id);
Элемент = ?(Ссылка.Пустая(), Справочники.ЗагрузкаТоваров.СоздатьЭлемент(), Ссылка.ПолучитьОбъект());
Элемент.Код = Товар.article;
Элемент.Наименование = Товар.name;
Элемент.Цена = Товар.price;
Элемент.Цвет = Товар.color;
Элемент.Записать(); Количество = Количество + 1;
КонецЦикла;
Сообщить("Загружено " + Количество);
КонецПроцедуры
&НаКлиенте
Асинх Процедура Загрузка(Команда)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.Фильтр = "ДжейсонСтетхэм (*.json) | *.json";
Если Ждать Диалог.ВыбратьАсинх() Тогда
ЗагрузкаНаСервере(Диалог.ПолноеИмяФайла);
КонецЕсли;
КонецПроцедуры
"""
# ═══════════════════════════════════════════════════════════════
# Словари ключевых слов
# ═══════════════════════════════════════════════════════════════
KEYWORDS = {
# Русские
"Если",
"Тогда",
"ИначеЕсли",
"Иначе",
"КонецЕсли",
"Для",
"Каждого",
"Из",
"По",
"Цикл",
"КонецЦикла",
"Пока",
"Процедура",
"КонецПроцедуры",
"Функция",
"КонецФункции",
"Перем",
"Возврат",
"Продолжить",
"Прервать",
"И",
"Или",
"Не",
"Попытка",
"Исключение",
"КонецПопытки",
"ВызватьИсключение",
"Новый",
"Выполнить",
"Знач",
"Экспорт",
"Истина",
"Ложь",
"Неопределено",
"NULL",
# Английские
"If",
"Then",
"ElsIf",
"Else",
"EndIf",
"For",
"Each",
"In",
"To",
"Do",
"EndDo",
"While",
"Procedure",
"EndProcedure",
"Function",
"EndFunction",
"Var",
"Return",
"Continue",
"Break",
"And",
"Or",
"Not",
"Try",
"Except",
"EndTry",
"Raise",
"New",
"Execute",
"Val",
"Export",
"True",
"False",
"Undefined",
}
CONTEXT_DIRECTIVES = {
"&НаКлиенте",
"&НаСервере",
"&НаКлиентеНаСервере",
"&НаСервереБезКонтекста",
"&AtClient",
"&AtServer",
"&AtClientAtServer",
"&AtServerNoContext",
}
COLORS = {
"keyword": "#ff0000",
"string": "#000000",
"comment": "#008000",
"operator": "#ff0000",
"identifier": "#0000ff",
"number": "#000000",
"directive": "#ff0000",
"context": "#884422",
}
OPERATORS = set("=+-*/<>().,;[]%?")
# ═══════════════════════════════════════════════════════════════
# Лексер
# ═══════════════════════════════════════════════════════════════
def tokenize(code):
tokens = []
i = 0
n = len(code)
while i < n:
ch = code[i]
# Перенос строки
if ch == "\r":
tokens.append(("newline", "\n"))
i += 2 if i + 1 < n and code[i + 1] == "\n" else 1
continue
if ch == "\n":
tokens.append(("newline", "\n"))
i += 1
continue
# Пробелы
if ch in " \t":
j = i
while j < n and code[j] in " \t":
j += 1
tokens.append(("space", code[i:j]))
i = j
continue
# Комментарий //
if ch == "/" and i + 1 < n and code[i + 1] == "/":
j = i
while j < n and code[j] != "\n":
j += 1
tokens.append(("comment", code[i:j]))
i = j
continue
# Строка "..."
if ch == '"':
j = i + 1
while j < n:
if code[j] == '"':
if j + 1 < n and code[j + 1] == '"':
j += 2
else:
j += 1
break
else:
j += 1
tokens.append(("string", code[i:j]))
i = j
continue
# Число
if ch.isdigit():
j = i
while j < n and (code[j].isdigit() or code[j] == "."):
j += 1
tokens.append(("number", code[i:j]))
i = j
continue
# Операторы
if ch in OPERATORS:
if ch == "<" and i + 1 < n and code[i + 1] == ">":
tokens.append(("operator", "<>"))
i += 2
continue
if ch in "<>" and i + 1 < n and code[i + 1] == "=":
tokens.append(("operator", code[i : i + 2]))
i += 2
continue
tokens.append(("operator", ch))
i += 1
continue
# Контекстная директива &НаКлиенте
if ch == "&":
j = i
while j < n and (
code[j].isalnum() or code[j] in {"_", "&"} or ord(code[j]) > 127
):
j += 1
word = code[i:j]
if word in CONTEXT_DIRECTIVES:
tokens.append(("context", word))
else:
tokens.append(("identifier", word))
i = j
continue
# Директива #
if ch == "#":
j = i
while j < n and code[j] not in "\r\n":
j += 1
tokens.append(("directive", code[i:j]))
i = j
continue
# Идентификатор / ключевое слово
if ch.isalpha() or ch == "_" or ord(ch) > 127:
j = i
while j < n and (code[j].isalnum() or code[j] == "_" or ord(code[j]) > 127):
j += 1
word = code[i:j]
tokens.append(("keyword" if word in KEYWORDS else "identifier", word))
i = j
continue
tokens.append(("other", ch))
i += 1
return tokens
# ═══════════════════════════════════════════════════════════════
# Генератор HTML
# ═══════════════════════════════════════════════════════════════
def escape(text):
return text.replace("&", "&").replace("<", "<").replace(">", ">")
def nbsp(text):
return text.replace(" ", " ")
def token_html(typ, text):
if typ == "newline":
return "<br>"
if typ == "space":
return nbsp(escape(text))
e = nbsp(escape(text))
color = COLORS.get(typ)
return f'<font color="{color}">{e}</font>' if color else e
def generate(code):
tokens = tokenize(code)
inner = "".join(token_html(t, v) for t, v in tokens)
return (
f'<p class="V8SH_chapter">Пример:</p>'
f'<table width="100%" bgcolor="#f7f7f7">'
f"<tbody><tr><td>"
f'<font face="Courier New"> {inner}</font><br>'
f"</td></tr></tbody></table>"
)
# ═══════════════════════════════════════════════════════════════
# Запуск
# ═══════════════════════════════════════════════════════════════
if __name__ == "__main__":
code = CODE_1C.strip()
result = generate(code)
# Сохраняем результат
with open("result_code.html", "w", encoding="utf-8") as f:
f.write(result)
# Сохраняем предпросмотр
preview = (
'<!DOCTYPE html><html><head><meta charset="utf-8">'
"<title>Предпросмотр</title></head><body>"
f"<h3>Предпросмотр:</h3>{result}"
f'<h3>HTML:</h3><textarea style="width:100%;height:300px">'
f"{escape(result)}</textarea></body></html>"
)
with open("preview_code.html", "w", encoding="utf-8") as f:
f.write(preview)
# Копируем в буфер обмена
try:
import subprocess
subprocess.run("clip", input=result.encode("utf-8"), check=True, shell=True)
print("HTML скопирован в буфер обмена!")
except Exception:
pass
print(f"Сохранено: result_code.html")
print(f"Предпросмотр: preview_code.html")
print(f"\nHTML ({len(result)} символов):")
print(result[:200] + "...")
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | e4_code_format.py — Генератор HTML-таблицы с подсветкой кода 1С |
| 4 | в стиле встроенной справки 1С:Предприятие |
| 5 | |
| 6 | Результат копируется в буфер обмена и сохраняется в файл. |
| 7 | """ |
| 8 | |
| 9 | # ═══════════════════════════════════════════════════════════════ |
| 10 | # ВВЕДИТЕ КОД 1С ЗДЕСЬ: |
| 11 | # ═══════════════════════════════════════════════════════════════ |
| 12 | |
| 13 | CODE_1C = """\ |
| 14 | // Пример чтения JSON |
| 15 | &НаСервере |
| 16 | Процедура ЗагрузкаНаСервере(Путь) |
| 17 | Чтение = Новый ЧтениеJSON; |
| 18 | Чтение.ОткрытьФайл(Путь); |
| 19 | Данные = ПрочитатьJSON(Чтение); |
| 20 | Чтение.Закрыть(); Количество = 0; |
| 21 | Для Каждого Товар Из Данные Цикл |
| 22 | Ссылка = Справочники.ЗагрузкаТоваров.НайтиПоКоду(Товар.id); |
| 23 | Элемент = ?(Ссылка.Пустая(), Справочники.ЗагрузкаТоваров.СоздатьЭлемент(), Ссылка.ПолучитьОбъект()); |
| 24 | Элемент.Код = Товар.article; |
| 25 | Элемент.Наименование = Товар.name; |
| 26 | Элемент.Цена = Товар.price; |
| 27 | Элемент.Цвет = Товар.color; |
| 28 | Элемент.Записать(); Количество = Количество + 1; |
| 29 | КонецЦикла; |
| 30 | Сообщить("Загружено " + Количество); |
| 31 | КонецПроцедуры |
| 32 | |
| 33 | &НаКлиенте |
| 34 | Асинх Процедура Загрузка(Команда) |
| 35 | Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие); |
| 36 | Диалог.Фильтр = "ДжейсонСтетхэм (*.json) | *.json"; |
| 37 | Если Ждать Диалог.ВыбратьАсинх() Тогда |
| 38 | ЗагрузкаНаСервере(Диалог.ПолноеИмяФайла); |
| 39 | КонецЕсли; |
| 40 | КонецПроцедуры |
| 41 | """ |
| 42 | |
| 43 | # ═══════════════════════════════════════════════════════════════ |
| 44 | # Словари ключевых слов |
| 45 | # ═══════════════════════════════════════════════════════════════ |
| 46 | |
| 47 | KEYWORDS = { |
| 48 | # Русские |
| 49 | "Если", |
| 50 | "Тогда", |
| 51 | "ИначеЕсли", |
| 52 | "Иначе", |
| 53 | "КонецЕсли", |
| 54 | "Для", |
| 55 | "Каждого", |
| 56 | "Из", |
| 57 | "По", |
| 58 | "Цикл", |
| 59 | "КонецЦикла", |
| 60 | "Пока", |
| 61 | "Процедура", |
| 62 | "КонецПроцедуры", |
| 63 | "Функция", |
| 64 | "КонецФункции", |
| 65 | "Перем", |
| 66 | "Возврат", |
| 67 | "Продолжить", |
| 68 | "Прервать", |
| 69 | "И", |
| 70 | "Или", |
| 71 | "Не", |
| 72 | "Попытка", |
| 73 | "Исключение", |
| 74 | "КонецПопытки", |
| 75 | "ВызватьИсключение", |
| 76 | "Новый", |
| 77 | "Выполнить", |
| 78 | "Знач", |
| 79 | "Экспорт", |
| 80 | "Истина", |
| 81 | "Ложь", |
| 82 | "Неопределено", |
| 83 | "NULL", |
| 84 | # Английские |
| 85 | "If", |
| 86 | "Then", |
| 87 | "ElsIf", |
| 88 | "Else", |
| 89 | "EndIf", |
| 90 | "For", |
| 91 | "Each", |
| 92 | "In", |
| 93 | "To", |
| 94 | "Do", |
| 95 | "EndDo", |
| 96 | "While", |
| 97 | "Procedure", |
| 98 | "EndProcedure", |
| 99 | "Function", |
| 100 | "EndFunction", |
| 101 | "Var", |
| 102 | "Return", |
| 103 | "Continue", |
| 104 | "Break", |
| 105 | "And", |
| 106 | "Or", |
| 107 | "Not", |
| 108 | "Try", |
| 109 | "Except", |
| 110 | "EndTry", |
| 111 | "Raise", |
| 112 | "New", |
| 113 | "Execute", |
| 114 | "Val", |
| 115 | "Export", |
| 116 | "True", |
| 117 | "False", |
| 118 | "Undefined", |
| 119 | } |
| 120 | |
| 121 | CONTEXT_DIRECTIVES = { |
| 122 | "&НаКлиенте", |
| 123 | "&НаСервере", |
| 124 | "&НаКлиентеНаСервере", |
| 125 | "&НаСервереБезКонтекста", |
| 126 | "&AtClient", |
| 127 | "&AtServer", |
| 128 | "&AtClientAtServer", |
| 129 | "&AtServerNoContext", |
| 130 | } |
| 131 | |
| 132 | COLORS = { |
| 133 | "keyword": "#ff0000", |
| 134 | "string": "#000000", |
| 135 | "comment": "#008000", |
| 136 | "operator": "#ff0000", |
| 137 | "identifier": "#0000ff", |
| 138 | "number": "#000000", |
| 139 | "directive": "#ff0000", |
| 140 | "context": "#884422", |
| 141 | } |
| 142 | |
| 143 | OPERATORS = set("=+-*/<>().,;[]%?") |
| 144 | |
| 145 | # ═══════════════════════════════════════════════════════════════ |
| 146 | # Лексер |
| 147 | # ═══════════════════════════════════════════════════════════════ |
| 148 | |
| 149 | |
| 150 | def tokenize(code): |
| 151 | tokens = [] |
| 152 | i = 0 |
| 153 | n = len(code) |
| 154 | |
| 155 | while i < n: |
| 156 | ch = code[i] |
| 157 | |
| 158 | # Перенос строки |
| 159 | if ch == "\r": |
| 160 | tokens.append(("newline", "\n")) |
| 161 | i += 2 if i + 1 < n and code[i + 1] == "\n" else 1 |
| 162 | continue |
| 163 | if ch == "\n": |
| 164 | tokens.append(("newline", "\n")) |
| 165 | i += 1 |
| 166 | continue |
| 167 | |
| 168 | # Пробелы |
| 169 | if ch in " \t": |
| 170 | j = i |
| 171 | while j < n and code[j] in " \t": |
| 172 | j += 1 |
| 173 | tokens.append(("space", code[i:j])) |
| 174 | i = j |
| 175 | continue |
| 176 | |
| 177 | # Комментарий // |
| 178 | if ch == "/" and i + 1 < n and code[i + 1] == "/": |
| 179 | j = i |
| 180 | while j < n and code[j] != "\n": |
| 181 | j += 1 |
| 182 | tokens.append(("comment", code[i:j])) |
| 183 | i = j |
| 184 | continue |
| 185 | |
| 186 | # Строка "..." |
| 187 | if ch == '"': |
| 188 | j = i + 1 |
| 189 | while j < n: |
| 190 | if code[j] == '"': |
| 191 | if j + 1 < n and code[j + 1] == '"': |
| 192 | j += 2 |
| 193 | else: |
| 194 | j += 1 |
| 195 | break |
| 196 | else: |
| 197 | j += 1 |
| 198 | tokens.append(("string", code[i:j])) |
| 199 | i = j |
| 200 | continue |
| 201 | |
| 202 | # Число |
| 203 | if ch.isdigit(): |
| 204 | j = i |
| 205 | while j < n and (code[j].isdigit() or code[j] == "."): |
| 206 | j += 1 |
| 207 | tokens.append(("number", code[i:j])) |
| 208 | i = j |
| 209 | continue |
| 210 | |
| 211 | # Операторы |
| 212 | if ch in OPERATORS: |
| 213 | if ch == "<" and i + 1 < n and code[i + 1] == ">": |
| 214 | tokens.append(("operator", "<>")) |
| 215 | i += 2 |
| 216 | continue |
| 217 | if ch in "<>" and i + 1 < n and code[i + 1] == "=": |
| 218 | tokens.append(("operator", code[i : i + 2])) |
| 219 | i += 2 |
| 220 | continue |
| 221 | tokens.append(("operator", ch)) |
| 222 | i += 1 |
| 223 | continue |
| 224 | |
| 225 | # Контекстная директива &НаКлиенте |
| 226 | if ch == "&": |
| 227 | j = i |
| 228 | while j < n and ( |
| 229 | code[j].isalnum() or code[j] in {"_", "&"} or ord(code[j]) > 127 |
| 230 | ): |
| 231 | j += 1 |
| 232 | word = code[i:j] |
| 233 | if word in CONTEXT_DIRECTIVES: |
| 234 | tokens.append(("context", word)) |
| 235 | else: |
| 236 | tokens.append(("identifier", word)) |
| 237 | i = j |
| 238 | continue |
| 239 | |
| 240 | # Директива # |
| 241 | if ch == "#": |
| 242 | j = i |
| 243 | while j < n and code[j] not in "\r\n": |
| 244 | j += 1 |
| 245 | tokens.append(("directive", code[i:j])) |
| 246 | i = j |
| 247 | continue |
| 248 | |
| 249 | # Идентификатор / ключевое слово |
| 250 | if ch.isalpha() or ch == "_" or ord(ch) > 127: |
| 251 | j = i |
| 252 | while j < n and (code[j].isalnum() or code[j] == "_" or ord(code[j]) > 127): |
| 253 | j += 1 |
| 254 | word = code[i:j] |
| 255 | tokens.append(("keyword" if word in KEYWORDS else "identifier", word)) |
| 256 | i = j |
| 257 | continue |
| 258 | |
| 259 | tokens.append(("other", ch)) |
| 260 | i += 1 |
| 261 | |
| 262 | return tokens |
| 263 | |
| 264 | |
| 265 | # ═══════════════════════════════════════════════════════════════ |
| 266 | # Генератор HTML |
| 267 | # ═══════════════════════════════════════════════════════════════ |
| 268 | |
| 269 | |
| 270 | def escape(text): |
| 271 | return text.replace("&", "&").replace("<", "<").replace(">", ">") |
| 272 | |
| 273 | |
| 274 | def nbsp(text): |
| 275 | return text.replace(" ", " ") |
| 276 | |
| 277 | |
| 278 | def token_html(typ, text): |
| 279 | if typ == "newline": |
| 280 | return "<br>" |
| 281 | if typ == "space": |
| 282 | return nbsp(escape(text)) |
| 283 | e = nbsp(escape(text)) |
| 284 | color = COLORS.get(typ) |
| 285 | return f'<font color="{color}">{e}</font>' if color else e |
| 286 | |
| 287 | |
| 288 | def generate(code): |
| 289 | tokens = tokenize(code) |
| 290 | inner = "".join(token_html(t, v) for t, v in tokens) |
| 291 | return ( |
| 292 | f'<p class="V8SH_chapter">Пример:</p>' |
| 293 | f'<table width="100%" bgcolor="#f7f7f7">' |
| 294 | f"<tbody><tr><td>" |
| 295 | f'<font face="Courier New"> {inner}</font><br>' |
| 296 | f"</td></tr></tbody></table>" |
| 297 | ) |
| 298 | |
| 299 | |
| 300 | # ═══════════════════════════════════════════════════════════════ |
| 301 | # Запуск |
| 302 | # ═══════════════════════════════════════════════════════════════ |
| 303 | |
| 304 | if __name__ == "__main__": |
| 305 | code = CODE_1C.strip() |
| 306 | result = generate(code) |
| 307 | |
| 308 | # Сохраняем результат |
| 309 | with open("result_code.html", "w", encoding="utf-8") as f: |
| 310 | f.write(result) |
| 311 | |
| 312 | # Сохраняем предпросмотр |
| 313 | preview = ( |
| 314 | '<!DOCTYPE html><html><head><meta charset="utf-8">' |
| 315 | "<title>Предпросмотр</title></head><body>" |
| 316 | f"<h3>Предпросмотр:</h3>{result}" |
| 317 | f'<h3>HTML:</h3><textarea style="width:100%;height:300px">' |
| 318 | f"{escape(result)}</textarea></body></html>" |
| 319 | ) |
| 320 | with open("preview_code.html", "w", encoding="utf-8") as f: |
| 321 | f.write(preview) |
| 322 | |
| 323 | # Копируем в буфер обмена |
| 324 | try: |
| 325 | import subprocess |
| 326 | |
| 327 | subprocess.run("clip", input=result.encode("utf-8"), check=True, shell=True) |
| 328 | print("HTML скопирован в буфер обмена!") |
| 329 | except Exception: |
| 330 | pass |
| 331 | |
| 332 | print(f"Сохранено: result_code.html") |
| 333 | print(f"Предпросмотр: preview_code.html") |
| 334 | print(f"\nHTML ({len(result)} символов):") |
| 335 | print(result[:200] + "...") |
| 336 |
e5_format_html.py
· 4.6 KiB · Python
Brut
#!/usr/bin/env python3
"""
e5_format_html.py — Форматирователь HTML: разбивает на строки по тегам,
БЕЗ добавления отступов. Просто каждый тег на новой строке.
Использование:
python e5_format_html.py input.html → выводит в stdout
python e5_format_html.py input.html output.html → сохраняет в файл
python e5_format_html.py --dir ./folder → все .html в папке (на месте)
"""
import os
import re
import sys
# Теги, после открывающего/закрывающего которых ставим перенос
BLOCK_TAGS = {
"html",
"head",
"body",
"div",
"p",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"ul",
"ol",
"li",
"table",
"tbody",
"thead",
"tfoot",
"tr",
"td",
"th",
"form",
"fieldset",
"section",
"article",
"nav",
"aside",
"header",
"footer",
"main",
"details",
"summary",
"blockquote",
"pre",
"script",
"style",
"link",
"meta",
"title",
}
# Теги, содержимое которых не трогаем
PRESERVE_TAGS = {"pre", "textarea", "script", "style"}
def format_html(html):
# Защищаем содержимое pre/textarea/script/style
preserved = []
def save_block(m):
preserved.append(m.group(0))
return f"\x00BLOCK{len(preserved) - 1}\x00"
html = re.sub(
r"(<(?:pre|textarea|script|style)\b[^>]*>)(.*?)(</(?:pre|textarea|script|style)>)",
save_block,
html,
flags=re.DOTALL | re.IGNORECASE,
)
# Убираем существующие переносы и лишние пробелы
html = re.sub(r"[\r\n\t]+", " ", html)
html = re.sub(r" {2,}", " ", html)
# Перенос ПЕРЕД открывающими блочными тегами
tags_pattern = "|".join(BLOCK_TAGS)
html = re.sub(
rf"(?<=>)\s*(<(?:{tags_pattern})\b)", r"\n\1", html, flags=re.IGNORECASE
)
# Перенос ПОСЛЕ закрывающих блочных тегов
html = re.sub(rf"(</(?:{tags_pattern})>)\s*", r"\1\n", html, flags=re.IGNORECASE)
# Перенос ПОСЛЕ самозакрывающихся тегов (meta, link, br, hr, img)
html = re.sub(r"(<(?:meta|link)\b[^>]*>)\s*", r"\1\n", html, flags=re.IGNORECASE)
# Перенос ПОСЛЕ <br> и <hr> (но br внутри кода не трогаем — они в preserved)
html = re.sub(r"(<[Hh][Rr]\s*/?>)\s*", r"\1\n", html)
# Убираем пустые строки (больше 2 переносов подряд)
html = re.sub(r"\n{3,}", "\n\n", html)
# Убираем пробелы в начале строк (но НЕ добавляем отступы)
lines = html.split("\n")
lines = [line.strip() for line in lines]
html = "\n".join(lines)
# Убираем пустые строки в начале и конце
html = html.strip()
# Восстанавливаем защищённые блоки
for i, block in enumerate(preserved):
html = html.replace(f"\x00BLOCK{i}\x00", block)
return html
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Использование:")
print(" python html_format.py input.html [output.html]")
print(" python html_format.py --dir ./folder")
sys.exit(1)
if sys.argv[1] == "--dir":
folder = sys.argv[2] if len(sys.argv) > 2 else "."
count = 0
for root, _, files in os.walk(folder):
for fname in files:
if not fname.lower().endswith(".html"):
continue
fpath = os.path.join(root, fname)
with open(fpath, "r", encoding="utf-8-sig") as f:
original = f.read()
result = format_html(original)
with open(fpath, "w", encoding="utf-8-sig") as f:
f.write(result)
count += 1
print(f" {os.path.relpath(fpath, folder)}")
print(f"\nОтформатировано: {count} файлов")
else:
input_file = sys.argv[1]
with open(input_file, "r", encoding="utf-8-sig") as f:
html = f.read()
result = format_html(html)
if len(sys.argv) > 2:
with open(sys.argv[2], "w", encoding="utf-8-sig") as f:
f.write(result)
print(f"Сохранено: {sys.argv[2]}")
else:
print(result)
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | e5_format_html.py — Форматирователь HTML: разбивает на строки по тегам, |
| 4 | БЕЗ добавления отступов. Просто каждый тег на новой строке. |
| 5 | |
| 6 | Использование: |
| 7 | python e5_format_html.py input.html → выводит в stdout |
| 8 | python e5_format_html.py input.html output.html → сохраняет в файл |
| 9 | python e5_format_html.py --dir ./folder → все .html в папке (на месте) |
| 10 | """ |
| 11 | |
| 12 | import os |
| 13 | import re |
| 14 | import sys |
| 15 | |
| 16 | # Теги, после открывающего/закрывающего которых ставим перенос |
| 17 | BLOCK_TAGS = { |
| 18 | "html", |
| 19 | "head", |
| 20 | "body", |
| 21 | "div", |
| 22 | "p", |
| 23 | "h1", |
| 24 | "h2", |
| 25 | "h3", |
| 26 | "h4", |
| 27 | "h5", |
| 28 | "h6", |
| 29 | "ul", |
| 30 | "ol", |
| 31 | "li", |
| 32 | "table", |
| 33 | "tbody", |
| 34 | "thead", |
| 35 | "tfoot", |
| 36 | "tr", |
| 37 | "td", |
| 38 | "th", |
| 39 | "form", |
| 40 | "fieldset", |
| 41 | "section", |
| 42 | "article", |
| 43 | "nav", |
| 44 | "aside", |
| 45 | "header", |
| 46 | "footer", |
| 47 | "main", |
| 48 | "details", |
| 49 | "summary", |
| 50 | "blockquote", |
| 51 | "pre", |
| 52 | "script", |
| 53 | "style", |
| 54 | "link", |
| 55 | "meta", |
| 56 | "title", |
| 57 | } |
| 58 | |
| 59 | # Теги, содержимое которых не трогаем |
| 60 | PRESERVE_TAGS = {"pre", "textarea", "script", "style"} |
| 61 | |
| 62 | |
| 63 | def format_html(html): |
| 64 | # Защищаем содержимое pre/textarea/script/style |
| 65 | preserved = [] |
| 66 | |
| 67 | def save_block(m): |
| 68 | preserved.append(m.group(0)) |
| 69 | return f"\x00BLOCK{len(preserved) - 1}\x00" |
| 70 | |
| 71 | html = re.sub( |
| 72 | r"(<(?:pre|textarea|script|style)\b[^>]*>)(.*?)(</(?:pre|textarea|script|style)>)", |
| 73 | save_block, |
| 74 | html, |
| 75 | flags=re.DOTALL | re.IGNORECASE, |
| 76 | ) |
| 77 | |
| 78 | # Убираем существующие переносы и лишние пробелы |
| 79 | html = re.sub(r"[\r\n\t]+", " ", html) |
| 80 | html = re.sub(r" {2,}", " ", html) |
| 81 | |
| 82 | # Перенос ПЕРЕД открывающими блочными тегами |
| 83 | tags_pattern = "|".join(BLOCK_TAGS) |
| 84 | html = re.sub( |
| 85 | rf"(?<=>)\s*(<(?:{tags_pattern})\b)", r"\n\1", html, flags=re.IGNORECASE |
| 86 | ) |
| 87 | |
| 88 | # Перенос ПОСЛЕ закрывающих блочных тегов |
| 89 | html = re.sub(rf"(</(?:{tags_pattern})>)\s*", r"\1\n", html, flags=re.IGNORECASE) |
| 90 | |
| 91 | # Перенос ПОСЛЕ самозакрывающихся тегов (meta, link, br, hr, img) |
| 92 | html = re.sub(r"(<(?:meta|link)\b[^>]*>)\s*", r"\1\n", html, flags=re.IGNORECASE) |
| 93 | |
| 94 | # Перенос ПОСЛЕ <br> и <hr> (но br внутри кода не трогаем — они в preserved) |
| 95 | html = re.sub(r"(<[Hh][Rr]\s*/?>)\s*", r"\1\n", html) |
| 96 | |
| 97 | # Убираем пустые строки (больше 2 переносов подряд) |
| 98 | html = re.sub(r"\n{3,}", "\n\n", html) |
| 99 | |
| 100 | # Убираем пробелы в начале строк (но НЕ добавляем отступы) |
| 101 | lines = html.split("\n") |
| 102 | lines = [line.strip() for line in lines] |
| 103 | html = "\n".join(lines) |
| 104 | |
| 105 | # Убираем пустые строки в начале и конце |
| 106 | html = html.strip() |
| 107 | |
| 108 | # Восстанавливаем защищённые блоки |
| 109 | for i, block in enumerate(preserved): |
| 110 | html = html.replace(f"\x00BLOCK{i}\x00", block) |
| 111 | |
| 112 | return html |
| 113 | |
| 114 | |
| 115 | if __name__ == "__main__": |
| 116 | if len(sys.argv) < 2: |
| 117 | print("Использование:") |
| 118 | print(" python html_format.py input.html [output.html]") |
| 119 | print(" python html_format.py --dir ./folder") |
| 120 | sys.exit(1) |
| 121 | |
| 122 | if sys.argv[1] == "--dir": |
| 123 | folder = sys.argv[2] if len(sys.argv) > 2 else "." |
| 124 | count = 0 |
| 125 | for root, _, files in os.walk(folder): |
| 126 | for fname in files: |
| 127 | if not fname.lower().endswith(".html"): |
| 128 | continue |
| 129 | fpath = os.path.join(root, fname) |
| 130 | with open(fpath, "r", encoding="utf-8-sig") as f: |
| 131 | original = f.read() |
| 132 | result = format_html(original) |
| 133 | with open(fpath, "w", encoding="utf-8-sig") as f: |
| 134 | f.write(result) |
| 135 | count += 1 |
| 136 | print(f" {os.path.relpath(fpath, folder)}") |
| 137 | print(f"\nОтформатировано: {count} файлов") |
| 138 | else: |
| 139 | input_file = sys.argv[1] |
| 140 | with open(input_file, "r", encoding="utf-8-sig") as f: |
| 141 | html = f.read() |
| 142 | |
| 143 | result = format_html(html) |
| 144 | |
| 145 | if len(sys.argv) > 2: |
| 146 | with open(sys.argv[2], "w", encoding="utf-8-sig") as f: |
| 147 | f.write(result) |
| 148 | print(f"Сохранено: {sys.argv[2]}") |
| 149 | else: |
| 150 | print(result) |
| 151 |
e6_minify_html.py
· 3.1 KiB · Python
Brut
#!/usr/bin/env python3
"""
e6_minify_html.py — Минимизатор HTML: убирает отступы, переносы, лишние пробелы.
Превращает HTML в одну строку.
Использование:
python e6_minify_html.py input.html → выводит в stdout
python e6_minify_html.py input.html output.html → сохраняет в файл
python e6_minify_html.py --dir ./folder → все .html в папке (на месте)
"""
import os
import re
import sys
def minify_html(html):
# Защищаем содержимое <pre>, <textarea>, <script>, <style>
preserved = []
def save_block(m):
preserved.append(m.group(0))
return f"\x00BLOCK{len(preserved) - 1}\x00"
html = re.sub(
r"(<(?:pre|textarea|script|style)\b[^>]*>)(.*?)(</(?:pre|textarea|script|style)>)",
save_block,
html,
flags=re.DOTALL | re.IGNORECASE,
)
# Убираем HTML-комментарии
html = re.sub(r"<!--.*?-->", "", html, flags=re.DOTALL)
# Убираем переносы строк и табы → один пробел
html = re.sub(r"[\r\n\t]+", " ", html)
# Множественные пробелы → один
html = re.sub(r" {2,}", " ", html)
# Пробелы вокруг тегов
html = re.sub(r">\s+<", "><", html)
html = re.sub(r">\s+", ">", html)
html = re.sub(r"\s+<", "<", html)
# Восстанавливаем защищённые блоки
for i, block in enumerate(preserved):
html = html.replace(f"\x00BLOCK{i}\x00", block)
return html.strip()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Использование:")
print(" python html_minify.py input.html [output.html]")
print(" python html_minify.py --dir ./folder")
sys.exit(1)
if sys.argv[1] == "--dir":
folder = sys.argv[2] if len(sys.argv) > 2 else "."
count = 0
for root, _, files in os.walk(folder):
for fname in files:
if not fname.lower().endswith(".html"):
continue
fpath = os.path.join(root, fname)
with open(fpath, "r", encoding="utf-8-sig") as f:
original = f.read()
result = minify_html(original)
if len(result) < len(original):
with open(fpath, "w", encoding="utf-8-sig") as f:
f.write(result)
saved = len(original) - len(result)
count += 1
print(f" {os.path.relpath(fpath, folder)} -{saved} байт")
print(f"\nМинимизировано: {count} файлов")
else:
input_file = sys.argv[1]
with open(input_file, "r", encoding="utf-8-sig") as f:
html = f.read()
result = minify_html(html)
if len(sys.argv) > 2:
with open(sys.argv[2], "w", encoding="utf-8-sig") as f:
f.write(result)
print(f"{len(html)} → {len(result)} байт")
else:
print(result)
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | e6_minify_html.py — Минимизатор HTML: убирает отступы, переносы, лишние пробелы. |
| 4 | Превращает HTML в одну строку. |
| 5 | |
| 6 | Использование: |
| 7 | python e6_minify_html.py input.html → выводит в stdout |
| 8 | python e6_minify_html.py input.html output.html → сохраняет в файл |
| 9 | python e6_minify_html.py --dir ./folder → все .html в папке (на месте) |
| 10 | """ |
| 11 | |
| 12 | import os |
| 13 | import re |
| 14 | import sys |
| 15 | |
| 16 | |
| 17 | def minify_html(html): |
| 18 | # Защищаем содержимое <pre>, <textarea>, <script>, <style> |
| 19 | preserved = [] |
| 20 | |
| 21 | def save_block(m): |
| 22 | preserved.append(m.group(0)) |
| 23 | return f"\x00BLOCK{len(preserved) - 1}\x00" |
| 24 | |
| 25 | html = re.sub( |
| 26 | r"(<(?:pre|textarea|script|style)\b[^>]*>)(.*?)(</(?:pre|textarea|script|style)>)", |
| 27 | save_block, |
| 28 | html, |
| 29 | flags=re.DOTALL | re.IGNORECASE, |
| 30 | ) |
| 31 | |
| 32 | # Убираем HTML-комментарии |
| 33 | html = re.sub(r"<!--.*?-->", "", html, flags=re.DOTALL) |
| 34 | |
| 35 | # Убираем переносы строк и табы → один пробел |
| 36 | html = re.sub(r"[\r\n\t]+", " ", html) |
| 37 | |
| 38 | # Множественные пробелы → один |
| 39 | html = re.sub(r" {2,}", " ", html) |
| 40 | |
| 41 | # Пробелы вокруг тегов |
| 42 | html = re.sub(r">\s+<", "><", html) |
| 43 | html = re.sub(r">\s+", ">", html) |
| 44 | html = re.sub(r"\s+<", "<", html) |
| 45 | |
| 46 | # Восстанавливаем защищённые блоки |
| 47 | for i, block in enumerate(preserved): |
| 48 | html = html.replace(f"\x00BLOCK{i}\x00", block) |
| 49 | |
| 50 | return html.strip() |
| 51 | |
| 52 | |
| 53 | if __name__ == "__main__": |
| 54 | if len(sys.argv) < 2: |
| 55 | print("Использование:") |
| 56 | print(" python html_minify.py input.html [output.html]") |
| 57 | print(" python html_minify.py --dir ./folder") |
| 58 | sys.exit(1) |
| 59 | |
| 60 | if sys.argv[1] == "--dir": |
| 61 | folder = sys.argv[2] if len(sys.argv) > 2 else "." |
| 62 | count = 0 |
| 63 | for root, _, files in os.walk(folder): |
| 64 | for fname in files: |
| 65 | if not fname.lower().endswith(".html"): |
| 66 | continue |
| 67 | fpath = os.path.join(root, fname) |
| 68 | with open(fpath, "r", encoding="utf-8-sig") as f: |
| 69 | original = f.read() |
| 70 | result = minify_html(original) |
| 71 | if len(result) < len(original): |
| 72 | with open(fpath, "w", encoding="utf-8-sig") as f: |
| 73 | f.write(result) |
| 74 | saved = len(original) - len(result) |
| 75 | count += 1 |
| 76 | print(f" {os.path.relpath(fpath, folder)} -{saved} байт") |
| 77 | print(f"\nМинимизировано: {count} файлов") |
| 78 | else: |
| 79 | input_file = sys.argv[1] |
| 80 | with open(input_file, "r", encoding="utf-8-sig") as f: |
| 81 | html = f.read() |
| 82 | |
| 83 | result = minify_html(html) |
| 84 | |
| 85 | if len(sys.argv) > 2: |
| 86 | with open(sys.argv[2], "w", encoding="utf-8-sig") as f: |
| 87 | f.write(result) |
| 88 | print(f"{len(html)} → {len(result)} байт") |
| 89 | else: |
| 90 | print(result) |
| 91 |
p1_unpack.py
· 5.3 KiB · Python
Brut
#!/usr/bin/env python3
"""
p1_unpack.py — Извлечение потоков из контейнера 1С v8 (.hbk)
"""
import json
import os
import struct
import sys
BLOCK_HDR = 31 # размер блочного адреса
NO_NEXT = 0x7FFFFFFF # нет следующей страницы
# ─── Чтение блочного адреса ──────────────────────────────────────────
def parse_block_hdr(data, offset):
"""
Разбирает блочный адрес: \\r\\nDOCSIZE PAGESIZE NEXTPAGE \\r\\n
Возвращает (doc_size, page_size, next_page) или None.
"""
if offset < 0 or offset + BLOCK_HDR > len(data):
return None
if (
data[offset : offset + 2] != b"\r\n"
or data[offset + 29 : offset + 31] != b"\r\n"
):
return None
try:
txt = data[offset + 2 : offset + 29].decode("ascii").strip().split()
if len(txt) != 3:
return None
return int(txt[0], 16), int(txt[1], 16), int(txt[2], 16)
except Exception:
return None
def read_block(data, offset):
"""
Читает ПОЛНОЕ содержимое блока (может быть цепочка страниц).
Возвращает bytes.
"""
first = parse_block_hdr(data, offset)
if first is None:
return None
doc_size = first[0] # реальный размер данных
buf = bytearray()
cur = offset
visited = set()
while cur != NO_NEXT and cur not in visited:
visited.add(cur)
hdr = parse_block_hdr(data, cur)
if hdr is None:
break
_, page_size, next_page = hdr
start = cur + BLOCK_HDR
buf.extend(data[start : start + page_size])
cur = next_page
return bytes(buf[:doc_size])
# ─── Разбор метаданных потока ────────────────────────────────────────
def parse_meta(raw):
"""
Метаданные потока:
creation FILETIME (8 байт)
modified FILETIME (8 байт)
attrs uint32 (4 байта)
name UTF-16LE с нулевым терминатором
"""
if not raw or len(raw) < 20:
return None, raw
name = raw[20:].decode("utf-16-le").rstrip("\x00")
return name, raw
# ─── Основная функция ────────────────────────────────────────────────
def extract(filepath, out_dir="hbk_streams"):
with open(filepath, "rb") as f:
data = f.read()
magic, page_sz, version, _ = struct.unpack_from("<IIII", data, 0)
print(f"Файл : {filepath} ({len(data):,} байт)")
print(f"Magic : 0x{magic:08X}")
print(f"PageSize : 0x{page_sz:X}")
print(f"Version : 0x{version:X}")
# ─── TOC ─────────────────────────────────────────────────────────
toc_raw = read_block(data, 16) # TOC идёт сразу после 16-байтного заголовка
if toc_raw is None:
sys.exit("ОШИБКА: не удалось прочитать TOC")
n_entries = len(toc_raw) // 12
print(f"TOC : {len(toc_raw)} байт → {n_entries} записей\n")
os.makedirs(out_dir, exist_ok=True)
manifest = []
for i in range(n_entries):
h_addr, d_addr, _ = struct.unpack_from("<III", toc_raw, i * 12)
if h_addr == 0 and d_addr == 0:
break
# метаданные
meta_raw = read_block(data, h_addr)
name, _ = parse_meta(meta_raw)
if not name:
name = f"stream_{i}"
# данные
content = read_block(data, d_addr)
c_len = len(content) if content else 0
print(f" [{i}] {name}")
print(f" header @ 0x{h_addr:X} data @ 0x{d_addr:X}")
print(f" content : {c_len:,} байт")
if content:
print(f" head hex: {content[:32].hex(' ')}")
# ─── сохраняем ──────────────────────────────────────────────
safe = name.replace("/", "_").replace("\\", "_")
with open(os.path.join(out_dir, f"{safe}.meta"), "wb") as f:
f.write(meta_raw)
if content:
with open(os.path.join(out_dir, safe), "wb") as f:
f.write(content)
manifest.append(
{
"index": i,
"name": name,
"header_addr": h_addr,
"data_addr": d_addr,
"content_size": c_len,
"meta_size": len(meta_raw) if meta_raw else 0,
}
)
with open(os.path.join(out_dir, "_manifest.json"), "w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
print(f"\n{'=' * 50}")
print(f"Извлечено потоков: {len(manifest)}")
print(f"Каталог: {out_dir}/")
return manifest
if __name__ == "__main__":
fp = sys.argv[1] if len(sys.argv) > 1 else "shcntx_ru.hbk"
extract(fp)
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | p1_unpack.py — Извлечение потоков из контейнера 1С v8 (.hbk) |
| 4 | """ |
| 5 | |
| 6 | import json |
| 7 | import os |
| 8 | import struct |
| 9 | import sys |
| 10 | |
| 11 | BLOCK_HDR = 31 # размер блочного адреса |
| 12 | NO_NEXT = 0x7FFFFFFF # нет следующей страницы |
| 13 | |
| 14 | |
| 15 | # ─── Чтение блочного адреса ────────────────────────────────────────── |
| 16 | |
| 17 | |
| 18 | def parse_block_hdr(data, offset): |
| 19 | """ |
| 20 | Разбирает блочный адрес: \\r\\nDOCSIZE PAGESIZE NEXTPAGE \\r\\n |
| 21 | Возвращает (doc_size, page_size, next_page) или None. |
| 22 | """ |
| 23 | if offset < 0 or offset + BLOCK_HDR > len(data): |
| 24 | return None |
| 25 | if ( |
| 26 | data[offset : offset + 2] != b"\r\n" |
| 27 | or data[offset + 29 : offset + 31] != b"\r\n" |
| 28 | ): |
| 29 | return None |
| 30 | try: |
| 31 | txt = data[offset + 2 : offset + 29].decode("ascii").strip().split() |
| 32 | if len(txt) != 3: |
| 33 | return None |
| 34 | return int(txt[0], 16), int(txt[1], 16), int(txt[2], 16) |
| 35 | except Exception: |
| 36 | return None |
| 37 | |
| 38 | |
| 39 | def read_block(data, offset): |
| 40 | """ |
| 41 | Читает ПОЛНОЕ содержимое блока (может быть цепочка страниц). |
| 42 | Возвращает bytes. |
| 43 | """ |
| 44 | first = parse_block_hdr(data, offset) |
| 45 | if first is None: |
| 46 | return None |
| 47 | |
| 48 | doc_size = first[0] # реальный размер данных |
| 49 | buf = bytearray() |
| 50 | cur = offset |
| 51 | visited = set() |
| 52 | |
| 53 | while cur != NO_NEXT and cur not in visited: |
| 54 | visited.add(cur) |
| 55 | hdr = parse_block_hdr(data, cur) |
| 56 | if hdr is None: |
| 57 | break |
| 58 | _, page_size, next_page = hdr |
| 59 | start = cur + BLOCK_HDR |
| 60 | buf.extend(data[start : start + page_size]) |
| 61 | cur = next_page |
| 62 | |
| 63 | return bytes(buf[:doc_size]) |
| 64 | |
| 65 | |
| 66 | # ─── Разбор метаданных потока ──────────────────────────────────────── |
| 67 | |
| 68 | |
| 69 | def parse_meta(raw): |
| 70 | """ |
| 71 | Метаданные потока: |
| 72 | creation FILETIME (8 байт) |
| 73 | modified FILETIME (8 байт) |
| 74 | attrs uint32 (4 байта) |
| 75 | name UTF-16LE с нулевым терминатором |
| 76 | """ |
| 77 | if not raw or len(raw) < 20: |
| 78 | return None, raw |
| 79 | name = raw[20:].decode("utf-16-le").rstrip("\x00") |
| 80 | return name, raw |
| 81 | |
| 82 | |
| 83 | # ─── Основная функция ──────────────────────────────────────────────── |
| 84 | |
| 85 | |
| 86 | def extract(filepath, out_dir="hbk_streams"): |
| 87 | with open(filepath, "rb") as f: |
| 88 | data = f.read() |
| 89 | |
| 90 | magic, page_sz, version, _ = struct.unpack_from("<IIII", data, 0) |
| 91 | print(f"Файл : {filepath} ({len(data):,} байт)") |
| 92 | print(f"Magic : 0x{magic:08X}") |
| 93 | print(f"PageSize : 0x{page_sz:X}") |
| 94 | print(f"Version : 0x{version:X}") |
| 95 | |
| 96 | # ─── TOC ───────────────────────────────────────────────────────── |
| 97 | toc_raw = read_block(data, 16) # TOC идёт сразу после 16-байтного заголовка |
| 98 | if toc_raw is None: |
| 99 | sys.exit("ОШИБКА: не удалось прочитать TOC") |
| 100 | |
| 101 | n_entries = len(toc_raw) // 12 |
| 102 | print(f"TOC : {len(toc_raw)} байт → {n_entries} записей\n") |
| 103 | |
| 104 | os.makedirs(out_dir, exist_ok=True) |
| 105 | manifest = [] |
| 106 | |
| 107 | for i in range(n_entries): |
| 108 | h_addr, d_addr, _ = struct.unpack_from("<III", toc_raw, i * 12) |
| 109 | if h_addr == 0 and d_addr == 0: |
| 110 | break |
| 111 | |
| 112 | # метаданные |
| 113 | meta_raw = read_block(data, h_addr) |
| 114 | name, _ = parse_meta(meta_raw) |
| 115 | if not name: |
| 116 | name = f"stream_{i}" |
| 117 | |
| 118 | # данные |
| 119 | content = read_block(data, d_addr) |
| 120 | c_len = len(content) if content else 0 |
| 121 | |
| 122 | print(f" [{i}] {name}") |
| 123 | print(f" header @ 0x{h_addr:X} data @ 0x{d_addr:X}") |
| 124 | print(f" content : {c_len:,} байт") |
| 125 | if content: |
| 126 | print(f" head hex: {content[:32].hex(' ')}") |
| 127 | |
| 128 | # ─── сохраняем ────────────────────────────────────────────── |
| 129 | safe = name.replace("/", "_").replace("\\", "_") |
| 130 | |
| 131 | with open(os.path.join(out_dir, f"{safe}.meta"), "wb") as f: |
| 132 | f.write(meta_raw) |
| 133 | |
| 134 | if content: |
| 135 | with open(os.path.join(out_dir, safe), "wb") as f: |
| 136 | f.write(content) |
| 137 | |
| 138 | manifest.append( |
| 139 | { |
| 140 | "index": i, |
| 141 | "name": name, |
| 142 | "header_addr": h_addr, |
| 143 | "data_addr": d_addr, |
| 144 | "content_size": c_len, |
| 145 | "meta_size": len(meta_raw) if meta_raw else 0, |
| 146 | } |
| 147 | ) |
| 148 | |
| 149 | with open(os.path.join(out_dir, "_manifest.json"), "w", encoding="utf-8") as f: |
| 150 | json.dump(manifest, f, indent=2, ensure_ascii=False) |
| 151 | |
| 152 | print(f"\n{'=' * 50}") |
| 153 | print(f"Извлечено потоков: {len(manifest)}") |
| 154 | print(f"Каталог: {out_dir}/") |
| 155 | return manifest |
| 156 | |
| 157 | |
| 158 | if __name__ == "__main__": |
| 159 | fp = sys.argv[1] if len(sys.argv) > 1 else "shcntx_ru.hbk" |
| 160 | extract(fp) |
| 161 |
p2_unzip.py
· 2.2 KiB · Python
Brut
#!/usr/bin/env python3
"""
p2_unzip.py — Распаковка ZIP-потоков из извлечённых стримов
"""
import io
import os
import zipfile
STREAMS_DIR = "hbk_streams"
OUTPUT_DIR = "hbk_content"
zip_streams = ["FileStorage", "PackBlock", "IndexPackBlock"]
for name in zip_streams:
path = os.path.join(STREAMS_DIR, name)
if not os.path.exists(path):
print(f" {name} — не найден, пропуск")
continue
out_dir = os.path.join(OUTPUT_DIR, name)
os.makedirs(out_dir, exist_ok=True)
try:
with zipfile.ZipFile(path, "r") as zf:
entries = zf.namelist()
print(f"\n{'=' * 60}")
print(f" {name}: {len(entries)} файлов")
# Показать первые 20
for e in entries[:20]:
info = zf.getinfo(e)
print(f" {e} ({info.file_size:,} байт)")
if len(entries) > 20:
print(f" ... и ещё {len(entries) - 20}")
# Извлечь всё
zf.extractall(out_dir)
print(f" Извлечено в: {out_dir}/")
except zipfile.BadZipFile as e:
print(f" {name}: не ZIP или повреждён — {e}")
except Exception as e:
print(f" {name}: ошибка — {e}")
# Также копируем текстовые потоки
for name in ["Book", "MainData", "IndexMainData", "PackLookup"]:
src = os.path.join(STREAMS_DIR, name)
if os.path.exists(src):
dst_dir = os.path.join(OUTPUT_DIR, "_metadata")
os.makedirs(dst_dir, exist_ok=True)
with open(src, "rb") as f:
data = f.read()
with open(os.path.join(dst_dir, name), "wb") as f:
f.write(data)
# Показать содержимое если текстовый
try:
text = data.decode("utf-8-sig")
print(f"\n{'=' * 60}")
print(f" {name}:")
for line in text.splitlines()[:10]:
print(f" {line}")
except:
pass
print(f"\n{'=' * 60}")
print("Готово! Теперь можно редактировать файлы в hbk_content/FileStorage/")
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | p2_unzip.py — Распаковка ZIP-потоков из извлечённых стримов |
| 4 | """ |
| 5 | |
| 6 | import io |
| 7 | import os |
| 8 | import zipfile |
| 9 | |
| 10 | STREAMS_DIR = "hbk_streams" |
| 11 | OUTPUT_DIR = "hbk_content" |
| 12 | |
| 13 | zip_streams = ["FileStorage", "PackBlock", "IndexPackBlock"] |
| 14 | |
| 15 | for name in zip_streams: |
| 16 | path = os.path.join(STREAMS_DIR, name) |
| 17 | if not os.path.exists(path): |
| 18 | print(f" {name} — не найден, пропуск") |
| 19 | continue |
| 20 | |
| 21 | out_dir = os.path.join(OUTPUT_DIR, name) |
| 22 | os.makedirs(out_dir, exist_ok=True) |
| 23 | |
| 24 | try: |
| 25 | with zipfile.ZipFile(path, "r") as zf: |
| 26 | entries = zf.namelist() |
| 27 | print(f"\n{'=' * 60}") |
| 28 | print(f" {name}: {len(entries)} файлов") |
| 29 | |
| 30 | # Показать первые 20 |
| 31 | for e in entries[:20]: |
| 32 | info = zf.getinfo(e) |
| 33 | print(f" {e} ({info.file_size:,} байт)") |
| 34 | if len(entries) > 20: |
| 35 | print(f" ... и ещё {len(entries) - 20}") |
| 36 | |
| 37 | # Извлечь всё |
| 38 | zf.extractall(out_dir) |
| 39 | print(f" Извлечено в: {out_dir}/") |
| 40 | |
| 41 | except zipfile.BadZipFile as e: |
| 42 | print(f" {name}: не ZIP или повреждён — {e}") |
| 43 | except Exception as e: |
| 44 | print(f" {name}: ошибка — {e}") |
| 45 | |
| 46 | # Также копируем текстовые потоки |
| 47 | for name in ["Book", "MainData", "IndexMainData", "PackLookup"]: |
| 48 | src = os.path.join(STREAMS_DIR, name) |
| 49 | if os.path.exists(src): |
| 50 | dst_dir = os.path.join(OUTPUT_DIR, "_metadata") |
| 51 | os.makedirs(dst_dir, exist_ok=True) |
| 52 | with open(src, "rb") as f: |
| 53 | data = f.read() |
| 54 | with open(os.path.join(dst_dir, name), "wb") as f: |
| 55 | f.write(data) |
| 56 | |
| 57 | # Показать содержимое если текстовый |
| 58 | try: |
| 59 | text = data.decode("utf-8-sig") |
| 60 | print(f"\n{'=' * 60}") |
| 61 | print(f" {name}:") |
| 62 | for line in text.splitlines()[:10]: |
| 63 | print(f" {line}") |
| 64 | except: |
| 65 | pass |
| 66 | |
| 67 | print(f"\n{'=' * 60}") |
| 68 | print("Готово! Теперь можно редактировать файлы в hbk_content/FileStorage/") |
| 69 |
p3_zip.py
· 2.4 KiB · Python
Brut
#!/usr/bin/env python3
"""
p3_zip.py — Запаковка отредактированного содержимого обратно в ZIP
"""
import os
import zipfile
CONTENT_DIR = "hbk_content"
STREAMS_DIR = "hbk_streams"
zip_streams = ["FileStorage", "PackBlock", "IndexPackBlock"]
for name in zip_streams:
src_dir = os.path.join(CONTENT_DIR, name)
dst_path = os.path.join(STREAMS_DIR, name)
if not os.path.isdir(src_dir):
print(f" {name}: каталог не найден, пропуск")
continue
# Собираем список файлов
all_files = []
for root, dirs, files in os.walk(src_dir):
for fname in files:
fpath = os.path.join(root, fname)
arcname = os.path.relpath(fpath, src_dir).replace("\\", "/")
all_files.append((fpath, arcname))
# Создаём ZIP с теми же параметрами сжатия
print(f"\n {name}: запаковка {len(all_files)} файлов...")
# Сначала читаем оригинальный ZIP чтобы узнать параметры
backup_path = dst_path + ".original"
if not os.path.exists(backup_path):
# Делаем бэкап оригинала
with open(dst_path, "rb") as f:
orig_data = f.read()
with open(backup_path, "wb") as f:
f.write(orig_data)
print(f" Бэкап: {backup_path}")
# Определяем уровень сжатия из оригинала
try:
with zipfile.ZipFile(backup_path, "r") as orig_zf:
sample = orig_zf.infolist()[0]
compress_type = sample.compress_type
print(
f" Сжатие: {compress_type} "
f"({'DEFLATED' if compress_type == 8 else 'STORED'})"
)
except:
compress_type = zipfile.ZIP_DEFLATED
# Собираем новый ZIP
with zipfile.ZipFile(
dst_path, "w", compression=compress_type, compresslevel=6
) as zf:
for fpath, arcname in sorted(all_files):
zf.write(fpath, arcname)
new_size = os.path.getsize(dst_path)
orig_size = os.path.getsize(backup_path)
print(f" Оригинал: {orig_size:>12,} байт")
print(f" Новый: {new_size:>12,} байт")
diff = new_size - orig_size
print(f" Разница: {diff:>+12,} байт")
print("\nZIP-потоки перепакованы!")
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | p3_zip.py — Запаковка отредактированного содержимого обратно в ZIP |
| 4 | """ |
| 5 | |
| 6 | import os |
| 7 | import zipfile |
| 8 | |
| 9 | CONTENT_DIR = "hbk_content" |
| 10 | STREAMS_DIR = "hbk_streams" |
| 11 | |
| 12 | zip_streams = ["FileStorage", "PackBlock", "IndexPackBlock"] |
| 13 | |
| 14 | for name in zip_streams: |
| 15 | src_dir = os.path.join(CONTENT_DIR, name) |
| 16 | dst_path = os.path.join(STREAMS_DIR, name) |
| 17 | |
| 18 | if not os.path.isdir(src_dir): |
| 19 | print(f" {name}: каталог не найден, пропуск") |
| 20 | continue |
| 21 | |
| 22 | # Собираем список файлов |
| 23 | all_files = [] |
| 24 | for root, dirs, files in os.walk(src_dir): |
| 25 | for fname in files: |
| 26 | fpath = os.path.join(root, fname) |
| 27 | arcname = os.path.relpath(fpath, src_dir).replace("\\", "/") |
| 28 | all_files.append((fpath, arcname)) |
| 29 | |
| 30 | # Создаём ZIP с теми же параметрами сжатия |
| 31 | print(f"\n {name}: запаковка {len(all_files)} файлов...") |
| 32 | |
| 33 | # Сначала читаем оригинальный ZIP чтобы узнать параметры |
| 34 | backup_path = dst_path + ".original" |
| 35 | if not os.path.exists(backup_path): |
| 36 | # Делаем бэкап оригинала |
| 37 | with open(dst_path, "rb") as f: |
| 38 | orig_data = f.read() |
| 39 | with open(backup_path, "wb") as f: |
| 40 | f.write(orig_data) |
| 41 | print(f" Бэкап: {backup_path}") |
| 42 | |
| 43 | # Определяем уровень сжатия из оригинала |
| 44 | try: |
| 45 | with zipfile.ZipFile(backup_path, "r") as orig_zf: |
| 46 | sample = orig_zf.infolist()[0] |
| 47 | compress_type = sample.compress_type |
| 48 | print( |
| 49 | f" Сжатие: {compress_type} " |
| 50 | f"({'DEFLATED' if compress_type == 8 else 'STORED'})" |
| 51 | ) |
| 52 | except: |
| 53 | compress_type = zipfile.ZIP_DEFLATED |
| 54 | |
| 55 | # Собираем новый ZIP |
| 56 | with zipfile.ZipFile( |
| 57 | dst_path, "w", compression=compress_type, compresslevel=6 |
| 58 | ) as zf: |
| 59 | for fpath, arcname in sorted(all_files): |
| 60 | zf.write(fpath, arcname) |
| 61 | |
| 62 | new_size = os.path.getsize(dst_path) |
| 63 | orig_size = os.path.getsize(backup_path) |
| 64 | print(f" Оригинал: {orig_size:>12,} байт") |
| 65 | print(f" Новый: {new_size:>12,} байт") |
| 66 | diff = new_size - orig_size |
| 67 | print(f" Разница: {diff:>+12,} байт") |
| 68 | |
| 69 | print("\nZIP-потоки перепакованы!") |
| 70 |
p4_pack.py
· 3.8 KiB · Python
Brut
#!/usr/bin/env python3
"""
p4_pack.py — Финальная сборка HBK-файла
"""
import json
import os
import struct
BLOCK_HDR = 31
NO_NEXT = 0x7FFFFFFF
STREAMS_DIR = "hbk_streams"
def make_block_hdr(doc_size, page_size, next_page=NO_NEXT):
return f"\r\n{doc_size:08x} {page_size:08x} {next_page:08x} \r\n".encode("ascii")
def repack_hbk(original_file, output_file):
# Читаем оригинальный заголовок
with open(original_file, "rb") as f:
orig = f.read(16)
magic, page_sz, version, reserved = struct.unpack_from("<IIII", orig, 0)
# Загружаем манифест (порядок потоков!)
with open(os.path.join(STREAMS_DIR, "_manifest.json"), "r") as f:
manifest = json.load(f)
print(f"Потоков: {len(manifest)}")
# Загружаем потоки
streams = []
for entry in manifest:
name = entry["name"]
safe = name.replace("/", "_").replace("\\", "_")
meta_path = os.path.join(STREAMS_DIR, f"{safe}.meta")
data_path = os.path.join(STREAMS_DIR, safe)
meta = open(meta_path, "rb").read()
content = open(data_path, "rb").read()
streams.append({"name": name, "meta": meta, "content": content})
print(f" {name:20s} data={len(content):>12,} байт")
# ─── Сборка ──────────────────────────────────────────────────────
out = bytearray()
# 1. Заголовок файла (16 байт)
out += struct.pack("<IIII", magic, page_sz, version, reserved)
# 2. TOC — заголовок + данные
n = len(streams)
toc_doc_size = n * 12
toc_page_size = ((toc_doc_size + page_sz - 1) // page_sz) * page_sz
toc_hdr_pos = len(out)
# Заголовок TOC (заполним позже)
out += b"\x00" * BLOCK_HDR
# Данные TOC (заполним позже)
toc_data_pos = len(out)
out += b"\x00" * toc_page_size
# 3. Потоки
positions = []
for stream in streams:
meta = stream["meta"]
content = stream["content"]
# Метаданные
h_pos = len(out)
meta_page_sz = ((len(meta) + page_sz - 1) // page_sz) * page_sz
out += make_block_hdr(len(meta), meta_page_sz)
out += meta
# Дополнение до page_size
pad = meta_page_sz - len(meta)
if pad > 0:
out += b"\x00" * pad
# Данные
d_pos = len(out)
data_page_sz = ((len(content) + page_sz - 1) // page_sz) * page_sz
out += make_block_hdr(len(content), data_page_sz)
out += content
pad = data_page_sz - len(content)
if pad > 0:
out += b"\x00" * pad
positions.append((h_pos, d_pos))
# 4. Заполняем TOC
toc_bytes = bytearray()
for h_pos, d_pos in positions:
toc_bytes += struct.pack("<III", h_pos, d_pos, NO_NEXT)
# Заголовок TOC
out[toc_hdr_pos : toc_hdr_pos + BLOCK_HDR] = make_block_hdr(
toc_doc_size, toc_page_size
)
# Данные TOC
out[toc_data_pos : toc_data_pos + len(toc_bytes)] = toc_bytes
# ─── Сохраняем ───────────────────────────────────────────────────
with open(output_file, "wb") as f:
f.write(out)
orig_sz = os.path.getsize(original_file)
new_sz = len(out)
print(f"\n{'=' * 50}")
print(f"Оригинал: {orig_sz:>12,} байт")
print(f"Новый: {new_sz:>12,} байт")
print(f"Разница: {new_sz - orig_sz:>+12,} байт")
print(f"Сохранён: {output_file}")
if __name__ == "__main__":
repack_hbk("shcntx_ru.hbk", "shcntx_ru_new.hbk")
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | p4_pack.py — Финальная сборка HBK-файла |
| 4 | """ |
| 5 | |
| 6 | import json |
| 7 | import os |
| 8 | import struct |
| 9 | |
| 10 | BLOCK_HDR = 31 |
| 11 | NO_NEXT = 0x7FFFFFFF |
| 12 | STREAMS_DIR = "hbk_streams" |
| 13 | |
| 14 | |
| 15 | def make_block_hdr(doc_size, page_size, next_page=NO_NEXT): |
| 16 | return f"\r\n{doc_size:08x} {page_size:08x} {next_page:08x} \r\n".encode("ascii") |
| 17 | |
| 18 | |
| 19 | def repack_hbk(original_file, output_file): |
| 20 | # Читаем оригинальный заголовок |
| 21 | with open(original_file, "rb") as f: |
| 22 | orig = f.read(16) |
| 23 | |
| 24 | magic, page_sz, version, reserved = struct.unpack_from("<IIII", orig, 0) |
| 25 | |
| 26 | # Загружаем манифест (порядок потоков!) |
| 27 | with open(os.path.join(STREAMS_DIR, "_manifest.json"), "r") as f: |
| 28 | manifest = json.load(f) |
| 29 | |
| 30 | print(f"Потоков: {len(manifest)}") |
| 31 | |
| 32 | # Загружаем потоки |
| 33 | streams = [] |
| 34 | for entry in manifest: |
| 35 | name = entry["name"] |
| 36 | safe = name.replace("/", "_").replace("\\", "_") |
| 37 | |
| 38 | meta_path = os.path.join(STREAMS_DIR, f"{safe}.meta") |
| 39 | data_path = os.path.join(STREAMS_DIR, safe) |
| 40 | |
| 41 | meta = open(meta_path, "rb").read() |
| 42 | content = open(data_path, "rb").read() |
| 43 | |
| 44 | streams.append({"name": name, "meta": meta, "content": content}) |
| 45 | print(f" {name:20s} data={len(content):>12,} байт") |
| 46 | |
| 47 | # ─── Сборка ────────────────────────────────────────────────────── |
| 48 | out = bytearray() |
| 49 | |
| 50 | # 1. Заголовок файла (16 байт) |
| 51 | out += struct.pack("<IIII", magic, page_sz, version, reserved) |
| 52 | |
| 53 | # 2. TOC — заголовок + данные |
| 54 | n = len(streams) |
| 55 | toc_doc_size = n * 12 |
| 56 | toc_page_size = ((toc_doc_size + page_sz - 1) // page_sz) * page_sz |
| 57 | |
| 58 | toc_hdr_pos = len(out) |
| 59 | # Заголовок TOC (заполним позже) |
| 60 | out += b"\x00" * BLOCK_HDR |
| 61 | # Данные TOC (заполним позже) |
| 62 | toc_data_pos = len(out) |
| 63 | out += b"\x00" * toc_page_size |
| 64 | |
| 65 | # 3. Потоки |
| 66 | positions = [] |
| 67 | for stream in streams: |
| 68 | meta = stream["meta"] |
| 69 | content = stream["content"] |
| 70 | |
| 71 | # Метаданные |
| 72 | h_pos = len(out) |
| 73 | meta_page_sz = ((len(meta) + page_sz - 1) // page_sz) * page_sz |
| 74 | out += make_block_hdr(len(meta), meta_page_sz) |
| 75 | out += meta |
| 76 | # Дополнение до page_size |
| 77 | pad = meta_page_sz - len(meta) |
| 78 | if pad > 0: |
| 79 | out += b"\x00" * pad |
| 80 | |
| 81 | # Данные |
| 82 | d_pos = len(out) |
| 83 | data_page_sz = ((len(content) + page_sz - 1) // page_sz) * page_sz |
| 84 | out += make_block_hdr(len(content), data_page_sz) |
| 85 | out += content |
| 86 | pad = data_page_sz - len(content) |
| 87 | if pad > 0: |
| 88 | out += b"\x00" * pad |
| 89 | |
| 90 | positions.append((h_pos, d_pos)) |
| 91 | |
| 92 | # 4. Заполняем TOC |
| 93 | toc_bytes = bytearray() |
| 94 | for h_pos, d_pos in positions: |
| 95 | toc_bytes += struct.pack("<III", h_pos, d_pos, NO_NEXT) |
| 96 | |
| 97 | # Заголовок TOC |
| 98 | out[toc_hdr_pos : toc_hdr_pos + BLOCK_HDR] = make_block_hdr( |
| 99 | toc_doc_size, toc_page_size |
| 100 | ) |
| 101 | |
| 102 | # Данные TOC |
| 103 | out[toc_data_pos : toc_data_pos + len(toc_bytes)] = toc_bytes |
| 104 | |
| 105 | # ─── Сохраняем ─────────────────────────────────────────────────── |
| 106 | with open(output_file, "wb") as f: |
| 107 | f.write(out) |
| 108 | |
| 109 | orig_sz = os.path.getsize(original_file) |
| 110 | new_sz = len(out) |
| 111 | print(f"\n{'=' * 50}") |
| 112 | print(f"Оригинал: {orig_sz:>12,} байт") |
| 113 | print(f"Новый: {new_sz:>12,} байт") |
| 114 | print(f"Разница: {new_sz - orig_sz:>+12,} байт") |
| 115 | print(f"Сохранён: {output_file}") |
| 116 | |
| 117 | |
| 118 | if __name__ == "__main__": |
| 119 | repack_hbk("shcntx_ru.hbk", "shcntx_ru_new.hbk") |
| 120 |