Skip to content
Snippets Groups Projects
Commit 80d4aa81 authored by Eliott Sammier's avatar Eliott Sammier
Browse files

Parse choice-comment associations in macao3

parent 50596317
No related branches found
No related tags found
1 merge request!1Main
...@@ -101,7 +101,8 @@ ...@@ -101,7 +101,8 @@
### http://www.semanticweb.org/eliott/ontologies/2024/4/macao/commentaireSugg ### http://www.semanticweb.org/eliott/ontologies/2024/4/macao/commentaireSugg
:commentaireSugg rdf:type owl:DatatypeProperty ; :commentaireSugg rdf:type owl:DatatypeProperty ;
rdfs:domain :Activite ; rdfs:domain :Activite,
:Reponse ;
rdfs:range rdf:XMLLiteral . rdfs:range rdf:XMLLiteral .
......
This diff is collapsed.
...@@ -17,8 +17,8 @@ log = get_logger("extract_page") ...@@ -17,8 +17,8 @@ log = get_logger("extract_page")
class Comment: class Comment:
def __init__(self): def __init__(self, id: str = ""):
self.id: str self.id = id
self.num: int self.num: int
self.text: str self.text: str
self.html: Any self.html: Any
...@@ -43,8 +43,8 @@ class Activity: ...@@ -43,8 +43,8 @@ class Activity:
coexist with a regular description""" coexist with a regular description"""
self.comment_success: Comment | None = None self.comment_success: Comment | None = None
"""Comment displayed on success, if applicable""" """Comment displayed on success, if applicable"""
self.comments_sugg: list[Comment] = [] self.comments_sugg: dict[str, Comment] = {}
"""Help comments displayed on failure, if applicable""" """Help comments displayed on failure, if applicable (keyed by ID)"""
self.comments_misc: list[Comment] = [] self.comments_misc: list[Comment] = []
"""Any other comments, if present""" """Any other comments, if present"""
self.ref: URIRef self.ref: URIRef
...@@ -68,7 +68,7 @@ class Activity: ...@@ -68,7 +68,7 @@ class Activity:
graph.add( graph.add(
(self.ref, NS["commentaireSucces"], Literal(self.comment_success.html)) (self.ref, NS["commentaireSucces"], Literal(self.comment_success.html))
) )
for comment in self.comments_sugg: for comment in self.comments_sugg.values():
graph.add((self.ref, NS["commentaireSugg"], Literal(comment.html))) graph.add((self.ref, NS["commentaireSugg"], Literal(comment.html)))
for comment in self.comments_misc: for comment in self.comments_misc:
graph.add((self.ref, NS["commentaireInfo"], Literal(comment.html))) graph.add((self.ref, NS["commentaireInfo"], Literal(comment.html)))
...@@ -96,15 +96,17 @@ class Activity: ...@@ -96,15 +96,17 @@ class Activity:
self.comments_misc.append(comment) self.comments_misc.append(comment)
case ["divSugg", num]: case ["divSugg", num]:
comment.num = int(num) comment.num = int(num)
self.comments_sugg.append(comment) self.comments_sugg[comment.id] = comment
case ["divCmtSucces", _]: case ["divCmtSucces", _]:
self.comment_success = comment self.comment_success = comment
case ["divConsigne", _]: case ["divConsigne", _]:
self.comment_consigne = comment self.comment_consigne = comment
case [alpha, num]: case alpha, num:
log.warning( log.warning(
f"No match for comment {alpha}[{num}] ('{comment.id}')" f"No match for comment {alpha}[{num}] ('{comment.id}')"
) )
case something:
log.warning(f"No match for comment '{something}'")
def get_name(self) -> str: def get_name(self) -> str:
return type(self).__name__ return type(self).__name__
...@@ -154,7 +156,12 @@ class Choice: ...@@ -154,7 +156,12 @@ class Choice:
"""A possible answer for a question, correct or not""" """A possible answer for a question, correct or not"""
def __init__( def __init__(
self, id: str = "", index: int = -1, is_correct: bool = False, html: str = "" self,
id: str = "",
index: int = -1,
is_correct: bool = False,
html: str = "",
comment: Comment | None = None,
): ):
self.id = id self.id = id
"""A string identifier for the choice""" """A string identifier for the choice"""
...@@ -162,6 +169,9 @@ class Choice: ...@@ -162,6 +169,9 @@ class Choice:
"""The order the choice appears in""" """The order the choice appears in"""
self.is_correct = is_correct self.is_correct = is_correct
self.html = html self.html = html
self.comment = comment
"""A `Comment` associated with this choice, displayed when the exercise
is incorrect and this choice is selected"""
@override @override
def __str__(self) -> str: def __str__(self) -> str:
...@@ -197,10 +207,23 @@ class ExerciceQC(Exercice): ...@@ -197,10 +207,23 @@ class ExerciceQC(Exercice):
# Choices have an 'id' attribute in the form 'lienrepX' (lowercase) # Choices have an 'id' attribute in the form 'lienrepX' (lowercase)
# where X is a number. The actual ID we're keeping is 'repX'. # where X is a number. The actual ID we're keeping is 'repX'.
id = choice_node.attrib["id"].replace("lien", "") id = choice_node.attrib["id"].replace("lien", "")
choice = self._get_or_create(id) choice = self.get_or_create_choice(id)
choice.index = index choice.index = index
choice.html = to_html(choice_node).strip() choice.html = to_html(choice_node).strip()
# The activity's comments have already been extracted in Activity.parse_html(),
# but some of them may be associated with a specific choice (this is
# detected by the JS parser earlier).
# Move these comments from the activity to their choice object.
for choice in self.choices.values():
if choice.comment is not None:
try:
choice.comment = self.comments_sugg.pop(choice.comment.id)
except KeyError:
log.warning(
f"Choice '{choice.id}' requested comment '{choice.comment.id}', which was not found in HTML."
)
@override @override
def save(self, graph: Graph): def save(self, graph: Graph):
super().save(graph) super().save(graph)
...@@ -213,6 +236,11 @@ class ExerciceQC(Exercice): ...@@ -213,6 +236,11 @@ class ExerciceQC(Exercice):
graph.add((choice_node, NS["index"], Literal(choice.index))) graph.add((choice_node, NS["index"], Literal(choice.index)))
graph.add((choice_node, NS["correct"], Literal(choice.is_correct))) graph.add((choice_node, NS["correct"], Literal(choice.is_correct)))
graph.add((choice_node, NS["html"], Literal(choice.html))) graph.add((choice_node, NS["html"], Literal(choice.html)))
# Save optional comment
if choice.comment is not None:
graph.add(
(choice_node, NS["commentaireSugg"], Literal(choice.comment.html))
)
graph.add( graph.add(
( (
choice_node, choice_node,
...@@ -226,19 +254,13 @@ class ExerciceQC(Exercice): ...@@ -226,19 +254,13 @@ class ExerciceQC(Exercice):
def set_correct(self, choice_id: str, correct: bool): def set_correct(self, choice_id: str, correct: bool):
"""Set the choice with ID `choice_id` as correct or not, creating it if needed.""" """Set the choice with ID `choice_id` as correct or not, creating it if needed."""
self._get_or_create(choice_id).is_correct = correct self.get_or_create_choice(choice_id).is_correct = correct
def set_html(self, choice_id: str, html: str): def set_html(self, choice_id: str, html: str):
"""Set the `html` attribute for the choice with ID `choice_id`, creating it if needed.""" """Set the `html` attribute for the choice with ID `choice_id`, creating it if needed."""
self._get_or_create(choice_id).html = html self.get_or_create_choice(choice_id).html = html
# def _get_or_create(self, index: int) -> Choice: def get_or_create_choice(self, id: str) -> Choice:
# """Returns the choice at `index`, creating it if needed."""
# for i in range(len(self.choices), index + 1):
# self.choices.append(Choice(i))
# return self.choices[index]
def _get_or_create(self, id: str) -> Choice:
"""Returns the choice with the `id`, creating it if needed.""" """Returns the choice with the `id`, creating it if needed."""
if id not in self.choices: if id not in self.choices:
self.choices[id] = Choice(id) self.choices[id] = Choice(id)
...@@ -341,18 +363,20 @@ class RegexParser(JSParser): ...@@ -341,18 +363,20 @@ class RegexParser(JSParser):
exo.set_correct(choice_id, True) exo.set_correct(choice_id, True)
else: else:
# tinker with this regex : https://regex101.com/r/qAkdDD/2 # Parse choices IDs and correctness
# ( tinker with this regex: https://regex101.com/r/qAkdDD/2 )
answers_regex = re.compile( answers_regex = re.compile(
r""" r"""
var[ ](?P<varname>\w+) # capture variable name, referenced in 2nd line var[ ](?P<varname>\w+) # Capture variable name, referenced in 2nd line
[ ]=[ ]new[ ]ItemReponse\( [ ]=[ ]new[ ]ItemReponse\(
'(?P<id>\w+)' # constructor parameter : answer ID (obfuscated) '(?P<id>\w+)' # Constructor parameter : answer ID (obfuscated)
\);\n\s* # new line and any indent \);\n\s* # New line and any indent
(?P=varname) # back-reference to the variable name captured earlier (?P=varname) # Back-reference to the variable name captured earlier
\.init\( \.init\(
\"\d*?(?P<correct>\d)\", # first parameter of "init" : correctness \"\d*?(?P<correct>\d)\" # First parameter of "init" : correctness
# Capture last digit only # (capture last digit only)
[ ]\"\",[ ]\"\",[ ]\"\"\); # empty params""", (?:,\s*\"\w*\"){3}\); # Skip 3 params""",
re.VERBOSE, re.VERBOSE,
) )
answers = list(answers_regex.finditer(code)) answers = list(answers_regex.finditer(code))
...@@ -364,9 +388,33 @@ class RegexParser(JSParser): ...@@ -364,9 +388,33 @@ class RegexParser(JSParser):
for match in answers: for match in answers:
# Answer ID is obfuscated by changing some digits # Answer ID is obfuscated by changing some digits
choice_id = decode_answer_id(match.group("id")) choice_id = decode_answer_id(match.group("id"))
choice = exo._get_or_create(choice_id) choice = exo.get_or_create_choice(choice_id)
choice.is_correct = (match.group("correct") == "1") != is_inverted choice.is_correct = (match.group("correct") == "1") != is_inverted
# Parse choice-comment associations
# ( tinker with this regex: https://regex101.com/r/qEzZ5R/1 )
comments_regex = re.compile(
r"""
var[ ](?P<varname>\w+) # Capture variable name, referenced in 2nd line
[ ]=[ ]
'(?P<comment_id>\w+)' # Constructor param : comment ID
;\n\s* # New line and any indent
EXO_ajouterCommentaire\(
(?P=varname) # Back-reference to the variable name captured earlier
(?:,\s*\"\w*\"){6} # Skip 6 parameters
,[ ]\"(?P<choice_id>\w+)\" # 8th parameter : choice ID
(?:,\s*\"\w*\"){10} # Skip 10 parameters
\);""",
re.VERBOSE,
)
for match in comments_regex.finditer(code):
choice = exo.get_or_create_choice(match.group("choice_id"))
# Save a Comment object with just the ID, other fields will be
# filled at the HTML parsing stage
choice.comment = Comment(match.group("comment_id"))
pass
def _parse_score(self, code: str): def _parse_score(self, code: str):
"""Parse the activity's 'total score' variable""" """Parse the activity's 'total score' variable"""
exception = ParseError("Failed to parse total score for this activity") exception = ParseError("Failed to parse total score for this activity")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment