ms-file-toolkit 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ms_file_toolkit-0.4.0.dist-info/METADATA +156 -0
- ms_file_toolkit-0.4.0.dist-info/RECORD +20 -0
- ms_file_toolkit-0.4.0.dist-info/WHEEL +5 -0
- ms_file_toolkit-0.4.0.dist-info/entry_points.txt +2 -0
- ms_file_toolkit-0.4.0.dist-info/top_level.txt +1 -0
- ms_toolkit/__init__.py +5 -0
- ms_toolkit/auto_tool.py +61 -0
- ms_toolkit/cli.py +61 -0
- ms_toolkit/docx_tool.py +41 -0
- ms_toolkit/docx_writer.py +141 -0
- ms_toolkit/legacy_tool.py +114 -0
- ms_toolkit/pdf_tool.py +56 -0
- ms_toolkit/pdf_writer.py +77 -0
- ms_toolkit/pptx_tool.py +38 -0
- ms_toolkit/pptx_writer.py +172 -0
- ms_toolkit/registry.py +119 -0
- ms_toolkit/schemas.py +510 -0
- ms_toolkit/security.py +57 -0
- ms_toolkit/xlsx_tool.py +47 -0
- ms_toolkit/xlsx_writer.py +186 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ms-file-toolkit
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Microsoft fayllarni (DOCX/XLSX/PPTX) o'qish va yozish uchun AI function-calling Python toolkit
|
|
5
|
+
Author: muslihiddinlive
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/muslihiddinlive/ms-file-toolkit
|
|
8
|
+
Project-URL: Repository, https://github.com/muslihiddinlive/ms-file-toolkit
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: python-docx>=1.1.0
|
|
12
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
13
|
+
Requires-Dist: python-pptx>=0.6.23
|
|
14
|
+
Requires-Dist: Pillow>=10.0.0
|
|
15
|
+
Requires-Dist: pypdf>=4.0.0
|
|
16
|
+
Requires-Dist: pdfplumber>=0.11.0
|
|
17
|
+
Requires-Dist: reportlab>=4.0.0
|
|
18
|
+
Provides-Extra: ai
|
|
19
|
+
Requires-Dist: anthropic>=0.40.0; extra == "ai"
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# ms-file-toolkit
|
|
24
|
+
|
|
25
|
+
Microsoft fayllarni va PDF'larni **o'qish va yozish** uchun Python toolkit — **AI function calling** (Claude, GPT va h.k.) uchun tayyor tool schemalari, **xavfsizlik nazorati**, **avtomatik format aniqlash** va **CLI** bilan.
|
|
26
|
+
|
|
27
|
+
## O'rnatish
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install -r requirements.txt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Yoki paket sifatida (PyPI'ga chiqarishga tayyor, CLI bilan birga o'rnatiladi):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install -e ".[ai,dev]"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Qo'llab-quvvatlanadigan formatlar
|
|
40
|
+
|
|
41
|
+
| Format | O'qish | Yozish |
|
|
42
|
+
| ------------------------------ | ------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
43
|
+
| `.docx` | matn, jadvallar, metama'lumot | yaratish, matn qo'shish/almashtirish, sarlavha, rasm, sahifa bo'linishi |
|
|
44
|
+
| `.xlsx` | varaq nomlari, ma'lumotlar, o'lcham xulosasi | yaratish, varaq/qator qo'shish, formula, formatlash, panellarni muzlatish, **diagramma** |
|
|
45
|
+
| `.pptx` | slayd matnlari, slaydlar soni, notes | yaratish, slayd/rasm qo'shish, fon rangi, **diagramma** |
|
|
46
|
+
| `.pdf` | matn, jadvallar, metama'lumot | yaratish, birlashtirish (merge), bo'lish (split) |
|
|
47
|
+
| `.doc` / `.xls` / `.ppt` (eski) | `read_legacy_file` — LibreOffice orqali | — |
|
|
48
|
+
| **Har qanday (avtomatik)** | `read_any_file`, `get_file_info` | — |
|
|
49
|
+
|
|
50
|
+
> Eski formatlar uchun tizimda **LibreOffice** (`soffice`) kerak: `apt-get install libreoffice`
|
|
51
|
+
|
|
52
|
+
## Xavfsizlik
|
|
53
|
+
|
|
54
|
+
Fayl yo'llari AI tomonidan tanlanadi, shuning uchun **path traversal** himoyasi o'rnatilgan (`ms_toolkit/security.py`):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export MS_TOOLKIT_BASE_DIR=/home/bot/user_files # faqat shu papka ichidagi fayllarga ruxsat
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
dispatch("read_docx_text", {"file_path": "../../etc/passwd"})
|
|
62
|
+
# -> {"error": "Xavfsizlik xatosi: ..."}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## CLI (terminal orqali)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
ms-toolkit list # barcha tool'lar ro'yxati
|
|
69
|
+
ms-toolkit run create_docx '{"file_path": "a.docx", "title": "Salom", "overwrite": true}'
|
|
70
|
+
ms-toolkit run read_any_file '{"file_path": "hujjat.xlsx"}'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Tez boshlash (API kalitisiz)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install -e ".[dev]"
|
|
77
|
+
pytest tests/ -v # 23 ta test
|
|
78
|
+
python test_toolkit.py # tezkor smoke-test
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Function calling bilan (Claude API)
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from ms_toolkit import TOOLS, dispatch
|
|
85
|
+
import anthropic
|
|
86
|
+
|
|
87
|
+
client = anthropic.Anthropic()
|
|
88
|
+
response = client.messages.create(
|
|
89
|
+
model="claude-sonnet-5",
|
|
90
|
+
max_tokens=1024,
|
|
91
|
+
tools=TOOLS,
|
|
92
|
+
messages=[{"role": "user", "content": "test.xlsx faylida nechta qator bor?"}],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
for block in response.content:
|
|
96
|
+
if block.type == "tool_use":
|
|
97
|
+
print(dispatch(block.name, block.input))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
To'liq agentic misol: [`example.py`](example.py).
|
|
101
|
+
|
|
102
|
+
## Diagramma qo'shish misoli
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from ms_toolkit import dispatch
|
|
106
|
+
|
|
107
|
+
dispatch("add_xlsx_chart", {
|
|
108
|
+
"file_path": "hisobot.xlsx", "chart_type": "bar",
|
|
109
|
+
"data_range": "B1:B5", "categories_range": "A2:A5", "title": "Oylik savdo",
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
dispatch("add_pptx_chart", {
|
|
113
|
+
"file_path": "taqdimot.pptx", "chart_type": "pie",
|
|
114
|
+
"categories": ["A", "B", "C"], "series": {"2026": [40, 35, 25]},
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Barcha tool'lar ro'yxati (36 ta)
|
|
119
|
+
|
|
120
|
+
**DOCX (9):** `read_docx_text`, `read_docx_tables`, `get_docx_metadata`, `create_docx`, `append_docx_text`, `replace_docx_text`, `add_docx_heading`, `add_docx_image`, `add_docx_page_break`
|
|
121
|
+
**XLSX (10):** `get_xlsx_sheet_names`, `read_xlsx_data`, `get_xlsx_summary`, `create_xlsx`, `add_xlsx_sheet`, `append_xlsx_rows`, `set_xlsx_formula`, `format_xlsx_cells`, `freeze_xlsx_panes`, `add_xlsx_chart`
|
|
122
|
+
**PPTX (8):** `read_pptx_text`, `get_pptx_slide_count`, `extract_pptx_notes`, `create_pptx`, `add_pptx_slide`, `add_pptx_image`, `set_pptx_background_color`, `add_pptx_chart`
|
|
123
|
+
**PDF (6):** `read_pdf_text`, `read_pdf_tables`, `get_pdf_metadata`, `create_pdf`, `merge_pdfs`, `split_pdf`
|
|
124
|
+
**Eski formatlar (1):** `read_legacy_file`
|
|
125
|
+
**Auto-detect (2):** `read_any_file`, `get_file_info`
|
|
126
|
+
|
|
127
|
+
## Struktura
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
ms_toolkit/
|
|
131
|
+
__init__.py # TOOLS, dispatch export qiladi
|
|
132
|
+
docx_tool.py / docx_writer.py # Word
|
|
133
|
+
xlsx_tool.py / xlsx_writer.py # Excel (+ chart)
|
|
134
|
+
pptx_tool.py / pptx_writer.py # PowerPoint (+ chart)
|
|
135
|
+
pdf_tool.py / pdf_writer.py # PDF
|
|
136
|
+
legacy_tool.py # Eski formatlar
|
|
137
|
+
auto_tool.py # Avtomatik format aniqlash
|
|
138
|
+
security.py # Path-traversal himoyasi
|
|
139
|
+
cli.py # Terminal interfeysi (ms-toolkit)
|
|
140
|
+
schemas.py # AI uchun tool schemalari
|
|
141
|
+
registry.py # tool_name -> funksiya dispatcher
|
|
142
|
+
example.py, test_toolkit.py, tests/ # Misollar va testlar
|
|
143
|
+
pyproject.toml # pip/PyPI paketlash + CLI entry point
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## PyPI'ga chiqarish
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pip install build twine
|
|
150
|
+
python -m build
|
|
151
|
+
twine upload dist/*
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Litsenziya
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
ms_toolkit/__init__.py,sha256=Z1W5C3-JR1AOKaG7CjW5d7UhRjALO3GdQG1GD5fh9eY,135
|
|
2
|
+
ms_toolkit/auto_tool.py,sha256=RPVPiRLxpJ9dhAETGQ-XdZPetSNmauyk3WOIevVFUIA,1898
|
|
3
|
+
ms_toolkit/cli.py,sha256=t9oacvxJNHdj2gyDP4md7g4IcpNrqcNzfZ8qPL1MoA8,1919
|
|
4
|
+
ms_toolkit/docx_tool.py,sha256=HlI_HHan6Yij2rc61vMuuMVq-HLor_Q5wHYDv1VsiHU,1370
|
|
5
|
+
ms_toolkit/docx_writer.py,sha256=qiCxW9Y-sIoBaIVx9TKPILCY9KceD_GMeEqLy34nlhQ,4786
|
|
6
|
+
ms_toolkit/legacy_tool.py,sha256=20Lg_Zi203KqqaGzLV7DO5qAzZk5IqlVPtll3IKnwzw,3703
|
|
7
|
+
ms_toolkit/pdf_tool.py,sha256=3eTVF-NkwnEMwqKw1t5_v9-0jtqWbhDNzXHJvuHz82Q,1892
|
|
8
|
+
ms_toolkit/pdf_writer.py,sha256=9obBykLonfvAUM7HikKeCuGxsuawCKSjJU9-QLrHpWo,2742
|
|
9
|
+
ms_toolkit/pptx_tool.py,sha256=5ylNTcJO0xuuwTuMidvqw3hIuidBY_6iCQh5DmZHDqw,1450
|
|
10
|
+
ms_toolkit/pptx_writer.py,sha256=8nsC2f-SorRadnc_gDv24vVXDXzSUv8dNVsvYAUxxrs,5903
|
|
11
|
+
ms_toolkit/registry.py,sha256=aERykycNpWDMRbY67ZxlmNgn0g0I3njfpJ3h_Y5VkhI,4775
|
|
12
|
+
ms_toolkit/schemas.py,sha256=fcSXwmh2uJJrCpTY3_CEtBB9ursF_-C0WAIAIxTjznM,22530
|
|
13
|
+
ms_toolkit/security.py,sha256=kFeM28aSZvDd_lWH-XHG6yInueAOzN_5xvcw0cbOPTU,2158
|
|
14
|
+
ms_toolkit/xlsx_tool.py,sha256=olL3fRFMVene9K3eTsc4rsyFVG_uo5zEYGnqg6c48Ko,1604
|
|
15
|
+
ms_toolkit/xlsx_writer.py,sha256=XgeOrzaOxnmGtueNn3qgNLGOXgS9gK81Z--Q_WwW76c,6190
|
|
16
|
+
ms_file_toolkit-0.4.0.dist-info/METADATA,sha256=oKUusuS9JM1Axbur1EGKIDFh2sD4QQLC_FJPKqfTYxs,6250
|
|
17
|
+
ms_file_toolkit-0.4.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
18
|
+
ms_file_toolkit-0.4.0.dist-info/entry_points.txt,sha256=CwTAHXr0k41Nw92z9BierGPbxik9JX1vtqji0n8HfqM,51
|
|
19
|
+
ms_file_toolkit-0.4.0.dist-info/top_level.txt,sha256=BMiFZC5zXYxp0YXqWU8kY2x2mQvEWbGe5Av_ShLEA40,11
|
|
20
|
+
ms_file_toolkit-0.4.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ms_toolkit
|
ms_toolkit/__init__.py
ADDED
ms_toolkit/auto_tool.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Fayl turini avtomatik aniqlab, mos reader'ni chaqiradigan yordamchi modul."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
_EXT_MAP = {
|
|
6
|
+
".docx": "docx",
|
|
7
|
+
".xlsx": "xlsx",
|
|
8
|
+
".pptx": "pptx",
|
|
9
|
+
".pdf": "pdf",
|
|
10
|
+
".doc": "legacy",
|
|
11
|
+
".xls": "legacy",
|
|
12
|
+
".ppt": "legacy",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_file_info(file_path: str) -> dict:
|
|
17
|
+
"""Fayl haqida asosiy ma'lumot: mavjudmi, kengaytmasi, hajmi, aniqlangan turi."""
|
|
18
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
19
|
+
exists = os.path.exists(file_path)
|
|
20
|
+
return {
|
|
21
|
+
"file_path": file_path,
|
|
22
|
+
"exists": exists,
|
|
23
|
+
"extension": ext,
|
|
24
|
+
"detected_type": _EXT_MAP.get(ext, "unknown"),
|
|
25
|
+
"size_bytes": os.path.getsize(file_path) if exists else None,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def read_any_file(file_path: str) -> dict:
|
|
30
|
+
"""Fayl kengaytmasiga qarab mos o'qish funksiyasini avtomatik tanlab chaqiradi.
|
|
31
|
+
|
|
32
|
+
Foydalanuvchi/AI fayl turini oldindan bilmasa yoki noaniq bo'lsa foydali —
|
|
33
|
+
.docx/.xlsx/.pptx va eski .doc/.xls/.ppt formatlarni avtomatik aniqlaydi.
|
|
34
|
+
"""
|
|
35
|
+
from .docx_tool import read_docx_text
|
|
36
|
+
from .xlsx_tool import read_xlsx_data
|
|
37
|
+
from .pptx_tool import read_pptx_text
|
|
38
|
+
from .pdf_tool import read_pdf_text
|
|
39
|
+
from .legacy_tool import read_legacy_file
|
|
40
|
+
|
|
41
|
+
if not os.path.exists(file_path):
|
|
42
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
43
|
+
|
|
44
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
45
|
+
kind = _EXT_MAP.get(ext)
|
|
46
|
+
|
|
47
|
+
if kind == "docx":
|
|
48
|
+
result = read_docx_text(file_path)
|
|
49
|
+
elif kind == "xlsx":
|
|
50
|
+
result = read_xlsx_data(file_path)
|
|
51
|
+
elif kind == "pptx":
|
|
52
|
+
result = read_pptx_text(file_path)
|
|
53
|
+
elif kind == "pdf":
|
|
54
|
+
result = read_pdf_text(file_path)
|
|
55
|
+
elif kind == "legacy":
|
|
56
|
+
result = read_legacy_file(file_path)
|
|
57
|
+
else:
|
|
58
|
+
return {"error": f"Qo'llab-quvvatlanmaydigan yoki noma'lum fayl turi: {ext or '(kengaytmasiz)'}"}
|
|
59
|
+
|
|
60
|
+
result["detected_type"] = kind
|
|
61
|
+
return result
|
ms_toolkit/cli.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ms_toolkit uchun komandalar qatori (CLI) interfeysi.
|
|
3
|
+
|
|
4
|
+
Ishlatish:
|
|
5
|
+
ms-toolkit list
|
|
6
|
+
ms-toolkit run create_docx '{"file_path": "test.docx", "title": "Salom"}'
|
|
7
|
+
ms-toolkit run read_docx_text '{"file_path": "test.docx"}'
|
|
8
|
+
ms-toolkit run read_any_file '{"file_path": "hujjat.xlsx"}'
|
|
9
|
+
|
|
10
|
+
MS_TOOLKIT_BASE_DIR muhit o'zgaruvchisi bilan fayl amallarini bitta papkaga
|
|
11
|
+
cheklash mumkin (xavfsizlik uchun tavsiya etiladi).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import json
|
|
16
|
+
import argparse
|
|
17
|
+
|
|
18
|
+
from .registry import REGISTRY, dispatch
|
|
19
|
+
from .schemas import TOOLS
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cmd_list(args):
|
|
23
|
+
for t in TOOLS:
|
|
24
|
+
print(f"{t['name']:<28} {t['description'][:80]}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def cmd_run(args):
|
|
28
|
+
if args.tool_name not in REGISTRY:
|
|
29
|
+
print(f"Xato: noma'lum tool '{args.tool_name}'. Ro'yxat uchun: ms-toolkit list", file=sys.stderr)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
tool_input = json.loads(args.json_input)
|
|
34
|
+
except json.JSONDecodeError as e:
|
|
35
|
+
print(f"Xato: JSON noto'g'ri formatda: {e}", file=sys.stderr)
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
result = dispatch(args.tool_name, tool_input)
|
|
39
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
|
|
40
|
+
|
|
41
|
+
if "error" in result:
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main():
|
|
46
|
+
parser = argparse.ArgumentParser(prog="ms-toolkit", description="MS fayllar (DOCX/XLSX/PPTX/PDF) uchun CLI")
|
|
47
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
48
|
+
|
|
49
|
+
sub.add_parser("list", help="Barcha mavjud tool'larni ro'yxatini chiqaradi").set_defaults(func=cmd_list)
|
|
50
|
+
|
|
51
|
+
run_parser = sub.add_parser("run", help="Bitta tool'ni ishga tushiradi")
|
|
52
|
+
run_parser.add_argument("tool_name", help="Ishga tushiriladigan tool nomi (masalan: create_docx)")
|
|
53
|
+
run_parser.add_argument("json_input", help="Tool uchun JSON formatidagi kirish, masalan '{\"file_path\": \"a.docx\"}'")
|
|
54
|
+
run_parser.set_defaults(func=cmd_run)
|
|
55
|
+
|
|
56
|
+
args = parser.parse_args()
|
|
57
|
+
args.func(args)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|
ms_toolkit/docx_tool.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""DOCX (Word) fayllarni o'qish va tahlil qilish funksiyalari."""
|
|
2
|
+
|
|
3
|
+
from docx import Document
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read_docx_text(file_path: str) -> dict:
|
|
7
|
+
"""Word hujjatidagi barcha matnni paragraflar bo'yicha o'qiydi."""
|
|
8
|
+
doc = Document(file_path)
|
|
9
|
+
paragraphs = [p.text for p in doc.paragraphs if p.text.strip()]
|
|
10
|
+
return {
|
|
11
|
+
"file_path": file_path,
|
|
12
|
+
"paragraph_count": len(paragraphs),
|
|
13
|
+
"text": "\n".join(paragraphs),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def read_docx_tables(file_path: str) -> dict:
|
|
18
|
+
"""Word hujjatidagi barcha jadvallarni o'qiydi."""
|
|
19
|
+
doc = Document(file_path)
|
|
20
|
+
tables = []
|
|
21
|
+
for table in doc.tables:
|
|
22
|
+
rows = []
|
|
23
|
+
for row in table.rows:
|
|
24
|
+
rows.append([cell.text for cell in row.cells])
|
|
25
|
+
tables.append(rows)
|
|
26
|
+
return {"file_path": file_path, "table_count": len(tables), "tables": tables}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_docx_metadata(file_path: str) -> dict:
|
|
30
|
+
"""Word hujjatining metama'lumotlarini (muallif, sarlavha va h.k.) qaytaradi."""
|
|
31
|
+
doc = Document(file_path)
|
|
32
|
+
props = doc.core_properties
|
|
33
|
+
return {
|
|
34
|
+
"file_path": file_path,
|
|
35
|
+
"title": props.title,
|
|
36
|
+
"author": props.author,
|
|
37
|
+
"created": str(props.created) if props.created else None,
|
|
38
|
+
"modified": str(props.modified) if props.modified else None,
|
|
39
|
+
"paragraph_count": len(doc.paragraphs),
|
|
40
|
+
"table_count": len(doc.tables),
|
|
41
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""DOCX (Word) fayllarni yaratish va tahrirlash (yozish) funksiyalari."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from docx import Document
|
|
5
|
+
from docx.shared import Pt, Inches
|
|
6
|
+
from docx.enum.text import WD_BREAK
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_docx(
|
|
10
|
+
file_path: str,
|
|
11
|
+
title: str = None,
|
|
12
|
+
paragraphs: list = None,
|
|
13
|
+
table_data: list = None,
|
|
14
|
+
overwrite: bool = False,
|
|
15
|
+
) -> dict:
|
|
16
|
+
"""Yangi Word (.docx) hujjat yaratadi.
|
|
17
|
+
|
|
18
|
+
paragraphs: matn qatorlari ro'yxati (har biri alohida paragraf bo'ladi).
|
|
19
|
+
table_data: jadval uchun 2-o'lchamli ro'yxat, masalan
|
|
20
|
+
[["Ism", "Yosh"], ["Ali", "25"], ["Vali", "30"]] — birinchi qator sarlavha
|
|
21
|
+
sifatida qalin (bold) qilib chiqariladi.
|
|
22
|
+
"""
|
|
23
|
+
if os.path.exists(file_path) and not overwrite:
|
|
24
|
+
return {"error": f"Fayl allaqachon mavjud: {file_path} (overwrite=True qiling)"}
|
|
25
|
+
|
|
26
|
+
doc = Document()
|
|
27
|
+
|
|
28
|
+
if title:
|
|
29
|
+
doc.add_heading(title, level=1)
|
|
30
|
+
|
|
31
|
+
for para in paragraphs or []:
|
|
32
|
+
doc.add_paragraph(para)
|
|
33
|
+
|
|
34
|
+
if table_data:
|
|
35
|
+
rows = len(table_data)
|
|
36
|
+
cols = len(table_data[0]) if rows else 0
|
|
37
|
+
if rows and cols:
|
|
38
|
+
table = doc.add_table(rows=rows, cols=cols)
|
|
39
|
+
table.style = "Light Grid Accent 1"
|
|
40
|
+
for r, row_data in enumerate(table_data):
|
|
41
|
+
for c, cell_value in enumerate(row_data):
|
|
42
|
+
cell = table.cell(r, c)
|
|
43
|
+
cell.text = str(cell_value)
|
|
44
|
+
if r == 0:
|
|
45
|
+
for p in cell.paragraphs:
|
|
46
|
+
for run in p.runs:
|
|
47
|
+
run.font.bold = True
|
|
48
|
+
|
|
49
|
+
os.makedirs(os.path.dirname(os.path.abspath(file_path)) or ".", exist_ok=True)
|
|
50
|
+
doc.save(file_path)
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
"file_path": file_path,
|
|
54
|
+
"status": "created",
|
|
55
|
+
"paragraph_count": len(paragraphs or []),
|
|
56
|
+
"table_added": bool(table_data),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def append_docx_text(file_path: str, text: str, bold: bool = False) -> dict:
|
|
61
|
+
"""Mavjud Word hujjatining oxiriga yangi paragraf qo'shadi."""
|
|
62
|
+
if not os.path.exists(file_path):
|
|
63
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
64
|
+
|
|
65
|
+
doc = Document(file_path)
|
|
66
|
+
p = doc.add_paragraph()
|
|
67
|
+
run = p.add_run(text)
|
|
68
|
+
run.bold = bold
|
|
69
|
+
doc.save(file_path)
|
|
70
|
+
|
|
71
|
+
return {"file_path": file_path, "status": "appended", "paragraph_count": len(doc.paragraphs)}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def replace_docx_text(file_path: str, find: str, replace: str, save_as: str = None) -> dict:
|
|
75
|
+
"""Hujjatdagi barcha 'find' matnini 'replace' bilan almashtiradi.
|
|
76
|
+
|
|
77
|
+
save_as berilmasa, asl faylning ustiga yoziladi.
|
|
78
|
+
"""
|
|
79
|
+
if not os.path.exists(file_path):
|
|
80
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
81
|
+
|
|
82
|
+
doc = Document(file_path)
|
|
83
|
+
replacements = 0
|
|
84
|
+
|
|
85
|
+
for p in doc.paragraphs:
|
|
86
|
+
if find in p.text:
|
|
87
|
+
for run in p.runs:
|
|
88
|
+
if find in run.text:
|
|
89
|
+
run.text = run.text.replace(find, replace)
|
|
90
|
+
replacements += 1
|
|
91
|
+
|
|
92
|
+
for table in doc.tables:
|
|
93
|
+
for row in table.rows:
|
|
94
|
+
for cell in row.cells:
|
|
95
|
+
if find in cell.text:
|
|
96
|
+
cell.text = cell.text.replace(find, replace)
|
|
97
|
+
replacements += 1
|
|
98
|
+
|
|
99
|
+
out_path = save_as or file_path
|
|
100
|
+
doc.save(out_path)
|
|
101
|
+
|
|
102
|
+
return {"file_path": out_path, "status": "replaced", "replacements": replacements}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def add_docx_heading(file_path: str, text: str, level: int = 1) -> dict:
|
|
106
|
+
"""Mavjud Word hujjatiga sarlavha (heading, 1-9 daraja) qo'shadi."""
|
|
107
|
+
if not os.path.exists(file_path):
|
|
108
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
109
|
+
if not 0 <= level <= 9:
|
|
110
|
+
return {"error": "level 0 dan 9 gacha bo'lishi kerak"}
|
|
111
|
+
|
|
112
|
+
doc = Document(file_path)
|
|
113
|
+
doc.add_heading(text, level=level)
|
|
114
|
+
doc.save(file_path)
|
|
115
|
+
return {"file_path": file_path, "status": "heading_added", "level": level}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def add_docx_image(file_path: str, image_path: str, width_inches: float = 6.0) -> dict:
|
|
119
|
+
"""Mavjud Word hujjatining oxiriga rasm qo'shadi."""
|
|
120
|
+
if not os.path.exists(file_path):
|
|
121
|
+
return {"error": f"Hujjat topilmadi: {file_path}"}
|
|
122
|
+
if not os.path.exists(image_path):
|
|
123
|
+
return {"error": f"Rasm fayli topilmadi: {image_path}"}
|
|
124
|
+
|
|
125
|
+
doc = Document(file_path)
|
|
126
|
+
doc.add_picture(image_path, width=Inches(width_inches))
|
|
127
|
+
doc.save(file_path)
|
|
128
|
+
return {"file_path": file_path, "status": "image_added", "image_path": image_path}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def add_docx_page_break(file_path: str) -> dict:
|
|
132
|
+
"""Mavjud Word hujjatining oxiriga sahifa bo'linishi (page break) qo'shadi."""
|
|
133
|
+
if not os.path.exists(file_path):
|
|
134
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
135
|
+
|
|
136
|
+
doc = Document(file_path)
|
|
137
|
+
p = doc.add_paragraph()
|
|
138
|
+
run = p.add_run()
|
|
139
|
+
run.add_break(WD_BREAK.PAGE)
|
|
140
|
+
doc.save(file_path)
|
|
141
|
+
return {"file_path": file_path, "status": "page_break_added"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Eski Microsoft formatlarini (.doc, .xls, .ppt) LibreOffice (soffice) yordamida
|
|
3
|
+
yangi formatga (.docx, .xlsx, .pptx) konvertatsiya qilib, keyin mavjud
|
|
4
|
+
docx_tool / xlsx_tool / pptx_tool funksiyalari bilan o'qish uchun modul.
|
|
5
|
+
|
|
6
|
+
Talab: tizimda `soffice` (LibreOffice) o'rnatilgan bo'lishi kerak.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import tempfile
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
_LEGACY_MAP = {
|
|
15
|
+
".doc": "docx",
|
|
16
|
+
".xls": "xlsx",
|
|
17
|
+
".ppt": "pptx",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_legacy_format(file_path: str) -> bool:
|
|
22
|
+
"""Fayl eski (.doc/.xls/.ppt) formatdami — shuni tekshiradi."""
|
|
23
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
24
|
+
return ext in _LEGACY_MAP
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def convert_legacy_file(file_path: str, timeout: int = 60) -> str:
|
|
28
|
+
"""Eski formatdagi faylni vaqtinchalik papkada yangi formatga o'giradi.
|
|
29
|
+
|
|
30
|
+
Qaytaradi: yangi (.docx/.xlsx/.pptx) faylning to'liq yo'li.
|
|
31
|
+
Chaqiruvchi tugagach vaqtinchalik faylni o'zi tozalashi kerak (yoki
|
|
32
|
+
read_legacy_file wrapper'idan foydalaning — u avtomatik tozalaydi).
|
|
33
|
+
"""
|
|
34
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
35
|
+
if ext not in _LEGACY_MAP:
|
|
36
|
+
raise ValueError(f"Qo'llab-quvvatlanmaydigan eski format: {ext}")
|
|
37
|
+
|
|
38
|
+
target_format = _LEGACY_MAP[ext]
|
|
39
|
+
out_dir = os.path.join(tempfile.gettempdir(), f"ms_toolkit_legacy_{uuid.uuid4().hex}")
|
|
40
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
result = subprocess.run(
|
|
43
|
+
[
|
|
44
|
+
"soffice",
|
|
45
|
+
"--headless",
|
|
46
|
+
"--convert-to",
|
|
47
|
+
target_format,
|
|
48
|
+
"--outdir",
|
|
49
|
+
out_dir,
|
|
50
|
+
file_path,
|
|
51
|
+
],
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True,
|
|
54
|
+
timeout=timeout,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if result.returncode != 0:
|
|
58
|
+
raise RuntimeError(f"LibreOffice konvertatsiya xatosi: {result.stderr or result.stdout}")
|
|
59
|
+
|
|
60
|
+
base_name = os.path.splitext(os.path.basename(file_path))[0]
|
|
61
|
+
converted_path = os.path.join(out_dir, f"{base_name}.{target_format}")
|
|
62
|
+
|
|
63
|
+
if not os.path.exists(converted_path):
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
f"Konvertatsiya qilingan fayl topilmadi: {converted_path} (chiqish: {result.stdout})"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return converted_path
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def read_legacy_file(file_path: str) -> dict:
|
|
72
|
+
"""Eski (.doc/.xls/.ppt) faylni avtomatik aniqlab, mos reader bilan o'qiydi.
|
|
73
|
+
|
|
74
|
+
Ichki ravishda yangi formatga o'giradi, tegishli o'qish funksiyasini
|
|
75
|
+
chaqiradi va natijaga original fayl yo'li va formatni qo'shib qaytaradi.
|
|
76
|
+
"""
|
|
77
|
+
from .docx_tool import read_docx_text
|
|
78
|
+
from .xlsx_tool import read_xlsx_data
|
|
79
|
+
from .pptx_tool import read_pptx_text
|
|
80
|
+
|
|
81
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
82
|
+
if ext not in _LEGACY_MAP:
|
|
83
|
+
return {"error": f"Qo'llab-quvvatlanmaydigan eski format: {ext}"}
|
|
84
|
+
|
|
85
|
+
if not os.path.exists(file_path):
|
|
86
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
87
|
+
|
|
88
|
+
converted_path = None
|
|
89
|
+
try:
|
|
90
|
+
converted_path = convert_legacy_file(file_path)
|
|
91
|
+
target_format = _LEGACY_MAP[ext]
|
|
92
|
+
|
|
93
|
+
if target_format == "docx":
|
|
94
|
+
result = read_docx_text(converted_path)
|
|
95
|
+
elif target_format == "xlsx":
|
|
96
|
+
result = read_xlsx_data(converted_path)
|
|
97
|
+
else:
|
|
98
|
+
result = read_pptx_text(converted_path)
|
|
99
|
+
|
|
100
|
+
result["original_file"] = file_path
|
|
101
|
+
result["original_format"] = ext
|
|
102
|
+
return result
|
|
103
|
+
except subprocess.TimeoutExpired:
|
|
104
|
+
return {"error": "Konvertatsiya vaqti tugadi (timeout)"}
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return {"error": f"{type(e).__name__}: {e}"}
|
|
107
|
+
finally:
|
|
108
|
+
if converted_path and os.path.exists(converted_path):
|
|
109
|
+
out_dir = os.path.dirname(converted_path)
|
|
110
|
+
try:
|
|
111
|
+
os.remove(converted_path)
|
|
112
|
+
os.rmdir(out_dir)
|
|
113
|
+
except OSError:
|
|
114
|
+
pass
|
ms_toolkit/pdf_tool.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""PDF fayllarni o'qish funksiyalari (pdfplumber — matn/jadval, pypdf — metama'lumot)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pdfplumber
|
|
5
|
+
from pypdf import PdfReader
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def read_pdf_text(file_path: str, max_pages: int = None) -> dict:
|
|
9
|
+
"""PDF fayldan barcha (yoki birinchi max_pages ta) sahifa matnini o'qiydi."""
|
|
10
|
+
if not os.path.exists(file_path):
|
|
11
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
12
|
+
|
|
13
|
+
pages_text = []
|
|
14
|
+
with pdfplumber.open(file_path) as pdf:
|
|
15
|
+
pages = pdf.pages[:max_pages] if max_pages else pdf.pages
|
|
16
|
+
for i, page in enumerate(pages):
|
|
17
|
+
pages_text.append(page.extract_text() or "")
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
"file_path": file_path,
|
|
21
|
+
"page_count": len(pages_text),
|
|
22
|
+
"text": "\n\n".join(pages_text),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def read_pdf_tables(file_path: str) -> dict:
|
|
27
|
+
"""PDF fayldagi har bir sahifadan jadvallarni ajratib oladi."""
|
|
28
|
+
if not os.path.exists(file_path):
|
|
29
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
30
|
+
|
|
31
|
+
all_tables = []
|
|
32
|
+
with pdfplumber.open(file_path) as pdf:
|
|
33
|
+
for page_num, page in enumerate(pdf.pages, start=1):
|
|
34
|
+
for table in page.extract_tables():
|
|
35
|
+
all_tables.append({"page": page_num, "table": table})
|
|
36
|
+
|
|
37
|
+
return {"file_path": file_path, "table_count": len(all_tables), "tables": all_tables}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_pdf_metadata(file_path: str) -> dict:
|
|
41
|
+
"""PDF faylning metama'lumotini (sarlavha, muallif, sahifalar soni) qaytaradi."""
|
|
42
|
+
if not os.path.exists(file_path):
|
|
43
|
+
return {"error": f"Fayl topilmadi: {file_path}"}
|
|
44
|
+
|
|
45
|
+
reader = PdfReader(file_path)
|
|
46
|
+
meta = reader.metadata or {}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"file_path": file_path,
|
|
50
|
+
"page_count": len(reader.pages),
|
|
51
|
+
"title": meta.get("/Title"),
|
|
52
|
+
"author": meta.get("/Author"),
|
|
53
|
+
"subject": meta.get("/Subject"),
|
|
54
|
+
"creator": meta.get("/Creator"),
|
|
55
|
+
"encrypted": reader.is_encrypted,
|
|
56
|
+
}
|