곰믹스 채점 기준 수정버전

This commit is contained in:
2025-06-18 17:58:12 +09:00
parent 1ec36b3e85
commit 425e9876fe
3 changed files with 156 additions and 290 deletions

Binary file not shown.

View File

@@ -181,8 +181,6 @@
} }
}, },
"2": { "2": {
"videoStartTime": 170,
"openingStartTime": 0,
"1": { "1": {
"ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex",
"type": "mediaOrder", "type": "mediaOrder",
@@ -378,63 +376,60 @@
"desc": "오버랩일 경우 Type속성값 16으로 변경" "desc": "오버랩일 경우 Type속성값 16으로 변경"
}, },
"22": { "22": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", "ele": "//CROwneUnit[{index}]/CRCUnitArr/@Name",
"ele2": "//CRCUnitArr[@Name='{search}']/@Name",
"type": "opening",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "video.Text",
"value": "전통 공원 (Traditional Park)", "value": "전통 공원 (Traditional Park)",
"point": 3 "point": 3
}, },
"23": { "23": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID102", "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID102",
"ele2": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit/@VID102",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "opening", "type": "video.Text",
"value": "궁서체", "value": "궁서체",
"point": 2 "point": 2
}, },
"24": { "24": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID101", "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID101",
"ele2": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit/@VID101",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "opening", "type": "video.Text",
"value": "140", "value": "140",
"point": 2 "point": 2
}, },
"25": { "25": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4']/@VID100", "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4']/@VID100",
"ele2": "//CRCUnitArr[@Name='{search}']//GCUnitPool/GCUnit[@Type='4']/@VID100",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "opening", "type": "video.Text.Color",
"value": "-14592003", "value": "fd5721",
"point": 2 "point": 2,
"desc": "컬러값은 RGB로 입력한다, [대소문자, #]허용 (#FFFFFF, ffffff 두 값 모두 허용)"
}, },
"26": { "26": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='2']/@*[name()='VID100' or name()='VID101']", "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='2']",
"ele2": "//CRCUnitArr[@Name='{search}']//GCUnitPool/GCUnit[@Type='2']/@*[name()='VID100' or name()='VID101']",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "opening", "type": "video.Text.Outline",
"value": [ "value": {
"0.30000001", "width": "30",
"-3868161" "color": "fff9c4"
], },
"point": 2 "point": 2,
"desc": "두께는 XML에서는 소수점으로 표기되지만, 프로그램 내부적으로 변환하여 사용하므로 현재 파일에서는 정수로 작성"
}, },
"27": { "27": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@*[name()='VID505' or name()='VID507']", "ele": "//CROwneUnit[{index}]/CRCUnitArr",
"ele2": "//CRCUnitArr[@Name='{search}']/@*[name()='VID505' or name()='VID507']",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "opening", "type": "opening.Text.FadeInEffect",
"value": [ "value": {
"4", "ID": "4",
"2" "PlayTime": "2"
], },
"point": 3 "point": 3,
"desc": "오프닝자막의 나타나기 효과를 확인하는 문항. id속성은 VID505, playtime속성은 VID507으로 XML 내부에 표기되어 있다."
}, },
"28": { "28": {
"ele": "{search}", "ele": "{search}",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "openingStartTime", "type": "opening.StartTime",
"value": 0, "value": 0,
"point": 2, "point": 2,
"desc": "오프닝자막의 시작시간 value 속성만 수정" "desc": "오프닝자막의 시작시간 value 속성만 수정"
@@ -442,21 +437,34 @@
"29": { "29": {
"ele": "{search}", "ele": "{search}",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "openingLength", "type": "opening.Length",
"value": 120, "value": 120,
"point": 2 "point": 2
}, },
"30": { "30": {
"ele": "//CRTrackList[@Name='오디오1'][@Count>='1']/CRTrackClip[1][not(@ClipIndex='-1')]", "ele": "",
"type": "audio.StartTime",
"media": "음악.mp3",
"value": 0,
"point": 2 "point": 2
}, },
"31": { "31": {
"ele": "//CRTrackArr/CRAudioTrackArr/CRTrackList[@Name='오디오1']/CRTrackClip[@Length='810']", "ele": "//CRTrackList[@Name='오디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']",
"type": "audio.EndTime",
"media": "음악.mp3",
"value": 810,
"point": 2 "point": 2
}, },
"32": { "32": {
"ele": "//CRTrackArr/CRAudioTrackArr/CRTrackList[@Name='오디오1']//CRFilter[@Type='2'][@ID='1'][@VID8='90']", "ele": "//CRTrackList[@Name='오디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter",
"point": 2 "type": "audio.Effect",
"media": "음악.mp3",
"value": {
"ID": "1",
"PlayTime": "90"
},
"point": 2,
"desc": "ID속성-페이드인[0]/페이드아웃[1]"
}, },
"33": { "33": {
"ele": "none", "ele": "none",

View File

@@ -182,7 +182,7 @@ function getGmepScore(gmepData, scoringJson, index) {
tolerance = 0, // 숫자 비교 시 허용 오차 tolerance = 0, // 숫자 비교 시 허용 오차
type = 'auto', // 'auto' | 'number[]' | 'string[]' | 'object' | 'primitive' type = 'auto', // 'auto' | 'number[]' | 'string[]' | 'object' | 'primitive'
} = options; } = options;
if (type === 'force-correct') { if (type === 'force-correct') {
isEqual = true; isEqual = true;
} }
@@ -196,7 +196,7 @@ function getGmepScore(gmepData, scoringJson, index) {
} }
} else if (type === 'object' || (type === 'auto' && typeof right === 'object' && typeof user === 'object')) { } else if (type === 'object' || (type === 'auto' && typeof right === 'object' && typeof user === 'object')) {
isEqual = JSON.stringify(user) === JSON.stringify(right); isEqual = JSON.stringify(user) === JSON.stringify(right);
} else { } else {
isEqual = user == right; // primitive 비교 isEqual = user == right; // primitive 비교
} }
@@ -238,12 +238,15 @@ function getGmepScore(gmepData, scoringJson, index) {
const scoringData = scoringJson[index]; const scoringData = scoringJson[index];
// console.log(scoringData); // console.log(scoringData);
// 동영상 시작시간, 오프닝 시작시간 상수값 저장
const videoStartTime = scoringData['10'].value;
const openingStartTime = scoringData['28'].value;
let totalScore = 0; let totalScore = 0;
// 채점기준표 문항별 분류 // 채점기준표 문항별 분류
for (const key in scoringData) { for (const key in scoringData) {
function getClipIndexByMediaPath(mediaName) { function getCRClipIndex(mediaName) {
// CRClipArr/CRClip 요소의 Path속성 리스트를 구함 // CRClipArr/CRClip 요소의 Path속성 리스트를 구함
// 모션 클립 이미지도 고려해 처리 // 모션 클립 이미지도 고려해 처리
const mediaPathList = xpath.select("//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", gmepXmlDoc); const mediaPathList = xpath.select("//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", gmepXmlDoc);
@@ -297,7 +300,7 @@ function getGmepScore(gmepData, scoringJson, index) {
// 자막의 인덱스를 구함 // 자막의 인덱스를 구함
function getCilpIndexByStartTime(startTime) { function getCilpIndexByStartTime(startTime) {
const crTrackClips = xpath.select("//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip", gmepXmlDoc); const crTrackClips = xpath.select("//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip", gmepXmlDoc);
const subtitleStartTimeList = getSubtitleStartTime(); const subtitleStartTimeList = getStartTimeList('텍스트');
const startTimeIndex = subtitleStartTimeList.findIndex(value => value === startTime); const startTimeIndex = subtitleStartTimeList.findIndex(value => value === startTime);
let subtitleClipIndex = null; let subtitleClipIndex = null;
@@ -316,8 +319,15 @@ function getGmepScore(gmepData, scoringJson, index) {
} }
// 영상내 존재하는 자막과 자막사이 공백의 시작시간 리스트를 구함 // 영상내 존재하는 자막과 자막사이 공백의 시작시간 리스트를 구함
function getSubtitleStartTime() { function getStartTimeList(type) {
const trackClips = xpath.select(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip`, gmepXmlDoc); let trackClips = null;
if (type === '텍스트') {
trackClips = xpath.select(`//CRTrackList[@Name='${type}' or @Name='비디오2']/CRTrackClip`, gmepXmlDoc);
}
else if (type === '비디오1' || type === '오디오1') {
trackClips = xpath.select(`//CRTrackList[@Name='${type}']/CRTrackClip`, gmepXmlDoc);
}
let cumulativeLengths = []; let cumulativeLengths = [];
let total = 0; let total = 0;
@@ -333,29 +343,30 @@ function getGmepScore(gmepData, scoringJson, index) {
return cumulativeLengths; return cumulativeLengths;
} }
function getTextTrackClipIndex(clipIndex) { function getCRTrackClipIndex(crclipIndex, type) {
const crTrackClips = xpath.select(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip`, gmepXmlDoc); let xpathQuery;
if (type === '텍스트') {
xpathQuery = `//CRTrackList[@Name='${type}' or @Name='비디오2']/CRTrackClip`;
}
else if (type === '비디오1' || type === '오디오1') {
xpathQuery = `//CRTrackList[@Name='${type}']/CRTrackClip`;
}
else {
throw new Error(`Unknown type: ${type}`);
}
let index = null; const crTrackClips = xpath.select(xpathQuery, gmepXmlDoc);
for (let i = 0; crTrackClips.length; i++) {
if (clipIndex == parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10)) { let index = -1;
// crTrackClips에서 ClipIndex가 crclipIndex와 일치하는 요소의 인덱스를 찾음
// 처음 발견하는 요소의 인덱스를 반환
for (let i = 0; i < crTrackClips.length; i++) {
if (crclipIndex == parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10)) {
index = i; index = i;
break; break;
} }
} }
return index;
}
function getVideoTrackClipIndex(clipIndex) {
const crTrackClips = xpath.select(`//CRTrackList[@Name='비디오1']/CRTrackClip`, gmepXmlDoc);
let index = null;
for (let i = 0; crTrackClips.length; i++) {
if (clipIndex == parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10)) {
index = i;
break;
}
}
return index; return index;
} }
@@ -369,8 +380,9 @@ function getGmepScore(gmepData, scoringJson, index) {
let search = scoringData[key].search; let search = scoringData[key].search;
const media = scoringData[key].media; const media = scoringData[key].media;
const videoStartTime = scoringData.videoStartTime; // const videoStartTime = scoringData.videoStartTime;
const openingStartTime = scoringData.openingStartTime; // const openingStartTime = scoringData.openingStartTime;
const image = scoringData[key].image; const image = scoringData[key].image;
let userAnswer = null; let userAnswer = null;
@@ -474,7 +486,7 @@ function getGmepScore(gmepData, scoringJson, index) {
} }
else if (type == "startEnd") { else if (type == "startEnd") {
const crclipIndex = getClipIndexByMediaPath(media); const crclipIndex = getCRClipIndex(media);
// 해당 미디어가 없을경우 clipIndex값 -1 // 해당 미디어가 없을경우 clipIndex값 -1
if (crclipIndex == -1) { if (crclipIndex == -1) {
userAnswer = null; userAnswer = null;
@@ -499,7 +511,7 @@ function getGmepScore(gmepData, scoringJson, index) {
// 동영상 클립 이펙트 [2-4] // 동영상 클립 이펙트 [2-4]
// 이미지 클립 오버레이 [2-14, 17, 20] // 이미지 클립 오버레이 [2-14, 17, 20]
else if (type == "effect" || type === "imageOverlay") { else if (type == "effect" || type === "imageOverlay") {
const crclipIndex = getClipIndexByMediaPath(media); const crclipIndex = getCRClipIndex(media);
// 해당 미디어가 없을경우 clipIndex값 -1 // 해당 미디어가 없을경우 clipIndex값 -1
if (crclipIndex == -1) { if (crclipIndex == -1) {
userAnswer = null; userAnswer = null;
@@ -510,6 +522,7 @@ function getGmepScore(gmepData, scoringJson, index) {
const crFilter = xpath.select1(xpathExpr, gmepXmlDoc); const crFilter = xpath.select1(xpathExpr, gmepXmlDoc);
if (!crFilter) { if (!crFilter) {
userAnswer = null; userAnswer = null;
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult);
} }
else { else {
userAnswer = {} userAnswer = {}
@@ -529,8 +542,8 @@ function getGmepScore(gmepData, scoringJson, index) {
// 동영상 클립 트랜지션 [2-15, 18, 21] // 동영상 클립 트랜지션 [2-15, 18, 21]
else if (type === 'clipTransition') { else if (type === 'clipTransition') {
const crclipIndex = getClipIndexByMediaPath(media); const crclipIndex = getCRClipIndex(media);
let crtrackClipIndex = getVideoTrackClipIndex(crclipIndex); let crtrackClipIndex = getCRTrackClipIndex(crclipIndex, "비디오1");
if (crtrackClipIndex == -1) { if (crtrackClipIndex == -1) {
userAnswer = null; userAnswer = null;
@@ -603,11 +616,10 @@ function getGmepScore(gmepData, scoringJson, index) {
} }
} }
} }
console.log('🟥🟧🟨🟩🟦🟪⬛')
} }
else if (type == "Mute") { else if (type == "Mute") {
const crclipIndex = getClipIndexByMediaPath(media); const crclipIndex = getCRClipIndex(media);
if (crclipIndex == -1) { if (crclipIndex == -1) {
userAnswer = null; userAnswer = null;
} }
@@ -620,7 +632,7 @@ function getGmepScore(gmepData, scoringJson, index) {
} }
// 이미지 클립 길이 [2-13, 16, 19] // 이미지 클립 길이 [2-13, 16, 19]
else if (type === 'imageLength') { else if (type === 'imageLength') {
const crclipIndex = getClipIndexByMediaPath(media); const crclipIndex = getCRClipIndex(media);
if (crclipIndex == -1) { if (crclipIndex == -1) {
userAnswer = null; userAnswer = null;
} }
@@ -632,6 +644,45 @@ function getGmepScore(gmepData, scoringJson, index) {
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult);
} }
else if (type.includes('audio')) {
const crclipIndex = getCRClipIndex(media);
const crtrackClipIndex = getCRTrackClipIndex(crclipIndex, "오디오1")
const startTimeList = getStartTimeList('오디오1');
if (type.includes('StartTime')) {
const startTime = startTimeList[crtrackClipIndex];
userAnswer = startTime;
}
else if (type.includes('EndTime')) {
const xpathQuery = ele?.replace(/{CRClipIndex}/g, crclipIndex);
const crTrackClip = xpath.select1(xpathQuery, gmepXmlDoc);
if (!crTrackClip) {
userAnswer = null;
} else {
const length = parseInt(crTrackClip.getAttribute('Length'), 10);
userAnswer = length;
}
}
else if (type.includes('Effect')) {
const xpathQuery = ele?.replace(/{CRClipIndex}/g, crclipIndex);
const fadeoutEffect = xpath.select1(xpathQuery, gmepXmlDoc);
if (!fadeoutEffect) {
userAnswer = null;
}
else {
const attributes = fadeoutEffect.attributes;
const id = attributes.getNamedItem('ID').value;
const playtime = attributes.getNamedItem('VID8').value;
userAnswer = {
"ID": id,
"PlayTime": playtime,
};
}
}
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult);
}
else if (type.includes('opening') || type.includes('video')) { else if (type.includes('opening') || type.includes('video')) {
// 자막의 정보를 이용해 CROwneUnit의 인덱스를 구함 // 자막의 정보를 이용해 CROwneUnit의 인덱스를 구함
// 1. 텍스트 // 1. 텍스트
@@ -652,8 +703,8 @@ function getGmepScore(gmepData, scoringJson, index) {
// <CRTrackClip> 요소들의 시작시간을 갖고 있는 startTimeList를 구하고 // <CRTrackClip> 요소들의 시작시간을 갖고 있는 startTimeList를 구하고
// 해당 인덱스의 자막 시작시간을 구함 // 해당 인덱스의 자막 시작시간을 구함
if (type.includes('StartTime')) { if (type.includes('StartTime')) {
const crtrackClipIndex = getTextTrackClipIndex(index) const crtrackClipIndex = getCRTrackClipIndex(index, "텍스트")
const startTimeList = getSubtitleStartTime(); const startTimeList = getStartTimeList('텍스트');
const startTime = startTimeList[crtrackClipIndex]; const startTime = startTimeList[crtrackClipIndex];
userAnswer = startTime; userAnswer = startTime;
@@ -662,14 +713,14 @@ function getGmepScore(gmepData, scoringJson, index) {
// 자막 길이 [2-11] [2-29] // 자막 길이 [2-11] [2-29]
// <CRTrackClip> 요소의 Length 속성을 구함 // <CRTrackClip> 요소의 Length 속성을 구함
else if (type.includes('Length')) { else if (type.includes('Length')) {
const crtrackClipIndex = getTextTrackClipIndex(index) + 1 // XML 1-based index const crtrackClipIndex = getCRTrackClipIndex(index, "텍스트") + 1 // XML 1-based index
const clipLength = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[${crtrackClipIndex}]/@Length`, gmepXmlDoc); const clipLength = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[${crtrackClipIndex}]/@Length`, gmepXmlDoc);
userAnswer = parseInt(clipLength.value, 10); userAnswer = parseInt(clipLength.value, 10);
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult);
} }
// 자막 텍스트(글자, 폰트, 크기, 색상) [2-5, 6, 7, 8] [2-22, 23, 24, 25] // 자막 텍스트(글자, 폰트, 크기, 색상) [2-5, 6, 7, 8] [2-22, 23, 24, 25]
else if (type.includes('Text') || type.includes('Color')) { else if (type.includes('Text')) {
const xmlIndex = index + 1 // XML 1-based index const xmlIndex = index + 1 // XML 1-based index
const subtitleXpath = ele?.replace(/{index}/g, xmlIndex); const subtitleXpath = ele?.replace(/{index}/g, xmlIndex);
const subtitleResult = xpath.select1(subtitleXpath, gmepXmlDoc); const subtitleResult = xpath.select1(subtitleXpath, gmepXmlDoc);
@@ -679,10 +730,31 @@ function getGmepScore(gmepData, scoringJson, index) {
const hex = convertColorToHex(subtitleResult.value); const hex = convertColorToHex(subtitleResult.value);
userAnswer = hex; userAnswer = hex;
} }
else if (type.includes('Outline')) {
const attributes = subtitleResult.attributes;
const width = Math.round(parseFloat(attributes.getNamedItem('VID100').value * 100)).toString();
const color = convertColorToHex(attributes.getNamedItem('VID101').value);
userAnswer = {
"width": width,
"color": color,
};
}
else if (type.includes('FadeInEffect')) {
const attributes = subtitleResult.attributes;
const id = attributes.getNamedItem('VID505').value;
const playtime = attributes.getNamedItem('VID507').value;
userAnswer = {
"ID": id,
"PlayTime": playtime,
};
}
else { else {
userAnswer = subtitleResult.value; userAnswer = subtitleResult.value;
} }
} else { }
else {
userAnswer = null; userAnswer = null;
} }
@@ -699,6 +771,7 @@ function getGmepScore(gmepData, scoringJson, index) {
userAnswer = subtitleResult.map(r => r.value); userAnswer = subtitleResult.map(r => r.value);
const errorRange = 0.1; const errorRange = 0.1;
// userAnswer가 정수형 배열인 경우 type을 'number[]'로 설정
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, { totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, {
tolerance: errorRange, tolerance: errorRange,
type: 'number[]', type: 'number[]',
@@ -710,222 +783,7 @@ function getGmepScore(gmepData, scoringJson, index) {
} }
} }
else if (type == "color") {
const result = xpath.select(ele, gmepXmlDoc);
if (result.length == 0) {
scoringResult[key] = 0;
continue;
}
console.log(`value: ${rightAnswer} result: ${result[0].value}`);
// value와 result[0].value를 비교하여 같으면 점수 point 부여
totalScore += result.length > 0 && rightAnswer === result[0].value ? point : 0;
scoringResult[key] = result.length > 0 && rightAnswer === result[0].value ? point : 0;
}
else if (type == "multi") {
try {
const result = xpath.select(ele, gmepXmlDoc);
let isSame = true;
// console.log(`ele: ${ele}, value: ${value} result: ${result}`);
if (result.length == 0) {
console.log('result length 0');
scoringResult[key] = 0;
continue;
}
result.forEach((v, i) => {
// value[i] 값이 정수형인 경우에는 float로 변환하여 비교
// 정수형 v값을 float 형으로 변환하고 소수점 3자리까지 버림
let temp = v.value;
let answer = rightAnswer[i];
if (Number.isFinite(rightAnswer[i]) && !Number.isInteger(rightAnswer[i])) {
temp = parseFloat(v.value);
answer = parseFloat(rightAnswer[i]);
// 소수점 3자리까지 버림
temp = Math.floor(temp * 1000) / 1000;
}
// answer 문자열 중 : 가 포함되어 있다면 각각 분리하고 그 값의 차이를 구함
if (typeof answer == "string" && answer.indexOf(':') > -1) {
const [answerStart, answerEnd] = answer.split(':').map(Number);
const [tempStart, tempEnd] = temp.split(':').map(Number);
answer = answerEnd - answerStart;
temp = tempEnd - tempStart;
}
console.log(`temp: ${temp} answer: ${answer}`);
if (answer !== temp) {
console.log(`answer !== temp`);
isSame = false;
}
});
totalScore += isSame ? point : 0;
scoringResult[key] = isSame ? point : 0;
} catch (e) {
console.log('err :', e);
scoringResult[key] = 0;
}
}
else if (type == "searchIndex") {
// let existEle = scoringData[key].existEle;
// XPath를 사용하여 ELE 요소가 존재하는지 확인
const crcUnitArrNode = xpath.select1(existEle, gmepXmlDoc);
if (crcUnitArrNode) {
// ELE 요소가 몇번째 요소인지 찾고 필요한 요소 확인
const unitOrderNode = xpath.select1(ele, gmepXmlDoc);
console.log(`unitOrderNode: ${unitOrderNode}`);
if (unitOrderNode === undefined) {
scoringResult[key] = 0;
continue;
}
if (unitOrderNode.value === rightAnswer) {
console.log(`unit: ${unitOrderNode.value} === ${rightAnswer}`);
scoringResult[key] = point;
totalScore += point;
}
else if (unitOrderNode === rightAnswer) {
console.log(`unitValue: ${unitOrderNode} === ${rightAnswer}`);
scoringResult[key] = point;
totalScore += point;
}
else {
scoringResult[key] = 0;
}
}
else {
console.log(`not found. ${existEle} `);
let result;
if (ele2 !== undefined) {
result = xpath.select1(ele2, gmepXmlDoc);
}
if (result == rightAnswer) {
totalScore += point;
scoringResult[key] = point;
}
else {
scoringResult[key] = 0;
}
}
}
// 문제의 타입이 video(동영상자막) 또는 opening(오프닝자막)일 경우
else if (type == "video" || type == "opening") {
// 찾으려는 자막이 존재하지 않는 경우
// (2-28) 문항의 경우 오프닝 자막이 없어도 xpath구문의 sum함수 결과값이 0이 반환되는것을 방지
if (trackClipNode === undefined && textClipIndex === null) {
scoringResult[key] = 0;
continue;
}
const result = ele ? xpath.select(ele, gmepXmlDoc) : [];
const result2 = ele2 ? xpath.select(ele2, gmepXmlDoc) : [];
const result3 = ele3 ? xpath.select(ele3, gmepXmlDoc) : [];
// 결과값이 배열이 아닌 경우 배열로 변환
// 예시) (2-9)는 xpath를 통해 클립(자막) 시작시간(number숫자자료형)을 반환받으므로 배열로 변환하여 비교
const resultValues = Array.isArray(result) ?
result.map(r => (typeof r === 'object' ? r.value : r)) : [result];
const resultValues2 = Array.isArray(result2) ?
result2.map(r => (typeof r === 'object' ? r.value : r)) : [result2];
const resultValues3 = Array.isArray(result3) ?
result3.map(r => (typeof r === 'object' ? r.value : r)) : [result3];
// 결과값들을 하나의 배열로 합침
const allResults = [...[resultValues], ...[resultValues2], ...[resultValues3]];
// console.log("🚀 ~ allResults:", allResults)
// 정답(rightAnswer)의 값이 단일값이 아닐 경우 값 비교를 위해 배열로 변환
// 예시) (2-11) 자막의 위치 좌표값 비교를 위해 [x, y] 값을 가져오므로 배열로 변환하여 비교
const rightAnswerArray = Array.isArray(rightAnswer) ? rightAnswer : [rightAnswer];
// 결과값이 범위값인 경우 소수점 3자리까지 비교
const formattedResults = allResults.map(result => {
// result의 길이가 1이상인 조건은 result값이 [x, y](좌표값, 두개 이상의 값)인 경우를 말한다
if (Array.isArray(result) && result.length > 1) {
return result.map(r => {
// xml파일에 저장된 곰믹스 좌표값이 소수점 3자리 아래 버리는 형식이므로
// 동일하게 결과값 소수점 3자리 아래 버린 후 반환
const parsedValue = parseFloat(r);
if (parsedValue >= 0 && parsedValue < 1) {
// 소수점 3자리 아래 버림
return (Math.floor(parsedValue * 1000) / 1000).toFixed(3);
}
return r;
});
}
return result;
});
console.log("🚀 ~ formattedResults:", formattedResults)
// 배열 비교 함수
function arraysEqual(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
if (arr2.length === 1) {
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
}
else if (arr2.length > 1) {
for (let i = 0; i < arr1.length; i++) {
// 좌표값 범위 비교
const errorRange = 0.1;
if (Math.abs(arr1[i] - arr2[i]) > errorRange) return false;
}
return true;
}
}
// allResults에 rightAnswerArray와 일치하는 배열이 있는지 확인
const isIncluded = formattedResults.some(arr => arraysEqual(arr, rightAnswerArray));
if (isIncluded) {
console.log("🚀 ~ getGmepScore ~ 정답:", rightAnswerArray);
totalScore += point;
scoringResult[key] = point;
} else {
console.log("🚀 ~ getGmepScore ~ 오답:", rightAnswerArray);
scoringResult[key] = 0;
}
}
else {
try {
console.log('Unknown type:', ele);
let result = ele ? xpath.select(ele, gmepXmlDoc) : null;
let result2 = null;
let isCheck = false;
if (!result || result.length === 0) {
isCheck = true;
}
if (isCheck && ele2) {
result2 = ele2 ? xpath.select(ele2, gmepXmlDoc) : null;
if (!result2 || result2.length == 0) {
scoringResult[key] = 0;
continue;
}
result = result2;
// console.log(`1st isChecked: ${isCheck}, result: ${result}`)
}
// value와 result[0].value를 비교하여 같으면 점수 point 부여
// console.log(`${(value === result[0].value)}, ${result.length > 0 && value === result[0].value} `)
// console.log(`2nd isChecked: ${isCheck}, result: ${result}`)
totalScore += result?.length > 0 ? point : 0;
scoringResult[key] = result?.length > 0 ? point : 0;
} catch (error) {
console.error(`Error processing XPath query for ele: ${ele}`, error);
scoringResult[key] = 0;
}
}
} }
scoringResult['총점'] = totalScore; scoringResult['총점'] = totalScore;
return scoringResult; return scoringResult;