diff --git a/00_DIC_2505B_TEST.xlsx b/00_DIC_2505B_TEST.xlsx index 1b0f55a..fb76c92 100644 Binary files a/00_DIC_2505B_TEST.xlsx and b/00_DIC_2505B_TEST.xlsx differ diff --git a/DIC_2505B.json b/DIC_2505B.json index bd26536..67b5995 100644 --- a/DIC_2505B.json +++ b/DIC_2505B.json @@ -115,10 +115,10 @@ "value": "GungsuhChe", "point": 2, "desc": { - "돋움체":"DotumChe", - "궁서체":"GungsuhChe", - "굴림체":"GulimChe", - "휴먼옛체":"YetR" + "돋움체": "DotumChe", + "궁서체": "GungsuhChe", + "굴림체": "GulimChe", + "휴먼옛체": "YetR" } }, "16": { @@ -181,7 +181,6 @@ } }, "2": { - "desc": "videoStartTime 항목은 동영상파일>자막>시작시간 문항의 정답을 작성", "videoStartTime": 170, "openingStartTime": 0, "1": { @@ -206,18 +205,18 @@ "desc": "100당 1배속 / 130 = 1.3배속" }, "3": { - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']", "type": "startEnd", "media": "동영상.mp4", "value": { - "start": "0", + "start": "0", "end": "380" }, "point": 2, "desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다." }, "4": { - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", "type": "effect", "media": "동영상.mp4", "value": { @@ -273,111 +272,110 @@ "search": "청량하고 시원한 폭포", "type": "video.StartTime", "value": 170, - "point": 2 + "point": 2, + "desc": "내부적으로 자막의 시작시간과 길이를 계산" }, "11": { "ele": "", "search": "청량하고 시원한 폭포", "type": "video.Length", "value": 150, - "point": 2 + "point": 2, + "desc": "내부적으로 자막의 시작시간과 길이를 계산" }, "12": { - "existEle": "//CRClip[@Path='동영상.mp4']", - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]/@Mute", - "type": "searchIndex", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Mute", + "type": "Mute", + "media": "동영상.mp4", "value": "1", "point": 2 }, "13": { - "existEle": "//CRClip[@Path='{image}'] | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']", - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]/@Length", - "image": "이미지2.jpg", - "type": "searchIndex", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지2.jpg", "value": "150", "point": 2 }, "14": { - "type": "multi", - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]//CRFilter/@*[name()='ID' or name()='VID102']", - "image": "이미지2.jpg", - "value": [ - "103", - "7" - ], + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지2.jpg", + "value": { + "ID": "103", + "VID102": "7" + }, "point": 2 }, "15": { - "type": "multi", - "ele": "//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)][@Type='2']/@*[name()='ID' or name()='Range' or name()='Type']", - "image": "이미지2.jpg", - "value": [ - "11", - "500:530", - "2" - ], + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지2.jpg", + "value": { + "ID": "11", + "Range": "500:530", + "Type": "2" + }, "point": 2, - "desc": "오버랩일 경우 XPATH구문에서 Type속성값 16으로 변경, 그리고 ClipIndex값은 트랜지션이 끝나는 지점 이미지의 ClipIndex값을 가지게 되어 다음 순서의 이미지로 변경해주어야한다." + "desc": "오버랩일 경우 Type속성값 16으로 변경" }, "16": { - "existEle": "//CRClip[@Path='{image}'] | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']", - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]/@Length", - "image": "이미지3.jpg", - "type": "searchIndex", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지3.jpg", "value": "150", "point": 2 }, "17": { - "type": "multi", - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]//CRFilter/@*[name()='ID' or name()='VID102']", - "image": "이미지3.jpg", - "value": [ - "184", - "30" - ], + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지3.jpg", + "value": { + "ID": "184", + "VID102": "30" + }, "point": 2 }, "18": { - "type": "multi", - "ele": "//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)][@Type='2']/@*[name()='ID' or name()='Range' or name()='Type']", - "image": "이미지3.jpg", - "value": [ - "19", - "650:680", - "2" - ], + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지3.jpg", + "value": { + "ID": "19", + "Range": "650:680", + "Type": "2" + }, "point": 2, - "desc": "오버랩일 경우 XPATH구문에서 Type속성값 16으로 변경, 그리고 ClipIndex값은 트랜지션이 끝나는 지점 이미지의 ClipIndex값을 가지게 되어 다음 순서의 이미지로 변경해주어야한다." + "desc": "오버랩일 경우 Type속성값 16으로 변경" }, "19": { - "existEle": "//CRClip[@Path='{image}'] | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']", - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]/@Length", - "image": "이미지1.jpg", - "type": "searchIndex", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지1.jpg", "value": "180", "point": 2 }, "20": { - "type": "multi", - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]//CRFilter/@*[name()='ID' or name()='VID102']", - "image": "이미지1.jpg", - "value": [ - "67", - "30" - ], + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지1.jpg", + "value": { + "ID": "67", + "VID102": "30" + }, "point": 2 }, "21": { - "type": "multi", - "ele": "//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='{image}']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='{image}']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)][@Type='2']/@*[name()='ID' or name()='Range' or name()='Type']", - "image": "이미지1.jpg", - "value": [ - "10", - "800:860", - "2" - ], + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지1.jpg", + "value": { + "ID": "10", + "Range": "800:860", + "Type": "2" + }, "point": 2, - "desc": "오버랩일 경우 XPATH구문에서 Type속성값 16으로 변경, 그리고 ClipIndex값은 트랜지션이 끝나는 지점 이미지의 ClipIndex값을 가지게 되어 다음 순서의 이미지로 변경해주어야한다." + "desc": "오버랩일 경우 Type속성값 16으로 변경" }, "22": { "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", @@ -434,7 +432,7 @@ "point": 3 }, "28": { - "ele":"{search}", + "ele": "{search}", "search": "전통 공원 (Traditional Park)", "type": "openingStartTime", "value": 0, @@ -442,7 +440,7 @@ "desc": "오프닝자막의 시작시간 value 속성만 수정" }, "29": { - "ele":"{search}", + "ele": "{search}", "search": "전통 공원 (Traditional Park)", "type": "openingLength", "value": 120, diff --git a/psdExport_2.js b/psdExport_2.js index 9db6f7d..f18c993 100644 --- a/psdExport_2.js +++ b/psdExport_2.js @@ -9,6 +9,8 @@ const { DOMParser } = require('xmldom'); const findSimilarString = require('./findSimilarString'); const getGpdpScore = require('./gpdpScoring.js'); const getToday = require('./getToday.js'); +const { userInfo } = require('os'); +const { get } = require('http'); const todayDate = getToday(); const examRound = '2505'; @@ -172,32 +174,39 @@ outputExcelFiles.forEach((outputFile, index) => { // scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐 // 채점 결과를 scoringResultList 배열에 저장 function getGmepScore(gmepData, scoringJson, index) { - function compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, tolerance = 0) { + function compareAndScore(user, right, point, key, scoringResult, options = {}) { let score = 0; + let isEqual = false; - let isEqual; - - if (Array.isArray(rightAnswer) && Array.isArray(userAnswer)) { - // 배열 길이 같아야 비교 가능 - if (rightAnswer.length === userAnswer.length) { - isEqual = rightAnswer.every((val, idx) => Math.abs(val - userAnswer[idx]) <= tolerance); - } else { - isEqual = false; + const { + tolerance = 0, // 숫자 비교 시 허용 오차 + type = 'auto', // 'auto' | 'number[]' | 'string[]' | 'object' | 'primitive' + } = options; + + if (type === 'force-correct') { + isEqual = true; + } + else if (Array.isArray(right) && Array.isArray(user)) { + if (right.length === user.length) { + if (type === 'number[]' || (type === 'auto' && typeof right[0] === 'number')) { + isEqual = right.every((val, idx) => Math.abs(val - user[idx]) <= tolerance); + } else if (type === 'string[]' || (type === 'auto' && typeof right[0] === 'string')) { + isEqual = right.every((val, idx) => val === user[idx]); + } } - } else if (typeof rightAnswer === "object" && typeof userAnswer === "object") { - // 객체일 때는 기존 방식 유지 (원하면 별도 로직 추가 가능) - isEqual = JSON.stringify(userAnswer) === JSON.stringify(rightAnswer); - } else { - isEqual = userAnswer == rightAnswer; + } else if (type === 'object' || (type === 'auto' && typeof right === 'object' && typeof user === 'object')) { + isEqual = JSON.stringify(user) === JSON.stringify(right); + } else { + isEqual = user == right; // primitive 비교 } if (isEqual) { score = point; - console.log('작성답안: ', userAnswer); - console.log('>⭕ 정답: ', rightAnswer); + console.log('작성답안: ', user); + console.log('>⭕ 정답: ', right); } else { - console.log('작성답안: ', userAnswer); - console.log('>❌ 오답: ', rightAnswer); + console.log('작성답안: ', user); + console.log('>❌ 오답: ', right); } scoringResult[key] = score; @@ -240,16 +249,9 @@ function getGmepScore(gmepData, scoringJson, index) { const mediaPathList = xpath.select("//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", gmepXmlDoc); // "동영상.mp4"의 clipIndex를 구함 - const videoClipIndex = mediaPathList.findIndex(mediaPath => mediaPath.value === mediaName); - let xpathList = [ele, ele2]; - xpathList = xpathList.map(e => e ? e - .replace(/{videoClipIndex}/g, videoClipIndex) - : e - ); - [ele, ele2] = xpathList; // clipIndex가 -1이면 해당 미디어가 존재하지 않는 것 - - return videoClipIndex; + const crclipIndex = mediaPathList.findIndex(mediaPath => mediaPath.value === mediaName); + return crclipIndex; } // 자막 텍스트로 자막클립인덱스 반환 @@ -265,7 +267,7 @@ function getGmepScore(gmepData, scoringJson, index) { break; } } - console.log('🟢 자막 텍스트로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); + // console.log('🟢 자막 텍스트로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); return subtitleClipIndex; } @@ -286,7 +288,7 @@ function getGmepScore(gmepData, scoringJson, index) { } } } - console.log('🟡 자막 순서로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); + // console.log('🟡 자막 순서로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); return subtitleClipIndex; } @@ -309,7 +311,7 @@ function getGmepScore(gmepData, scoringJson, index) { break; } } - console.log('🟠 자막 시작시간으로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); + // console.log('🟠 자막 시작시간으로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); return subtitleClipIndex; } @@ -327,11 +329,11 @@ function getGmepScore(gmepData, scoringJson, index) { total += length; } - console.log("🔵 자막 구간 시작시간 : ", cumulativeLengths); + // console.log("🔵 자막 구간 시작시간 : ", cumulativeLengths); return cumulativeLengths; } - function getCrtrackClipIndex(clipIndex) { + function getTextTrackClipIndex(clipIndex) { const crTrackClips = xpath.select(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip`, gmepXmlDoc); let index = null; @@ -344,37 +346,41 @@ function getGmepScore(gmepData, scoringJson, index) { 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; + } + let ele = scoringData[key].ele; let ele2 = scoringData[key].ele2; let ele3 = scoringData[key].ele3; let existEle = scoringData[key].existEle; const rightAnswer = scoringData[key].value; const point = scoringData[key].point; - const type = scoringData[key].type; + const type = scoringData[key].type ? scoringData[key].type : ''; let search = scoringData[key].search; const media = scoringData[key].media; + const videoStartTime = scoringData.videoStartTime; const openingStartTime = scoringData.openingStartTime; - const image = scoringData[key].image; + let userAnswer = null; + console.log(`example number: ${key}`) // xpath 전처리 const trackClipNode = getTrackClipNode(gmepXmlDoc, type, videoStartTime, openingStartTime); const subtitleIndex = trackClipNode ? parseInt(trackClipNode.getAttribute('ClipIndex'), 10) + 1 : null; const textClipIndex = getTextClipIndex(gmepXmlDoc, search); - // const typeToOrderMap = { - // opening: 1, - // openingStartTime: 1, - // openingLength: 1, - // openingText: 1, - // video: 2, - // videoStartTime: 2, - // videoLength: 2, - // videoText: 2, - // }; - // const subtitleOrder = typeToOrderMap[type] ?? null; const subtitleOrder = type.includes('opening') ? 1 : type.includes('video') ? 2 : null; const startTime = type === 'video' ? videoStartTime : type === 'opening' ? openingStartTime : null; @@ -408,10 +414,6 @@ function getGmepScore(gmepData, scoringJson, index) { } } - // console.log("🚀 ~ getGmepScore ~ ele:", ele) - // console.log("🚀 ~ getGmepScore ~ ele2:", ele2) - // console.log("🚀 ~ getGmepScore ~ ele3:", ele3) - // xpath if (ele === 'none') { scoringResult[key] = "확인필요"; @@ -425,7 +427,7 @@ function getGmepScore(gmepData, scoringJson, index) { else if (type == "oneAnswer") { const result = xpath.select1(ele, gmepXmlDoc); - let userAnswer = {}; + // userAnswer = {}; if ("speed" in rightAnswer) { userAnswer = { "speed": result ? result.value : null, @@ -437,8 +439,8 @@ function getGmepScore(gmepData, scoringJson, index) { totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); } - // [3-1] 문항 + // [3-1] 문항 else if (type == "mediaOrder") { // 미디어 순서를 저장할 배열 const mediaOrderList = []; @@ -466,50 +468,52 @@ function getGmepScore(gmepData, scoringJson, index) { } }); const userAnswer = mediaOrderList; - totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, { + type: 'string[]' + }); } else if (type == "startEnd") { - const videoClipIndex = getClipIndexByMediaPath(media); - + const crclipIndex = getClipIndexByMediaPath(media); // 해당 미디어가 없을경우 clipIndex값 -1 - if (videoClipIndex == -1) { - scoringResult[key] = 0; - continue; + if (crclipIndex == -1) { + userAnswer = null; } else { - // //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='동영상.mp4'] 요소를 찾음 - const CRTrackClipNode = xpath.select1(ele, gmepXmlDoc); - if (!CRTrackClipNode) { - scoringResult[key] = 0; - continue; + //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='동영상.mp4'] 요소를 찾음 + const xpathExpr = ele?.replace(/{CRClipIndex}/g, crclipIndex); + const crTrackClip = xpath.select1(xpathExpr, gmepXmlDoc); + if (!crTrackClip) { + userAnswer = null; } else { // CRTrackClip 요소의 Pos(시작시간)과 Length(재생길이)를 구함 - const pos = xpath.select1('@Pos', CRTrackClipNode); - const length = xpath.select1('@Length', CRTrackClipNode); - const userAnswer = { start: pos.value, end: length.value } - - totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + const pos = xpath.select1('@Pos', crTrackClip); + const length = xpath.select1('@Length', crTrackClip); + userAnswer = { start: pos.value, end: length.value } } } + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); } - else if (type == "effect") { - const videoClipIndex = getClipIndexByMediaPath(media); - if (videoClipIndex == -1) { - scoringResult[key] = 0; - continue; + + // 동영상 클립 이펙트 [2-4] + // 이미지 클립 오버레이 [2-14, 17, 20] + else if (type == "effect" || type === "imageOverlay") { + const crclipIndex = getClipIndexByMediaPath(media); + // 해당 미디어가 없을경우 clipIndex값 -1 + if (crclipIndex == -1) { + userAnswer = null; } else { - //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter - const CRFilterNode = xpath.select1(ele, gmepXmlDoc); - if (!CRFilterNode) { - scoringResult[key] = 0; - continue; + //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter + const xpathExpr = ele?.replace(/{CRClipIndex}/g, crclipIndex); + const crFilter = xpath.select1(xpathExpr, gmepXmlDoc); + if (!crFilter) { + userAnswer = null; } else { - const userAnswer = {} - const attributes = CRFilterNode.attributes; + userAnswer = {} + const attributes = crFilter.attributes; // rightAnswer의 key값을 순회하면서 // CRFilterNode의 속성명과 일치하는 값을 userAnswer에 저장 @@ -523,16 +527,112 @@ function getGmepScore(gmepData, scoringJson, index) { } } - // 자막관련 type검사하는 구문을 opening포함 video포함으로 변경 - // 6/16(월)시작 지점 - // 1. JSON파일 10,11번 처럼 변경 - else if (type.includes('opening') || type.includes('video')) { - function toHexColor(value) { + // 동영상 클립 트랜지션 [2-15, 18, 21] + else if (type === 'clipTransition') { + const crclipIndex = getClipIndexByMediaPath(media); + let crtrackClipIndex = getVideoTrackClipIndex(crclipIndex); + if (crtrackClipIndex == -1) { + userAnswer = null; } - // else if (type === 'openingStartTime' || type === 'openingLength' - // || type === 'videoStartTime' || type === 'videoLength') { + else { + // 트랜지션 위치 + // - 앞으로 이동 : Type = '1' + // - 뒤로 이동 : Type = '2' + // - 오버랩 : Type = '16' + // 트랜지션 위치가 오버랩 일 경우 ( Type 속성이 16인 경우 ) + // 적용된 이미지(ClipIndex속성값은) 끝나는 지점의 이미지로 적용된다 + if (rightAnswer['Type'] === '16') { + crtrackClipIndex += 1; + } + const xpathExpr = ele?.replace(/{CRTrackClipIndex}/g, crtrackClipIndex); + const crTransFilter = xpath.select(xpathExpr, gmepXmlDoc); + if (!crTransFilter) { + userAnswer = null; + } + else { + userAnswer = {}; + let isMatched = false; + for (let i = 0; i < crTransFilter.length; i++) { + const crTransFilterNode = crTransFilter[i]; + const attributes = crTransFilterNode.attributes; + + const idAttr = attributes.getNamedItem('ID'); + const typeAttr = attributes.getNamedItem('Type'); + const rangeAttr = attributes.getNamedItem('Range'); + + const userId = idAttr ? idAttr.value : null; + const userType = typeAttr ? typeAttr.value : null; + const userRange = rangeAttr ? rangeAttr.value : null; + + userAnswer = { + ID: userId, + Range: userRange, + Type: userType, + }; + + // ID와 Type은 같고, Range(재생시간 = 종료시간 - 시작시간)도 일치하는지 확인 + if ( + userId === rightAnswer.ID && + userType === rightAnswer.Type && + userRange && + rightAnswer.Range + ) { + const [start1, end1] = userRange.split(':').map(Number); + const [start2, end2] = rightAnswer.Range.split(':').map(Number); + + const userPlayTime = end1 - start1; + const rightPlayTime = end2 - start2; + + if (userPlayTime === rightPlayTime) { + isMatched = true; + break; + } + } + } + // 일치하지 않으면 null 처리 + if (!isMatched) { + // userAnswer = null; + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + } + else { + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, { + type: 'force-correct' + }); + } + } + } + console.log('🟥🟧🟨🟩🟦🟪⬛') + } + + else if (type == "Mute") { + const crclipIndex = getClipIndexByMediaPath(media); + if (crclipIndex == -1) { + userAnswer = null; + } + else { + const xpathExpr = ele?.replace(/{CRClipIndex}/g, crclipIndex); + const muteStatus = xpath.select1(xpathExpr, gmepXmlDoc); + userAnswer = muteStatus.value; + } + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + } + // 이미지 클립 길이 [2-13, 16, 19] + else if (type === 'imageLength') { + const crclipIndex = getClipIndexByMediaPath(media); + if (crclipIndex == -1) { + userAnswer = null; + } + else { + const xpathExpr = ele?.replace(/{CRClipIndex}/g, crclipIndex); + const imageLength = xpath.select1(xpathExpr, gmepXmlDoc); + userAnswer = imageLength.value; + } + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + } + + else if (type.includes('opening') || type.includes('video')) { // 자막의 정보를 이용해 CROwneUnit의 인덱스를 구함 // 1. 텍스트 // 2. 순서 @@ -549,8 +649,10 @@ function getGmepScore(gmepData, scoringJson, index) { const index = indexByText ?? indexByOrder ?? indexByStartTime; if (index != null) { // 자막 시작시간 [2-10] [2-28] + // 요소들의 시작시간을 갖고 있는 startTimeList를 구하고 + // 해당 인덱스의 자막 시작시간을 구함 if (type.includes('StartTime')) { - const crtrackClipIndex = getCrtrackClipIndex(index) + const crtrackClipIndex = getTextTrackClipIndex(index) const startTimeList = getSubtitleStartTime(); const startTime = startTimeList[crtrackClipIndex]; @@ -558,8 +660,9 @@ function getGmepScore(gmepData, scoringJson, index) { totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); } // 자막 길이 [2-11] [2-29] + // 요소의 Length 속성을 구함 else if (type.includes('Length')) { - const crtrackClipIndex = getCrtrackClipIndex(index) + 1 // XML 1-based index + const crtrackClipIndex = getTextTrackClipIndex(index) + 1 // XML 1-based index const clipLength = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[${crtrackClipIndex}]/@Length`, gmepXmlDoc); userAnswer = parseInt(clipLength.value, 10); @@ -596,7 +699,10 @@ function getGmepScore(gmepData, scoringJson, index) { userAnswer = subtitleResult.map(r => r.value); const errorRange = 0.1; - totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, tolerance = errorRange); + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, { + tolerance: errorRange, + type: 'number[]', + }); } } else { diff --git a/z.xbook b/z.xbook index ebe66f0..a6c5450 100644 --- a/z.xbook +++ b/z.xbook @@ -1 +1 @@ -[{"kind":2,"language":"xpath","value":"//Layer[Name[@value='Tracking']]/Effects/Item/Name/@value"},{"kind":2,"language":"xpath","value":"sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[@ClipIndex=0]/preceding-sibling::CRTrackClip/@Length)"},{"kind":2,"language":"xpath","value":"//CROwneUnit/CRCUnitArr[@Name=\"아름다운 꽃 축제 (Happy Flower Festival)\"]/@Name"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='{layer}']]/Effects/Item[EffectData/{option}]/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='{layer}']]/Effects/Item/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value\r\n"},{"kind":2,"language":"xpath","value":"//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)][@Type='2']/@*[name()='ID' or name()='Range' or name()='Type']"},{"kind":2,"language":"xpath","value":"//CROwneUnit[1]/CRCUnitArr/@*[name()='VID600' or name()='VID601']"},{"kind":2,"language":"xpath","value":"//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path"}] \ No newline at end of file +[{"kind":2,"language":"xpath","value":"//Layer[Name[@value='Tracking']]/Effects/Item/Name/@value"},{"kind":2,"language":"xpath","value":"sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[@ClipIndex=0]/preceding-sibling::CRTrackClip/@Length)"},{"kind":2,"language":"xpath","value":"//CROwneUnit/CRCUnitArr[@Name=\"아름다운 꽃 축제 (Happy Flower Festival)\"]/@Name"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='{layer}']]/Effects/Item[EffectData/{option}]/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='3']/@Length"},{"kind":2,"language":"xpath","value":"//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)][@Type='2']/@*[name()='ID' or name()='Range' or name()='Type']"},{"kind":2,"language":"xpath","value":"//CROwneUnit[1]/CRCUnitArr/@*[name()='VID600' or name()='VID601']"},{"kind":2,"language":"xpath","value":"//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path"}] \ No newline at end of file diff --git a/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx b/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx index 4c76091..d9966dd 100644 Binary files a/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx and b/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx differ