import re import json def process_labels_and_create_links(in_file="../main.tex", out_file="../main-struct.json"): """ Przetwarza plik TeX, usuwa komentarze/puste linie i buduje drzewo paragrafów i list z 9 poziomami: - par (poziom 0, w numeracji: '\\S1') - ust (głębokość listy=1) - pkt (głębokość listy=2) - ppkt (głębokość listy=3) - lit (głębokość listy=4) - plit (głębokość listy=5) - tir (głębokość listy=6) - lev7 (głębokość listy=7) - lev8 (głębokość listy=8) Jeśli głębokość listy > 8 – ostrzeżenie i pominięcie \\item. Zapisuje wynik w pliku JSON z polami: type, numbering, label, text, children. - type: "par" / "ust" / "pkt" / "ppkt" / "lit" / "plit" / "tir" / "lev7" / "lev8" - numbering: np. '\\S1 ust. 2 pkt. 1 ppkt. 1 lit. a ...' - label: jeśli w tej samej linii występuje \\label{itm:...}, to tu jest ten fragment, w p.p. "" - text: linia z \\item - children: (lista obiektów zagnieżdżonych) """ with open(in_file, 'r', encoding='utf-8') as file: content = file.readlines() # -- Usunięcie komentarzy i linii pustych -- cleaned_content = [] for line in content: # usuń wszystko po '%', jeśli nie poprzedza go backslash line = re.sub(r'(? 0. """ parts = [] # par => \S + numer if counters["par"] > 0: parts.append(f"\\S{counters['par']}") # ust => ust. X if counters["ust"] > 0: parts.append(f"ust. {counters['ust']}") # pkt => pkt. X if counters["pkt"] > 0: parts.append(f"pkt. {counters['pkt']}") # ppkt => ppkt. X if counters["ppkt"] > 0: parts.append(f"ppkt. {counters['ppkt']})") # lit => lit. a, b, c... if counters["lit"] > 0: letter = chr(96 + counters["lit"]) # 1->a, 2->b, etc. parts.append(f"lit. {letter}") # plit => plit. a) if counters["plit"] > 0: letter = chr(96 + counters["plit"]) parts.append(f"plit. {letter})") # tir => tir. X if counters["tir"] > 0: parts.append(f"tir. {counters['tir']}") # lev7 => lev7. X if counters["lev7"] > 0: parts.append(f"lev7. {counters['lev7']}") # lev8 => lev8. X if counters["lev8"] > 0: parts.append(f"lev8. {counters['lev8']}") # scal całość w jeden łańcuch, oddzielając spacjami return " ".join(parts) def get_current_parent(): """ Zwraca "bieżący" obiekt-rodzic w label_tree, do którego dołączymy kolejny element (children). """ if not label_tree: return None parent = label_tree[-1] # Głębokość = ile razy mamy 'list' w current_env_stack depth = sum(1 for env in current_env_stack if env=="list") # 'par' jest poziomem zerowym. Dla depth=1 => wchodzimy do children paragrafu itd. for _ in range(depth-1): if not parent["children"]: break parent = parent["children"][-1] return parent # Regexy do wykrywania \begin{longenum} itp. begin_enum_regex = re.compile(r'\\begin\{(long[a-z0-9]*enum|customenum)\}') end_enum_regex = re.compile(r'\\end\{(long[a-z0-9]*enum|customenum)\}') fixed_content = [] for line_number, line in enumerate(cleaned_content, start=1): # --- wykrywanie paragrafu \paragraf{...} --- paragraf_match = re.search(r'\\paragraf\{([^}]+)\}', line) if paragraf_match: counters["par"] += 1 reset_counters("par") par_title = paragraf_match.group(1).strip() label_tree.append({ "type": "par", "numbering": get_current_numbering(), "label": "", "text": line.strip(), "children": [] }) fixed_content.append(line) continue # --- wykrywanie \begin{...enum} --- if begin_enum_regex.search(line): current_env_stack.append("list") fixed_content.append(line) continue # --- wykrywanie \end{...enum} --- if end_enum_regex.search(line): if "list" in current_env_stack: # Usunięcie ze stosu "list" od prawej stack_rev = current_env_stack[::-1] stack_rev.remove("list") current_env_stack = stack_rev[::-1] fixed_content.append(line) continue # --- wykrywanie \item --- if r'\item' in line: depth = sum(env=="list" for env in current_env_stack) if depth==0: errors.append(f"[Linia {line_number}] \\item poza listą. Ignoruję.") else: if depth==1: counters["ust"] += 1 reset_counters("ust") level_type = "ust" elif depth==2: counters["pkt"] += 1 reset_counters("pkt") level_type = "pkt" elif depth==3: counters["ppkt"] += 1 reset_counters("ppkt") level_type = "ppkt" elif depth==4: counters["lit"] += 1 reset_counters("lit") level_type = "lit" elif depth==5: counters["plit"] += 1 reset_counters("plit") level_type = "plit" elif depth==6: counters["tir"] += 1 reset_counters("tir") level_type = "tir" elif depth==7: counters["lev7"] += 1 reset_counters("lev7") level_type = "lev7" elif depth==8: counters["lev8"] += 1 reset_counters("lev8") level_type = "lev8" else: errors.append(f"[Linia {line_number}] Zbyt głębokie zagnieżdżenie (>8). Pomijam \\item.") fixed_content.append(line) continue label_match = re.search(r'\\label\{(itm:[^}]+)\}', line) if label_match: label_str = label_match.group(1) else: label_str = "" parent = get_current_parent() if parent is not None: new_item = { "type": level_type, "numbering": get_current_numbering(), "label": label_str, "text": line.strip(), "children": [] } parent["children"].append(new_item) fixed_content.append(line) continue fixed_content.append(line) # Zapisz drzewo w pliku JSON with open(out_file, "w", encoding="utf-8") as f: json.dump(label_tree, f, ensure_ascii=False, indent=2) if errors: print("== OSTRZEŻENIA / KOMUNIKATY ==") for err in errors: print("•", err) if __name__ == "__main__": import sys args = sys.argv[1:] if len(args)==0: process_labels_and_create_links() elif len(args)==1: process_labels_and_create_links(in_file=args[0]) elif len(args)==2: process_labels_and_create_links(in_file=args[0], out_file=args[1]) else: print("Użycie: python script.py [plik_wejściowy] [plik_wyjściowy]")