Guest User

Untitled

a guest
Sep 25th, 2025
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.70 KB | None | 0 0
  1. import sqlite3
  2. import os
  3. import struct
  4. from pathlib import Path
  5. import tkinter as tk
  6. from tkinter import ttk, filedialog, messagebox
  7. import pandas as pd
  8.  
  9.  
  10. class ParadoxTableApp:
  11. def __init__(self, root):
  12. self.root = root
  13. self.root.title("Paradox Database Viewer")
  14. self.root.geometry("900x600")
  15.  
  16. self.paradox_path = r"C:\вааш путь к файлу БД"
  17. self.db_filename = None
  18. self.column_names = []
  19.  
  20. self.setup_ui()
  21.  
  22. def setup_ui(self):
  23. # Main frame
  24. main_frame = ttk.Frame(self.root, padding="10")
  25. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  26.  
  27. # Title
  28. title_label = ttk.Label(main_frame, text="Paradox Database Viewer",
  29. font=('Arial', 16, 'bold'))
  30. title_label.grid(row=0, column=0, columnspan=2, pady=(0, 10))
  31.  
  32. # Buttons frame
  33. button_frame = ttk.Frame(main_frame)
  34. button_frame.grid(row=1, column=0, columnspan=2, pady=(0, 10))
  35.  
  36. # Load data button
  37. self.load_btn = ttk.Button(button_frame, text="Загрузить данные",
  38. command=self.load_data)
  39. self.load_btn.pack(side=tk.LEFT, padx=(0, 10))
  40.  
  41. # Export to Excel button
  42. self.export_btn = ttk.Button(button_frame, text="Экспорт в Excel",
  43. command=self.export_to_excel,
  44. state=tk.DISABLED)
  45. self.export_btn.pack(side=tk.LEFT)
  46.  
  47. # Treeview для отображения данных
  48. self.tree = ttk.Treeview(main_frame, show='headings')
  49. self.tree.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  50.  
  51. # Scrollbars
  52. v_scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.tree.yview)
  53. v_scrollbar.grid(row=2, column=1, sticky=(tk.N, tk.S))
  54. self.tree.configure(yscrollcommand=v_scrollbar.set)
  55.  
  56. h_scrollbar = ttk.Scrollbar(main_frame, orient=tk.HORIZONTAL, command=self.tree.xview)
  57. h_scrollbar.grid(row=3, column=0, sticky=(tk.W, tk.E))
  58. self.tree.configure(xscrollcommand=h_scrollbar.set)
  59.  
  60. # Status label
  61. self.status_label = ttk.Label(main_frame, text="Нажмите 'Загрузить данные' для начала")
  62. self.status_label.grid(row=4, column=0, columnspan=2, pady=(10, 0))
  63.  
  64. # Configure grid weights
  65. self.root.columnconfigure(0, weight=1)
  66. self.root.rowconfigure(0, weight=1)
  67. main_frame.columnconfigure(0, weight=1)
  68. main_frame.rowconfigure(2, weight=1)
  69.  
  70. def decode_cp866(self, data):
  71. """Декодируем из cp866 в UTF-8"""
  72. try:
  73. if isinstance(data, bytes):
  74. decoded = data.decode('cp866', errors='ignore')
  75. cleaned = ''.join(char for char in decoded if char.isprintable() or char in ' \t\n\r')
  76. return cleaned.strip()
  77. return str(data)
  78. except:
  79. return str(data)
  80.  
  81. def find_paradox_file(self):
  82. """Находим файл Paradox"""
  83. possible_files = [
  84. "TABLE.db", "TABLE.DB", "TABLE.dbf", "TABLE.DBF",
  85. "TABLE.mb", "TABLE.MB", "TABLE", "SKLAD1", "SKLAD1.db"
  86. ]
  87.  
  88. base_dir = os.path.dirname(self.paradox_path)
  89.  
  90. for filename in possible_files:
  91. file_path = os.path.join(base_dir, filename)
  92. if os.path.exists(file_path):
  93. return file_path
  94.  
  95. return None
  96.  
  97. def parse_paradox_structure(self, file_path):
  98. """Анализирует структуру Paradox файла для определения количества столбцов"""
  99. try:
  100. with open(file_path, 'rb') as f:
  101. header = f.read(1024)
  102.  
  103. f.seek(0)
  104. data = f.read(4096)
  105.  
  106. records = self.extract_records_with_dynamic_columns(data)
  107.  
  108. if records:
  109. # Определяем максимальное количество столбцов в записях
  110. max_columns = max(len(record) for record in records if record)
  111. return max_columns, records
  112. else:
  113. return 0, []
  114.  
  115. except Exception as e:
  116. print(f"Ошибка анализа структуры: {e}")
  117. return 0, []
  118.  
  119. def extract_records_with_dynamic_columns(self, data):
  120. """Извлекает записи с динамическим определением количества столбцов"""
  121. records = []
  122. pos = 0
  123. record_count = 0
  124.  
  125. while pos < len(data) - 10 and record_count < 100:
  126. while pos < len(data) and data[pos] == 0:
  127. pos += 1
  128.  
  129. if pos >= len(data) - 1:
  130. break
  131.  
  132. record_start = pos
  133. record_fields = []
  134. field_start = pos
  135.  
  136. # Парсим запись до маркера конца записи или значительного перерыва
  137. while pos < len(data) - 1:
  138. # Если встречаем последовательность нулей - возможный разделитель полей
  139. if data[pos] == 0 and data[pos + 1] == 0:
  140. # Извлекаем поле
  141. if pos > field_start:
  142. field_data = data[field_start:pos]
  143. decoded_field = self.decode_cp866(field_data)
  144. if decoded_field and decoded_field.strip():
  145. record_fields.append(decoded_field.strip())
  146.  
  147. field_start = pos + 2
  148. pos += 1
  149.  
  150. # Если встречаем непечатаемый символ - возможный разделитель
  151. elif data[pos] < 32 and data[pos] != 9 and data[pos] != 10 and data[pos] != 13:
  152. if pos > field_start and pos - field_start > 1:
  153. field_data = data[field_start:pos]
  154. decoded_field = self.decode_cp866(field_data)
  155. if decoded_field and decoded_field.strip():
  156. record_fields.append(decoded_field.strip())
  157.  
  158. field_start = pos + 1
  159.  
  160. pos += 1
  161.  
  162. # Критерий окончания записи: много нулей подряд или достигли лимита
  163. if pos - record_start > 200: # Максимальная длина записи
  164. break
  165.  
  166. # Добавляем последнее поле
  167. if pos > field_start:
  168. field_data = data[field_start:pos]
  169. decoded_field = self.decode_cp866(field_data)
  170. if decoded_field and decoded_field.strip():
  171. record_fields.append(decoded_field.strip())
  172.  
  173. if record_fields:
  174. records.append(record_fields)
  175. record_count += 1
  176.  
  177. # Пропускаем возможные разделители между записями
  178. while pos < len(data) and data[pos] == 0:
  179. pos += 1
  180.  
  181. return records
  182.  
  183. def create_dynamic_sqlite_table(self, records):
  184. """Создает SQLite таблицу с динамическими столбцами"""
  185. if not records:
  186. return None
  187.  
  188.  
  189. max_columns = max(len(record) for record in records)
  190. self.column_names = [f"Колонка_{i + 1}" for i in range(max_columns)]
  191.  
  192. self.db_filename = "paradox_temp.db"
  193.  
  194. if os.path.exists(self.db_filename):
  195. os.remove(self.db_filename)
  196.  
  197. conn = sqlite3.connect(self.db_filename)
  198. cursor = conn.cursor()
  199.  
  200. columns_sql = ", ".join([f'"{col}" TEXT' for col in self.column_names])
  201. create_table_sql = f'CREATE TABLE paradox_data (id INTEGER PRIMARY KEY AUTOINCREMENT, {columns_sql})'
  202.  
  203. cursor.execute(create_table_sql)
  204.  
  205. for record in records:
  206. padded_record = record + [""] * (max_columns - len(record))
  207. placeholders = ", ".join(["?"] * len(padded_record))
  208. insert_sql = f'INSERT INTO paradox_data ({", ".join([f"[{col}]" for col in self.column_names])}) VALUES ({placeholders})'
  209.  
  210. cursor.execute(insert_sql, padded_record)
  211.  
  212. conn.commit()
  213.  
  214.  
  215. cursor.execute('''
  216. CREATE TABLE table_info (
  217. table_name TEXT,
  218. columns_count INTEGER,
  219. rows_count INTEGER
  220. )
  221. ''')
  222.  
  223. cursor.execute('''
  224. INSERT INTO table_info (table_name, columns_count, rows_count)
  225. VALUES (?, ?, ?)
  226. ''', ('paradox_data', max_columns, len(records)))
  227.  
  228. conn.commit()
  229. conn.close()
  230.  
  231. return self.db_filename
  232.  
  233. def load_data(self):
  234. """Загружаем данные из Paradox файла"""
  235. self.status_label.config(text="Поиск Paradox файла...")
  236. self.root.update()
  237.  
  238. file_path = self.find_paradox_file()
  239. if not file_path:
  240. messagebox.showerror("Ошибка", "Paradox файл не найден")
  241. self.status_label.config(text="Файл не найден")
  242. return
  243.  
  244. self.status_label.config(text="Анализ структуры данных...")
  245. self.root.update()
  246.  
  247. columns_count, records = self.parse_paradox_structure(file_path)
  248.  
  249. if columns_count == 0 or not records:
  250. messagebox.showerror("Ошибка", "Не удалось определить структуру данных")
  251. self.status_label.config(text="Ошибка анализа структуры")
  252. return
  253.  
  254. self.status_label.config(text=f"Найдено столбцов: {columns_count}, записей: {len(records)}")
  255. self.root.update()
  256.  
  257. self.status_label.config(text="Создание базы данных...")
  258. self.root.update()
  259.  
  260. self.create_dynamic_sqlite_table(records)
  261.  
  262. self.display_data()
  263. self.export_btn.config(state=tk.NORMAL)
  264. self.status_label.config(text=f"Загружено: {len(records)} записей, {columns_count} столбцов")
  265.  
  266. def display_data(self):
  267. """Отображаем данные в таблице"""
  268. if not self.db_filename or not os.path.exists(self.db_filename):
  269. return
  270.  
  271. conn = sqlite3.connect(self.db_filename)
  272. cursor = conn.cursor()
  273.  
  274. try:
  275. cursor.execute("SELECT columns_count, rows_count FROM table_info")
  276. info = cursor.fetchone()
  277.  
  278. if info:
  279. columns_count, rows_count = info
  280. self.status_label.config(text=f"Столбцов: {columns_count}, Записей: {rows_count}")
  281.  
  282. cursor.execute("PRAGMA table_info(paradox_data)")
  283. columns_info = cursor.fetchall()
  284. actual_columns = [col[1] for col in columns_info if col[1] != 'id']
  285.  
  286. cursor.execute(f"SELECT {', '.join([f'[{col}]' for col in actual_columns])} FROM paradox_data")
  287. rows = cursor.fetchall()
  288.  
  289. for item in self.tree.get_children():
  290. self.tree.delete(item)
  291.  
  292. self.tree['columns'] = actual_columns
  293. for col in actual_columns:
  294. self.tree.heading(col, text=col)
  295. self.tree.column(col, width=150, minwidth=100, stretch=tk.YES)
  296.  
  297. for row in rows:
  298. self.tree.insert('', tk.END, values=row)
  299.  
  300. except Exception as e:
  301. messagebox.showerror("Ошибка", f"Ошибка отображения данных: {e}")
  302. finally:
  303. cursor.close()
  304. conn.close()
  305.  
  306. def export_to_excel(self):
  307. """Экспортируем данные в Excel"""
  308. if not self.db_filename or not os.path.exists(self.db_filename):
  309. messagebox.showerror("Ошибка", "Нет данных для экспорта")
  310. return
  311.  
  312. file_path = filedialog.asksaveasfilename(
  313. defaultextension=".xlsx",
  314. filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
  315. title="Сохранить как Excel файл"
  316. )
  317.  
  318. if not file_path:
  319. return
  320.  
  321. try:
  322. conn = sqlite3.connect(self.db_filename)
  323.  
  324. cursor = conn.cursor()
  325. cursor.execute("PRAGMA table_info(paradox_data)")
  326. columns_info = cursor.fetchall()
  327. actual_columns = [col[1] for col in columns_info if col[1] != 'id']
  328.  
  329. columns_sql = ', '.join([f'[{col}]' for col in actual_columns])
  330. df = pd.read_sql_query(f"SELECT {columns_sql} FROM paradox_data", conn)
  331. df.to_excel(file_path, index=False, engine='openpyxl')
  332. conn.close()
  333.  
  334. messagebox.showinfo("Успех", f"Данные экспортированы в:\n{file_path}")
  335.  
  336. except Exception as e:
  337. messagebox.showerror("Ошибка", f"Ошибка экспорта: {e}")
  338.  
  339. def __del__(self):
  340. """Очистка временных файлов при закрытии"""
  341. if self.db_filename and os.path.exists(self.db_filename):
  342. try:
  343. os.remove(self.db_filename)
  344. except:
  345. pass
  346.  
  347.  
  348. def main():
  349. root = tk.Tk()
  350. app = ParadoxTableApp(root)
  351. root.mainloop()
  352.  
  353.  
  354. if __name__ == "__main__":
  355. main()
Advertisement
Add Comment
Please, Sign In to add comment