Skip to content

Commit

Permalink
refactor: remove element removal in _RenderErrorCollector
Browse files Browse the repository at this point in the history
  • Loading branch information
janbritz committed Nov 27, 2024
1 parent b84f92c commit e29c7c7
Showing 1 changed file with 53 additions and 62 deletions.
115 changes: 53 additions & 62 deletions questionpy_sdk/webserver/question_ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,55 @@ def _format_floats(self) -> None:
parent.remove(element)


class QuestionFormulationUIRenderer(QuestionUIRenderer):
"""Renderer for the formulation UI part that provides metadata."""

def __init__(
self,
xml: str,
placeholders: dict[str, str],
options: QuestionDisplayOptions,
seed: int | None = None,
attempt: dict | None = None,
) -> None:
super().__init__(xml, placeholders, options, seed, attempt)
self.metadata = self._get_metadata()

def _get_metadata(self) -> QuestionMetadata:
"""Extracts metadata from the question UI."""
question_metadata = QuestionMetadata()
namespaces: dict[str, str] = {"xhtml": _XHTML_NAMESPACE, "qpy": _QPY_NAMESPACE}

# Extract correct responses
for element in self._xml.findall(".//*[@qpy:correct-response]", namespaces=namespaces):
name = element.get("name")
if not name:
continue

if element.tag.endswith("input") and element.get("type") == "radio":
value = element.get("value")
else:
value = element.get(f"{{{_QPY_NAMESPACE}}}correct-response")

if not value:
continue

question_metadata.correct_response[name] = value

# Extract other metadata
for element_type in ["input", "select", "textarea", "button"]:
for element in self._xml.findall(f".//xhtml:{element_type}", namespaces=namespaces):
name = element.get("name")
if not name:
continue

question_metadata.expected_data[name] = "Any"
if element.get("required") is not None:
question_metadata.required_fields.append(name)

return question_metadata


class _RenderErrorCollector:
def __init__(
self,
Expand Down Expand Up @@ -615,14 +664,14 @@ def _validate_shuffle_contents(self) -> None:
format_style = index_element.get("format", "123")
if format_style not in {"123", "abc", "ABC", "iii", "III"}:
self.errors.insert(InvalidAttributeValueError(index_element, "format", format_style))
_remove_element(index_element)

def _look_for_unknown_qpy_elements(self) -> None:
"""Checks if there are any unknown qpy elements."""
for element in _assert_element_list(self._xpath("//qpy:*")):
known_elements = ["shuffled-index", "format-float"]
xpath = " and ".join([f"not(contains(name(), '{element}'))" for element in known_elements])
for element in _assert_element_list(self._xpath(f"//*[starts-with(name(), 'qpy:') and {xpath}]")):
error = UnknownElementError(element=element)
self.errors.insert(error)
_remove_element(element)

def _validate_format_floats(self) -> None:
"""Validates the `qpy:format-float` element."""
Expand All @@ -647,10 +696,8 @@ def _validate_format_floats(self) -> None:
element=element, attribute="precision", value=precision_text
)
self.errors.insert(precision_error)
elif precision_text.isnumeric():
elif not precision_text.isnumeric():
# We disallow the usage of underscores to separate numeric literals, see above for an explanation.
int(precision_text)
else:
conversion_error = ConversionError(
element=element, value=precision_text, to_type=int, attribute="precision"
)
Expand All @@ -663,59 +710,3 @@ def _validate_format_floats(self) -> None:
element=element, attribute="thousands-separator", value=thousands_sep_attr, expected=expected
)
self.errors.insert(thousands_sep_error)

new_text = etree.Element("span")
parent = element.getparent()
new_text.tail = element.tail
if parent is not None:
parent.insert(parent.index(element), new_text)
parent.remove(element)


class QuestionFormulationUIRenderer(QuestionUIRenderer):
"""Renderer for the formulation UI part that provides metadata."""

def __init__(
self,
xml: str,
placeholders: dict[str, str],
options: QuestionDisplayOptions,
seed: int | None = None,
attempt: dict | None = None,
) -> None:
super().__init__(xml, placeholders, options, seed, attempt)
self.metadata = self._get_metadata()

def _get_metadata(self) -> QuestionMetadata:
"""Extracts metadata from the question UI."""
question_metadata = QuestionMetadata()
namespaces: dict[str, str] = {"xhtml": _XHTML_NAMESPACE, "qpy": _QPY_NAMESPACE}

# Extract correct responses
for element in self._xml.findall(".//*[@qpy:correct-response]", namespaces=namespaces):
name = element.get("name")
if not name:
continue

if element.tag.endswith("input") and element.get("type") == "radio":
value = element.get("value")
else:
value = element.get(f"{{{_QPY_NAMESPACE}}}correct-response")

if not value:
continue

question_metadata.correct_response[name] = value

# Extract other metadata
for element_type in ["input", "select", "textarea", "button"]:
for element in self._xml.findall(f".//xhtml:{element_type}", namespaces=namespaces):
name = element.get("name")
if not name:
continue

question_metadata.expected_data[name] = "Any"
if element.get("required") is not None:
question_metadata.required_fields.append(name)

return question_metadata

0 comments on commit e29c7c7

Please sign in to comment.