om4r932 commited on
Commit
a5c3314
·
1 Parent(s): a7f45db

Update methods

Browse files
Files changed (4) hide show
  1. app.py +59 -280
  2. static/script.js +0 -196
  3. static/style.css +0 -171
  4. templates/index.html +0 -42
app.py CHANGED
@@ -1,26 +1,20 @@
1
  import requests
2
- import json
3
  import os
4
- import uuid
5
  import zipfile
6
- import io
7
  import subprocess
8
  import os
9
  import re
10
  import warnings
11
  from fastapi import FastAPI, HTTPException
12
  from fastapi.middleware.cors import CORSMiddleware
13
- from fastapi.responses import FileResponse
14
- from fastapi.staticfiles import StaticFiles
15
  from pydantic import BaseModel
16
- from typing import Any, Dict, List, Literal, Optional
17
 
18
  warnings.filterwarnings("ignore")
19
 
20
  app = FastAPI(title="3GPP Specification Splitter API",
21
- description="API to split and display specifications by their chapters & sub-chapters")
22
-
23
- app.mount("/static", StaticFiles(directory="static"), name="static")
24
 
25
  origins = [
26
  "*",
@@ -34,73 +28,49 @@ app.add_middleware(
34
  allow_headers=["*"],
35
  )
36
 
37
- regex = r"^(\d+[a-z]?(?:\.\d+)*)\t[\ \S]+$"
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- def get_text(specification: str, version: str):
40
- """Récupère les bytes du PDF à partir d'une spécification et d'une version."""
41
- doc_id = specification
42
- series = doc_id.split(".")[0]
43
 
 
44
  response = requests.get(
45
- f"https://www.3gpp.org/ftp/Specs/archive/{series}_series/{doc_id}/{doc_id.replace('.', '')}-{version}.zip",
46
  verify=False,
47
- headers={"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
 
48
  )
 
 
 
 
 
 
 
 
49
 
50
- if response.status_code != 200:
51
- raise Exception(f"Téléchargement du ZIP échoué pour {specification}-{version}")
52
-
53
- zip_bytes = io.BytesIO(response.content)
54
-
55
- with zipfile.ZipFile(zip_bytes) as zf:
56
- for file_name in zf.namelist():
57
- if file_name.endswith("zip"):
58
- print("Another ZIP !")
59
- zip_bytes = io.BytesIO(zf.read(file_name))
60
- zf = zipfile.ZipFile(zip_bytes)
61
- for file_name2 in zf.namelist():
62
- if file_name2.endswith("doc") or file_name2.endswith("docx"):
63
- if "cover" in file_name2.lower():
64
- print("COVER !")
65
- continue
66
- ext = file_name2.split(".")[-1]
67
- doc_bytes = zf.read(file_name2)
68
- temp_id = str(uuid.uuid4())
69
- input_path = f"/tmp/{temp_id}.{ext}"
70
- output_path = f"/tmp/{temp_id}.txt"
71
-
72
- with open(input_path, "wb") as f:
73
- f.write(doc_bytes)
74
 
75
- subprocess.run([
76
- "libreoffice",
77
- "--headless",
78
- "--convert-to", "txt",
79
- "--outdir", "/tmp",
80
- input_path
81
- ], check=True)
82
-
83
- with open(output_path, "r") as f:
84
- txt_data = [line.strip() for line in f if line.strip()]
85
-
86
- os.remove(input_path)
87
- os.remove(output_path)
88
- return txt_data
89
- elif file_name.endswith("doc") or file_name.endswith("docx"):
90
- if "cover" in file_name.lower():
91
- print("COVER !")
92
- continue
93
- ext = file_name.split(".")[-1]
94
- doc_bytes = zf.read(file_name)
95
- temp_id = str(uuid.uuid4())
96
- input_path = f"/tmp/{temp_id}.{ext}"
97
- output_path = f"/tmp/{temp_id}.txt"
98
-
99
- print("Ecriture")
100
- with open(input_path, "wb") as f:
101
- f.write(doc_bytes)
102
-
103
- print("Convertissement")
104
  subprocess.run([
105
  "libreoffice",
106
  "--headless",
@@ -108,224 +78,33 @@ def get_text(specification: str, version: str):
108
  "--outdir", "/tmp",
109
  input_path
110
  ], check=True)
111
-
112
- print("Ecriture TXT")
113
- with open(output_path, "r", encoding="utf-8") as f:
114
  txt_data = [line.strip() for line in f if line.strip()]
115
-
116
  os.remove(input_path)
117
  os.remove(output_path)
118
- return txt_data
119
-
120
- raise Exception(f"Aucun fichier .doc/.docx trouvé dans le ZIP pour {specification}-{version}")
121
-
122
- def get_latest_version(spec: str) -> str:
123
- try:
124
- req = requests.post("https://organizedprogrammers-3gppdocfinder.hf.space/find", headers={"Content-Type": "application/json"}, data=json.dumps({"doc_id": spec}), verify=False)
125
- except Exception as e:
126
- raise HTTPException(500, f"An error has occured while getting latest version: {e}")
127
- if req.status_code == 200:
128
- reqJS = req.json()
129
- return reqJS['version']
130
  else:
131
- raise HTTPException(req.status_code, "An error has occured while getting latest version")
132
-
133
- def create_nested_structure(chapters, text, real_toc_indexes):
134
- """Crée une structure hiérarchique où les sous-sections sont imbriquées dans leurs sections parentes."""
135
- result = {}
136
-
137
- # Trier les chapitres par numéro de section
138
- sorted_chapters = sorted(chapters, key=lambda x: [int(p) if p.isdigit() else p for p in x.split()[0].split('.')])
139
-
140
- # Préparer les contenus des chapitres
141
- chapter_contents = {}
142
- for i, chapter in enumerate(sorted_chapters):
143
- current_index = real_toc_indexes[chapter]
144
-
145
- # Déterminer l'index de fin
146
- end_index = len(text)
147
- if i < len(sorted_chapters) - 1:
148
- next_chapter = sorted_chapters[i + 1]
149
- end_index = real_toc_indexes[next_chapter]
150
-
151
- # Extraire et nettoyer le contenu
152
- content = text[current_index + 1:end_index]
153
- cleaned_content = "\n".join(content).strip()
154
- chapter_contents[chapter] = cleaned_content
155
-
156
- # Fonction récursive pour construire la structure hiérarchique
157
- def insert_section(root, section_path, title, content):
158
- """Insère une section dans l'arborescence hiérarchique."""
159
- parts = section_path.split('.')
160
-
161
- # Ignorer les sections sans titre réel (seulement si le titre est vide ou juste des espaces)
162
- if not title.strip():
163
- # Si c'est une section sans titre mais avec du contenu, on peut le fusionner avec sa première sous-section
164
- # ou simplement l'ignorer selon votre besoin
165
- return None
166
-
167
- # Cas de base: section de premier niveau
168
- if len(parts) == 1:
169
- key = section_path + " " + title
170
- if key not in root:
171
- root[key] = {"content": content, "subsections": {}}
172
- else:
173
- root[key]["content"] = content
174
- return root[key]
175
-
176
- # Trouver ou créer le parent
177
- parent_path = '.'.join(parts[:-1])
178
- for key in root.keys():
179
- if key.startswith(parent_path + " "):
180
- # Parent trouvé, insérer dans ses sous-sections
181
- section_key = section_path + " " + title
182
- if section_key not in root[key]["subsections"]:
183
- root[key]["subsections"][section_key] = {"content": content, "subsections": {}}
184
- else:
185
- root[key]["subsections"][section_key]["content"] = content
186
- return root[key]["subsections"][section_key]
187
-
188
- # Parent non trouvé, il faut le créer d'abord
189
- # Rechercher le titre du parent
190
- parent_title = ""
191
- for chapter in sorted_chapters:
192
- if chapter.split()[0] == parent_path:
193
- parts = chapter.split(maxsplit=1)
194
- parent_title = parts[1] if len(parts) > 1 else ""
195
- break
196
-
197
- # Si le parent n'a pas de titre, on cherche un parent plus haut
198
- if not parent_title.strip():
199
- # On peut soit ignorer cette branche, soit essayer de trouver un parent valide plus haut
200
- grand_parent_parts = parent_path.split('.')
201
- if len(grand_parent_parts) > 1:
202
- grand_parent_path = '.'.join(grand_parent_parts[:-1])
203
- for key in root.keys():
204
- if key.startswith(grand_parent_path + " "):
205
- # On a trouvé un grand-parent valide, on insère directement dedans
206
- section_key = section_path + " " + title
207
- if section_key not in root[key]["subsections"]:
208
- root[key]["subsections"][section_key] = {"content": content, "subsections": {}}
209
- return root[key]["subsections"][section_key]
210
- # Si on n'a pas trouvé de grand-parent valide, on insère à la racine
211
- section_key = section_path + " " + title
212
- root[section_key] = {"content": content, "subsections": {}}
213
- return root[section_key]
214
-
215
- # Créer le parent récursivement
216
- parent_section = insert_section(root, parent_path, parent_title, "")
217
-
218
- # Si le parent n'a pas pu être créé (car sans titre), on insère à la racine
219
- if parent_section is None:
220
- section_key = section_path + " " + title
221
- root[section_key] = {"content": content, "subsections": {}}
222
- return root[section_key]
223
-
224
- # Maintenant insérer cette section dans le parent nouvellement créé
225
- section_key = section_path + " " + title
226
- parent_section["subsections"][section_key] = {"content": content, "subsections": {}}
227
- return parent_section["subsections"][section_key]
228
-
229
- # Traiter chaque chapitre
230
- for chapter in sorted_chapters:
231
- parts = chapter.split(maxsplit=1)
232
- section_num = parts[0]
233
- section_title = parts[1] if len(parts) > 1 else ""
234
-
235
- # Ne traiter que les sections avec un titre
236
- if section_title.strip():
237
- insert_section(result, section_num, section_title, chapter_contents[chapter])
238
-
239
- return result
240
-
241
- class SpecRequest(BaseModel):
242
- specification: str
243
- version: Optional[str] = None
244
 
245
- @app.get("/")
246
- def main_page():
247
- return FileResponse(os.path.join("templates", "index.html"))
248
-
249
- @app.post("/online/plain")
250
- def get_file_from_spec_id_version(req: SpecRequest) -> Dict[str, str]:
251
- spec = req.specification
252
- version = req.version
253
- if not version:
254
- version = get_latest_version(spec)
255
-
256
- text = get_text(spec, version)
257
- forewords = []
258
- for x in range(len(text)):
259
- line = text[x]
260
- if "Foreword" in line:
261
- forewords.append(x)
262
- if len(forewords) >= 2:
263
- break
264
-
265
- toc_brut = text[forewords[1]:]
266
  chapters = []
267
- for line in toc_brut:
268
- x = line.split("\t")
269
- m = re.search(regex, line)
270
- if m and any(line in c for c in text[forewords[0]:forewords[1]]):
271
- chapters.append(line)
272
- print(line)
273
-
274
- real_toc_indexes = {}
275
 
276
- for chapter in chapters:
277
- x = text.index(chapter)
278
- real_toc_indexes[chapter] = x
279
 
280
  document = {}
281
- toc = list(real_toc_indexes.keys())
282
- index_toc = list(real_toc_indexes.values())
283
- curr_index = 0
284
- for x in range(1, len(toc)):
285
- document[toc[curr_index].replace("\t", " ")] = re.sub(r"[\ \t]+", " ", "\n".join(text[index_toc[curr_index]+1:index_toc[x]]))
286
- curr_index = x
287
-
288
- document[toc[curr_index].replace("\t", " ")] = re.sub(r"\s+", " ", " ".join(text[index_toc[curr_index]+1:]))
289
- return document
290
-
291
- @app.post("/online")
292
- def get_file_from_spec_id_version(req: SpecRequest) -> Dict:
293
- spec = req.specification
294
- version = req.version
295
- if not version:
296
- version = get_latest_version(spec)
297
 
298
- text = get_text(spec, version)
299
- forewords = []
300
- for x in range(len(text)):
301
- line = text[x]
302
- if "Foreword" in line:
303
- forewords.append(x)
304
- if len(forewords) >= 2:
305
- break
306
-
307
- toc_brut = text[forewords[1]:]
308
- chapters = []
309
- for line in toc_brut:
310
- x = line.split("\t")
311
- m = re.search(regex, line)
312
- if m and any(line in c for c in text[forewords[0]:forewords[1]]):
313
- chapters.append(line)
314
- print(line)
315
-
316
- real_toc_indexes = {}
317
-
318
- for chapter in chapters:
319
- x = text.index(chapter)
320
- real_toc_indexes[chapter] = x
321
-
322
- document = {}
323
- toc = list(real_toc_indexes.keys())
324
- index_toc = list(real_toc_indexes.values())
325
- curr_index = 0
326
- for x in range(1, len(toc)):
327
- document[toc[curr_index].replace("\t", " ")] = re.sub(r"[\ \t]+", " ", "\n".join(text[index_toc[curr_index]+1:index_toc[x]]))
328
- curr_index = x
329
-
330
- document[toc[curr_index].replace("\t", " ")] = re.sub(r"\s+", " ", " ".join(text[index_toc[curr_index]+1:]))
331
- return create_nested_structure(chapters, text, real_toc_indexes)
 
1
  import requests
 
2
  import os
 
3
  import zipfile
4
+ from io import BytesIO
5
  import subprocess
6
  import os
7
  import re
8
  import warnings
9
  from fastapi import FastAPI, HTTPException
10
  from fastapi.middleware.cors import CORSMiddleware
 
 
11
  from pydantic import BaseModel
 
12
 
13
  warnings.filterwarnings("ignore")
14
 
15
  app = FastAPI(title="3GPP Specification Splitter API",
16
+ description="API to split and display specifications by their chapters & sub-chapters",
17
+ docs_url="/")
 
18
 
19
  origins = [
20
  "*",
 
28
  allow_headers=["*"],
29
  )
30
 
31
+ class SpecRequest(BaseModel):
32
+ spec_id: str
33
+
34
+ @app.post("/get_full_text")
35
+ def get_text(request: SpecRequest):
36
+ specification = request.spec_id
37
+ total_file = []
38
+ url = requests.post(
39
+ "https://organizedprogrammers-3gppdocfinder.hf.space/find",
40
+ verify=False,
41
+ headers={"Content-Type": "application/json"},
42
+ json={"doc_id": specification}
43
+ )
44
 
45
+ if url.status_code != 200:
46
+ raise HTTPException(404, detail="Not found")
 
 
47
 
48
+ url = url.json()['url']
49
  response = requests.get(
50
+ url,
51
  verify=False,
52
+ headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"}
53
+
54
  )
55
+
56
+ zip_bytes = BytesIO(response.content)
57
+ current_zip_file = zipfile.ZipFile(zip_bytes)
58
+ for file_info in current_zip_file.infolist():
59
+ if file_info.filename.endswith(".zip") and len(current_zip_file.namelist()) == 1:
60
+ nested_zip_bytes = BytesIO(current_zip_file.read(file_info.filename))
61
+ current_zip_file = zipfile.ZipFile(nested_zip_bytes)
62
+ break
63
 
64
+ for file_info in current_zip_file.infolist():
65
+ filename = file_info.filename
66
+ if (filename.endswith('.doc') or filename.endswith('.docx')) and ("cover" not in filename.lower() and "annex" not in filename.lower()):
67
+ doc_bytes = current_zip_file.read(filename)
68
+ ext = filename.split(".")[-1]
69
+ input_path = f"/tmp/{specification}.{ext}"
70
+ output_path = f"/tmp/{specification}.txt"
71
+ with open(input_path, "wb") as f:
72
+ f.write(doc_bytes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  subprocess.run([
75
  "libreoffice",
76
  "--headless",
 
78
  "--outdir", "/tmp",
79
  input_path
80
  ], check=True)
81
+
82
+ with open(output_path, "r") as f:
 
83
  txt_data = [line.strip() for line in f if line.strip()]
84
+
85
  os.remove(input_path)
86
  os.remove(output_path)
87
+ total_file.extend(txt_data)
88
+ if total_file == []:
89
+ raise HTTPException(status_code=404, detail="Not found !")
 
 
 
 
 
 
 
 
 
90
  else:
91
+ return total_file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ @app.post("/get_spec_content")
94
+ def get_spec_content(request: SpecRequest):
95
+ text = get_text(request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  chapters = []
97
+ chapter_regex = re.compile(r"^(\d+[a-z]?(?:\.\d+)*)\t[A-Z0-9][\ \S]+$")
 
 
 
 
 
 
 
98
 
99
+ for i, line in enumerate(text):
100
+ if chapter_regex.fullmatch(line):
101
+ chapters.append((i, line))
102
 
103
  document = {}
104
+ for i in range(len(chapters)):
105
+ start_index, chapter_title = chapters[i]
106
+ end_index = chapters[i+1][0] if i+1 < len(chapters) else len(text)
107
+ content_lines = text[start_index + 1 : end_index]
108
+ document[chapter_title.replace('\t', " ")] = "\n".join(content_lines)
 
 
 
 
 
 
 
 
 
 
 
109
 
110
+ return document
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/script.js DELETED
@@ -1,196 +0,0 @@
1
- const jsonTab = document.getElementById("tab1-btn")
2
- const specTab = document.getElementById("tab2-btn")
3
-
4
- document.getElementById("tab1").style.display = "block";
5
- document.getElementById('fileInput').addEventListener('change', handleFileSelect);
6
- document.getElementById("versCheck").addEventListener('change', event=>{
7
- if(event.target.checked){document.getElementById("versionInput").removeAttribute("disabled")} else {document.getElementById("versionInput").value = ""; document.getElementById("versionInput").setAttribute("disabled", "")}
8
- })
9
- function openTab(evt, tabName) {
10
- var i, tabcontent, tablinks;
11
- tabcontent = document.getElementsByClassName("tabcontent");
12
- for (i = 0; i < tabcontent.length; i++) {
13
- tabcontent[i].style.display = "none";
14
- }
15
- tablinks = document.getElementsByClassName("tablinks");
16
- for (i = 0; i < tablinks.length; i++) {
17
- tablinks[i].className = tablinks[i].className.replace(" active", "");
18
- }
19
- document.getElementById(tabName).style.display = "block";
20
- evt.currentTarget.className += " active";
21
- }
22
-
23
- function handleSpecSearch(){
24
- let versCheck = document.getElementById("versCheck").checked;
25
- let body = {"specification": document.getElementById("specInput").value};
26
- if(versCheck && document.getElementById("versionInput").value.length > 0){
27
- body["version"] = document.getElementById("versionInput").value;
28
- }
29
-
30
- fetch("/online", {
31
- method: "POST",
32
- headers: {"Content-Type": "application/json"},
33
- body: JSON.stringify(body)
34
- })
35
- .then(data => data.json())
36
- .then(resp => renderDocument("tab2", resp))
37
- .catch(error => console.error(error))
38
- }
39
-
40
- function handleFileSelect(event) {
41
- const file = event.target.files[0];
42
- if (!file) return;
43
-
44
- const fileNameMatch = file.name.match(/(\d+\.\d+(?:-\d+)?)[_-]([a-z0-9]+)\.json/i);
45
- let fileInfoText = '';
46
-
47
- if (fileNameMatch) {
48
- const specNumber = fileNameMatch[1];
49
- const versionCode = fileNameMatch[2];
50
-
51
- // Conversion des caractères en numéro de version
52
- let versionString = "";
53
- for (let i = 0; i < versionCode.length; i++) {
54
- let char = versionCode[i].toLowerCase();
55
- let versionPart;
56
-
57
- if (/[0-9]/.test(char)) {
58
- versionPart = parseInt(char, 10);
59
- } else if (/[a-z]/.test(char)) {
60
- versionPart = char.charCodeAt(0) - 'a'.charCodeAt(0) + 10;
61
- } else {
62
- versionPart = "?";
63
- }
64
-
65
- if (i > 0) versionString += ".";
66
- versionString += versionPart;
67
- }
68
-
69
- fileInfoText = `Spécification ${specNumber}, Version ${versionString}`;
70
- document.getElementById('fileInfo').textContent = fileInfoText;
71
- } else {
72
- document.getElementById('fileInfo').textContent = file.name;
73
- }
74
-
75
- const reader = new FileReader();
76
- reader.onload = function (e) {
77
- try {
78
- const jsonContent = JSON.parse(e.target.result);
79
- renderDocument("tab1", jsonContent);
80
- } catch (error) {
81
- document.querySelector('#tab1 #document-container').innerHTML =
82
- `<div class="error">Erreur lors du traitement du fichier JSON: ${error.message}</div>`;
83
- }
84
- };
85
- reader.readAsText(file);
86
- }
87
-
88
- function renderDocument(tab, data) {
89
- const container = document.querySelector(`#${tab} #document-container`);
90
- container.innerHTML = '';
91
-
92
- function renderSection(sectionKey, sectionData, level) {
93
- const sectionDiv = document.createElement('div');
94
- sectionDiv.className = `section level-${level}`;
95
-
96
- // Extraire le numéro et le titre de la clé
97
- const parts = sectionKey.split(/\s(.+)/); // Divise à partir du premier espace
98
- const sectionNumber = parts[0];
99
- const sectionTitle = parts[1] || "";
100
-
101
- // Créer l'en-tête de section
102
- const header = document.createElement(`h${Math.min(level + 1, 6)}`);
103
- header.textContent = sectionKey; // Utiliser la clé complète comme titre
104
- sectionDiv.appendChild(header);
105
-
106
- // Ajouter le contenu
107
- if (sectionData.content) {
108
- const content = document.createElement('div');
109
- content.className = 'content';
110
- content.textContent = sectionData.content;
111
- sectionDiv.appendChild(content);
112
- }
113
-
114
- // Ajouter les sous-sections récursivement
115
- if (sectionData.subsections && Object.keys(sectionData.subsections).length > 0) {
116
- const subsectionsDiv = document.createElement('div');
117
- subsectionsDiv.className = 'subsections';
118
-
119
- for (const [subKey, subData] of Object.entries(sectionData.subsections)) {
120
- const subSection = renderSection(subKey, subData, level + 1);
121
- subsectionsDiv.appendChild(subSection);
122
- }
123
-
124
- sectionDiv.appendChild(subsectionsDiv);
125
- }
126
-
127
- return sectionDiv;
128
- }
129
-
130
- // Parcourir les sections de premier niveau
131
- for (const [sectionKey, sectionData] of Object.entries(data)) {
132
- const sectionElement = renderSection(sectionKey, sectionData, 1);
133
- container.appendChild(sectionElement);
134
- }
135
- }
136
-
137
-
138
-
139
- function formatText(text) {
140
- if (!text) return '';
141
-
142
- // Remplacer les sauts de ligne
143
- let formattedText = text.replace(/\n/g, '<br>');
144
-
145
- // Formatage des tableaux (détection basique de structures tabulaires)
146
- if (text.includes('Byte') && (text.includes('b8') || text.includes('b7'))) {
147
- // Tenter de détecter et convertir les représentations de tables de bits
148
- formattedText = formatBitTables(formattedText);
149
- }
150
-
151
- // Mise en évidence des termes techniques
152
- formattedText = formattedText.replace(/\b([A-Z]{2,}(?:-[A-Z]+)*)\b/g, '<span class="code-block">$1</span>');
153
-
154
- return formattedText;
155
- }
156
-
157
- function formatBitTables(text) {
158
- // Exemple simple pour détecter et formater les tableaux de bits
159
- // Une implémentation plus robuste nécessiterait une analyse plus complexe
160
-
161
- // Détection basique d'un en-tête de table de bits
162
- const tableHeaders = text.match(/b8\s+b7\s+b6\s+b5\s+b4\s+b3\s+b2\s+b1/g);
163
-
164
- if (tableHeaders) {
165
- // Remplacer les occurrences par une table HTML
166
- tableHeaders.forEach(header => {
167
- const tableStart =
168
- '<table class="byte-table"><tr><th>b8</th><th>b7</th><th>b6</th><th>b5</th><th>b4</th><th>b3</th><th>b2</th><th>b1</th></tr>';
169
- const tableEnd = '</table>';
170
-
171
- // Essayer de capturer les lignes suivantes qui pourraient être des données de table
172
- const headerPos = text.indexOf(header);
173
- const nextLineStart = text.indexOf('<br>', headerPos) + 4;
174
- let tableContent = '';
175
-
176
- // Ajouter des lignes jusqu'à ce qu'on atteigne une ligne vide ou un nouveau paragraphe
177
- let currentPos = nextLineStart;
178
- let endPos = text.indexOf('<br><br>', currentPos);
179
- if (endPos === -1) endPos = text.length;
180
-
181
- const potentialTableData = text.substring(currentPos, endPos);
182
- const rows = potentialTableData.split('<br>');
183
-
184
- rows.forEach(row => {
185
- if (row.trim() && !row.includes('Byte') && !row.includes('b8')) {
186
- tableContent += '<tr><td colspan="8">' + row + '</td></tr>';
187
- }
188
- });
189
-
190
- const tableHTML = tableStart + tableContent + tableEnd;
191
- text = text.replace(header + potentialTableData, tableHTML);
192
- });
193
- }
194
-
195
- return text;
196
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/style.css DELETED
@@ -1,171 +0,0 @@
1
- body {
2
- font-family: Arial, sans-serif;
3
- line-height: 1.6;
4
- max-width: 1100px;
5
- margin: 0 auto;
6
- padding: 20px;
7
- color: #333;
8
- }
9
-
10
- div[class^='section level']{
11
- padding: 10px;
12
- border: 1px solid #bbb;
13
- }
14
-
15
- .header {
16
- margin-bottom: 30px;
17
- padding-bottom: 15px;
18
- }
19
-
20
- .file-input-container {
21
- margin-bottom: 20px;
22
- }
23
-
24
- #fileInfo {
25
- margin-top: 10px;
26
- font-style: italic;
27
- color: #666;
28
- }
29
-
30
- h1 {
31
- color: #2c5282;
32
- }
33
-
34
- h2 {
35
- color: #2a4365;
36
- margin-top: 40px;
37
- padding-bottom: 8px;
38
- border-bottom: 1px solid #eaeaea;
39
- }
40
-
41
- h3 {
42
- color: #2c5282;
43
- margin-top: 25px;
44
- }
45
-
46
- #document-container {
47
- background-color: white;
48
- padding: 20px;
49
- border: 1px solid #ddd;
50
- border-radius: 5px;
51
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
52
- }
53
-
54
- .section {
55
- margin-bottom: 20px;
56
- }
57
-
58
- .subsection {
59
- margin-left: 15px;
60
- }
61
-
62
- table {
63
- width: 100%;
64
- border-collapse: collapse;
65
- margin: 15px 0;
66
- }
67
-
68
- table,
69
- th,
70
- td {
71
- border: 1px solid #ddd;
72
- }
73
-
74
- th,
75
- td {
76
- padding: 12px;
77
- text-align: left;
78
- }
79
-
80
- th {
81
- background-color: #f2f2f2;
82
- }
83
-
84
- pre {
85
- background-color: #f8f8f8;
86
- padding: 10px;
87
- border-radius: 5px;
88
- overflow-x: auto;
89
- font-family: Consolas, monospace;
90
- }
91
-
92
- .loading {
93
- text-align: center;
94
- padding: 40px;
95
- font-size: 20px;
96
- color: #666;
97
- }
98
-
99
- .error {
100
- color: #e53e3e;
101
- padding: 10px;
102
- background-color: #fff5f5;
103
- border-left: 4px solid #e53e3e;
104
- margin: 15px 0;
105
- }
106
-
107
- #specInput, #versionInput {
108
- padding: 10px;
109
- border: 1px solid #ddd;
110
- border-radius: 4px;
111
- }
112
-
113
- #fileInput {
114
- padding: 10px;
115
- border: 1px solid #ddd;
116
- border-radius: 4px;
117
- width: 100%;
118
- max-width: 400px;
119
- }
120
-
121
- button {
122
- background-color: #3182ce;
123
- color: white;
124
- border: none;
125
- padding: 10px 15px;
126
- border-radius: 4px;
127
- cursor: pointer;
128
- transition: background-color 0.3s;
129
- }
130
-
131
- button:hover {
132
- background-color: #2c5282;
133
- }
134
-
135
- button.active {
136
- background-color: #223c5d;
137
- }
138
-
139
- .tabcontent {
140
- display: none;
141
- }
142
-
143
- @media print {
144
- .file-input-container {
145
- display: none;
146
- }
147
-
148
- body {
149
- padding: 0;
150
- max-width: none;
151
- }
152
-
153
- #document-container {
154
- border: none;
155
- box-shadow: none;
156
- padding: 0;
157
- }
158
- }
159
-
160
- /* Styles pour meilleure lisibilité des tableaux et contenus techniques */
161
- .byte-table td {
162
- font-family: Consolas, monospace;
163
- font-size: 14px;
164
- }
165
-
166
- .code-block {
167
- font-family: Consolas, monospace;
168
- background-color: #f8f8f8;
169
- padding: 2px 4px;
170
- border-radius: 3px;
171
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/index.html DELETED
@@ -1,42 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="fr">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>3GPP Specification Visualizor</title>
8
- <link rel="stylesheet" href="static/style.css">
9
- </head>
10
-
11
- <body>
12
- <div class="header">
13
- <h1>Visualiseur de Spécifications 3GPP</h1>
14
- <div class="tabs">
15
- <button class="tablinks active" onclick="openTab(event, 'tab1')" data-loading="Veuillez charger un fichier de spécification 3GPP">Fichier JSON</button>
16
- <button class="tablinks" onclick="openTab(event, 'tab2')" data-loading="Veuillez chercher une spécification 3GPP">Spécification</button>
17
- </div>
18
- <div id="tab1" class="tabcontent">
19
- <div class="file-input-container"> <input type="file" id="fileInput" accept=".json">
20
- <div id="fileInfo"></div>
21
- </div>
22
- <div id="document-container">
23
-
24
- </div>
25
- </div>
26
- <div id="tab2" class="tabcontent">
27
- <div class="file-input-container">
28
- <label for="specInput">Spécification N°</label>
29
- <input type="text" id="specInput" style="width: 45px;">
30
- <label for="versionInput">Version <input type="checkbox" id="versCheck"></label>
31
- <input type="text" id="versionInput" style="width: 45px;" disabled>
32
- <button onclick="handleSpecSearch()">Afficher</button>
33
- </div>
34
- <div id="document-container">
35
-
36
- </div>
37
- </div>
38
- </div>
39
- <script src="static/script.js"></script>
40
- </body>
41
-
42
- </html>