From d4781a350de0eeaa4e9f9908e522dc51e22ef09c Mon Sep 17 00:00:00 2001 From: dragdra Date: Tue, 9 Sep 2025 17:58:09 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=80=EC=83=81=EC=9E=90=20=EC=B1=84?= =?UTF-8?q?=EC=A0=90=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95=20[2-10]=20?= =?UTF-8?q?=EA=B9=8C=EC=A7=80=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 250909_DIW_2508C_TEST.xlsx | Bin 0 -> 8165 bytes DIW_2508C.json | 44 ++++------ diwScoring2.py | 170 +++++++++++++++++++++++++------------ zzz.xbook | 2 +- 4 files changed, 133 insertions(+), 83 deletions(-) create mode 100644 250909_DIW_2508C_TEST.xlsx diff --git a/250909_DIW_2508C_TEST.xlsx b/250909_DIW_2508C_TEST.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..729751891e30e995defed7148c604000e669ffa1 GIT binary patch literal 8165 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 literal 0 HcmV?d00001 diff --git a/DIW_2508C.json b/DIW_2508C.json index 3df41c9..ca67d29 100644 --- a/DIW_2508C.json +++ b/DIW_2508C.json @@ -351,89 +351,79 @@ "item": "② 다단 2단" }, "4": { - "path": "//RECTANGLE//CHAR[text()='{searchValue}']/ancestor::RECTANGLE/SHAPEOBJECT/SIZE/@Width", - "searchValue": "구강건강관리", + "path": "//RECTANGLE/SHAPEOBJECT/SIZE/@Width", "value": "60", "points": 2, - "category": "mmSize", + "category": "Rectangle.mmSize", "item": "문구 (구강건강관리)/① 크기-너비 (60 mm)" }, "5": { - "path": "//RECTANGLE//CHAR[text()='{searchValue}']/ancestor::RECTANGLE/SHAPEOBJECT/SIZE/@Height", - "searchValue": "구강건강관리", + "path": "//RECTANGLE/SHAPEOBJECT/SIZE/@Height", "value": "12", "points": 2, - "category": "mmSize", + "category": "Rectangle.mmSize", "item": "문구 (구강건강관리)/② 크기-높이 (12 mm)" }, "6": { - "path": "//RECTANGLE[.//CHAR[text()='{searchValue}']]//LINESHAPE", - "searchValue": "구강건강관리", + "path": "//RECTANGLE//LINESHAPE", "value": { "Style": "DoubleSlim", "Width": "283" }, "points": 2, - "category": "LineShape", + "category": "Rectangle.LineShape", "item": "문구 (구강건강관리)/③ 테두리 : 이중 실선(1.00mm)", "desc": "1mm = 283pt value['Width']에 pt값 입력" }, "7": { - "path": "//RECTANGLE[.//CHAR[text()='{searchValue}']]/@Ratio", - "searchValue": "구강건강관리", + "path": "//RECTANGLE/@Ratio", "value": "20", "points": 2, - "category": "OneAnswer", + "category": "Rectangle.OneAnswer", "item": "문구 (구강건강관리)/④ 글상자 모서리 (반원)", "desc": "모서리 비율 반원:50 / 둥근모양:20" }, "8": { - "path": "//RECTANGLE[.//CHAR[text()='{searchValue}']]//WINDOWBRUSH/@FaceColor", - "searchValue": "구강건강관리", + "path": "//RECTANGLE//WINDOWBRUSH/@FaceColor", "value": "187,140,209", "points": 2, - "category": "Color", + "category": "Rectangle.Color", "item": "문구 (구강건강관리)/⑤ 채우기 : 색상(RGB:187,140,209)" }, "9": { - "path": "//RECTANGLE[.//CHAR[text()='{searchValue}']]/SHAPEOBJECT/POSITION/@TreatAsChar", - "searchValue": "구강건강관리", + "path": "//RECTANGLE/SHAPEOBJECT/POSITION/@TreatAsChar", "value": "true", "points": 1, - "category": "OneAnswer", + "category": "Rectangle.OneAnswer", "item": "문구 (구강건강관리)/⑥ 글상자 위치 (글자처럼 취급)" }, "10": { - "path": "//PARASHAPE[@Id=//RECTANGLE//CHAR[text()='{searchValue}']/ancestor::P[last()]/@ParaShape]/@Align", - "searchValue": "구강건강관리", + "path": "//PARASHAPE[@Id='{rectangle_parashape_id}']/@Align", "value": "Center", "points": 1, - "category": "OneAnswer", + "category": "Rectangle.TextBoxAlign", "item": "문구 (구강건강관리)/⑦ 글상자 정렬 (가운데 정렬)" }, "11": { "path": "//TEXT[CHAR[text()='{searchValue}']]/@CharShape", - "searchValue": "구강건강관리", "value": "맑은 고딕", "points": 1, - "category": "FontName", + "category": "Rectangle.FontName", "item": "문구 (구강건강관리)/⑧ 글씨체 (맑은 고딕)" }, "12": { "path": "//CHARSHAPE[@Id=//RECTANGLE//TEXT[./CHAR[text()='{searchValue}']]/@CharShape]/@Height", - "searchValue": "구강건강관리", "value": "2300", "points": 1, - "category": "OneAnswer", + "category": "Rectangle.OneAnswer", "item": "문구 (구강건강관리)/⑨ 글씨크기 (2300)", "desc": "1pt당 100" }, "13": { "path": "//PARASHAPE[@Id=//RECTANGLE//P[.//CHAR[text()='{searchValue}']]/@ParaShape]/@Align", - "searchValue": "구강건강관리", "value": "Center", "points": 1, - "category": "OneAnswer", + "category": "Rectangle.OneAnswer", "item": "문구 (구강건강관리)/⑩ 정렬 (가운데 정렬)" }, "14": { diff --git a/diwScoring2.py b/diwScoring2.py index da7ff8d..a01c983 100644 --- a/diwScoring2.py +++ b/diwScoring2.py @@ -3,7 +3,7 @@ import difflib import json from pathlib import Path import os -from lxml import etree as ET +from lxml import etree import re from difflib import SequenceMatcher import pandas as pd @@ -133,32 +133,6 @@ class XMLScorer: # 하나의 XML 파일 채점 def _score_xml_file(self, xml_file, chart_xml): - # def parse_pages_by_bookmark(root): - # """ - # P/TEXT/BOOKMARK 구조를 가진 XML에서 페이지 구간별

요소를 파싱하여 반환 - # """ - # pages = {} - # all_p_tags = root.xpath('//P') - - # current_page = None - # page_start_index = None - - # for i, p in enumerate(all_p_tags): - # # BOOKMARK가 존재하는지 확인 (어디에 있든 탐색) - # bookmarks = p.xpath('.//BOOKMARK') - # for bm in bookmarks: - # name = bm.get('Name') - # if name and name.endswith('_start'): - # current_page = name.replace('_start', '') - # page_start_index = i - # elif name and name.endswith('_end') and current_page is not None: - # page_end_index = i - # page_content = all_p_tags[page_start_index:page_end_index + 1] - # pages[current_page] = page_content - # current_page = None - # page_start_index = None - - # return pages def parse_pages_by_bookmark(root): """ BOOKMARK(Name="Page_X_start" ~ "Page_X_end") 사이의

요소들을 @@ -201,7 +175,7 @@ class XMLScorer: return full_text try: - tree = ET.parse(xml_file) + tree = etree.parse(xml_file) root = tree.getroot() # XML문서 페이지 파싱 전처리 @@ -216,9 +190,9 @@ class XMLScorer: # 차트 XML 파일이 없는 경우 0점 채점을 위헤 빈 XML 생성 if chart_xml is None: - chart_tree = ET.fromstring('') + chart_tree = etree.fromstring('') else: - chart_tree = ET.fromstring(chart_xml) + chart_tree = etree.fromstring(chart_xml) # 결과값을 Dictionary로 저장 # 하나의 xml파일 = 수험생 한명의 답안지 @@ -275,6 +249,9 @@ class XMLScorer: } try: + # 머릿말과 관련된 문항에서 1페이지에 머릿말이 없는 경우의 처리 + # [1-25, 26, 27] 문항 'DIAT' 머릿말 채점시 1페이지에 머릿말이 없으면 + # 채점하지 않고 0점 처리 if "Header" in (category or ""): def has_elements(ptags, xpath): for p in ptags: @@ -285,9 +262,27 @@ class XMLScorer: page1_ptags = pages.get('Page_1', []) header_xpath = ".//HEADER//P" - has_page1_element = has_elements(page1_ptags, header_xpath) + has_page1_header = has_elements(page1_ptags, header_xpath) - if not has_page1_element: + if not has_page1_header: + user_answer = None + self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") + continue + + has_page2_rectangle = False + if "Rectangle" in (category or ""): + def has_elements(ptags, xpath): + for p in ptags: + element_list = p.xpath(xpath) if xpath else [] + if element_list: + return True + return False + + page2_ptags = pages.get('Page_2', []) + rectangle_xpath = ".//RECTANGLE" + has_page2_rectangle = has_elements(page2_ptags, rectangle_xpath) + + if not has_page2_rectangle: user_answer = None self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") continue @@ -458,13 +453,48 @@ class XMLScorer: self.evaluate_answer(scoring, user_answer, right_answer, points) if scoring['points'] > 0: break - + + # 글상자 정렬 [2-10] 문항 + # 2페이지의 글상자의 ParaShape ID를 동적으로 찾아서 채점 + elif "TextBoxAlign" in (category or ""): + if has_page2_rectangle: + # 2페이지 내에서만 검색 + search_root = etree.Element("Page_2") + for p in page2_ptags: + search_root.append(p) + rectangle_parashape_id = search_root.xpath(".//RECTANGLE/ancestor::P[last()]/@ParaShape") + else: + # 전체 root에서 검색 + rectangle_parashape_id = root.xpath(".//RECTANGLE/ancestor::P[last()]/@ParaShape") + + # ParaShape ID가 있는 경우에만 xpath 치환 & 실행 + if rectangle_parashape_id: + xpath = xpath.replace('{rectangle_parashape_id}', rectangle_parashape_id[0]) + items = root.xpath(xpath) + else: + # RECTANGLE이 없으면 items는 빈 리스트 + items = [None] + + for item in items: + user_answer = item + self.evaluate_answer(scoring, user_answer, right_answer, points) + if scoring['points'] > 0: + break + # 정답이 하나인 경우 # elif (category or "") in ["OneAnswer", "ChartOneAnswer"]: - elif "OneAnswer" in (category or ""): - items = root.xpath(xpath) if xpath else [] - items2 = root.xpath(xpath2) if xpath2 else [] + elif "OneAnswer" in (category or ""): + if has_page2_rectangle: + search_root = etree.Element("Page_2") + for p in page2_ptags: + search_root.append(p) + items = search_root.xpath(xpath) if xpath else [] + items2 = search_root.xpath(xpath2) if xpath2 else [] + + else: + items = root.xpath(xpath) if xpath else [] + items2 = root.xpath(xpath2) if xpath2 else [] # 차트 XML에서 정답을 찾는 경우 # 차트 종류가 @@ -496,8 +526,15 @@ class XMLScorer: break # [2-6] 테두리 이중실선 1.00mm - elif (category or "") == "LineShape": - line_shapes = root.xpath(xpath) if xpath else [] + elif "LineShape" in (category or ""): + if has_page2_rectangle: + search_root = etree.Element("Page_2") + for p in page2_ptags: + search_root.append(p) + line_shapes = search_root.xpath(xpath) if xpath else [] + + else: + line_shapes = root.xpath(xpath) if xpath else [] user_answer = { 'Style': None, @@ -516,8 +553,16 @@ class XMLScorer: break # 사용자 입력값이 mm단위인 경우 - elif (category or "") == "mmSize": - items = root.xpath(xpath) + # elif (category or "") == "mmSize": + elif "mmSize" in (category or ""): + if has_page2_rectangle: + search_root = etree.Element("Page_2") + for p in page2_ptags: + search_root.append(p) + items = search_root.xpath(xpath) + + else: + items = root.xpath(xpath) # 오차범위 설정 # 한글 프로그램 내부에서 드물게 0mm이지만 1pt로 저장되는 경우가 있음 # @@ -567,10 +612,18 @@ class XMLScorer: self.evaluate_answer(scoring, user_answer, right_answer, points) # 채점기준표 파일에 작성된 rgb값을 그대로 읽어와 HML파일 요소의 int형 rgb값과 비교 - elif (category or "") == "Color": - items = root.xpath(xpath) if xpath else [] - items2 = root.xpath(xpath2) if xpath2 else [] - + elif "Color" in (category or ""): + if has_page2_rectangle: + search_root = etree.Element("Page_2") + for p in page2_ptags: + search_root.append(p) + items = search_root.xpath(xpath) if xpath else [] + items2 = search_root.xpath(xpath2) if xpath2 else [] + + else: + items = root.xpath(xpath) if xpath else [] + items2 = root.xpath(xpath2) if xpath2 else [] + rgb_text = right_answer # 정규식을 이용해 숫자만 리스트로 추출 @@ -605,8 +658,15 @@ class XMLScorer: # 폰트명 elif "FontName" in (category or ""): - charshape_list = root.xpath(xpath) - + if has_page2_rectangle: + search_root = etree.Element("Page_2") + for p in page2_ptags: + search_root.append(p) + charshape_list = search_root.xpath(xpath) + + else: + charshape_list = root.xpath(xpath) + # 문자속성이 없는 경우 if not charshape_list: user_answer = "" @@ -978,7 +1038,7 @@ class XMLScorer: onePersonResult['total_score'] = self.total_score return onePersonResult - except ET.ParseError as e: + except etree.ParseError as e: return { 'filename': os.path.basename(xml_file), 'error': f"XML 파싱 오류: {str(e)}", @@ -986,7 +1046,7 @@ class XMLScorer: } def binary_to_chartxml(self, xml_path): - tree = ET.parse(xml_path) + tree = etree.parse(xml_path) root = tree.getroot() binary_data = root.xpath('//BINDATA[@Id=//BINITEM[@Format="OLE"]/@BinData]/text()') @@ -1038,8 +1098,8 @@ class XMLScorer: # 2. 공백제거, 특정 형식 제거 # 3. 리스트를 문자열로 변환 - user_answer_root = ET.parse(user_answer_file).getroot() - correct_answer_root = ET.parse(correct_answer_file).getroot() + user_answer_root = etree.parse(user_answer_file).getroot() + correct_answer_root = etree.parse(correct_answer_file).getroot() # xpath로 바이너리 부분추출 user_input_text = user_answer_root.xpath('//CHAR//text()[not(ancestor::HEADER) and not(ancestor::TABLE)]') @@ -1054,7 +1114,7 @@ class XMLScorer: # 차트 XML에서 차트제목 추출 if chart_xml is not None: - chart_xml_tree = ET.fromstring(chart_xml) + chart_xml_tree = etree.fromstring(chart_xml) ns = {'c': 'http://schemas.openxmlformats.org/drawingml/2006/chart', 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'} xpath_expr = '/c:chartSpace/c:chart/c:title/c:tx/c:rich/a:p/a:r/a:t' @@ -1325,12 +1385,12 @@ def main(): exam_types = [ # 'A', # 'B', - # 'C', - 'D', + 'C', + # 'D', ] - test_mode = False - # test_mode = True #/TEST 폴더 채점시 + # test_mode = False + test_mode = True #/TEST 폴더 채점시 output_excel_paths = [] for exam_type in exam_types: diff --git a/zzz.xbook b/zzz.xbook index 0b62787..ccd3958 100644 --- a/zzz.xbook +++ b/zzz.xbook @@ -1 +1 @@ -[{"kind":2,"language":"xpath","value":"//a:t[text()='클라우드 보안투자']/ancestor::a:r//a:ea/@typeface"},{"kind":2,"language":"xpath","value":"boolean(//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE/FONTID/@Hangul]/@Name='바탕' and //CHARSHAPE/@Height='1000' and //PARASHAPE/PARAMARGIN/@LineSpacing='160' and //PARASHAPE/@Align='Justify')"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE/FONTID/@Hangul]/@Name='바탕'"},{"kind":2,"language":"xpath","value":"//RECTANGLE//CHAR[text()='구강건강관거리']/ancestor::RECTANGLE/SHAPEOBJECT/SIZE/@Width"},{"kind":2,"language":"xpath","value":"//CHAR[contains(text(),'예방')][contains(text(),'豫防')]"},{"kind":2,"language":"xpath","value":"//TEXT[CHAR[text()='DIAT']]"},{"kind":2,"language":"xpath","value":"//HEADER//P"},{"kind":2,"language":"xpath","value":"//P[.//FIELDBEGIN[@Type='Hyperlink'] and .//CHAR[contains(., 'http')]]"},{"kind":2,"language":"xpath","value":"//PICTURE[./IMAGE[@BinItem=//BINITEM[@Format='JPG']/@BinData]]/SHAPEOBJECT/POSITION[not(@TreatAsChar='true')]/@HorzOffset"},{"kind":2,"language":"xpath","value":"//CHAR[contains(string(.), '※')]/descendant-or-self::text()"},{"kind":2,"language":"xpath","value":"//P[@ParaShape=\"17\"]/TEXT[@CharShape='7']//CHAR[string(.)]"},{"kind":2,"language":"xpath","value":"//CHAR[contains(string(.), '기타')]/text()"}] \ No newline at end of file +[{"kind":2,"language":"xpath","value":"//a:t[text()='클라우드 보안투자']/ancestor::a:r//a:ea/@typeface"},{"kind":2,"language":"xpath","value":"boolean(//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE/FONTID/@Hangul]/@Name='바탕' and //CHARSHAPE/@Height='1000' and //PARASHAPE/PARAMARGIN/@LineSpacing='160' and //PARASHAPE/@Align='Justify')"},{"kind":2,"language":"xpath","value":"//FONTFACE[@Lang='Hangul']/FONT[@Id=//CHARSHAPE/FONTID/@Hangul]/@Name='바탕'"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//RECTANGLE/ancestor::P[last()]/@ParaShape]/@Align"},{"kind":2,"language":"xpath","value":"//RECTANGLE//LINESHAPE"},{"kind":2,"language":"xpath","value":"//RECTANGLE/SHAPEOBJECT/SIZE/@Width"},{"kind":2,"language":"xpath","value":"//HEADER//P"},{"kind":2,"language":"xpath","value":"//P[.//FIELDBEGIN[@Type='Hyperlink'] and .//CHAR[contains(., 'http')]]"},{"kind":2,"language":"xpath","value":"//PICTURE[./IMAGE[@BinItem=//BINITEM[@Format='JPG']/@BinData]]/SHAPEOBJECT/POSITION[not(@TreatAsChar='true')]/@HorzOffset"},{"kind":2,"language":"xpath","value":"//CHAR[contains(string(.), '※')]/descendant-or-self::text()"},{"kind":2,"language":"xpath","value":"//P[@ParaShape=\"17\"]/TEXT[@CharShape='7']//CHAR[string(.)]"},{"kind":2,"language":"xpath","value":"//CHAR[contains(string(.), '기타')]/text()"}] \ No newline at end of file