v2 - 적용완료 / 2504회 A형 테스트중 (B,C형 JSON파일 작성후 테스트필요)

This commit is contained in:
2025-05-16 17:58:33 +09:00
parent 20c4359cb8
commit 2d307d77a0
12 changed files with 220 additions and 96 deletions

View File

@@ -41,57 +41,7 @@ class XMLScorer:
hwp_internal_conversion_method = 100
pt = math.trunc(mm * one_mm_per_pt * hwp_internal_conversion_method)
return pt
# XML 파일에서 element의 값을 찾아 반환
def query_xml(self, root, *args):
first_xpath = args[0]
second_xpath = args[1]
points = args[2]
category = args[3]
if ("특수문자" in category) and (second_xpath is not None):
try:
result = root.xpath(first_xpath)
# 결과값이 리스트형인데 내부에 정보가 없는경우
# 결과값이 없음
if type(result) is list and len(result) == 0:
return None
elif result < points:
result = root.xpath(second_xpath)
return result
else:
return result
except ET.XPathEvalError as e:
return None
elif second_xpath is not None:
try:
result1 = root.xpath(first_xpath)
result2 = root.xpath(second_xpath)
if (type(result1) is list and len(result1) == 0) and (type(result2) is list and len(result2) == 0):
return None
return result1 if result1 else result2
except ET.XPathEvalError as e:
return None
else:
try:
result = root.xpath(first_xpath)
if type(result) is list and len(result) == 0:
return None
return result
except ET.XPathEvalError as e:
return None
def chart_query_xml(self, tree, xpath, namespaces):
result = tree.xpath(xpath, namespaces=namespaces)
if type(result) is list and len(result) == 0:
return None
return result
# 유사한 텍스트 찾기
def find_similar_text(self, root, target_text, threshold=0.7):
"""
@@ -199,6 +149,7 @@ class XMLScorer:
xpath = criterion.get('path', None)
xpath2 = criterion.get('path2', None)
xpath3 = criterion.get('path3', None)
chart_xpath = criterion.get('chart_xpath', None)
search_value = criterion.get('searchValue', None)
right_answer = criterion.get('value', None)
points = criterion.get('points', 0)
@@ -211,13 +162,14 @@ class XMLScorer:
if search_value is not None:
# search_value를 포함하는 텍스트 찾기
similar_text = self.find_similar_text(root, search_value)
xpath = xpath.replace('{searchValue}', similar_text)
if xpath2 is not None:
xpath2 = xpath2.replace('{searchValue}', similar_text)
xpath = xpath.replace('{searchValue}', similar_text) if xpath else ""
xpath2 = xpath2.replace('{searchValue}', similar_text) if xpath2 else ""
chart_xpath = chart_xpath.replace('{searchValue}', similar_text) if chart_xpath else ""
if option:
xpath = xpath.replace('{option}', option) if xpath else ""
xpath2 = xpath2.replace('{option}', option) if xpath2 else ""
chart_xpath = chart_xpath.replace('{option}', option) if chart_xpath else ""
# 문항 별 채점 결과 저장
scoring = {
@@ -269,12 +221,14 @@ class XMLScorer:
# 수험자 답안
user_answer = {
########################################################################
# 보통 정답은 글꼴이 [바탕]이어야 하지만 대부분 설정하지 않고
# 정답은 글꼴이 [바탕]이어야 하지만 대부분 설정하지 않고
# 기본 [함초롱바탕]으로 두는 경우가 많아
# 폰트명을 정답과 비교하면 감점이 많이 발생해서
# 폰트명은 정답과 비교하지 않게 적용된 상태
# 폰트명을 정답과 비교하면 감점이 많이 발생
#
# 수험자가 지정한 폰트명과 정답을 비교할때 'FontName': font_name[0], 적용
# 폰트명은 상관없이 정답을 비교하고자 할때 'FontName': "바탕", 적용
########################################################################
#'FontName': font_name[0],
# 'FontName': font_name[0],
'FontName': "바탕",
'FontSize': font_size[0],
'Alignment': alignment[0],
@@ -292,10 +246,11 @@ class XMLScorer:
# 정답이 하나인 경우
elif (category or "") == "OneAnswer":
items = root.xpath(xpath)
items = root.xpath(xpath) if xpath else []
items2 = root.xpath(xpath2) if xpath2 else []
chart_items = chart_tree.xpath(chart_xpath, namespaces=namespaces) if chart_xpath else []
for item in chain(items, items2):
for item in chain(items, items2, chart_items):
user_answer = item
self.evaluate_answer(scoring, user_answer, right_answer, points)
@@ -304,7 +259,7 @@ class XMLScorer:
break
elif (category or "") == "DoubleAnswer":
items1 = root.xpath(xpath)
items1 = root.xpath(xpath) if xpath else []
items2 = root.xpath(xpath2) if xpath else []
user_answer = []
@@ -323,7 +278,11 @@ class XMLScorer:
for item in items:
user_answer = float(item)
right_answer = self.convert_mm_to_pt(float(right_answer))
# JSON 파일 value키값에 mm나 공백이 입력될 경우 제거
# 예) "80.2 mm" >> 80.2 로 변환
float_string = right_answer.strip().replace("mm", "")
right_answer = self.convert_mm_to_pt(float(float_string))
self.evaluate_answer(scoring, user_answer, right_answer, points, method="tolerance")
@@ -347,10 +306,11 @@ class XMLScorer:
# Boolean 타입 정답인 경우
elif (category or "") == "Boolean":
items = root.xpath(xpath)
items = root.xpath(xpath) if xpath else False
items2 = root.xpath(xpath2) if xpath2 else False
chart_items = chart_tree.xpath(chart_xpath, namespaces=namespaces) if chart_xpath else False
user_answer = bool( items or items2 )
user_answer = bool( items or items2 or chart_items )
self.evaluate_answer(scoring, user_answer, right_answer, points)
@@ -453,7 +413,7 @@ class XMLScorer:
self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal")
# 특수문자 갯수 채점
elif "SpecialChar" in (category or ""):
elif (category or "") == "SpecialChar":
ch1 = criterion.get('char1', None)
ch2 = criterion.get('char2', None)
ch3 = criterion.get('char3', None)
@@ -493,7 +453,7 @@ class XMLScorer:
self.evaluate_answer(scoring, user_answer, right_answer, points, method="partial_score")
# 쪽 테두리 (이중 실선, 머리말 포함) 설정
elif "PageBorder" in (category or ""):
elif (category or "") == "PageBorder":
user_answer = {
"header_inside": False,
"all_double_slim": False
@@ -505,33 +465,33 @@ class XMLScorer:
# print("머릿말포함: ",header_inside)
if "true" in header_inside:
user_answer["header_inside"] = True
break
# BORDERFILL요소의 자녀
# LEFTBORDER, RIGHTBORDER, TOPBORDER, BOTTOMBORDER 요소의 Type속성이
# 모두 DoubleSlim이면 정답
border_fill_elements = root.xpath(xpath2)
for bf in border_fill_elements:
border_tags = ["LEFTBORDER", "RIGHTBORDER", "TOPBORDER", "BOTTOMBORDER"]
border_tags = ["LEFTBORDER", "RIGHTBORDER", "TOPBORDER", "BOTTOMBORDER"]
borderfill_elements = root.xpath(xpath2)
for borderfill in borderfill_elements:
all_double_slim = True
for tag in border_tags:
element = bf.find(tag)
if element is None or element.get("Type") != "DoubleSlim":
element = borderfill.find(tag)
if (element is None) or (element.get("Type") != "DoubleSlim"):
all_double_slim = False
break
#모든 BORDER 태그의 Type 속성이 'DoubleSlim'
#모든 BORDER 태그의 Type 속성이 'DoubleSlim'인 객체가 있다면 반복문 탈출
if all_double_slim:
user_answer["all_double_slim"] = True
# 하나 이상의 BORDER 태그가 'DoubleSlim'이 아님
else:
user_answer["all_double_slim"] = False
break
self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal")
# 한자
elif "Hanja" in (category or ""):
elif (category or "") == "Hanja":
word_list = criterion.get('word', [])
# 점수 계산
@@ -557,7 +517,7 @@ class XMLScorer:
self.evaluate_answer(scoring, user_answer, right_answer, points, method="partial_score")
elif (category or "") == "chart_type":
elif (category or "") == "ChartType":
chart_type_list = {
'꺾은선형': "//c:lineChart",
'가로막대형': "//c:barChart[c:barDir[@val='bar']]",
@@ -627,20 +587,35 @@ class XMLScorer:
return xml_data
def typo_check(self, correct_answer_file, user_answer_file):
def typo_check(self, correct_answer_file, user_answer_file, chart_xml):
user_answer_tree = ET.parse(user_answer_file)
user_answer_root = user_answer_tree.getroot()
correct_answer_tree = ET.parse(correct_answer_file)
correct_answer_root = correct_answer_tree.getroot()
# xpath로 바이너리 부분추출
user_input_text = user_answer_root.xpath('//CHAR//text()[not(ancestor::HEADER) and not(ancestor::TABLE)]')
user_table_text = user_answer_root.xpath('//TABLE//CHAR//text()')
user_input_text += user_table_text
correct_input_text = correct_answer_root.xpath('//CHAR//text()[not(ancestor::HEADER) and not(ancestor::TABLE)]')
correct_table_text = correct_answer_root.xpath('//TABLE//CHAR//text()')
correct_input_text += correct_table_text
# 차트 XML에서 제목 추출
if chart_xml is not None:
chart_xml_tree = ET.fromstring(chart_xml)
# 차트 제목 추출
user_chart_title = chart_xml_tree.xpath('/c:chartSpace/c:chart/c:title/c:tx/c:rich/a:p/a:r/a:t', namespaces={'c': 'http://schemas.openxmlformats.org/drawingml/2006/chart', 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'})
# 차트 제목이 존재하는 경우
if user_chart_title:
user_input_text.append(user_chart_title[0].text)
# 차트 제목 정답 텍스트 추출
correct_chart_title = self.scoring_criteria["2"]["50"]["searchValue"]
correct_input_text.append(correct_chart_title)
# 각 요소에서 공백 제거
user_input_text = [text.replace(' ', '') for text in user_input_text]
@@ -652,8 +627,9 @@ class XMLScorer:
correct_input_text = [re.sub(r'\d+\.\s*|-', '', text) for text in correct_input_text]
try :
xpath = self.scoring_criteria["2-29"]['path'].split("'")[1]
ignore_word = xpath.split("'")[1]
# xpath = self.scoring_criteria["2"]["29"]['path'].split("'")[1]
# ignore_word = xpath.split("'")[1]
ignore_word = self.scoring_criteria["2"]["29"]["ignoreWord"]
# 특정 단어 제거
# 오타와 누락의 경우만 판단하면 정상작동하지만
# 추가 된 단어의 경우를 채점기준에 추가하면 정확하게 채점 되지 않을 수 있음
@@ -746,7 +722,7 @@ class XMLScorer:
for user_answer_file in xml_files:
score_result = {}
chart_xml = self.binary_to_chartxml(user_answer_file)
score_result['typo'] = self.typo_check(correct_answer_file, user_answer_file)
score_result['typo'] = self.typo_check(correct_answer_file, user_answer_file, chart_xml)
score_result['score'] = self._score_xml_file(user_answer_file, chart_xml)
# score_result['score']['score_results'][2]['points'] = score_result['typo'][0]
score_results.append(score_result)
@@ -869,8 +845,8 @@ def main():
# 'B',
# 'C',
]
# test_mode = False
test_mode = True
test_mode = False
# test_mode = True
output_excel_paths = []
for exam_type in exam_types: