diff --git a/250519_DIW_2504회_A형_TEST.xlsx b/250519_DIW_2504회_A형_TEST.xlsx new file mode 100644 index 0000000..b0bb8b6 Binary files /dev/null and b/250519_DIW_2504회_A형_TEST.xlsx differ diff --git a/250519_DIW_2504회_A형_채점결과.xlsx b/250519_DIW_2504회_A형_채점결과.xlsx new file mode 100644 index 0000000..5da9d37 Binary files /dev/null and b/250519_DIW_2504회_A형_채점결과.xlsx differ diff --git a/DIW_2504A_new.json b/DIW_2504A_new.json index d4a5ca1..e04d5fa 100644 --- a/DIW_2504A_new.json +++ b/DIW_2504A_new.json @@ -185,6 +185,7 @@ }, "17": { "path": "//CHARSHAPE[@Id=//TEXT[CHAR[text()='{searchValue}']]/@CharShape]", + "hyperlink_ptag": "//P[.//FIELDBEGIN[@Type='Hyperlink']]", "searchValue": "홈페이지(http://www.ihd.or.kr) 참조", "value": "ITALIC", "points": 1, @@ -193,6 +194,7 @@ }, "18": { "path": "//CHARSHAPE[@Id=//TEXT[CHAR[text()='{searchValue}']]/@CharShape]", + "hyperlink_ptag": "//P[.//FIELDBEGIN[@Type='Hyperlink']]", "searchValue": "홈페이지(http://www.ihd.or.kr) 참조", "value": "UNDERLINE", "points": 1, @@ -280,9 +282,9 @@ "path": "//PAGENUM/@FormatType", "value": "LatinCapital", "points": 2, - "category": "OneAnswer", + "category": "PageNumber", "item": "① 쪽 번호 매기기 (A,B,C 순으로)", - "desc": { + "desc1": { "가,나,다":"HangulSyllable", "1,2,3":"Digit", "갑,을,병":"DecagonCircle", @@ -293,14 +295,16 @@ "ⓐ,ⓑ,ⓒ":"CircledLatinSmall", "i,ii,iii":"RomanSmall", "정답에 맞는 값 value에 입력":"" - } + }, + "desc2": "1, 2페이지 모두 정답이어야 점수 부여" }, "29": { "path": "//PAGENUM/@Pos", "value": "BottomRight", "points": 2, - "category": "OneAnswer", - "item": "오른쪽 아래" + "category": "PageNumber", + "item": "오른쪽 아래", + "desc": "1, 2페이지 모두 정답이어야 점수 부여" }, "30": { "path": "not(//PARASHAPE[@Id=//SECTION[1]/P/@ParaShape]/PARAMARGIN[@LineSpacing!='200'])", diff --git a/diwScoring2.py b/diwScoring2.py index f833245..56cc97e 100644 --- a/diwScoring2.py +++ b/diwScoring2.py @@ -43,7 +43,7 @@ class XMLScorer: return pt # 유사한 텍스트 찾기 - def find_similar_text(self, root, target_text, threshold=0.7): + def find_similar_text(self, root, chart_tree, target_text, threshold=0.7): """ 전체 문서에서 유사한 텍스트를 찾아 반환 @@ -64,7 +64,10 @@ class XMLScorer: 'c': 'http://schemas.openxmlformats.org/drawingml/2006/chart' } - all_text = root.xpath(f"//BODY//text() | //TEXTART/@Text | //c:chart//text()", namespaces=namespaces) + hwp_text = root.xpath(f"//BODY//text() | //TEXTART/@Text") + chart_text = chart_tree.xpath(f"//c:chart//text()", namespaces=namespaces) if chart_tree is not None else [] + + all_text = hwp_text + chart_text # 유사도 비교 max_score = 0 @@ -161,7 +164,7 @@ class XMLScorer: # search_value가 있는 경우 if search_value is not None: # search_value를 포함하는 텍스트 찾기 - similar_text = self.find_similar_text(root, search_value) + similar_text = self.find_similar_text(root, chart_tree, search_value) 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 "" @@ -237,6 +240,23 @@ class XMLScorer: self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") + # 1, 2페이지 모두 정답이어야 함 + elif (category or "") == "PageNumber": + items = root.xpath(xpath) if xpath else [] + + all_match = True + for item in chain(items): + user_answer = item + if right_answer != user_answer: + all_match = False + break + + if all_match: + self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") + else: + self.evaluate_answer(scoring, user_answer, right_answer, 0, method="equal") + + # 오타 감점 부분은 미리 계산 하고, 이후 점수만 계산 elif (category or "") == "오타감점": points = self.get_typo_score() @@ -388,9 +408,10 @@ class XMLScorer: if right_answer in ["견고딕", "중고딕"]: user_answer = user_answer.replace("한양", "") + # 하나라도 다르면 바로 오답 처리 if user_answer != right_answer: all_match = False - break # 하나라도 다르면 바로 오답 처리 + break if all_match: self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") @@ -399,18 +420,96 @@ class XMLScorer: # 폰트 속성 elif (category or "") == "FontAttribute": - charshape = root.xpath(xpath) - if not charshape: - charshape = None - user_answer = None - else: - font_attribute = charshape[0].find(right_answer) - if font_attribute is not None: - user_answer = font_attribute.tag - else: - user_answer = None + # 하이퍼링크 처리 + hyperlink_ptag = criterion.get('hyperlink_ptag', None) + has_ptag = root.xpath(hyperlink_ptag) if hyperlink_ptag else False + + # hyperlink가 아닌 경우(일반적인 텍스트 일 경우) + if not has_ptag: + charshape = root.xpath(xpath) + if not charshape: + charshape = None + user_answer = None + else: + 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 has_ptag: + hyperlink_text = search_value.replace(" ", "") + + p_elements = has_ptag + + for p in p_elements: + text_list = p.xpath(".//CHAR/text()") + full_text = ''.join(text_list).replace(" ", "") + # print("full_text: ", full_text) + + # 채점하고자 하는 문자열 (search_value)의 첫 문자 + first_char = search_value[0] + + # 수험자 답안에서 첫 문자 인덱스 위치 + user_answer_first_index = full_text.find(first_char) + + if user_answer_first_index != -1: + # 수험자 답안에서 첫 문자 인덱스 위치부터 search_value 길이만큼 잘라서 비교 + trimmed_full_text = full_text[user_answer_first_index:] + else: + trimmed_full_text = full_text + + # 두 문자열의 유사도 계산 + similarity = difflib.SequenceMatcher(None, trimmed_full_text, hyperlink_text).ratio() + + # 두 문자열의 유사도에 따라 하이퍼링크 확인 + # 유사도가 낮은 경우 오답처리 + if similarity < 0.7: + self.evaluate_answer(scoring, user_answer, right_answer, 0, method="equal") + + # 유사도가 높은 경우 + else: + inside_field = False + charshape_list = [] + + for elem in p.iter(): + # 시작 지점 확인 + if elem.tag == "FIELDBEGIN": + inside_field = True + elif elem.tag == "FIELDEND": + inside_field = False + elif inside_field and elem.tag == "TEXT": + charshape = elem.get("CharShape") + if charshape: + charshape_list.append(charshape) + + # 하이퍼링크에 해당하는 P태그 내 존재하는 charshape ID값 모두를 비교해 해당 속성(ITALIC, BOLD, UNDERLINE) 확인 + # 모든 charshape ID값이 정답과 일치하는 경우에만 점수 부여 + all_attributes_match = True + if charshape_list: + for charshape_id in charshape_list: + charshape = root.xpath(f"//CHARSHAPE[@Id='{charshape_id}']") + + # 속성 태그가 존재하는지 확인 + font_attribute = charshape[0].find(right_answer) + if font_attribute is None: + user_answer = None + all_attributes_match = False + break + + else: + user_answer = font_attribute.tag + + if all_attributes_match: + self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") + else: + self.evaluate_answer(scoring, user_answer, right_answer, 0, method="equal") + + - self.evaluate_answer(scoring, user_answer, right_answer, points, method="equal") # 특수문자 갯수 채점 elif (category or "") == "SpecialChar": diff --git a/zzz.xbook b/zzz.xbook index 2709891..adbc23e 100644 --- a/zzz.xbook +++ b/zzz.xbook @@ -1 +1 @@ -[{"kind":2,"language":"xpath","value":"//BORDERFILL[@Id=//PAGEBORDERFILL[@Type='Both' or @Type='Even']/@BorferFill]/*[contains(local-name(), 'BORDER')]/@Type"},{"kind":2,"language":"xpath","value":"//BORDERFILL[@Id=//SECTION[2]//PAGEBORDERFILL[@Type='Both' or @Type='Even']/@BorferFill]"},{"kind":2,"language":"xpath","value":"//RECTANGLE//CHAR[text()='클라우드 컴퓨팅']/ancestor::RECTANGLE/descendant::LINESHAPE"},{"kind":2,"language":"xpath","value":"//RECTANGLE[.//CHAR[text()='클라우드 컴퓨팅']]//LINESHAPE"},{"kind":2,"language":"xpath","value":"//RECTANGLE[.//CHAR[text()='클라우드 컴퓨팅']]/SHAPEOBJECT/POSITION/@TreatAsChar"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//RECTANGLE//CHAR[text()='{searchValue}']/ancestor::P[last()]/@ParaShape]/@Align"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id=//RECTANGLE//CHAR[text()='클라우드 컴퓨팅']/parent::TEXT/@CharShape]/@Height"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//RECTANGLE//P[.//CHAR[text()='{searchValue}']]/@ParaShape]/@Align"},{"kind":2,"language":"xpath","value":"//IMAGE[@BinItem=//BINITEM[@Format='JPG']/@BinData]/preceding-sibling::SHAPEOBJECT/SIZE/@Width"},{"kind":2,"language":"xpath","value":"//CHAR[text()='{searchValue}']/parent::TEXT/@CharShape"},{"kind":2,"language":"xpath","value":"//TEXT[CHAR[text()='{searchValue}']]/@CharShape"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//P[.//TEXTART[@Text='클라우드컴퓨팅컨퍼런스']]/@ParaShape]/@Align"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id=//TEXT[CHAR[contains(text(),'클라우드 보안(단위: 백만 달러)')]]/@CharShape]/@Height"},{"kind":2,"language":"xpath","value":"//TEXT[CHAR[contains(text(),'클라우드')]]/FOOTNOTE[CHAR[contains(text(),'인터넷을 통해 액세스할 수 있는 가상화된 서버에서 실행되는 프로그램과 데이터베이스를 제공하는 환경')]]"},{"kind":2,"language":"xpath","value":"//TEXT[CHAR[contains(text(),'클라우드')]]/FOOTNOTE[.//CHAR[contains(text(),'인터넷을 통해 액세스할 수 있는 가상화된 서버에서 실행되는 프로그램과 데이터베이스를 제공하는 환경')]]"},{"kind":2,"language":"xpath","value":"//TEXT[CHAR[contains(text(),'클라우드')]]/FOOTNOTE"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id=//TEXT[CHAR[text()='클라우드 보안(단위: 백만 달러)']]/@CharShape]"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//P[.//CHAR[text()='클라우드 보안(단위: 백만 달러)']]/@ParaShape]/@Align"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//CHAR[contains(text(),'클라우드 보안(단위: 백만 달러)')]/ancestor::P/@ParaShape]/@Align"},{"kind":2,"language":"xpath","value":"//BORDERFILL[@Id=//CELLZONE[@StartRowAddr='0' and @EndRowAddr='0' and @StartColAddr='0' and @EndColAddr=(ancestor::TABLE[1]/@ColCount)-1]/@BorderFill]/FILLBRUSH/WINDOWBRUSH/@FaceColor"},{"kind":2,"language":"xpath","value":"//BORDERFILL[@Id=//CELLZONE[@EndColAddr=../../@ColCount]]"},{"kind":2,"language":"xpath","value":"//CHARSHAPE[@Id=//TABLE//TEXT/@CharShape]/@Height"},{"kind":2,"language":"xpath","value":"//PARASHAPE[@Id=//TABLE/ROW//P/@ParaShape]/@Align"},{"kind":2,"language":"xpath","value":"//TABLE[1]/ROW[last()]/CELL[last()-1]//FIELDBEGIN[starts-with(@Command, '=SUM')]"},{"kind":2,"language":"xpath","value":"//c:chart and not(//c:pt[not(ancestor::c:tx)]/c:v[text()='합계' or text()='평균'])"},{"kind":2,"language":"xpath","value":"//BORDERFILL[@Id=//PAGEBORDERFILL[@Type='Both' or @Type='Even']/@BorferFill]"},{"kind":2,"language":"xpath","value":"//PAGEBORDERFILL[@Type='Both' or @Type='Even']/@HeaderInside"}] \ No newline at end of file +[{"kind":2,"language":"xpath","value":"//a:t[text()='클라우드 보안투자']/ancestor::a:r//a:ea/@typeface"}] \ No newline at end of file diff --git a/구버전신버전점수비교A형.xlsx b/구버전신버전점수비교A형.xlsx index 918dd34..9c95126 100644 Binary files a/구버전신버전점수비교A형.xlsx and b/구버전신버전점수비교A형.xlsx differ diff --git a/오류및문제점.md b/오류및문제점.md new file mode 100644 index 0000000..de37f91 --- /dev/null +++ b/오류및문제점.md @@ -0,0 +1,12 @@ +# 오류 및 문제점 + +## 1. 원인 파악이나 해결이 어려운 경우 + +### 한글문서(.hwpx)에 수험자가 차트 텍스트 속성을 변경해서 저장했지만 차트 xml파일에 옵션이 적용되지 않는 경우가 있음 + + 1. 정상적으로 텍스트 속성이 적용되는 수험자의 차트를 복사 + 2. 텍스트 속성이 적용되지 않는 수험자의 한글 파일에 차트를 붙여넣을 경우 + 3. 정상적으로 XML파일에 텍스트 속성이 적용 + +- 이로 미루어 볼 때, 차트객체의 문제일 것으로 판단되지만 해결 방안은 없음 + \ No newline at end of file diff --git a/회차별채점자료/2504/excel_채점기준표/DIW_2504A.xlsx b/회차별채점자료/2504/excel_채점기준표/DIW_2504A.xlsx index 871f8dd..a02f5c7 100644 Binary files a/회차별채점자료/2504/excel_채점기준표/DIW_2504A.xlsx and b/회차별채점자료/2504/excel_채점기준표/DIW_2504A.xlsx differ