最后活跃于 6 days ago

Редактирование файлов справки синтаксис-помощника 1С

README.md 原始文件

Редактирование файлов справки синтаксис-помощника 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 и замените оригинальный файл

Код был сгенерирован с помощью ИИ

e1_analyze.py 原始文件
1#!/usr/bin/env python3
2"""
3e1_analyze.py — Анализирует извлечённые потоки: сжатие, тип данных
4"""
5
6import os
7import re
8import zlib
9
10STREAMS_DIR = "hbk_streams"
11
12for 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#!/usr/bin/env python3
2"""
3e2_explore.py — Обзор содержимого FileStorage
4"""
5
6import os
7
8CONTENT_DIR = os.path.join("hbk_content", "FileStorage")
9
10html_files = []
11other_files = []
12
13for 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
24print(f"HTML-файлов: {len(html_files)}")
25print(f"Других файлов: {len(other_files)}")
26print(f"\nПримеры HTML:")
27for path, size in sorted(html_files)[:20]:
28 print(f" {path} ({size:,} Б)")
29
30if other_files:
31 print(f"\nДругие файлы:")
32 for path, size in sorted(other_files)[:20]:
33 print(f" {path} ({size:,} Б)")
34
35# Показать содержимое первого HTML
36if 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#!/usr/bin/env python3
2"""
3e3_find_example.py — Найти файлы с примерами кода, чтобы узнать разметку
4"""
5
6import os
7
8CONTENT_DIR = os.path.join("hbk_content", "FileStorage")
9
10keywords = [
11 b"V8SH_example",
12 b"V8SH_code",
13 b'class="code"',
14 b"<pre",
15 b"Example",
16 "Пример".encode("utf-8"),
17]
18
19for 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 原始文件
1#!/usr/bin/env python3
2"""
3e4_code_format.py — Генератор HTML-таблицы с подсветкой кода 1С
4в стиле встроенной справки 1С:Предприятие
5
6Результат копируется в буфер обмена и сохраняется в файл.
7"""
8
9# ═══════════════════════════════════════════════════════════════
10# ВВЕДИТЕ КОД 1С ЗДЕСЬ:
11# ═══════════════════════════════════════════════════════════════
12
13CODE_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
47KEYWORDS = {
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
121CONTEXT_DIRECTIVES = {
122 "&НаКлиенте",
123 "&НаСервере",
124 "&НаКлиентеНаСервере",
125 "&НаСервереБезКонтекста",
126 "&AtClient",
127 "&AtServer",
128 "&AtClientAtServer",
129 "&AtServerNoContext",
130}
131
132COLORS = {
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
143OPERATORS = set("=+-*/<>().,;[]%?")
144
145# ═══════════════════════════════════════════════════════════════
146# Лексер
147# ═══════════════════════════════════════════════════════════════
148
149
150def 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
270def escape(text):
271 return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
272
273
274def nbsp(text):
275 return text.replace(" ", "&nbsp;")
276
277
278def 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
288def 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
304if __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 原始文件
1#!/usr/bin/env python3
2"""
3e5_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
12import os
13import re
14import sys
15
16# Теги, после открывающего/закрывающего которых ставим перенос
17BLOCK_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# Теги, содержимое которых не трогаем
60PRESERVE_TAGS = {"pre", "textarea", "script", "style"}
61
62
63def 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
115if __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 原始文件
1#!/usr/bin/env python3
2"""
3e6_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
12import os
13import re
14import sys
15
16
17def 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
53if __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 原始文件
1#!/usr/bin/env python3
2"""
3p1_unpack.py — Извлечение потоков из контейнера 1С v8 (.hbk)
4"""
5
6import json
7import os
8import struct
9import sys
10
11BLOCK_HDR = 31 # размер блочного адреса
12NO_NEXT = 0x7FFFFFFF # нет следующей страницы
13
14
15# ─── Чтение блочного адреса ──────────────────────────────────────────
16
17
18def 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
39def 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
69def 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
86def 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
158if __name__ == "__main__":
159 fp = sys.argv[1] if len(sys.argv) > 1 else "shcntx_ru.hbk"
160 extract(fp)
161
p2_unzip.py 原始文件
1#!/usr/bin/env python3
2"""
3p2_unzip.py — Распаковка ZIP-потоков из извлечённых стримов
4"""
5
6import io
7import os
8import zipfile
9
10STREAMS_DIR = "hbk_streams"
11OUTPUT_DIR = "hbk_content"
12
13zip_streams = ["FileStorage", "PackBlock", "IndexPackBlock"]
14
15for 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# Также копируем текстовые потоки
47for 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
67print(f"\n{'=' * 60}")
68print("Готово! Теперь можно редактировать файлы в hbk_content/FileStorage/")
69
p3_zip.py 原始文件
1#!/usr/bin/env python3
2"""
3p3_zip.py — Запаковка отредактированного содержимого обратно в ZIP
4"""
5
6import os
7import zipfile
8
9CONTENT_DIR = "hbk_content"
10STREAMS_DIR = "hbk_streams"
11
12zip_streams = ["FileStorage", "PackBlock", "IndexPackBlock"]
13
14for 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
69print("\nZIP-потоки перепакованы!")
70
p4_pack.py 原始文件
1#!/usr/bin/env python3
2"""
3p4_pack.py — Финальная сборка HBK-файла
4"""
5
6import json
7import os
8import struct
9
10BLOCK_HDR = 31
11NO_NEXT = 0x7FFFFFFF
12STREAMS_DIR = "hbk_streams"
13
14
15def 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
19def 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
118if __name__ == "__main__":
119 repack_hbk("shcntx_ru.hbk", "shcntx_ru_new.hbk")
120