Merge branch 'main' into feature/table

This commit is contained in:
Prof. Dr. Matthias Jung DL9MJ
2025-05-14 20:50:53 +02:00
9 changed files with 32978 additions and 10 deletions
+57
View File
@@ -0,0 +1,57 @@
Prüfungsfragen zum Erwerb von Amateurfunkprüfungsbescheinigungen
3. Auflage, März 2024
Hinweis:
========
Die Prüfungsfragen werden in maschinenlesbarer Form von der
Bundesnetzagentur für Elektrizität, Gas, Telekommunikation, Post und Eisenbahnen,
Tulpenfeld 4, 53113 Bonn
unter der Datenlizenz DL-DE->BY-2.0 bereitgestellt.
Die bereitgestellten Daten und Metadaten dürfen für die kommerzielle und nicht kommerzielle Nutzung insbesondere
vervielfältigt, ausgedruckt, präsentiert, verändert, bearbeitet sowie an Dritte übermittelt werden;
mit eigenen Daten und Daten Anderer zusammengeführt und zu selbständigen neuen Datensätzen verbunden werden;
in interne und externe Geschäftsprozesse, Produkte und Anwendungen in öffentlichen und nicht öffentlichen
elektronischen Netzwerken eingebunden werden.
Bei der Nutzung ist sicherzustellen, dass folgende Angaben als Quellenvermerk enthalten sind:
"Prüfungsfragen zum Erwerb von Amateurfunkprüfungsbescheinigungen, Bundesnetzagentur, 3. Auflage, März 2024,
(www.bundesnetzagentur.de/amateurfunk), Datenlizenz Deutschland – Namensnennung – Version 2.0
(www.govdata.de/dl-de/by-2-0)"
Veränderungen, Bearbeitungen, neue Gestaltungen oder sonstige Abwandlungen sind im Quellenvermerk mit dem Hinweis
zu versehen, dass die Daten geändert wurden.
Anleitung:
==========
Die maschinenlesbare Version der Prüfungsfragen teilt sich in zwei Teile auf:
1. Die Struktur des Katalogs und die Prüfungsfragen als JSON-Datei
2. Alle Bilder zu den Prüfungsfragen als SVG-Dateien
Das JSON-Schema besteht aus Kapiteln "sections", die jeweils einen Titel
"title" beinhalten. Eine Section kann entweder weitere Unterkapitel "sections"
beinhalten, oder eine Liste von Prüfungsfragen "questions". Eine Prüfungsfrage
beinhaltet folgende Attribute:
- "number" : Die Prüfungsnummer im Fragenkatalog
- "class" : Die Klasse (1:N, 2:E, 3:A)
- "question" : Der Fragenkopf
- "answer_a" : Die richtige Antwort A
- "answer_b" : Ein Distraktor B (falsche Antwort)
- "answer_c" : Ein Distraktor C (falsche Antwort)
- "answer_d" : Ein Distraktor D (falsche Antwort)
- "picture_question" : Das Fragenbild [Optional]
- "picture_a" : Das Antwortbild A [Optional]
- "picture_b" : Das Antwortbild B [Optional]
- "picture_c" : Das Antwortbild C [Optional]
- "picture_d" : Das Antwortbild D [Optional]
Die Prüfungsfragen enthalten LaTeX-Code für die Darstellung der Formeln. Diese
können durch verschiedene Bibliotheken z. B. durch KaTeX (https://katex.org)
angezeigt werden.
File diff suppressed because it is too large Load Diff
+15752
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -115,7 +115,7 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer):
\label{{{ref}}}"""
def render_photo(self, token) :
return self.render_picture_helper(token.id, token.ref, token.text, token.number)
return self.render_photo_helper(token.id, token.ref, token.text, token.number)
def render_table(self, token):
alignments = token.alignment
+1 -1
View File
@@ -10,7 +10,7 @@ class Photo(SpanToken):
"""
parse_inner = False
pattern = re.compile(r"\[photo:(\d+):(.+):(.+)\]")
pattern = re.compile(r"\[photo:(\d+):([^:\]]+):([^:\]]+)\]")
def __init__(self, match_object):
self.id = match_object.group(1)
+25
View File
@@ -0,0 +1,25 @@
<div class="question layout-{{ layout }}" id="{{ number }}">
<div class="question-text">{{ number }}: {{ question }}</div>
{% if picture_question %}
<div class="question-picture"><img src="img/picture_{{ picture_question }}.svg"></div>
{% endif %}
<div class="answers">
{% for i in range(4) %}
<button onclick="answer(this, {{ 'true' if i == 0 else 'false' }} );">
{% if answers[i] %}
{{ answers[i] }}
{% endif %}
{% if answer_pictures[i] %}
<div class="question-picture"><img src="img/picture_{{ answer_pictures[i] }}.svg"></div>
{% endif %}
{% if i == 0 %}
<div class="result material-icons">check</div>
{% else %}
<div class="result material-icons">clear</div>
{% endif %}
</button>
{% endfor %}
</div>
</div>
+69 -3
View File
@@ -1,11 +1,76 @@
import json
from bs4 import BeautifulSoup
from jinja2 import Environment, FileSystemLoader
from mistletoe import Document
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
def question_stub(input):
return f"<question>{input}</question>" # TODO implement this stub
def parse_katalog():
with open('data/fragenkatalog3b.json') as fragenkatalog_file:
fragenkatalog = json.load(fragenkatalog_file)
questions = {}
for exampart in fragenkatalog["sections"] :
for chapter in exampart["sections"] :
if "questions" in chapter :
for question in chapter["questions"] :
questions[question["number"]] = question
if "sections" in chapter :
for section in chapter["sections"] :
for question in section["questions"] :
questions[question["number"]] = question
return questions
env = Environment(loader=FileSystemLoader('templates/html'))
question_template = env.get_template('question.html')
questions = parse_katalog()
def question_stub(input):
""" Combines the original question dataset from BNetzA with our internal metadata """
with open('data/metadata.json') as metadata_file :
metadata = json.load(metadata_file)
number = metadata[f"{input}"]["number"] # Fragennummer z.B. AB123
question = questions[number]
metadata = metadata[f"{input}"]
if "answer_a" in question :
answers = [
question["answer_a"],
question["answer_b"],
question["answer_c"],
question["answer_d"]
]
else :
answers = []
if metadata["picture_a"] != "" :
answer_pictures = [
metadata["picture_a"],
metadata["picture_b"],
metadata["picture_c"],
metadata["picture_d"]
]
else :
answer_pictures = []
if "picture_question" in question :
picture_question = metadata["picture_question"]
else :
picture_question = ""
return question_template.render(
question=question["question"],
number=number,
layout=metadata["layout"],
picture_question=picture_question,
answers=answers,
answer_pictures=answer_pictures
)
def test_html(capsys):
with capsys.disabled():
@@ -14,4 +79,5 @@ def test_html(capsys):
with FiftyOhmHtmlRenderer(question_stub) as renderer:
output = renderer.render(Document(content))
with open("test/acceptanceTest.html", "w") as output_file:
output_file.write(output)
pretty = BeautifulSoup(output, 'html.parser').prettify()
output_file.write(pretty)
+26
View File
@@ -0,0 +1,26 @@
import mistletoe
import pytest
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
from renderer.fifty_ohm_latex_renderer import FiftyOhmLaTeXRenderer
from test.util import paragraph
@pytest.mark.html
def test_photo_html():
assertions = {
"[photo:123:abc:Text]": FiftyOhmHtmlRenderer.render_photo_helper("123", "abc", "Text", "TODO"),
}
for assertion in assertions:
assert mistletoe.markdown(assertion, FiftyOhmHtmlRenderer) == paragraph(assertions[assertion])
@pytest.mark.latex
def test_photo_latex():
assertions = {
"[photo:123:abc:Text]": FiftyOhmLaTeXRenderer.render_photo_helper("123", "abc", "Text", "TODO"),
}
for assertion in assertions:
assert mistletoe.markdown(assertion, FiftyOhmLaTeXRenderer) == "\n" + assertions[assertion] + "\n"
+25 -5
View File
@@ -1,3 +1,5 @@
import json
import pytest
from mistletoe import Document
@@ -22,18 +24,36 @@ def test_question_html():
for assertion in assertions:
assert renderer.render(Document(assertion)) == assertions[assertion]
@pytest.mark.html
def test_question_translation_html():
assertions = {
"[question:123]": "ED111\n",
"\n[question:123]\n": "ED111\n",
"\n[question:123]\n[question:123]": "ED111\nED111\n",
"Foo\n[question:123]\nBar": paragraph("Foo") + "ED111\n" + paragraph("Bar"),
}
def test_function(input):
with open('data/metadata.json') as file:
metadata = json.load(file)
return metadata[f"{input}"].get("number")
with FiftyOhmHtmlRenderer(test_function) as renderer:
for assertion in assertions:
assert renderer.render(Document(assertion)) == assertions[assertion]
@pytest.mark.latex
def test_question_latex():
assertions = {
"[question:123]": "123",
"\n[question:123]\n": "123",
"\n[question:123]\n[question:123]": "123123",
"Foo\n[question:123]\nBar": "\nFoo\n123\nBar\n",
"[question:123]": "\question{123}",
"\n[question:123]\n": "\question{123}",
"\n[question:123]\n[question:123]": "\question{123}\question{123}",
"Foo\n[question:123]\nBar": "\nFoo\n\question{123}\nBar\n",
}
def test_function(input):
return f"{input}"
return "\\question{"+input+"}"
with FiftyOhmLaTeXRenderer(test_function) as renderer:
for assertion in assertions: