xsl 0.1.5__py3-none-any.whl → 0.1.8__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/__init__.py +19 -1
- xsl/__main__.py +15 -0
- xsl/cli.py +337 -1103
- xsl/editor.py +488 -2
- xsl/server.py +164 -2
- xsl/utils.py +103 -2
- xsl-0.1.8.dist-info/METADATA +372 -0
- xsl-0.1.8.dist-info/RECORD +11 -0
- xsl-0.1.8.dist-info/entry_points.txt +4 -0
- xsl-0.1.5.dist-info/METADATA +0 -110
- xsl-0.1.5.dist-info/RECORD +0 -10
- xsl-0.1.5.dist-info/entry_points.txt +0 -4
- {xsl-0.1.5.dist-info → xsl-0.1.8.dist-info}/LICENSE +0 -0
- {xsl-0.1.5.dist-info → xsl-0.1.8.dist-info}/WHEEL +0 -0
xsl/cli.py
CHANGED
@@ -1,942 +1,354 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
1
|
"""
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
CLI interface for xsl.
|
3
|
+
"""
|
4
|
+
|
5
|
+
"""
|
6
|
+
Command-line interface for xsl.
|
7
|
+
|
8
|
+
This module provides the CLI interface for the xsl package.
|
6
9
|
"""
|
7
10
|
|
8
11
|
import argparse
|
9
|
-
import json
|
10
|
-
import os
|
11
12
|
import sys
|
12
|
-
import
|
13
|
+
import os
|
13
14
|
from pathlib import Path
|
14
|
-
from typing import
|
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'))
|
15
|
+
from typing import Optional, List, Dict, Any
|
703
16
|
|
704
|
-
|
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')
|
17
|
+
from . import __version__, FileEditor
|
711
18
|
|
712
19
|
|
713
20
|
class CLI:
|
714
|
-
"""
|
21
|
+
"""Command-line interface for xsl."""
|
715
22
|
|
716
23
|
def __init__(self):
|
717
|
-
self.editor = None
|
24
|
+
self.editor: Optional[FileEditor] = None
|
718
25
|
|
719
26
|
def run(self):
|
720
|
-
"""
|
721
|
-
parser = argparse.ArgumentParser(
|
722
|
-
|
27
|
+
"""Run the CLI with command line arguments."""
|
28
|
+
parser = argparse.ArgumentParser(
|
29
|
+
description="xsl - Universal File Editor for XML/SVG/HTML",
|
30
|
+
epilog="For more information, visit: https://github.com/veridock/xsl",
|
31
|
+
)
|
32
|
+
parser.add_argument("--version", action="version", version=f"xsl {__version__}")
|
33
|
+
|
34
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
723
35
|
|
724
36
|
# Load command
|
725
|
-
load_parser = subparsers.add_parser(
|
726
|
-
load_parser.add_argument(
|
37
|
+
load_parser = subparsers.add_parser("load", help="Load file (local or remote)")
|
38
|
+
load_parser.add_argument("file", help="File path or URL")
|
727
39
|
|
728
40
|
# Query command
|
729
|
-
query_parser = subparsers.add_parser(
|
730
|
-
query_parser.add_argument(
|
731
|
-
query_parser.add_argument(
|
732
|
-
|
41
|
+
query_parser = subparsers.add_parser("query", help="Query elements using XPath")
|
42
|
+
query_parser.add_argument("xpath", help="XPath expression")
|
43
|
+
query_parser.add_argument(
|
44
|
+
"--type",
|
45
|
+
choices=["text", "attribute"],
|
46
|
+
default="text",
|
47
|
+
help="Query type (default: text)",
|
48
|
+
)
|
49
|
+
query_parser.add_argument(
|
50
|
+
"--attr", help="Attribute name (required for attribute type)"
|
51
|
+
)
|
733
52
|
|
734
53
|
# Set command
|
735
|
-
set_parser = subparsers.add_parser(
|
736
|
-
set_parser.add_argument(
|
737
|
-
set_parser.add_argument(
|
738
|
-
set_parser.add_argument(
|
739
|
-
|
54
|
+
set_parser = subparsers.add_parser("set", help="Set element value")
|
55
|
+
set_parser.add_argument("xpath", help="XPath expression")
|
56
|
+
set_parser.add_argument("value", help="New value")
|
57
|
+
set_parser.add_argument(
|
58
|
+
"--type",
|
59
|
+
choices=["text", "attribute"],
|
60
|
+
default="text",
|
61
|
+
help="Set type (default: text)",
|
62
|
+
)
|
63
|
+
set_parser.add_argument(
|
64
|
+
"--attr", help="Attribute name (required for attribute type)"
|
65
|
+
)
|
740
66
|
|
741
67
|
# Extract Data URI command
|
742
|
-
extract_parser = subparsers.add_parser(
|
743
|
-
|
744
|
-
|
745
|
-
extract_parser.add_argument(
|
68
|
+
extract_parser = subparsers.add_parser(
|
69
|
+
"extract", help="Extract Data URI from element"
|
70
|
+
)
|
71
|
+
extract_parser.add_argument("xpath", help="XPath to element with Data URI")
|
72
|
+
extract_parser.add_argument("--output", help="Save extracted data to file")
|
73
|
+
extract_parser.add_argument(
|
74
|
+
"--info", action="store_true", help="Show Data URI info only"
|
75
|
+
)
|
746
76
|
|
747
77
|
# List command
|
748
|
-
list_parser = subparsers.add_parser(
|
749
|
-
list_parser.add_argument(
|
750
|
-
|
78
|
+
list_parser = subparsers.add_parser("list", help="List elements")
|
79
|
+
list_parser.add_argument(
|
80
|
+
"--xpath", default="//*", help="XPath filter (default: //*)"
|
81
|
+
)
|
82
|
+
list_parser.add_argument(
|
83
|
+
"--limit", type=int, default=20, help="Limit results (default: 20)"
|
84
|
+
)
|
85
|
+
|
86
|
+
# Add command
|
87
|
+
add_parser = subparsers.add_parser("add", help="Add new element")
|
88
|
+
add_parser.add_argument("parent_xpath", help="XPath to parent element")
|
89
|
+
add_parser.add_argument("tag", help="New element tag name")
|
90
|
+
add_parser.add_argument("--text", help="Element text content")
|
91
|
+
add_parser.add_argument("--attrs", help="Attributes as key=value,key=value")
|
92
|
+
|
93
|
+
# Remove command
|
94
|
+
remove_parser = subparsers.add_parser("remove", help="Remove element")
|
95
|
+
remove_parser.add_argument("xpath", help="XPath to element to remove")
|
751
96
|
|
752
97
|
# Save command
|
753
|
-
save_parser = subparsers.add_parser(
|
754
|
-
save_parser.add_argument(
|
98
|
+
save_parser = subparsers.add_parser("save", help="Save file")
|
99
|
+
save_parser.add_argument("--output", help="Output file path")
|
100
|
+
save_parser.add_argument(
|
101
|
+
"--backup", action="store_true", help="Create backup before saving"
|
102
|
+
)
|
755
103
|
|
756
|
-
#
|
757
|
-
|
758
|
-
server_parser.add_argument('--port', type=int, default=8080, help='Server port')
|
759
|
-
server_parser.add_argument('--host', default='localhost', help='Server host')
|
104
|
+
# Info command
|
105
|
+
info_parser = subparsers.add_parser("info", help="Show file information")
|
760
106
|
|
761
107
|
# Shell command
|
762
|
-
shell_parser = subparsers.add_parser(
|
108
|
+
shell_parser = subparsers.add_parser("shell", help="Interactive shell mode")
|
763
109
|
|
764
|
-
#
|
765
|
-
examples_parser = subparsers.add_parser(
|
766
|
-
examples_parser.add_argument(
|
110
|
+
# Examples command
|
111
|
+
examples_parser = subparsers.add_parser("examples", help="Create example files")
|
112
|
+
examples_parser.add_argument(
|
113
|
+
"--dir", default=".", help="Directory to create examples (default: .)"
|
114
|
+
)
|
767
115
|
|
768
116
|
args = parser.parse_args()
|
769
117
|
|
770
|
-
if args.command
|
771
|
-
|
772
|
-
|
773
|
-
self.start_shell()
|
774
|
-
elif args.command == 'examples':
|
775
|
-
self.create_examples(args.dir)
|
776
|
-
else:
|
777
|
-
self.execute_command(args)
|
118
|
+
if not args.command:
|
119
|
+
parser.print_help()
|
120
|
+
return
|
778
121
|
|
779
|
-
def execute_command(self, args):
|
780
|
-
"""Wykonaj komendę CLI"""
|
781
122
|
try:
|
782
|
-
if args.command ==
|
783
|
-
self.
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
123
|
+
if args.command == "shell":
|
124
|
+
self.start_shell()
|
125
|
+
elif args.command == "examples":
|
126
|
+
self.create_examples(args.dir)
|
127
|
+
else:
|
128
|
+
self.execute_command(args)
|
129
|
+
except KeyboardInterrupt:
|
130
|
+
print("\n👋 Interrupted by user")
|
131
|
+
sys.exit(1)
|
132
|
+
except Exception as e:
|
133
|
+
print(f"❌ Error: {e}")
|
134
|
+
sys.exit(1)
|
794
135
|
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
136
|
+
def execute_command(self, args):
|
137
|
+
"""Execute a single command."""
|
138
|
+
if args.command == "load":
|
139
|
+
self.editor = FileEditor(args.file)
|
140
|
+
file_type = "remote" if args.file.startswith("http") else "local"
|
141
|
+
print(f"✅ Loaded {file_type} file: {args.file} ({self.editor.file_type})")
|
142
|
+
|
143
|
+
info = self.editor.get_info()
|
144
|
+
if "elements_count" in info:
|
145
|
+
print(f" Found {info['elements_count']} elements")
|
146
|
+
|
147
|
+
elif args.command == "query":
|
148
|
+
self._require_loaded_file()
|
149
|
+
|
150
|
+
if args.type == "text":
|
151
|
+
result = self.editor.get_element_text(args.xpath)
|
152
|
+
print(f"Text: {result}")
|
153
|
+
elif args.type == "attribute":
|
154
|
+
if not args.attr:
|
155
|
+
print("❌ --attr required for attribute queries")
|
808
156
|
return
|
157
|
+
result = self.editor.get_element_attribute(args.xpath, args.attr)
|
158
|
+
print(f"Attribute {args.attr}: {result}")
|
809
159
|
|
810
|
-
|
811
|
-
|
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]}...")
|
160
|
+
elif args.command == "extract":
|
161
|
+
self._require_loaded_file()
|
834
162
|
|
835
|
-
|
836
|
-
|
837
|
-
|
163
|
+
if args.info:
|
164
|
+
# Show Data URI information only
|
165
|
+
result = self.editor.extract_data_uri(args.xpath)
|
166
|
+
if "error" in result:
|
167
|
+
print(f"❌ {result['error']}")
|
168
|
+
else:
|
169
|
+
print(f"✅ Data URI found:")
|
170
|
+
print(f" MIME type: {result['mime_type']}")
|
171
|
+
print(f" Encoding: {result['encoding']}")
|
172
|
+
print(f" Size: {result['size']} bytes")
|
173
|
+
print(f" Base64 length: {len(result['base64_data'])} chars")
|
174
|
+
elif args.output:
|
175
|
+
# Save to file
|
176
|
+
success = self.editor.save_data_uri_to_file(args.xpath, args.output)
|
177
|
+
if not success:
|
178
|
+
print("❌ Failed to extract and save Data URI")
|
179
|
+
else:
|
180
|
+
# Show raw data info
|
181
|
+
result = self.editor.extract_data_uri(args.xpath)
|
182
|
+
if "error" in result:
|
183
|
+
print(f"❌ {result['error']}")
|
184
|
+
else:
|
185
|
+
print(f"MIME: {result['mime_type']}")
|
186
|
+
print(f"Size: {result['size']} bytes")
|
187
|
+
preview = (
|
188
|
+
result["base64_data"][:100] + "..."
|
189
|
+
if len(result["base64_data"]) > 100
|
190
|
+
else result["base64_data"]
|
191
|
+
)
|
192
|
+
print(f"Base64 data: {preview}")
|
193
|
+
|
194
|
+
elif args.command == "set":
|
195
|
+
self._require_loaded_file()
|
196
|
+
|
197
|
+
if args.type == "text":
|
198
|
+
success = self.editor.set_element_text(args.xpath, args.value)
|
199
|
+
elif args.type == "attribute":
|
200
|
+
if not args.attr:
|
201
|
+
print("❌ --attr required for attribute updates")
|
838
202
|
return
|
203
|
+
success = self.editor.set_element_attribute(
|
204
|
+
args.xpath, args.attr, args.value
|
205
|
+
)
|
839
206
|
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
print("❌ --attr required for attribute updates")
|
845
|
-
return
|
846
|
-
success = self.editor.set_element_attribute(args.xpath, args.attr, args.value)
|
207
|
+
if success:
|
208
|
+
print("✅ Element updated")
|
209
|
+
else:
|
210
|
+
print("❌ Element not found")
|
847
211
|
|
848
|
-
|
849
|
-
|
850
|
-
else:
|
851
|
-
print("❌ Element not found")
|
212
|
+
elif args.command == "list":
|
213
|
+
self._require_loaded_file()
|
852
214
|
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
215
|
+
elements = self.editor.list_elements(args.xpath)
|
216
|
+
if not elements:
|
217
|
+
print("No elements found")
|
218
|
+
return
|
857
219
|
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
220
|
+
print(f"Found {len(elements)} elements:")
|
221
|
+
for i, elem in enumerate(elements[: args.limit]):
|
222
|
+
print(f"\n{i + 1}. Path: {elem['path']}")
|
223
|
+
print(f" Tag: {elem['tag']}")
|
224
|
+
if elem["text"]:
|
225
|
+
text_preview = (
|
226
|
+
elem["text"][:100] + "..."
|
227
|
+
if len(elem["text"]) > 100
|
228
|
+
else elem["text"]
|
229
|
+
)
|
230
|
+
print(f" Text: {repr(text_preview)}")
|
231
|
+
if elem["attributes"]:
|
232
|
+
print(f" Attributes: {elem['attributes']}")
|
233
|
+
|
234
|
+
if len(elements) > args.limit:
|
235
|
+
print(
|
236
|
+
f"\n... and {len(elements) - args.limit} more (use --limit to see more)"
|
237
|
+
)
|
238
|
+
|
239
|
+
elif args.command == "add":
|
240
|
+
self._require_loaded_file()
|
241
|
+
|
242
|
+
# Parse attributes
|
243
|
+
attributes = {}
|
244
|
+
if args.attrs:
|
245
|
+
for pair in args.attrs.split(","):
|
246
|
+
if "=" in pair:
|
247
|
+
key, value = pair.split("=", 1)
|
248
|
+
attributes[key.strip()] = value.strip()
|
249
|
+
|
250
|
+
success = self.editor.add_element(
|
251
|
+
args.parent_xpath, args.tag, args.text or "", attributes
|
252
|
+
)
|
253
|
+
if success:
|
254
|
+
print("✅ Element added")
|
255
|
+
else:
|
256
|
+
print("❌ Parent element not found")
|
862
257
|
|
863
|
-
|
864
|
-
|
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
|
258
|
+
elif args.command == "remove":
|
259
|
+
self._require_loaded_file()
|
880
260
|
|
881
|
-
|
882
|
-
|
883
|
-
|
261
|
+
success = self.editor.remove_element(args.xpath)
|
262
|
+
if success:
|
263
|
+
print("✅ Element removed")
|
264
|
+
else:
|
265
|
+
print("❌ Element not found")
|
884
266
|
|
885
|
-
|
886
|
-
|
887
|
-
save_path = args.output or self.editor.file_path
|
888
|
-
print(f"✅ File saved to {save_path}")
|
889
|
-
else:
|
890
|
-
print("❌ Save failed")
|
267
|
+
elif args.command == "info":
|
268
|
+
self._require_loaded_file()
|
891
269
|
|
892
|
-
|
893
|
-
print(
|
270
|
+
info = self.editor.get_info()
|
271
|
+
print("📄 File Information:")
|
272
|
+
print(f" Path: {info['file_path']}")
|
273
|
+
print(f" Type: {info['file_type']}")
|
274
|
+
print(f" Remote: {info['is_remote']}")
|
275
|
+
print(f" Size: {info['size']} bytes")
|
276
|
+
if "elements_count" in info:
|
277
|
+
print(f" Elements: {info['elements_count']}")
|
278
|
+
|
279
|
+
elif args.command == "save":
|
280
|
+
self._require_loaded_file()
|
281
|
+
|
282
|
+
if self.editor.is_remote and not args.output:
|
283
|
+
print("❌ Remote file requires --output parameter")
|
284
|
+
return
|
285
|
+
|
286
|
+
if args.backup and not self.editor.is_remote:
|
287
|
+
backup_path = self.editor.backup()
|
288
|
+
print(f"📋 Backup created: {backup_path}")
|
289
|
+
|
290
|
+
success = self.editor.save(args.output)
|
291
|
+
if success:
|
292
|
+
save_path = args.output or self.editor.file_path
|
293
|
+
print(f"✅ File saved to {save_path}")
|
294
|
+
else:
|
295
|
+
print("❌ Save failed")
|
296
|
+
|
297
|
+
def _require_loaded_file(self):
|
298
|
+
"""Check if a file is loaded, exit if not."""
|
299
|
+
if not self.editor:
|
300
|
+
print("❌ No file loaded. Use 'load' command first.")
|
301
|
+
sys.exit(1)
|
894
302
|
|
895
303
|
def start_shell(self):
|
896
|
-
"""
|
897
|
-
print("🚀 Interactive
|
898
|
-
print("
|
899
|
-
print("
|
900
|
-
print("
|
901
|
-
print("
|
902
|
-
print("
|
903
|
-
print("
|
904
|
-
print("
|
905
|
-
print("
|
906
|
-
print("
|
907
|
-
print("
|
908
|
-
print("
|
304
|
+
"""Start interactive shell mode."""
|
305
|
+
print("🚀 xsl Interactive Shell")
|
306
|
+
print(f"Version: {__version__}")
|
307
|
+
print("\nCommands:")
|
308
|
+
print(" load <file> - Load file (local path or URL)")
|
309
|
+
print(" query <xpath> - Query element text")
|
310
|
+
print(" attr <xpath> <name> - Get element attribute")
|
311
|
+
print(" set <xpath> <value> - Set element text")
|
312
|
+
print(" setattr <xpath> <n> <v> - Set element attribute")
|
313
|
+
print(" extract <xpath> - Show Data URI info")
|
314
|
+
print(" save [path] - Save file")
|
315
|
+
print(" list [xpath] - List elements")
|
316
|
+
print(" info - Show file info")
|
317
|
+
print(" help - Show this help")
|
318
|
+
print(" exit - Exit shell")
|
909
319
|
print()
|
910
320
|
|
911
321
|
while True:
|
912
322
|
try:
|
913
|
-
command_line = input("
|
323
|
+
command_line = input("xsl> ").strip()
|
914
324
|
if not command_line:
|
915
325
|
continue
|
916
326
|
|
917
|
-
if command_line ==
|
327
|
+
if command_line == "exit":
|
918
328
|
break
|
919
|
-
elif command_line ==
|
920
|
-
print(
|
329
|
+
elif command_line == "help":
|
330
|
+
print(
|
331
|
+
"Available commands: load, query, attr, set, setattr, extract, save, list, info, help, exit"
|
332
|
+
)
|
921
333
|
continue
|
922
334
|
|
923
335
|
parts = command_line.split()
|
924
336
|
command = parts[0]
|
925
337
|
|
926
|
-
if command ==
|
338
|
+
if command == "load" and len(parts) == 2:
|
927
339
|
self.editor = FileEditor(parts[1])
|
928
|
-
file_type = "remote" if parts[1].startswith(
|
340
|
+
file_type = "remote" if parts[1].startswith("http") else "local"
|
929
341
|
print(f"✅ Loaded {file_type} file ({self.editor.file_type})")
|
930
342
|
|
931
|
-
elif command ==
|
343
|
+
elif command == "query" and len(parts) >= 2:
|
932
344
|
if not self.editor:
|
933
345
|
print("❌ No file loaded")
|
934
346
|
continue
|
935
|
-
xpath =
|
347
|
+
xpath = " ".join(parts[1:])
|
936
348
|
result = self.editor.get_element_text(xpath)
|
937
349
|
print(f"Result: {result}")
|
938
350
|
|
939
|
-
elif command ==
|
351
|
+
elif command == "attr" and len(parts) >= 3:
|
940
352
|
if not self.editor:
|
941
353
|
print("❌ No file loaded")
|
942
354
|
continue
|
@@ -945,47 +357,66 @@ class CLI:
|
|
945
357
|
result = self.editor.get_element_attribute(xpath, attr_name)
|
946
358
|
print(f"Attribute {attr_name}: {result}")
|
947
359
|
|
948
|
-
elif command ==
|
360
|
+
elif command == "set" and len(parts) >= 3:
|
949
361
|
if not self.editor:
|
950
362
|
print("❌ No file loaded")
|
951
363
|
continue
|
952
364
|
xpath = parts[1]
|
953
|
-
value =
|
365
|
+
value = " ".join(parts[2:])
|
954
366
|
success = self.editor.set_element_text(xpath, value)
|
955
367
|
print("✅ Updated" if success else "❌ Not found")
|
956
368
|
|
957
|
-
elif command ==
|
369
|
+
elif command == "setattr" and len(parts) >= 4:
|
958
370
|
if not self.editor:
|
959
371
|
print("❌ No file loaded")
|
960
372
|
continue
|
961
373
|
xpath = parts[1]
|
962
374
|
attr_name = parts[2]
|
963
|
-
attr_value =
|
964
|
-
success = self.editor.set_element_attribute(
|
375
|
+
attr_value = " ".join(parts[3:])
|
376
|
+
success = self.editor.set_element_attribute(
|
377
|
+
xpath, attr_name, attr_value
|
378
|
+
)
|
965
379
|
print("✅ Updated" if success else "❌ Not found")
|
966
380
|
|
967
|
-
elif command ==
|
381
|
+
elif command == "extract" and len(parts) >= 2:
|
968
382
|
if not self.editor:
|
969
383
|
print("❌ No file loaded")
|
970
384
|
continue
|
971
|
-
xpath =
|
385
|
+
xpath = " ".join(parts[1:])
|
972
386
|
result = self.editor.extract_data_uri(xpath)
|
973
387
|
if "error" in result:
|
974
388
|
print(f"❌ {result['error']}")
|
975
389
|
else:
|
976
|
-
print(
|
390
|
+
print(
|
391
|
+
f"✅ MIME: {result['mime_type']}, Size: {result['size']} bytes"
|
392
|
+
)
|
977
393
|
|
978
|
-
elif command ==
|
394
|
+
elif command == "list":
|
979
395
|
if not self.editor:
|
980
396
|
print("❌ No file loaded")
|
981
397
|
continue
|
982
398
|
xpath = parts[1] if len(parts) > 1 else "//*"
|
983
399
|
elements = self.editor.list_elements(xpath)[:10] # limit to 10
|
984
400
|
for elem in elements:
|
985
|
-
text_preview =
|
401
|
+
text_preview = (
|
402
|
+
elem["text"][:50] + "..."
|
403
|
+
if len(elem["text"]) > 50
|
404
|
+
else elem["text"]
|
405
|
+
)
|
986
406
|
print(f"{elem['tag']}: {repr(text_preview)}")
|
987
407
|
|
988
|
-
elif command ==
|
408
|
+
elif command == "info":
|
409
|
+
if not self.editor:
|
410
|
+
print("❌ No file loaded")
|
411
|
+
continue
|
412
|
+
info = self.editor.get_info()
|
413
|
+
print(
|
414
|
+
f"File: {info['file_path']} ({info['file_type']}, {info['size']} bytes)"
|
415
|
+
)
|
416
|
+
if "elements_count" in info:
|
417
|
+
print(f"Elements: {info['elements_count']}")
|
418
|
+
|
419
|
+
elif command == "save":
|
989
420
|
if not self.editor:
|
990
421
|
print("❌ No file loaded")
|
991
422
|
continue
|
@@ -997,7 +428,9 @@ class CLI:
|
|
997
428
|
print("✅ Saved" if success else "❌ Save failed")
|
998
429
|
|
999
430
|
else:
|
1000
|
-
print(
|
431
|
+
print(
|
432
|
+
"❌ Unknown command or wrong arguments. Type 'help' for available commands."
|
433
|
+
)
|
1001
434
|
|
1002
435
|
except KeyboardInterrupt:
|
1003
436
|
print("\n👋 Goodbye!")
|
@@ -1005,233 +438,34 @@ class CLI:
|
|
1005
438
|
except Exception as e:
|
1006
439
|
print(f"❌ Error: {e}")
|
1007
440
|
|
1008
|
-
def
|
1009
|
-
"""
|
1010
|
-
|
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()
|
441
|
+
def create_examples(self, directory: str):
|
442
|
+
"""Create example files for testing."""
|
443
|
+
from .examples import create_example_files
|
1019
444
|
|
1020
|
-
server = HTTPServer((host, port), FileEditorServer)
|
1021
445
|
try:
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
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"""
|
446
|
+
create_example_files(directory)
|
447
|
+
print(f"✅ Example files created in {directory}")
|
448
|
+
except Exception as e:
|
449
|
+
print(f"❌ Error creating examples: {e}")
|
450
|
+
|
451
|
+
|
452
|
+
def main(args: List[str] = None) -> int:
|
453
|
+
"""Entry point for the xsl CLI command.
|
454
|
+
|
455
|
+
Args:
|
456
|
+
args: Command line arguments (default: None, uses sys.argv)
|
457
|
+
|
458
|
+
Returns:
|
459
|
+
int: Exit code (0 for success, non-zero for error)
|
460
|
+
"""
|
1232
461
|
cli = CLI()
|
1233
|
-
|
462
|
+
try:
|
463
|
+
cli.run(args)
|
464
|
+
return 0
|
465
|
+
except Exception as e:
|
466
|
+
print(f"Error: {e}", file=sys.stderr)
|
467
|
+
return 1
|
1234
468
|
|
1235
469
|
|
1236
470
|
if __name__ == "__main__":
|
1237
|
-
main()
|
471
|
+
main()
|