u: add py/struct.py py/ref.py ->m.pdf

This commit is contained in:
baiobelfer 2025-03-30 14:21:21 +02:00
parent 3d5d5fc262
commit 2992f20284
7 changed files with 2485 additions and 11 deletions

BIN
m.pdf Normal file

Binary file not shown.

2097
m.tex Normal file

File diff suppressed because it is too large Load Diff

BIN
main.pdf

Binary file not shown.

View File

@ -632,7 +632,7 @@ System oceniania opracowany w oparciu o:
%----------------------
% G. INFORMACJA O OCENIE KOŃCOWEJ
%----------------------
\item Ocena końcowa
\paragraf{Ocena końcowa}
\begin{longenum}
\item Ocena końcowa w~danym roku szkolnym jest równoważna z~oceną roczną z~danego przedmiotu
(z~uwzględnieniem ewentualnej oceny śródrocznej), przy czym decydujące znaczenie mają:
@ -1643,12 +1643,12 @@ i~technikum z~dnia 28 czerwca 2024~r., opracowana przez Ministerstwo Edukacji i~
\subsection{Klasyfikacja śródroczna (okresowa)}
\label{subsec:klasyfikacja-srodroczna}
\paragraph{Klasyfikacja środroczna}
\paragraf{Klasyfikacja środroczna}
\begin{longenum}
\item \label{itm:srodroczna-zasady-ogolne} Zasady ogólne
\item Zasady ogólne
\begin{longenum}
\item Klasyfikacja śródroczna (okresowa) przeprowadzana jest jednorazowo w ciągu roku szkolnego.
\item Klasyfikacja śródroczna przeprowadzana jest jednorazowo w ciągu roku szkolnego.
\item Obejmuje ona podsumowanie osiągnięć edukacyjnych ucznia w okresie od początku roku szkolnego do końca pierwszego semestru.
\item W jej ramach ustala się:
\begin{longenum}
@ -1658,16 +1658,16 @@ i~technikum z~dnia 28 czerwca 2024~r., opracowana przez Ministerstwo Edukacji i~
\item Klasyfikacja stanowi podstawę do monitorowania postępów ucznia oraz planowania dalszej pracy pedagogicznej.
\end{longenum}
\item \label{itm:srodroczna-terminy} Terminy klasyfikacyjne:
\item Terminy klasyfikacyjne:
\begin{longenum}
\item Terminy ustala dyrektor szkoły.
\item Informację o~nieklasyfikowaniu lub przewidywanych niedostatetycznych ocenach śródrocznych nauczyciele przekazują uczniom w~następujących terminach:
\item Termin posiedzenia środrocznej rady klasyfikacyjnej ustala dyrektor szkoły zgodnie z~zasadami określonymi w~[\ref{itm:po-klasyf-dyrektor-okresla-terminy}].
\item Informację przewidywanych ocenach śródrocznych nauczyciele przekazują uczniom w~następujących terminach:
\begin{longenum}
\item W~przypadku zagrożenia oceną niedostateczną — \emph{co najmniej 30~dni} przed terminem tego posiedzenia, zgodnie z zasadami określonymi w [\ref{itm:po-klasyf-informacja-o-zagrozeniu}].
\item W~przypadku zagrożenia nieklasyfikacją lub oceną niedostateczną — \emph{co najmniej 30~dni} przed terminem tego posiedzenia, zgodnie z zasadami określonymi w [\ref{itm:po-klasyf-informacja-o-zagrozeniu}].
\end{longenum}
\end{longenum}
\item \label{itm:srodroczna-podstawa-punktowa} Podstawa punktowa i~zasady ustalania ocen śródrocznych z~zajęć edukacyjnych:
\item Podstawa punktowa i~zasady ustalania ocen śródrocznych z~zajęć edukacyjnych:
\begin{longenum}
\item Śródroczna ocena klasyfikacyjna z~danego przedmiotu opiera się na liczbie punktów uzyskanych w~pierwszym okresie roku szkolnego, z~uwzględnieniem różnych form sprawdzania wiedzy.
\item Ostateczna ocena śródroczna wyliczana jest automatycznie w~programie~Librus jako średnia ważona, przy czym uwzględnia się:
@ -1678,12 +1678,12 @@ i~technikum z~dnia 28 czerwca 2024~r., opracowana przez Ministerstwo Edukacji i~
\end{longenum}
\end{longenum}
\item \label{itm:srodroczna-ocena-zachowania} Ocena klasyfikacyjna zachowania:
\item Ocena klasyfikacyjna zachowania:
\begin{longenum}
\item Śródroczną ocenę klasyfikacyjną zachowania wystawia wychowawca klasy po zasięgnięciu opinii nauczycieli, uczniów oraz opinii samego ocenianego ucznia, zgodnie z punktowymi zasadami oceny zachowania określonymi w Wewnątrzszkolnym Ocenianiu (WO), opisanymi w [\ref{itm:po-klasyf-wychowawca-ocena-zachowania}].
\end{longenum}
\item \label{itm:srodroczna-nieklasyfikowanie} Nieklasyfikowanie śródroczne:
\item Nieklasyfikowanie śródroczne:
\begin{longenum}
\item Warunki nieklasyfikowania ucznia w klasyfikacji śródrocznej są określone w [\ref{itm:po-klasyf-warunki-klasyfikowalnosci}].
\item W przypadku nieklasyfikacji, nauczyciel zobowiązany jest określić ramy i tryb, w jakim uczeń może nadrobić zaległości w drugim półroczu:

