v2 - 검수중 (하이퍼링크 처리구문 추가)

This commit is contained in:
2025-05-19 17:50:18 +09:00
parent 2d307d77a0
commit 8e8abfd9f1
8 changed files with 136 additions and 21 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -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'])",

View File

@@ -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,6 +420,12 @@ class XMLScorer:
# 폰트 속성
elif (category or "") == "FontAttribute":
# 하이퍼링크 처리
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
@@ -412,6 +439,78 @@ class XMLScorer:
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")
# 특수문자 갯수 채점
elif (category or "") == "SpecialChar":
ch1 = criterion.get('char1', None)

View File

@@ -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"}]
[{"kind":2,"language":"xpath","value":"//a:t[text()='클라우드 보안투자']/ancestor::a:r//a:ea/@typeface"}]

12
오류및문제점.md Normal file
View File

@@ -0,0 +1,12 @@
# 오류 및 문제점
## 1. 원인 파악이나 해결이 어려운 경우
### 한글문서(.hwpx)에 수험자가 차트 텍스트 속성을 변경해서 저장했지만 차트 xml파일에 옵션이 적용되지 않는 경우가 있음
1. 정상적으로 텍스트 속성이 적용되는 수험자의 차트를 복사
2. 텍스트 속성이 적용되지 않는 수험자의 한글 파일에 차트를 붙여넣을 경우
3. 정상적으로 XML파일에 텍스트 속성이 적용
- 이로 미루어 볼 때, 차트객체의 문제일 것으로 판단되지만 해결 방안은 없음