From c16af0063d857b3d02124759fcbe391227c242db Mon Sep 17 00:00:00 2001 From: dragdra Date: Fri, 25 Apr 2025 17:57:04 +0900 Subject: [PATCH] =?UTF-8?q?diwScoring.py=20=EB=AC=B8=EC=A0=9C1=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DIW_2504A.json | 10 +- DIW_2504A_test.json | 141 +++++++++++++++++- diwScoring2.py | 67 ++++++++- zzz.xbook | 2 +- .../2504_sub/hml_변환/DIW_2504A.hml | 2 +- 5 files changed, 213 insertions(+), 9 deletions(-) diff --git a/DIW_2504A.json b/DIW_2504A.json index 922c9cb..2d390c7 100644 --- a/DIW_2504A.json +++ b/DIW_2504A.json @@ -181,22 +181,22 @@ "item": "문구 (■ 행사안내 ■)/② 정렬 (가운데 정렬)" }, "17": { - "path": "boolean(//CHARSHAPE[@Id=//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape][BOLD])", + "path": "boolean(//CHARSHAPE[@Id=//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape][ITALIC])", "path2": null, - "searchValue": "한옥에 대한 체험과 교육이 준비된 사생대회", + "searchValue": "홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수", "value": true, "points": 1, "category": "글꼴 속성", - "item": "문구 (한옥에 대한 체험과 교육이 준비된 사생대회)/① 진하게" + "item": "문구 (홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수)/① 진하게" }, "18": { "path": "boolean(//CHARSHAPE[@Id=//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape][UNDERLINE])", "path2": null, - "searchValue": "한옥에 대한 체험과 교육이 준비된 사생대회", + "searchValue": "홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수", "value": true, "points": 1, "category": "글꼴 속성", - "item": "문구 (한옥에 대한 체험과 교육이 준비된 사생대회)/② 밑줄" + "item": "문구 (홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수)/② 밑줄" }, "19": { "path": "boolean(//PARASHAPE[@Id=//CHAR[contains(text(),'{searchValue}')]/ancestor::P/following-sibling::P[1]/@ParaShape]/PARAMARGIN/@Left=3000 and //PARASHAPE[@Id=//CHAR[contains(text(),'{searchValue}')]/ancestor::P/following-sibling::P[1]/@ParaShape]/PARAMARGIN/@Indent=-2400)", diff --git a/DIW_2504A_test.json b/DIW_2504A_test.json index c1247b8..11f2eaa 100644 --- a/DIW_2504A_test.json +++ b/DIW_2504A_test.json @@ -153,12 +153,151 @@ }, "13": { "path": "//CHARSHAPE[@Id=//CHAR[contains(text(),'{searchValue}')]/parent::TEXT/@CharShape]", - "path2": null, "searchValue": "한옥에 대한 체험과 교육이 준비된 사생대회", "value": "UNDERLINE", "points": 2, "category": "FontAttribute", "item": "문구 (한옥에 대한 체험과 교육이 준비된 사생대회)/② 밑줄" + }, + "14": { + "path": "//CHAR[contains(text(),'{char1}')]", + "path2": "//CHAR[contains(text(),'{char2}')]", + "char1": "■", + "char2": "※", + "value": 3, + "points": 3, + "category": "SpecialChar", + "item": "① ■, ② ■, ③ ※" + }, + "15": { + "path": "//CHAR[contains(text(),'{searchValue}')]/parent::TEXT/@CharShape", + "searchValue": "■ 행사안내 ■", + "value": "돋움", + "points": 1, + "category": "FontName", + "item": "문구 (■ 행사안내 ■)/① 글씨체 (돋움)" + }, + "16": { + "path": "//PARASHAPE[@Id=//CHAR[contains(text(),'{searchValue}')]/ancestor::P/@ParaShape]/@Align", + "searchValue": "■ 행사안내 ■", + "value": "Center", + "points": 1, + "category": "SingleAnswer", + "item": "문구 (■ 행사안내 ■)/② 정렬 (가운데 정렬)" + }, + "17": { + "path": "//CHARSHAPE[@Id=//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape]", + "searchValue": "홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수", + "value": "ITALIC", + "points": 1, + "category": "FontAttribute", + "item": "문구 (홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수)/① 기울임" + }, + "18": { + "path": "//CHARSHAPE[@Id=//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape]", + "searchValue": "홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수", + "value": "UNDERLINE", + "points": 1, + "category": "FontAttribute", + "item": "문구 (홈페이지(http://www.ihd.or.kr)에서 개별 신청, 선착순 접수)/② 밑줄" + }, + "19": { + "path": "//PARASHAPE[@Id=//CHAR[contains(text(),'{searchValue}')]/ancestor::P/following-sibling::P[1]/@ParaShape]/PARAMARGIN", + "searchValue": "기타사항", + "value": { + "Left": 15, + "Indent": 12 + }, + "points": 2, + "category": "ParaShape", + "item": "문구 (※ 기타… 이하 문단)/왼쪽여백 (15pt), 내어쓰기 (12pt)", + "desc": "내부적으로 내어쓰기는 음수값" + }, + "20": { + "path": "//CHARSHAPE[@Id=//CHAR[contains(text(),'{searchValue}')]/parent::TEXT/@CharShape]/@Height", + "searchValue": "2025. 03. 22.", + "value": "1300", + "points": 1, + "category": "SingleAnswer", + "item": "문구 (2025. 03. 22.)/① 크기 (13pt)" + }, + "21": { + "path": "//PARASHAPE[@Id=//CHAR[contains(text(),'{searchValue}')]/ancestor::P/@ParaShape]/@Align", + "searchValue": "2025. 03. 22.", + "value": "Center", + "points": 1, + "category": "SingleAnswer", + "item": "문구 (2025. 03. 22.)/② 정렬 (가운데 정렬)" + }, + "22": { + "path": "//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape", + "searchValue": "한국고건축협회", + "value": "궁서", + "points": 1, + "category": "FontName", + "item": "문구 (한국고건축협회)/① 글씨체 (궁서)" + }, + "23": { + "path": "//CHARSHAPE[@Id=//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape]/@Height", + "searchValue": "한국고건축협회", + "value": "2400", + "points": 1, + "category": "SingleAnswer", + "item": "문구 (한국고건축협회)/② 크기 (24pt)" + }, + "24": { + "path": "//PARASHAPE[@Id=//CHAR[text()='{searchValue}']/ancestor::P/@ParaShape]/@Align", + "searchValue": "한국고건축협회", + "value": "Center", + "points": 1, + "category": "SingleAnswer", + "item": "문구 (한국고건축협회)/③ 정렬 (가운데 정렬)" + }, + "25": { + "path": "//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape", + "searchValue": "DIAT", + "value": "굴림", + "points": 1, + "category": "FontName", + "item": "문구 (DIAT)/① 글꼴 (굴림)" + }, + "26": { + "path": "//CHARSHAPE[@Id=//SECTION[1]//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape]/@Height", + "searchValue": "DIAT", + "value": "900", + "points": 1, + "category": "SingleAnswer", + "item": "문구 (DIAT)/② 크기 (9pt)" + }, + "27": { + "path": "//PARASHAPE[@Id=//SECTION[1]//CHAR[text()='{searchValue}']/parent::TEXT/parent::P/@ParaShape]/@Align", + "searchValue": "DIAT", + "value": "Right", + "points": 1, + "category": "SingleAnswer", + "item": "문구 (DIAT)/③ 정렬 (오른쪽 정렬)" + }, + "28": { + "path": "//PAGENUM/@FormatType", + "value": "HangulSyllable", + "points": 2, + "category": "SingleAnswer", + "item": "① 쪽 번호 매기기 (가,나,다 순으로)" + }, + "29": { + "path": "//PAGENUM/@Pos", + "value": "BottomCenter", + "points": 2, + "category": "SingleAnswer", + "item": "② 가운데 아래" + }, + "30": { + "path": "not(//PARASHAPE[@Id=//SECTION[1]/P/@ParaShape]/PARAMARGIN[@LineSpacing!='180'])", + "value": true, + "points": 2, + "category": "Boolean", + "item": "문제 1 줄간격 180% 설정", + "desc": "1페이지 문단의 줄간격이 180%가 아닌 문단이 있으면 False(감점)" } } } \ No newline at end of file diff --git a/diwScoring2.py b/diwScoring2.py index 5d293f0..94e3ced 100644 --- a/diwScoring2.py +++ b/diwScoring2.py @@ -139,6 +139,10 @@ class XMLScorer: is_correct = abs(user_answer - right_answer) <= tolerance elif method == "in": is_correct = user_answer in right_answer + elif method == "partial_score": + # 부분 점수 계산 + is_correct = isinstance(user_answer, (int, float)) and user_answer <= right_answer + points = min(points, user_answer) else: raise ValueError(f"Unknown comparison method: {method}") @@ -183,7 +187,7 @@ class XMLScorer: for criterion_id, criterion in section.items(): id = criterion_id xpath = criterion.get('path', None) - xpath2 = criterion.get('path2', None) + xpath2 = criterion.get('path2', None) search_value = criterion.get('searchValue', None) right_answer = criterion.get('value', None) points = criterion.get('points', 0) @@ -196,6 +200,7 @@ class XMLScorer: # search_value를 포함하는 텍스트 찾기 similar_text = self.find_similar_text(root, search_value) xpath = xpath.replace('{searchValue}', similar_text) + # xpath2 = xpath2.replace('{searchValue}', similar_text) # 문항 별 채점 결과 저장 scoring = { @@ -273,6 +278,21 @@ class XMLScorer: if scoring['points'] > 0: break + elif "ParaShape" in (category or ""): + items = root.xpath(xpath) + + for item in items: + user_answer = { + 'Left': float(item.get('Left', 0)) / 200, + 'Indent': float(item.get('Indent', 0)) / -200, + } + + # 정답과 수험자 답안 비교 + self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") + + if scoring['points'] > 0: + break + # Boolean 타입 정답인 경우 elif "Boolean" in (category or ""): items = root.xpath(xpath) @@ -296,6 +316,7 @@ class XMLScorer: if scoring['points'] > 0: break + # 문단 첫글자 장식 채점 elif "TwoLineSize" in (category or ""): items = root.xpath(xpath) error_range = criterion.get('tolerance', 0) @@ -309,6 +330,7 @@ class XMLScorer: if scoring['points'] > 0: break + # 폰트명 elif "FontName" in (category or ""): charshape_id = root.xpath(xpath) if not charshape_id: @@ -321,6 +343,7 @@ class XMLScorer: self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") + # 폰트 속성 elif "FontAttribute" in (category or ""): charshape = root.xpath(xpath) if not charshape: @@ -330,8 +353,50 @@ class XMLScorer: font_attribute = charshape[0].find(right_answer) if font_attribute is not None: user_answer = font_attribute.tag + else: + user_answer = None self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") + + # 특수문자 갯수 채점 + elif "SpecialChar" in (category or ""): + ch1 = criterion.get('char1', None) + ch2 = criterion.get('char2', None) + xpath = xpath.replace('{char1}', ch1) + xpath2 = xpath2.replace('{char2}', ch2) + char1_ele = root.xpath(xpath) + char2_ele = root.xpath(xpath2) + sum_char = 0 + + # char1 요소에서 특수문자 갯수 세기 (최대 2점) + if not char1_ele: + user_answer = 0 + else: + for item in char1_ele: + count_char1 = item.text.count(ch1) + sum_char += count_char1 + if sum_char >= 2: + sum_char = 2 + break + + # char2 요소에서 특수문자 갯수 세기 (최대 1점) + if not char2_ele: + user_answer = 0 + else: + count_char2 = char2_ele[0].text.count(ch2) + if count_char2 > 1: + count_char2 = 1 + sum_char += count_char2 + + user_answer = sum_char + + self.evaluate_answer(scoring, user_answer, right_answer, points, method="partial_score") + + # 줄간격 + elif "LineSpacing" in (category or ""): + + break + onePersonResult['score_results'].append(scoring) print(f'scoring: {scoring}') diff --git a/zzz.xbook b/zzz.xbook index 764fac5..ea8c9f9 100644 --- a/zzz.xbook +++ b/zzz.xbook @@ -1 +1 @@ -[{"kind":2,"language":"xpath","value":"boolean(//PARASHAPE[@Id=//CHAR[contains(text(),'기타')]/ancestor::P/following-sibling::P[1]/@ParaShape]/PARAMARGIN/@Left=3000 and //PARASHAPE[@Id=//CHAR[contains(text(),'기타')]/ancestor::P/following-sibling::P[1]/@ParaShape]/PARAMARGIN/@Indent=-2400)"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE/FONTID/@Hangul][@Name='바탕']"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Name='바탕']/@Id"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id='6']/FONTID/@Hangul"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id='0']/@Align"},{"kind":2,"language":"xpath","value":"boolean(//RECTANGLE[.//CHAR[text()='지']][.//SIZE[(@Height >= 2600 and @Height <= 2800)and(@Width >= 2600 and @Width <= 2800)]])"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE[@Id=//TEXT[CHAR[text()='지']]/@CharShape]/FONTID/@Hangul]/@Name"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE[@Id={charshape_id}]/FONTID/@Hangul]/@Name"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id={font_id}]/@Name"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id=//CHAR[contains(text()[1],'전 세계적으로 차량의 수는 약 13억 대가 있고 국내는 약 2,500만 대')]/parent::TEXT/@CharShape][ITALIC]"},{"kind":2,"language":"xpath","value":"//CHAR[contains(text(),'전 세계적으로 차량의 수는 약 13억 대가 있고 국내는 약 2,500만 대')]/parent::TEXT/@CharShape"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id=//CHAR[contains(text(),'한옥에 대한 체험과 교육이 준비된 사생대회')]/parent::TEXT/@CharShape]"}] \ No newline at end of file +[{"kind":2,"language":"xpath","value":"boolean(//PARASHAPE[@Id=//CHAR[contains(text(),'기타')]/ancestor::P/following-sibling::P[1]/@ParaShape]/PARAMARGIN/@Left=3000 and //PARASHAPE[@Id=//CHAR[contains(text(),'기타')]/ancestor::P/following-sibling::P[1]/@ParaShape]/PARAMARGIN/@Indent=-2400)"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE/FONTID/@Hangul][@Name='바탕']"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Name='바탕']/@Id"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id='6']/FONTID/@Hangul"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id='0']/@Align"},{"kind":2,"language":"xpath","value":"boolean(//RECTANGLE[.//CHAR[text()='지']][.//SIZE[(@Height >= 2600 and @Height <= 2800)and(@Width >= 2600 and @Width <= 2800)]])"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE[@Id=//TEXT[CHAR[text()='지']]/@CharShape]/FONTID/@Hangul]/@Name"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE[@Id={charshape_id}]/FONTID/@Hangul]/@Name"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id={font_id}]/@Name"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id=//CHAR[contains(text()[1],'전 세계적으로 차량의 수는 약 13억 대가 있고 국내는 약 2,500만 대')]/parent::TEXT/@CharShape][ITALIC]"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//SECTION[1]/P/@ParaShape]/PARAMARGIN/@LineSpacing"},{"kind":2,"language":"xpath","value":"not(//PARASHAPE[@Id=//SECTION[1]/P/@ParaShape]/PARAMARGIN[@LineSpacing!='180'])"}] \ No newline at end of file diff --git a/회차별채점자료/2504_sub/hml_변환/DIW_2504A.hml b/회차별채점자료/2504_sub/hml_변환/DIW_2504A.hml index 64b9833..75873a2 100644 --- a/회차별채점자료/2504_sub/hml_변환/DIW_2504A.hml +++ b/회차별채점자료/2504_sub/hml_변환/DIW_2504A.hml @@ -523,7 +523,7 @@ - +