u: add py/struct.py py/ref.py ->m.pdf
This commit is contained in:
parent
3d5d5fc262
commit
2992f20284
22
main.tex
22
main.tex
@ -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
2
py/opis.txt
Normal 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
110
py/ref.py
Normal 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
265
py/struct.py
Normal 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 są > 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]")
|
||||
|
Loading…
x
Reference in New Issue
Block a user