xsl 0.1.5__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.
xsl/cli.py ADDED
@@ -0,0 +1,1237 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Universal File Editor CLI Tool
4
+ Edytor plików SVG/HTML/XML z obsługą XPath i CSS selectors
5
+ Obsługuje tryb CLI oraz serwer HTTP dla zdalnej edycji
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ import sys
12
+ import xml.etree.ElementTree as ET
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional, Union
15
+ import re
16
+ from http.server import HTTPServer, BaseHTTPRequestHandler
17
+ from urllib.parse import urlparse, parse_qs
18
+ import urllib.parse
19
+ import logging
20
+ import base64
21
+
22
+ try:
23
+ from lxml import etree, html
24
+
25
+ LXML_AVAILABLE = True
26
+ except ImportError:
27
+ LXML_AVAILABLE = False
28
+ print("Warning: lxml not available. Installing: pip install lxml")
29
+
30
+ try:
31
+ from bs4 import BeautifulSoup
32
+
33
+ BS4_AVAILABLE = True
34
+ except ImportError:
35
+ BS4_AVAILABLE = False
36
+ print("Warning: BeautifulSoup4 not available. Installing: pip install beautifulsoup4")
37
+
38
+ try:
39
+ import requests
40
+
41
+ REQUESTS_AVAILABLE = True
42
+ except ImportError:
43
+ REQUESTS_AVAILABLE = False
44
+ print("Warning: requests not available. Installing: pip install requests")
45
+
46
+
47
+ class FileEditor:
48
+ """Główna klasa do edycji plików XML/HTML/SVG"""
49
+
50
+ def __init__(self, file_path: str):
51
+ self.file_path = Path(file_path) if not file_path.startswith('http') else file_path
52
+ self.tree = None
53
+ self.root = None
54
+ self.file_type = None
55
+ self.original_content = None
56
+ self.is_remote = file_path.startswith('http')
57
+ self._load_file()
58
+
59
+ def _load_file(self):
60
+ """Ładuje plik i określa jego typ"""
61
+ if self.is_remote:
62
+ if not REQUESTS_AVAILABLE:
63
+ raise ImportError("requests library required for remote files")
64
+ try:
65
+ response = requests.get(self.file_path)
66
+ response.raise_for_status()
67
+ self.original_content = response.text
68
+ except Exception as e:
69
+ raise ConnectionError(f"Cannot fetch remote file: {e}")
70
+ else:
71
+ if not self.file_path.exists():
72
+ raise FileNotFoundError(f"File not found: {self.file_path}")
73
+ with open(self.file_path, 'r', encoding='utf-8') as f:
74
+ self.original_content = f.read()
75
+
76
+ # Określ typ pliku
77
+ if self.is_remote:
78
+ extension = Path(urlparse(str(self.file_path)).path).suffix.lower()
79
+ else:
80
+ extension = self.file_path.suffix.lower()
81
+
82
+ content_lower = self.original_content.lower()
83
+
84
+ if extension == '.svg' or '<svg' in content_lower:
85
+ self.file_type = 'svg'
86
+ elif extension in ['.html', '.htm'] or '<!doctype html' in content_lower or '<html' in content_lower:
87
+ self.file_type = 'html'
88
+ elif extension == '.xml' or '<?xml' in content_lower:
89
+ self.file_type = 'xml'
90
+ else:
91
+ self.file_type = 'xml' # domyślnie XML
92
+
93
+ # Parsuj plik
94
+ if LXML_AVAILABLE:
95
+ try:
96
+ if self.file_type == 'html':
97
+ self.tree = html.fromstring(self.original_content)
98
+ else:
99
+ self.tree = etree.fromstring(self.original_content.encode('utf-8'))
100
+ self.root = self.tree
101
+ except Exception as e:
102
+ print(f"lxml parsing failed: {e}, trying ElementTree...")
103
+ self._fallback_parse()
104
+ else:
105
+ self._fallback_parse()
106
+
107
+ def _fallback_parse(self):
108
+ """Fallback parsing using ElementTree"""
109
+ try:
110
+ self.tree = ET.fromstring(self.original_content)
111
+ self.root = self.tree
112
+ except ET.ParseError as e:
113
+ raise ValueError(f"Cannot parse file: {e}")
114
+
115
+ def find_by_xpath(self, xpath: str) -> List:
116
+ """Znajdź elementy używając XPath"""
117
+ if not LXML_AVAILABLE:
118
+ raise NotImplementedError("XPath requires lxml library")
119
+
120
+ try:
121
+ if isinstance(self.tree, etree._Element):
122
+ # Zarejestruj standardowe przestrzenie nazw
123
+ namespaces = {
124
+ 'svg': 'http://www.w3.org/2000/svg',
125
+ 'xlink': 'http://www.w3.org/1999/xlink',
126
+ 'html': 'http://www.w3.org/1999/xhtml'
127
+ }
128
+ return self.tree.xpath(xpath, namespaces=namespaces)
129
+ else:
130
+ return []
131
+ except Exception as e:
132
+ raise ValueError(f"Invalid XPath expression: {e}")
133
+
134
+ def find_by_css(self, css_selector: str) -> List:
135
+ """Znajdź elementy używając CSS selectors (tylko HTML)"""
136
+ if not BS4_AVAILABLE:
137
+ raise NotImplementedError("CSS selectors require beautifulsoup4 library")
138
+
139
+ if self.file_type != 'html':
140
+ raise ValueError("CSS selectors work only with HTML files")
141
+
142
+ soup = BeautifulSoup(self.original_content, 'html.parser')
143
+ return soup.select(css_selector)
144
+
145
+ def extract_data_uri(self, xpath: str) -> Dict[str, str]:
146
+ """Wyciągnij Data URI z SVG i zdekoduj zawartość"""
147
+ elements = self.find_by_xpath(xpath)
148
+ if not elements:
149
+ return {"error": "No elements found with given XPath"}
150
+
151
+ element = elements[0]
152
+ data_uri = None
153
+
154
+ # Sprawdź różne atrybuty, które mogą zawierać Data URI
155
+ for attr in ['href', 'xlink:href', 'src', 'data']:
156
+ if hasattr(element, 'get'):
157
+ uri = element.get(attr)
158
+ elif hasattr(element, 'attrib'):
159
+ uri = element.attrib.get(attr)
160
+ else:
161
+ continue
162
+
163
+ if uri and uri.startswith('data:'):
164
+ data_uri = uri
165
+ break
166
+
167
+ if not data_uri:
168
+ return {"error": "No Data URI found in element"}
169
+
170
+ try:
171
+ # Rozdziel nagłówek i dane
172
+ header, base64_data = data_uri.split(',', 1)
173
+ mime_type = header.split(':')[1].split(';')[0]
174
+
175
+ # Dekoduj base64
176
+ decoded_data = base64.b64decode(base64_data)
177
+
178
+ return {
179
+ "mime_type": mime_type,
180
+ "size": len(decoded_data),
181
+ "base64_data": base64_data,
182
+ "decoded_size": len(decoded_data)
183
+ }
184
+ except Exception as e:
185
+ return {"error": f"Failed to decode Data URI: {e}"}
186
+
187
+ def save_data_uri_to_file(self, xpath: str, output_path: str) -> bool:
188
+ """Zapisz zawartość Data URI do pliku"""
189
+ result = self.extract_data_uri(xpath)
190
+ if "error" in result:
191
+ print(f"Error: {result['error']}")
192
+ return False
193
+
194
+ try:
195
+ decoded_data = base64.b64decode(result['base64_data'])
196
+ with open(output_path, 'wb') as f:
197
+ f.write(decoded_data)
198
+ print(f"Data URI saved to {output_path} ({result['size']} bytes, {result['mime_type']})")
199
+ return True
200
+ except Exception as e:
201
+ print(f"Error saving file: {e}")
202
+ return False
203
+
204
+ def get_element_text(self, xpath: str) -> str:
205
+ """Pobierz tekst elementu"""
206
+ elements = self.find_by_xpath(xpath)
207
+ if elements:
208
+ element = elements[0]
209
+ if hasattr(element, 'text'):
210
+ return element.text or ""
211
+ return str(element)
212
+ return ""
213
+
214
+ def get_element_attribute(self, xpath: str, attr_name: str) -> str:
215
+ """Pobierz atrybut elementu"""
216
+ elements = self.find_by_xpath(xpath)
217
+ if elements:
218
+ element = elements[0]
219
+ if hasattr(element, 'get'):
220
+ return element.get(attr_name, "")
221
+ elif hasattr(element, 'attrib'):
222
+ return element.attrib.get(attr_name, "")
223
+ return ""
224
+
225
+ def set_element_text(self, xpath: str, new_text: str) -> bool:
226
+ """Ustaw tekst elementu"""
227
+ elements = self.find_by_xpath(xpath)
228
+ if elements:
229
+ element = elements[0]
230
+ if hasattr(element, 'text'):
231
+ element.text = new_text
232
+ return True
233
+ return False
234
+
235
+ def set_element_attribute(self, xpath: str, attr_name: str, attr_value: str) -> bool:
236
+ """Ustaw atrybut elementu"""
237
+ elements = self.find_by_xpath(xpath)
238
+ if elements:
239
+ element = elements[0]
240
+ if hasattr(element, 'set'):
241
+ element.set(attr_name, attr_value)
242
+ elif hasattr(element, 'attrib'):
243
+ element.attrib[attr_name] = attr_value
244
+ return True
245
+ return False
246
+
247
+ def add_element(self, parent_xpath: str, tag_name: str, text: str = "", attributes: Dict[str, str] = None) -> bool:
248
+ """Dodaj nowy element"""
249
+ parents = self.find_by_xpath(parent_xpath)
250
+ if parents:
251
+ parent = parents[0]
252
+ if LXML_AVAILABLE:
253
+ new_element = etree.SubElement(parent, tag_name)
254
+ if text:
255
+ new_element.text = text
256
+ if attributes:
257
+ for key, value in attributes.items():
258
+ new_element.set(key, value)
259
+ return True
260
+ return False
261
+
262
+ def remove_element(self, xpath: str) -> bool:
263
+ """Usuń element"""
264
+ elements = self.find_by_xpath(xpath)
265
+ if elements:
266
+ element = elements[0]
267
+ parent = element.getparent()
268
+ if parent is not None:
269
+ parent.remove(element)
270
+ return True
271
+ return False
272
+
273
+ def list_elements(self, xpath: str = "//*") -> List[Dict]:
274
+ """Wylistuj elementy z ich ścieżkami"""
275
+ elements = self.find_by_xpath(xpath)
276
+ result = []
277
+
278
+ for i, element in enumerate(elements):
279
+ if LXML_AVAILABLE:
280
+ element_path = self.tree.getpath(element) if hasattr(self.tree, 'getpath') else f"element[{i}]"
281
+ element_info = {
282
+ 'path': element_path,
283
+ 'tag': element.tag,
284
+ 'text': (element.text or "").strip(),
285
+ 'attributes': dict(element.attrib) if hasattr(element, 'attrib') else {}
286
+ }
287
+ result.append(element_info)
288
+
289
+ return result
290
+
291
+ def save(self, output_path: str = None) -> bool:
292
+ """Zapisz zmiany do pliku"""
293
+ if self.is_remote and not output_path:
294
+ raise ValueError("Cannot save remote file without specifying output path")
295
+
296
+ save_path = output_path or str(self.file_path)
297
+
298
+ try:
299
+ if LXML_AVAILABLE:
300
+ if self.file_type == 'html':
301
+ content = etree.tostring(self.tree, encoding='unicode', method='html', pretty_print=True)
302
+ else:
303
+ content = etree.tostring(self.tree, encoding='unicode', pretty_print=True)
304
+ if not content.startswith('<?xml'):
305
+ content = '<?xml version="1.0" encoding="UTF-8"?>\n' + content
306
+ else:
307
+ content = ET.tostring(self.tree, encoding='unicode')
308
+
309
+ with open(save_path, 'w', encoding='utf-8') as f:
310
+ f.write(content)
311
+ return True
312
+ except Exception as e:
313
+ print(f"Error saving file: {e}")
314
+ return False
315
+
316
+ def backup(self) -> str:
317
+ """Utwórz kopię zapasową"""
318
+ if self.is_remote:
319
+ raise ValueError("Cannot create backup of remote file")
320
+ backup_path = f"{self.file_path}.backup"
321
+ with open(backup_path, 'w', encoding='utf-8') as f:
322
+ f.write(self.original_content)
323
+ return backup_path
324
+
325
+
326
+ class FileEditorServer(BaseHTTPRequestHandler):
327
+ """HTTP Server dla zdalnej edycji plików"""
328
+
329
+ editors: Dict[str, FileEditor] = {}
330
+
331
+ def do_GET(self):
332
+ """Obsługa żądań GET"""
333
+ parsed_url = urlparse(self.path)
334
+ path = parsed_url.path
335
+ query = parse_qs(parsed_url.query)
336
+
337
+ if path == '/':
338
+ self._serve_interface()
339
+ elif path == '/api/files':
340
+ self._list_files()
341
+ elif path.startswith('/api/file/'):
342
+ file_path = path[10:] # usuń /api/file/
343
+ self._serve_file_info(file_path)
344
+ elif path == '/api/extract':
345
+ # Endpoint do ekstrakcji Data URI z URL + XPath
346
+ self._extract_from_url(query)
347
+ else:
348
+ self._send_error(404, "Not Found")
349
+
350
+ def do_POST(self):
351
+ """Obsługa żądań POST"""
352
+ content_length = int(self.headers['Content-Length'])
353
+ post_data = self.rfile.read(content_length).decode('utf-8')
354
+
355
+ try:
356
+ data = json.loads(post_data)
357
+ except json.JSONDecodeError:
358
+ self._send_error(400, "Invalid JSON")
359
+ return
360
+
361
+ parsed_url = urlparse(self.path)
362
+ path = parsed_url.path
363
+
364
+ if path == '/api/load':
365
+ self._load_file(data)
366
+ elif path == '/api/query':
367
+ self._query_elements(data)
368
+ elif path == '/api/update':
369
+ self._update_element(data)
370
+ elif path == '/api/save':
371
+ self._save_file(data)
372
+ elif path == '/api/extract_data_uri':
373
+ self._extract_data_uri(data)
374
+ else:
375
+ self._send_error(404, "Not Found")
376
+
377
+ def _extract_from_url(self, query):
378
+ """Ekstrakcja Data URI z URL + XPath (GET endpoint)"""
379
+ try:
380
+ url = query.get('url', [''])[0]
381
+ xpath = query.get('xpath', [''])[0]
382
+
383
+ if not url or not xpath:
384
+ self._send_json_response({'error': 'Missing url or xpath parameter'})
385
+ return
386
+
387
+ # Załaduj plik z URL
388
+ editor = FileEditor(url)
389
+
390
+ # Wyciągnij Data URI
391
+ result = editor.extract_data_uri(xpath)
392
+
393
+ self._send_json_response(result)
394
+
395
+ except Exception as e:
396
+ self._send_json_response({'error': str(e)})
397
+
398
+ def _serve_interface(self):
399
+ """Serwuj interfejs webowy"""
400
+ html = """
401
+ <!DOCTYPE html>
402
+ <html>
403
+ <head>
404
+ <title>File Editor Server</title>
405
+ <style>
406
+ body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
407
+ .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
408
+ .section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; background: #fafafa; }
409
+ input, textarea, select { padding: 8px; margin: 5px; width: 300px; border: 1px solid #ccc; border-radius: 4px; }
410
+ button { padding: 10px 15px; background: #007cba; color: white; border: none; border-radius: 4px; cursor: pointer; }
411
+ button:hover { background: #005a87; }
412
+ .result { background: #f5f5f5; padding: 10px; margin: 10px 0; border-radius: 3px; font-family: monospace; white-space: pre-wrap; max-height: 300px; overflow-y: auto; }
413
+ .error { background: #ffe6e6; color: #d00; }
414
+ .success { background: #e6ffe6; color: #0a0; }
415
+ .url-extractor { background: #e6f3ff; border-left: 4px solid #007cba; }
416
+ .tabs { display: flex; border-bottom: 1px solid #ddd; }
417
+ .tab { padding: 10px 20px; cursor: pointer; border-bottom: 2px solid transparent; }
418
+ .tab.active { border-bottom-color: #007cba; background: #f0f8ff; }
419
+ .tab-content { display: none; }
420
+ .tab-content.active { display: block; }
421
+ </style>
422
+ </head>
423
+ <body>
424
+ <div class="container">
425
+ <h1>🛠️ Universal File Editor Server</h1>
426
+ <p>Edytuj pliki SVG/HTML/XML za pomocą XPath i CSS selectors</p>
427
+
428
+ <div class="tabs">
429
+ <div class="tab active" onclick="showTab('local')">Pliki lokalne</div>
430
+ <div class="tab" onclick="showTab('remote')">Ekstrakcja z URL</div>
431
+ </div>
432
+
433
+ <div id="local" class="tab-content active">
434
+ <div class="section">
435
+ <h3>1. Załaduj plik lokalny</h3>
436
+ <input type="text" id="filePath" placeholder="Ścieżka do pliku (np. ./data.svg)" value="./example.svg">
437
+ <button onclick="loadFile()">Załaduj</button>
438
+ <div id="loadResult" class="result"></div>
439
+ </div>
440
+
441
+ <div class="section">
442
+ <h3>2. Zapytania XPath/CSS</h3>
443
+ <input type="text" id="query" placeholder="XPath (np. //text[@id='title']) lub CSS (np. .my-class)">
444
+ <select id="queryType">
445
+ <option value="xpath">XPath</option>
446
+ <option value="css">CSS Selector</option>
447
+ </select>
448
+ <button onclick="queryElements()">Wykonaj</button>
449
+ <div id="queryResult" class="result"></div>
450
+ </div>
451
+
452
+ <div class="section">
453
+ <h3>3. Edycja elementów</h3>
454
+ <input type="text" id="updateXPath" placeholder="XPath elementu do edycji">
455
+ <select id="updateType">
456
+ <option value="text">Tekst</option>
457
+ <option value="attribute">Atrybut</option>
458
+ </select>
459
+ <input type="text" id="attributeName" placeholder="Nazwa atrybutu (jeśli atrybut)">
460
+ <textarea id="newValue" placeholder="Nowa wartość"></textarea>
461
+ <button onclick="updateElement()">Aktualizuj</button>
462
+ <div id="updateResult" class="result"></div>
463
+ </div>
464
+
465
+ <div class="section">
466
+ <h3>4. Zapisz zmiany</h3>
467
+ <input type="text" id="savePath" placeholder="Ścieżka zapisu (puste = nadpisz)">
468
+ <button onclick="saveFile()">Zapisz</button>
469
+ <div id="saveResult" class="result"></div>
470
+ </div>
471
+ </div>
472
+
473
+ <div id="remote" class="tab-content">
474
+ <div class="section url-extractor">
475
+ <h3>🌐 Ekstrakcja Data URI z URL</h3>
476
+ <p>Pobierz i wyciągnij Data URI z pliku SVG/XML za pomocą XPath</p>
477
+ <input type="text" id="remoteUrl" placeholder="URL pliku (np. http://localhost/file.svg)" style="width: 400px;">
478
+ <input type="text" id="remoteXPath" placeholder="XPath (np. //svg:image/@xlink:href)" style="width: 300px;">
479
+ <button onclick="extractFromUrl()">Wyciągnij</button>
480
+ <div id="extractResult" class="result"></div>
481
+ </div>
482
+
483
+ <div class="section">
484
+ <h3>Przykłady XPath dla Data URI</h3>
485
+ <ul>
486
+ <li><code>//svg:image/@xlink:href</code> - atrybut href w elemencie image</li>
487
+ <li><code>//*[contains(@href,'data:')]/@href</code> - dowolny element z data: w href</li>
488
+ <li><code>//svg:object/@data</code> - atrybut data w object</li>
489
+ <li><code>//svg:foreignObject//text()</code> - tekst w foreignObject</li>
490
+ </ul>
491
+ </div>
492
+ </div>
493
+ </div>
494
+
495
+ <script>
496
+ function showTab(tabName) {
497
+ // Ukryj wszystkie zakładki
498
+ document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
499
+ document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
500
+
501
+ // Pokaż wybraną zakładkę
502
+ document.getElementById(tabName).classList.add('active');
503
+ event.target.classList.add('active');
504
+ }
505
+
506
+ async function apiCall(endpoint, data) {
507
+ try {
508
+ const response = await fetch(endpoint, {
509
+ method: 'POST',
510
+ headers: {'Content-Type': 'application/json'},
511
+ body: JSON.stringify(data)
512
+ });
513
+ return await response.json();
514
+ } catch (error) {
515
+ return {success: false, error: error.message};
516
+ }
517
+ }
518
+
519
+ async function extractFromUrl() {
520
+ const url = document.getElementById('remoteUrl').value;
521
+ const xpath = document.getElementById('remoteXPath').value;
522
+
523
+ if (!url || !xpath) {
524
+ document.getElementById('extractResult').textContent = 'Podaj URL i XPath';
525
+ return;
526
+ }
527
+
528
+ try {
529
+ const response = await fetch(`/api/extract?url=${encodeURIComponent(url)}&xpath=${encodeURIComponent(xpath)}`);
530
+ const result = await response.json();
531
+
532
+ document.getElementById('extractResult').textContent = JSON.stringify(result, null, 2);
533
+ document.getElementById('extractResult').className = 'result ' + (result.error ? 'error' : 'success');
534
+ } catch (error) {
535
+ document.getElementById('extractResult').textContent = 'Błąd: ' + error.message;
536
+ document.getElementById('extractResult').className = 'result error';
537
+ }
538
+ }
539
+
540
+ async function loadFile() {
541
+ const filePath = document.getElementById('filePath').value;
542
+ const result = await apiCall('/api/load', {file_path: filePath});
543
+ document.getElementById('loadResult').textContent = JSON.stringify(result, null, 2);
544
+ document.getElementById('loadResult').className = 'result ' + (result.success ? 'success' : 'error');
545
+ }
546
+
547
+ async function queryElements() {
548
+ const query = document.getElementById('query').value;
549
+ const queryType = document.getElementById('queryType').value;
550
+ const result = await apiCall('/api/query', {query, type: queryType});
551
+ document.getElementById('queryResult').textContent = JSON.stringify(result, null, 2);
552
+ document.getElementById('queryResult').className = 'result ' + (result.success ? 'success' : 'error');
553
+ }
554
+
555
+ async function updateElement() {
556
+ const xpath = document.getElementById('updateXPath').value;
557
+ const updateType = document.getElementById('updateType').value;
558
+ const attributeName = document.getElementById('attributeName').value;
559
+ const newValue = document.getElementById('newValue').value;
560
+
561
+ const data = {xpath, type: updateType, value: newValue};
562
+ if (updateType === 'attribute') {
563
+ data.attribute = attributeName;
564
+ }
565
+
566
+ const result = await apiCall('/api/update', data);
567
+ document.getElementById('updateResult').textContent = JSON.stringify(result, null, 2);
568
+ document.getElementById('updateResult').className = 'result ' + (result.success ? 'success' : 'error');
569
+ }
570
+
571
+ async function saveFile() {
572
+ const savePath = document.getElementById('savePath').value;
573
+ const result = await apiCall('/api/save', {output_path: savePath || null});
574
+ document.getElementById('saveResult').textContent = JSON.stringify(result, null, 2);
575
+ document.getElementById('saveResult').className = 'result ' + (result.success ? 'success' : 'error');
576
+ }
577
+ </script>
578
+ </body>
579
+ </html>
580
+ """
581
+ self._send_response(200, html, 'text/html')
582
+
583
+ def _load_file(self, data):
584
+ """Załaduj plik do edycji"""
585
+ try:
586
+ file_path = data['file_path']
587
+ editor = FileEditor(file_path)
588
+ self.editors[file_path] = editor
589
+
590
+ response = {
591
+ 'success': True,
592
+ 'message': f'File loaded: {file_path}',
593
+ 'file_type': editor.file_type,
594
+ 'is_remote': editor.is_remote,
595
+ 'elements_count': len(editor.find_by_xpath("//*")) if LXML_AVAILABLE else 0
596
+ }
597
+ except Exception as e:
598
+ response = {'success': False, 'error': str(e)}
599
+
600
+ self._send_json_response(response)
601
+
602
+ def _query_elements(self, data):
603
+ """Wykonaj zapytanie XPath/CSS"""
604
+ try:
605
+ query = data['query']
606
+ query_type = data.get('type', 'xpath')
607
+
608
+ # Znajdź pierwszy załadowany plik
609
+ if not self.editors:
610
+ raise ValueError("No files loaded")
611
+
612
+ editor = list(self.editors.values())[0]
613
+
614
+ if query_type == 'xpath':
615
+ elements = editor.list_elements(query)
616
+ else:
617
+ elements = editor.find_by_css(query)
618
+ # Konwertuj wyniki CSS na format podobny do XPath
619
+ elements = [{'tag': str(elem.name), 'text': elem.get_text(), 'attributes': elem.attrs}
620
+ for elem in elements]
621
+
622
+ response = {
623
+ 'success': True,
624
+ 'elements': elements,
625
+ 'count': len(elements)
626
+ }
627
+ except Exception as e:
628
+ response = {'success': False, 'error': str(e)}
629
+
630
+ self._send_json_response(response)
631
+
632
+ def _update_element(self, data):
633
+ """Aktualizuj element"""
634
+ try:
635
+ xpath = data['xpath']
636
+ update_type = data['type']
637
+ value = data['value']
638
+
639
+ if not self.editors:
640
+ raise ValueError("No files loaded")
641
+
642
+ editor = list(self.editors.values())[0]
643
+
644
+ if update_type == 'text':
645
+ success = editor.set_element_text(xpath, value)
646
+ elif update_type == 'attribute':
647
+ attribute = data['attribute']
648
+ success = editor.set_element_attribute(xpath, attribute, value)
649
+ else:
650
+ raise ValueError(f"Unknown update type: {update_type}")
651
+
652
+ response = {
653
+ 'success': success,
654
+ 'message': f'Element updated successfully' if success else 'Element not found'
655
+ }
656
+ except Exception as e:
657
+ response = {'success': False, 'error': str(e)}
658
+
659
+ self._send_json_response(response)
660
+
661
+ def _save_file(self, data):
662
+ """Zapisz plik"""
663
+ try:
664
+ output_path = data.get('output_path')
665
+
666
+ if not self.editors:
667
+ raise ValueError("No files loaded")
668
+
669
+ editor = list(self.editors.values())[0]
670
+ success = editor.save(output_path)
671
+
672
+ response = {
673
+ 'success': success,
674
+ 'message': f'File saved successfully to {output_path or editor.file_path}'
675
+ }
676
+ except Exception as e:
677
+ response = {'success': False, 'error': str(e)}
678
+
679
+ self._send_json_response(response)
680
+
681
+ def _extract_data_uri(self, data):
682
+ """Wyciągnij Data URI z załadowanego pliku"""
683
+ try:
684
+ xpath = data['xpath']
685
+
686
+ if not self.editors:
687
+ raise ValueError("No files loaded")
688
+
689
+ editor = list(self.editors.values())[0]
690
+ result = editor.extract_data_uri(xpath)
691
+
692
+ self._send_json_response(result)
693
+ except Exception as e:
694
+ self._send_json_response({'error': str(e)})
695
+
696
+ def _send_response(self, status_code, content, content_type='text/plain'):
697
+ """Wyślij odpowiedź HTTP"""
698
+ self.send_response(status_code)
699
+ self.send_header('Content-type', content_type)
700
+ self.send_header('Access-Control-Allow-Origin', '*')
701
+ self.end_headers()
702
+ self.wfile.write(content.encode('utf-8'))
703
+
704
+ def _send_json_response(self, data):
705
+ """Wyślij odpowiedź JSON"""
706
+ self._send_response(200, json.dumps(data, indent=2), 'application/json')
707
+
708
+ def _send_error(self, status_code, message):
709
+ """Wyślij błąd"""
710
+ self._send_response(status_code, json.dumps({'error': message}), 'application/json')
711
+
712
+
713
+ class CLI:
714
+ """Interfejs CLI"""
715
+
716
+ def __init__(self):
717
+ self.editor = None
718
+
719
+ def run(self):
720
+ """Uruchom CLI"""
721
+ parser = argparse.ArgumentParser(description='Universal File Editor CLI')
722
+ subparsers = parser.add_subparsers(dest='command', help='Available commands')
723
+
724
+ # Load command
725
+ load_parser = subparsers.add_parser('load', help='Load file (local or remote)')
726
+ load_parser.add_argument('file', help='File path or URL')
727
+
728
+ # Query command
729
+ query_parser = subparsers.add_parser('query', help='Query elements')
730
+ query_parser.add_argument('xpath', help='XPath expression')
731
+ query_parser.add_argument('--type', choices=['text', 'attribute'], default='text')
732
+ query_parser.add_argument('--attr', help='Attribute name')
733
+
734
+ # Set command
735
+ set_parser = subparsers.add_parser('set', help='Set element value')
736
+ set_parser.add_argument('xpath', help='XPath expression')
737
+ set_parser.add_argument('value', help='New value')
738
+ set_parser.add_argument('--type', choices=['text', 'attribute'], default='text')
739
+ set_parser.add_argument('--attr', help='Attribute name')
740
+
741
+ # Extract Data URI command
742
+ extract_parser = subparsers.add_parser('extract', help='Extract Data URI from element')
743
+ extract_parser.add_argument('xpath', help='XPath to element with Data URI')
744
+ extract_parser.add_argument('--output', help='Save extracted data to file')
745
+ extract_parser.add_argument('--info', action='store_true', help='Show Data URI info only')
746
+
747
+ # List command
748
+ list_parser = subparsers.add_parser('list', help='List elements')
749
+ list_parser.add_argument('--xpath', default='//*', help='XPath filter')
750
+ list_parser.add_argument('--limit', type=int, default=20, help='Limit results')
751
+
752
+ # Save command
753
+ save_parser = subparsers.add_parser('save', help='Save file')
754
+ save_parser.add_argument('--output', help='Output file path')
755
+
756
+ # Server command
757
+ server_parser = subparsers.add_parser('server', help='Start HTTP server')
758
+ server_parser.add_argument('--port', type=int, default=8080, help='Server port')
759
+ server_parser.add_argument('--host', default='localhost', help='Server host')
760
+
761
+ # Shell command
762
+ shell_parser = subparsers.add_parser('shell', help='Interactive shell')
763
+
764
+ # Create examples command
765
+ examples_parser = subparsers.add_parser('examples', help='Create example files')
766
+ examples_parser.add_argument('--dir', default='.', help='Directory to create examples')
767
+
768
+ args = parser.parse_args()
769
+
770
+ if args.command == 'server':
771
+ self.start_server(args.host, args.port)
772
+ elif args.command == 'shell':
773
+ self.start_shell()
774
+ elif args.command == 'examples':
775
+ self.create_examples(args.dir)
776
+ else:
777
+ self.execute_command(args)
778
+
779
+ def execute_command(self, args):
780
+ """Wykonaj komendę CLI"""
781
+ try:
782
+ if args.command == 'load':
783
+ self.editor = FileEditor(args.file)
784
+ file_type = "remote" if args.file.startswith('http') else "local"
785
+ print(f"✅ Loaded {file_type} file: {args.file} ({self.editor.file_type})")
786
+ if LXML_AVAILABLE:
787
+ elements_count = len(self.editor.find_by_xpath("//*"))
788
+ print(f" Found {elements_count} elements")
789
+
790
+ elif args.command == 'query':
791
+ if not self.editor:
792
+ print("❌ No file loaded. Use 'load' command first.")
793
+ return
794
+
795
+ if args.type == 'text':
796
+ result = self.editor.get_element_text(args.xpath)
797
+ print(f"Text: {result}")
798
+ elif args.type == 'attribute':
799
+ if not args.attr:
800
+ print("❌ --attr required for attribute queries")
801
+ return
802
+ result = self.editor.get_element_attribute(args.xpath, args.attr)
803
+ print(f"Attribute {args.attr}: {result}")
804
+
805
+ elif args.command == 'extract':
806
+ if not self.editor:
807
+ print("❌ No file loaded. Use 'load' command first.")
808
+ return
809
+
810
+ if args.info:
811
+ # Tylko informacje o Data URI
812
+ result = self.editor.extract_data_uri(args.xpath)
813
+ if "error" in result:
814
+ print(f"❌ {result['error']}")
815
+ else:
816
+ print(f"✅ Data URI found:")
817
+ print(f" MIME type: {result['mime_type']}")
818
+ print(f" Size: {result['size']} bytes")
819
+ print(f" Base64 length: {len(result['base64_data'])} chars")
820
+ elif args.output:
821
+ # Zapisz do pliku
822
+ success = self.editor.save_data_uri_to_file(args.xpath, args.output)
823
+ if not success:
824
+ print("❌ Failed to extract and save Data URI")
825
+ else:
826
+ # Wyświetl surowe dane
827
+ result = self.editor.extract_data_uri(args.xpath)
828
+ if "error" in result:
829
+ print(f"❌ {result['error']}")
830
+ else:
831
+ print(f"MIME: {result['mime_type']}")
832
+ print(f"Size: {result['size']} bytes")
833
+ print(f"Base64 data: {result['base64_data'][:100]}...")
834
+
835
+ elif args.command == 'set':
836
+ if not self.editor:
837
+ print("❌ No file loaded. Use 'load' command first.")
838
+ return
839
+
840
+ if args.type == 'text':
841
+ success = self.editor.set_element_text(args.xpath, args.value)
842
+ elif args.type == 'attribute':
843
+ if not args.attr:
844
+ print("❌ --attr required for attribute updates")
845
+ return
846
+ success = self.editor.set_element_attribute(args.xpath, args.attr, args.value)
847
+
848
+ if success:
849
+ print("✅ Element updated")
850
+ else:
851
+ print("❌ Element not found")
852
+
853
+ elif args.command == 'list':
854
+ if not self.editor:
855
+ print("❌ No file loaded. Use 'load' command first.")
856
+ return
857
+
858
+ elements = self.editor.list_elements(args.xpath)
859
+ if not elements:
860
+ print("No elements found")
861
+ return
862
+
863
+ print(f"Found {len(elements)} elements:")
864
+ for i, elem in enumerate(elements[:args.limit]):
865
+ print(f"\n{i + 1}. Path: {elem['path']}")
866
+ print(f" Tag: {elem['tag']}")
867
+ if elem['text']:
868
+ text_preview = elem['text'][:100] + "..." if len(elem['text']) > 100 else elem['text']
869
+ print(f" Text: {repr(text_preview)}")
870
+ if elem['attributes']:
871
+ print(f" Attributes: {elem['attributes']}")
872
+
873
+ if len(elements) > args.limit:
874
+ print(f"\n... and {len(elements) - args.limit} more (use --limit to see more)")
875
+
876
+ elif args.command == 'save':
877
+ if not self.editor:
878
+ print("❌ No file loaded. Use 'load' command first.")
879
+ return
880
+
881
+ if self.editor.is_remote and not args.output:
882
+ print("❌ Remote file requires --output parameter")
883
+ return
884
+
885
+ success = self.editor.save(args.output)
886
+ if success:
887
+ save_path = args.output or self.editor.file_path
888
+ print(f"✅ File saved to {save_path}")
889
+ else:
890
+ print("❌ Save failed")
891
+
892
+ except Exception as e:
893
+ print(f"❌ Error: {e}")
894
+
895
+ def start_shell(self):
896
+ """Uruchom interaktywny shell"""
897
+ print("🚀 Interactive File Editor Shell")
898
+ print("Commands:")
899
+ print(" load <file> - Load file (local path or URL)")
900
+ print(" query <xpath> - Query element text")
901
+ print(" attr <xpath> <name> - Get element attribute")
902
+ print(" set <xpath> <value> - Set element text")
903
+ print(" setattr <xpath> <name> <value> - Set element attribute")
904
+ print(" extract <xpath> - Show Data URI info")
905
+ print(" save [path] - Save file")
906
+ print(" list [xpath] - List elements")
907
+ print(" help - Show this help")
908
+ print(" exit - Exit shell")
909
+ print()
910
+
911
+ while True:
912
+ try:
913
+ command_line = input("📝 > ").strip()
914
+ if not command_line:
915
+ continue
916
+
917
+ if command_line == 'exit':
918
+ break
919
+ elif command_line == 'help':
920
+ print("Available commands: load, query, attr, set, setattr, extract, save, list, help, exit")
921
+ continue
922
+
923
+ parts = command_line.split()
924
+ command = parts[0]
925
+
926
+ if command == 'load' and len(parts) == 2:
927
+ self.editor = FileEditor(parts[1])
928
+ file_type = "remote" if parts[1].startswith('http') else "local"
929
+ print(f"✅ Loaded {file_type} file ({self.editor.file_type})")
930
+
931
+ elif command == 'query' and len(parts) >= 2:
932
+ if not self.editor:
933
+ print("❌ No file loaded")
934
+ continue
935
+ xpath = ' '.join(parts[1:])
936
+ result = self.editor.get_element_text(xpath)
937
+ print(f"Result: {result}")
938
+
939
+ elif command == 'attr' and len(parts) >= 3:
940
+ if not self.editor:
941
+ print("❌ No file loaded")
942
+ continue
943
+ xpath = parts[1]
944
+ attr_name = parts[2]
945
+ result = self.editor.get_element_attribute(xpath, attr_name)
946
+ print(f"Attribute {attr_name}: {result}")
947
+
948
+ elif command == 'set' and len(parts) >= 3:
949
+ if not self.editor:
950
+ print("❌ No file loaded")
951
+ continue
952
+ xpath = parts[1]
953
+ value = ' '.join(parts[2:])
954
+ success = self.editor.set_element_text(xpath, value)
955
+ print("✅ Updated" if success else "❌ Not found")
956
+
957
+ elif command == 'setattr' and len(parts) >= 4:
958
+ if not self.editor:
959
+ print("❌ No file loaded")
960
+ continue
961
+ xpath = parts[1]
962
+ attr_name = parts[2]
963
+ attr_value = ' '.join(parts[3:])
964
+ success = self.editor.set_element_attribute(xpath, attr_name, attr_value)
965
+ print("✅ Updated" if success else "❌ Not found")
966
+
967
+ elif command == 'extract' and len(parts) >= 2:
968
+ if not self.editor:
969
+ print("❌ No file loaded")
970
+ continue
971
+ xpath = ' '.join(parts[1:])
972
+ result = self.editor.extract_data_uri(xpath)
973
+ if "error" in result:
974
+ print(f"❌ {result['error']}")
975
+ else:
976
+ print(f"✅ MIME: {result['mime_type']}, Size: {result['size']} bytes")
977
+
978
+ elif command == 'list':
979
+ if not self.editor:
980
+ print("❌ No file loaded")
981
+ continue
982
+ xpath = parts[1] if len(parts) > 1 else "//*"
983
+ elements = self.editor.list_elements(xpath)[:10] # limit to 10
984
+ for elem in elements:
985
+ text_preview = elem['text'][:50] + "..." if len(elem['text']) > 50 else elem['text']
986
+ print(f"{elem['tag']}: {repr(text_preview)}")
987
+
988
+ elif command == 'save':
989
+ if not self.editor:
990
+ print("❌ No file loaded")
991
+ continue
992
+ output = parts[1] if len(parts) > 1 else None
993
+ if self.editor.is_remote and not output:
994
+ print("❌ Remote file requires output path")
995
+ continue
996
+ success = self.editor.save(output)
997
+ print("✅ Saved" if success else "❌ Save failed")
998
+
999
+ else:
1000
+ print("❌ Unknown command or wrong arguments. Type 'help' for available commands.")
1001
+
1002
+ except KeyboardInterrupt:
1003
+ print("\n👋 Goodbye!")
1004
+ break
1005
+ except Exception as e:
1006
+ print(f"❌ Error: {e}")
1007
+
1008
+ def start_server(self, host, port):
1009
+ """Uruchom serwer HTTP"""
1010
+ print(f"🌐 Starting File Editor Server on {host}:{port}")
1011
+ print(f"Open http://{host}:{port} in your browser")
1012
+ print("API endpoints:")
1013
+ print(f" GET http://{host}:{port}/api/extract?url=<URL>&xpath=<XPATH>")
1014
+ print(f" POST http://{host}:{port}/api/load")
1015
+ print(f" POST http://{host}:{port}/api/query")
1016
+ print(f" POST http://{host}:{port}/api/update")
1017
+ print(f" POST http://{host}:{port}/api/save")
1018
+ print()
1019
+
1020
+ server = HTTPServer((host, port), FileEditorServer)
1021
+ try:
1022
+ server.serve_forever()
1023
+ except KeyboardInterrupt:
1024
+ print("\n👋 Server stopped")
1025
+
1026
+ def create_examples(self, directory):
1027
+ """Utwórz przykładowe pliki do testowania"""
1028
+ base_path = Path(directory)
1029
+ base_path.mkdir(exist_ok=True)
1030
+
1031
+ # Przykładowy SVG z Data URI
1032
+ svg_with_data_uri = '''<?xml version="1.0" encoding="UTF-8"?>
1033
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="300">
1034
+ <metadata>
1035
+ {
1036
+ "title": "Example SVG with Data URI",
1037
+ "description": "Test file for editor",
1038
+ "author": "File Editor Tool"
1039
+ }
1040
+ </metadata>
1041
+ <rect x="10" y="10" width="50" height="50" fill="red" id="square1"/>
1042
+ <circle cx="100" cy="100" r="30" fill="blue" id="circle1"/>
1043
+ <text x="50" y="150" id="text1" font-size="16">Hello World</text>
1044
+
1045
+ <!-- Embedded PDF as Data URI -->
1046
+ <image x="200" y="50" width="150" height="100"
1047
+ xlink:href="data:application/pdf;base64,JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKPD4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA2MTIgNzkyXQovUmVzb3VyY2VzIDw8Ci9Gb250IDw8Ci9GMSA0IDAgUgo+Pgo+PgovQ29udGVudHMgNSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9CYXNlRm9udCAvSGVsdmV0aWNhCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9MZW5ndGggNDQKPj4Kc3RyZWFtCkJUCi9GMSAxMiBUZgoxMDAgNzAwIFRkCihIZWxsbyBXb3JsZCEpIFRqCkVUCmVuZHN0cmVhbQplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQ1IDAwMDAwIG4gCjAwMDAwMDAzMTYgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSA2Ci9Sb290IDEgMCBSCj4+CnN0YXJ0eHJlZgo0MTAKJSVFT0Y=" />
1048
+
1049
+ <!-- JSON data in foreignObject -->
1050
+ <foreignObject x="0" y="250" width="400" height="50">
1051
+ <script type="application/json">
1052
+ {
1053
+ "data": [10, 20, 30, 40],
1054
+ "labels": ["A", "B", "C", "D"],
1055
+ "config": {"type": "chart", "animated": true}
1056
+ }
1057
+ </script>
1058
+ </foreignObject>
1059
+ </svg>'''
1060
+
1061
+ # Przykładowy XML z danymi
1062
+ xml_with_data = '''<?xml version="1.0" encoding="UTF-8"?>
1063
+ <data xmlns:xlink="http://www.w3.org/1999/xlink">
1064
+ <metadata>
1065
+ <title>Example Data XML</title>
1066
+ <version>1.0</version>
1067
+ <created>2025-01-01</created>
1068
+ </metadata>
1069
+ <records>
1070
+ <record id="1" type="user">
1071
+ <name>John Doe</name>
1072
+ <age>30</age>
1073
+ <email>john@example.com</email>
1074
+ <avatar xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" />
1075
+ </record>
1076
+ <record id="2" type="admin">
1077
+ <name>Jane Smith</name>
1078
+ <age>25</age>
1079
+ <email>jane@example.com</email>
1080
+ <settings>{"theme": "dark", "notifications": true}</settings>
1081
+ </record>
1082
+ </records>
1083
+ <files>
1084
+ <file name="document.pdf"
1085
+ data="data:application/pdf;base64,JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKPD4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA2MTIgNzkyXQovUmVzb3VyY2VzIDw8Ci9Gb250IDw8Ci9GMSA0IDAgUgo+Pgo+PgovQ29udGVudHMgNSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9CYXNlRm9udCAvSGVsdmV0aWNhCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9MZW5ndGggNDQKPj4Kc3RyZWFtCkJUCi9GMSAxMiBUZgoxMDAgNzAwIFRkCihIZWxsbyBXb3JsZCEpIFRqCkVUCmVuZHN0cmVhbQplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAowMDAwMDAwMjQ1IDAwMDAwIG4gCjAwMDAwMDAzMTYgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSA2Ci9Sb290IDEgMCBSCj4+CnN0YXJ0eHJlZgo0MTAKJSVFT0Y=" />
1086
+ </files>
1087
+ </data>'''
1088
+
1089
+ # Przykładowy HTML z osadzonym SVG
1090
+ html_with_svg = '''<!DOCTYPE html>
1091
+ <html xmlns="http://www.w3.org/1999/xhtml">
1092
+ <head>
1093
+ <title>Example HTML with embedded SVG</title>
1094
+ <meta name="description" content="Test HTML file with SVG and data">
1095
+ <style>
1096
+ body { font-family: Arial, sans-serif; margin: 20px; }
1097
+ .data-container { background: #f0f0f0; padding: 10px; margin: 10px 0; }
1098
+ </style>
1099
+ </head>
1100
+ <body>
1101
+ <h1 id="main-title">Example HTML Document</h1>
1102
+
1103
+ <div class="content">
1104
+ <p id="intro">This HTML contains embedded SVG with Data URI.</p>
1105
+
1106
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
1107
+ width="300" height="200" id="embedded-svg">
1108
+ <rect x="10" y="10" width="100" height="50" fill="green" />
1109
+ <text x="20" y="35" fill="white">Embedded SVG</text>
1110
+ <image x="150" y="20" width="100" height="60"
1111
+ xlink:href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjYwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iNjAiIGZpbGw9InJlZCIvPjx0ZXh0IHg9IjEwIiB5PSIzNSIgZmlsbD0id2hpdGUiPkRhdGEgVVJJPC90ZXh0Pjwvc3ZnPg==" />
1112
+ </svg>
1113
+
1114
+ <div class="data-container">
1115
+ <h3>JSON Data</h3>
1116
+ <script type="application/json" id="page-data">
1117
+ {
1118
+ "page": "example",
1119
+ "data": {
1120
+ "users": [
1121
+ {"id": 1, "name": "Alice"},
1122
+ {"id": 2, "name": "Bob"}
1123
+ ],
1124
+ "settings": {
1125
+ "theme": "light",
1126
+ "language": "en"
1127
+ }
1128
+ }
1129
+ }
1130
+ </script>
1131
+ </div>
1132
+
1133
+ <ul>
1134
+ <li class="item" data-value="1">Item 1</li>
1135
+ <li class="item" data-value="2">Item 2</li>
1136
+ <li class="item" data-value="3">Item 3</li>
1137
+ </ul>
1138
+ </div>
1139
+ </body>
1140
+ </html>'''
1141
+
1142
+ # Zapisz pliki
1143
+ files = [
1144
+ ('example.svg', svg_with_data_uri),
1145
+ ('example.xml', xml_with_data),
1146
+ ('example.html', html_with_svg)
1147
+ ]
1148
+
1149
+ for filename, content in files:
1150
+ file_path = base_path / filename
1151
+ with open(file_path, 'w', encoding='utf-8') as f:
1152
+ f.write(content)
1153
+ print(f"✅ Created: {file_path}")
1154
+
1155
+ # Utwórz plik z przykładami użycia
1156
+ examples_usage = '''# File Editor Tool - Examples
1157
+
1158
+ ## Example XPath queries for created files:
1159
+
1160
+ ### example.svg:
1161
+ - Get text content: `//svg:text[@id='text1']`
1162
+ - Get image Data URI: `//svg:image/@xlink:href`
1163
+ - Get JSON metadata: `//svg:metadata`
1164
+ - Get all elements with fill attribute: `//*[@fill]`
1165
+
1166
+ ### example.xml:
1167
+ - Get user names: `//record[@type='user']/name`
1168
+ - Get file Data URI: `//file[@name='document.pdf']/@data`
1169
+ - Get user by ID: `//record[@id='1']`
1170
+ - Get JSON settings: `//record[@id='2']/settings`
1171
+
1172
+ ### example.html:
1173
+ - Get page title: `//html:h1[@id='main-title']`
1174
+ - Get embedded SVG Data URI: `//svg:image/@xlink:href`
1175
+ - Get JSON data: `//script[@type='application/json']`
1176
+ - Get list items: `//html:li[@class='item']`
1177
+
1178
+ ## CLI Usage Examples:
1179
+
1180
+ ```bash
1181
+ # Load and explore SVG file
1182
+ python file_editor.py load example.svg
1183
+ python file_editor.py list --xpath "//svg:*"
1184
+ python file_editor.py query "//svg:text[@id='text1']"
1185
+
1186
+ # Extract Data URI from SVG
1187
+ python file_editor.py extract "//svg:image/@xlink:href" --info
1188
+ python file_editor.py extract "//svg:image/@xlink:href" --output extracted.pdf
1189
+
1190
+ # Edit elements
1191
+ python file_editor.py set "//svg:text[@id='text1']" "Modified Text"
1192
+ python file_editor.py save --output modified.svg
1193
+
1194
+ # Work with remote files
1195
+ python file_editor.py load "http://example.com/file.svg"
1196
+ python file_editor.py extract "//svg:image/@xlink:href" --info
1197
+
1198
+ # Start server
1199
+ python file_editor.py server --port 8080
1200
+ # Then open http://localhost:8080 in browser
1201
+ ```
1202
+
1203
+ ## Server API Examples:
1204
+
1205
+ ```bash
1206
+ # Extract Data URI from remote file
1207
+ curl "http://localhost:8080/api/extract?url=http://example.com/file.svg&xpath=//svg:image/@xlink:href"
1208
+
1209
+ # Load file via POST
1210
+ curl -X POST http://localhost:8080/api/load \\
1211
+ -H "Content-Type: application/json" \\
1212
+ -d '{"file_path": "example.svg"}'
1213
+
1214
+ # Query elements
1215
+ curl -X POST http://localhost:8080/api/query \\
1216
+ -H "Content-Type: application/json" \\
1217
+ -d '{"query": "//svg:text", "type": "xpath"}'
1218
+ ```
1219
+ '''
1220
+
1221
+ usage_path = base_path / 'EXAMPLES.md'
1222
+ with open(usage_path, 'w', encoding='utf-8') as f:
1223
+ f.write(examples_usage)
1224
+ print(f"✅ Created: {usage_path}")
1225
+
1226
+ print(f"\n🎉 Example files created in {base_path}")
1227
+ print("Use 'python file_editor.py examples --help' to see usage examples")
1228
+
1229
+
1230
+ def main():
1231
+ """Główna funkcja"""
1232
+ cli = CLI()
1233
+ cli.run()
1234
+
1235
+
1236
+ if __name__ == "__main__":
1237
+ main()