2
py/opis.txt Normal file
View File

@ -0,0 +1,2 @@
1. struct.py generuje ../main-struct.json
2. ref.py zamienia [1] na \S1 ust.1 pkt. 1

110
py/ref.py Normal file
View File

@ -0,0 +1,110 @@
import re
import json
import sys
def build_label_dictionary(struct_json_file):
"""
Wczytuje plik .json z drzewem (type, numbering, label, children)
i tworzy słownik mapujący 'itm:xxx' -> '...numbering...'.
"""
with open(struct_json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
label_map = {}
def traverse(nodes):
for node in nodes:
lbl = node.get('label', '')
if lbl.startswith('itm:'):
# np. 'itm:srodroczna-terminy'
numbering = node.get('numbering', '')
label_map[lbl] = numbering
# rekurencja po dzieciach
if 'children' in node and isinstance(node['children'], list):
traverse(node['children'])
traverse(data)
return label_map
def replace_refs_in_tex(tex_in, tex_out, label_dict):
with open(tex_in, 'r', encoding='utf-8') as f:
lines = f.readlines()
# Regex sprawdza, czy występuje opcjonalny nawias otwierający,
# polecenie \ref{itm:...}, i opcjonalny nawias zamykający.
# - group(1) => '[' lub None
# - group(2) => 'itm:...' (np. 'itm:srodroczna-terminy')
# - group(3) => ']' lub None
pattern = re.compile(r'(\[)?\\ref\{(itm:[^}]+)\}(\])?')
def ref_replacer(match):
bracket_open = match.group(1) # '[' albo None
key = match.group(2) # np. 'itm:srodroczna-terminy'
bracket_close = match.group(3) # ']' albo None
if key in label_dict:
numbering = label_dict[key]
# zamieniamy na \hyperref[itm:xxx]{numbering} bez nawiasów kwadratowych
return f"\\hyperref[{key}]{{{numbering}}}"
else:
# brak w słowniku -> zostawiamy oryginalny zapis
# (wraz z ewentualnymi nawiasami)
left = bracket_open if bracket_open else ''
right = bracket_close if bracket_close else ''
return f"{left}\\ref{{{key}}}{right}"
new_lines = []
for line in lines:
new_line = pattern.sub(ref_replacer, line)
new_lines.append(new_line)
with open(tex_out, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
def main():
# Domyślne nazwy plików:
default_struct_json = "../main-struct.json"
default_tex_in = "../main.tex"
default_tex_out = "../m.tex"
args = sys.argv[1:]
if len(args) == 0:
# brak argumentów -> używamy domyślnych
struct_json_file = default_struct_json
tex_in_file = default_tex_in
tex_out_file = default_tex_out
elif len(args) == 1:
# 1 argument (plik .json), rest domyślny
struct_json_file = args[0]
tex_in_file = default_tex_in
tex_out_file = default_tex_out
elif len(args) == 2:
# 2 argumenty: .json + .tex (in), out = m.tex
struct_json_file = args[0]
tex_in_file = args[1]
tex_out_file = default_tex_out
elif len(args) == 3:
# 3 argumenty: .json + .tex(in) + .tex(out)
struct_json_file = args[0]
tex_in_file = args[1]
tex_out_file = args[2]
else:
print("Użycie:")
print(" python script.py # domyślne: ../main-struct.json, ../main.tex => m.tex")
print(" python script.py PLIK.json")
print(" python script.py PLIK.json in.tex")
print(" python script.py PLIK.json in.tex out.tex")
sys.exit(1)
label_map = build_label_dictionary(struct_json_file)
replace_refs_in_tex(tex_in_file, tex_out_file, label_map)
print(f"Gotowe! Zmodyfikowany plik został zapisany do: {tex_out_file}")
if __name__ == "__main__":
main()

265
py/struct.py Normal file
View File

@ -0,0 +1,265 @@
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'(?<!\\)%.*$', '', line).strip()
if line:
cleaned_content.append(line)
# -- Liczniki poziomów --
counters = {
"par": 0, # paragraf (\S)
"ust": 0, # 1-szy poziom list
"pkt": 0, # 2-gi
"ppkt": 0, # 3-ci
"lit": 0, # 4-ty
"plit": 0, # 5-ty
"tir": 0, # 6-ty
"lev7": 0, # 7-my
"lev8": 0 # 8-my
}
MAX_LIST_DEPTH = 8 # maksymalna głębokość zagnieżdżenia list
label_tree = [] # główna struktura do zapisu
current_env_stack = [] # stos środowisk list
errors = []
level_order = ["par","ust","pkt","ppkt","lit","plit","tir","lev7","lev8"]
def reset_counters(from_level):
"""
Zeruje liczniki dla poziomów "niższych" (w level_order) niż from_level.
Jeśli from_level=='pkt', zerujemy ppkt, lit, plit, tir, lev7, lev8.
"""
try:
idx = level_order.index(from_level)
except ValueError:
return
for lv in level_order[idx+1:]:
counters[lv] = 0
def get_current_numbering():
"""
Zwraca łańcuch numerujący, np:
'\\S1 ust. 2 pkt. 3 ppkt. 1 lit. a plit. a) tir. 1 lev7. 2 lev8. 1'
z uwzględnieniem tych liczników, które > 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]")