diff --git a/00_DIC_2505B_TEST.xlsx b/00_DIC_2505B_TEST.xlsx new file mode 100644 index 0000000..1b0f55a Binary files /dev/null and b/00_DIC_2505B_TEST.xlsx differ diff --git a/250529_DIC_2505C_채점결과.xlsx b/250529_DIC_2505C_채점결과.xlsx index 2e10673..300db88 100644 Binary files a/250529_DIC_2505C_채점결과.xlsx and b/250529_DIC_2505C_채점결과.xlsx differ diff --git a/DIC_2505A.json b/DIC_2505A.json index 3c08431..dc79026 100644 --- a/DIC_2505A.json +++ b/DIC_2505A.json @@ -181,6 +181,7 @@ } }, "2": { + "desc": "videoStartTime 항목은 동영상파일>자막>시작시간 문항의 정답을 작성", "videoStartTime": 170, "openingStartTime": 0, "1": { @@ -193,7 +194,7 @@ "이미지2.jpg" ], "point": 4, - "desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5이하인 경우는 제외한다." + "desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다." }, "2": { "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed", @@ -205,7 +206,7 @@ "desc": "100당 1배속 / 130 = 1.3배속" }, "3": { - "ele": "//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']", "type": "startEnd", "media": "동영상.mp4", "value": { @@ -216,19 +217,20 @@ "desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다." }, "4": { - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]//CRFilter[@ID='168'][@VID100='0.80000001'][@VID102='10']", - "type": "", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter", + "type": "effect", + "media": "동영상.mp4", "value": { "ID": "168", - "VID100": "0.80000001", - "VID102": "10" + "VID102": "10", + "VID100": "0.80000001" }, "point": 3, - "desc": "/CROASTERP/CRTrackArr/CRVideoTrackArr/CRTrackList[1]/CRTrackClip[1]/CRFilterArr/CRFilter 요소의 속성값 확인" + "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요" }, "5": { - "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", - "ele2": "//CRCUnitArr[@Name='{search}']/@Name", + "ele": "//CRCUnitArr[@Name='{search}']/@Name", + "ele2": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", "type": "video", "value": "모래 촉감 놀이", "search": "모래 촉감 놀이", @@ -270,19 +272,17 @@ "point": 2 }, "10": { - "ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", - "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)", + "ele": "{search}", "search": "모래 촉감 놀이", - "type": "video", + "type": "videoStartTime", "value": 170, "point": 2 }, "11": { - "ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", - "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length", + "ele": "{search}", "search": "모래 촉감 놀이", - "type": "video", - "value": "150", + "type": "videoLength", + "value": 150, "point": 2 }, "12": { @@ -438,7 +438,7 @@ }, "28": { "ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", - "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)", + "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={textClipIndex}]/preceding-sibling::CRTrackClip/@Length)", "search": "자연 놀이터 (Nature Playground)", "type": "opening", "value": 0, @@ -446,7 +446,7 @@ }, "29": { "ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", - "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length", + "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={textClipIndex}]/@Length", "search": "자연 놀이터 (Nature Playground)", "type": "opening", "value": "120", diff --git a/DIC_2505B.json b/DIC_2505B.json index 7c62061..cf1e9e2 100644 --- a/DIC_2505B.json +++ b/DIC_2505B.json @@ -181,40 +181,52 @@ } }, "2": { + "desc": "videoStartTime 항목은 동영상파일>자막>시작시간 문항의 정답을 작성", "videoStartTime": 170, "openingStartTime": 0, "1": { - "ele": "//CRClipArr/CRClip[position() = //CRTrackList[1]/CRTrackClip/@ClipIndex]/@Path", - "type": "array", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex", + "type": "mediaOrder", "value": [ "동영상.mp4", "이미지2.jpg", "이미지3.jpg", "이미지1.jpg" ], - "point": 4 + "point": 4, + "desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다." }, "2": { - "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1][@Speed='120']", - "point": 2 + "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed", + "type": "oneAnswer", + "value": { + "speed": "120" + }, + "point": 2, + "desc": "100당 1배속 / 130 = 1.3배속" }, "3": { - "ele": "count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)", - "type": "startend", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']", + "type": "startEnd", + "media": "동영상.mp4", + "value": { "start": "0", - "end": "380", - "point": 2 + "end": "360" + }, + "point": 2, + "desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다." }, "4": { - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]//CRFilter[@ID='70'][@VID100='30'][@VID101='20']", - "type": "", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter", + "type": "effect", + "media": "동영상.mp4", "value": { "ID": "70", "VID100": "30", "VID101": "20" }, "point": 3, - "desc": "/CROASTERP/CRTrackArr/CRVideoTrackArr/CRTrackList[1]/CRTrackClip[1]/CRFilterArr/CRFilter 요소의 속성값 확인" + "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요" }, "5": { "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", @@ -260,19 +272,17 @@ "point": 2 }, "10": { - "ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", - "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)", + "ele": "{search}", "search": "청량하고 시원한 폭포", - "type": "video", + "type": "videoStartTime", "value": 170, "point": 2 }, "11": { - "ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", - "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length", + "ele": "{search}", "search": "청량하고 시원한 폭포", - "type": "video", - "value": "150", + "type": "videoLength", + "value": 150, "point": 2 }, "12": { @@ -427,19 +437,18 @@ "point": 3 }, "28": { - "ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", - "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)", + "ele":"{search}", "search": "전통 공원 (Traditional Park)", - "type": "opening", + "type": "openingStartTime", "value": 0, - "point": 2 + "point": 2, + "desc": "오프닝자막의 시작시간 value 속성만 수정" }, "29": { - "ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", - "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length", + "ele":"{search}", "search": "전통 공원 (Traditional Park)", - "type": "opening", - "value": "120", + "type": "openingLength", + "value": 120, "point": 2 }, "30": { diff --git a/DIC_2505C.json b/DIC_2505C.json index ea6a3f4..7bf210f 100644 --- a/DIC_2505C.json +++ b/DIC_2505C.json @@ -186,37 +186,48 @@ "videoStartTime": 160, "openingStartTime": 0, "1": { - "ele": "//CRClipArr/CRClip[position() = //CRTrackList[1]/CRTrackClip/@ClipIndex]/@Path", - "type": "array", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex", + "type": "mediaOrder", "value": [ "동영상.mp4", "이미지2.jpg", "이미지1.jpg", "이미지3.jpg" ], - "point": 4 + "point": 4, + "desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다." }, "2": { - "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1][@Speed='130']", - "point": 2 + "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed", + "type": "oneAnswer", + "value": { + "speed": "130" + }, + "point": 2, + "desc": "100당 1배속 / 130 = 1.3배속" }, "3": { - "ele": "count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)", - "type": "startend", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']", + "type": "startEnd", + "media": "동영상.mp4", + "value": { "start": "0", - "end": "350", - "point": 2 + "end": "350" + }, + "point": 2, + "desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다." }, "4": { - "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]//CRFilter[@ID='56'][@VID100='45'][@VID101='60']", - "type": "", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter", + "type": "effect", + "media": "동영상.mp4", "value": { "ID": "56", "VID100": "45", "VID101": "60" }, "point": 3, - "desc": "/CROASTERP/CRTrackArr/CRVideoTrackArr/CRTrackList[1]/CRTrackClip[1]/CRFilterArr/CRFilter 요소의 속성값 확인" + "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요" }, "5": { "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", @@ -262,19 +273,17 @@ "point": 2 }, "10": { - "ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", - "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)", + "ele": "{search}", "search": "연못의 연잎들", - "type": "video", - "value": 160, + "type": "videoStartTime", + "value": 170, "point": 2 }, "11": { - "ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", - "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length", + "ele": "{search}", "search": "연못의 연잎들", - "type": "video", - "value": "120", + "type": "videoLength", + "value": 120, "point": 2 }, "12": { @@ -427,19 +436,17 @@ "point": 3 }, "28": { - "ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", - "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)", + "ele":"{search}", "search": "초록빛 이파리들 Green leaves", - "type": "opening", + "type": "openingStartTime", "value": 0, "point": 2 }, "29": { - "ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", - "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length", + "ele":"{search}", "search": "초록빛 이파리들 Green leaves", - "type": "opening", - "value": "120", + "type": "openingLength", + "value": 120, "point": 2 }, "30": { diff --git a/psdExport_2.js b/psdExport_2.js index 88708db..8b05282 100644 --- a/psdExport_2.js +++ b/psdExport_2.js @@ -15,8 +15,8 @@ const examRound = '2505'; const dic_or_dpi = 'DIC' // const dic_or_dpi = 'DPI' const examTypes = [ - 'A', - // 'B', + // 'A', + 'B', // 'C', // 'D' ]; @@ -172,7 +172,6 @@ outputExcelFiles.forEach((outputFile, index) => { // scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐 // 채점 결과를 scoringResultList 배열에 저장 function getGmepScore(gmepData, scoringJson, index) { - function compareAndScore(userAnswer, rightAnswer, point, key, scoringResult) { let score = 0; @@ -192,9 +191,6 @@ function getGmepScore(gmepData, scoringJson, index) { scoringResult[key] = score; return score; } - function getMediaOrderbyClipIndex(gmepXmlDoc, clipIndex) { - - } const gmepXmlDoc = gmepData; const scoringResult = {}; @@ -207,6 +203,116 @@ function getGmepScore(gmepData, scoringJson, index) { // 채점기준표 문항별 분류 for (const key in scoringData) { + function getClipIndexByMediaPath(mediaName) { + // CRClipArr/CRClip 요소의 Path속성 리스트를 구함 + // 모션 클립 이미지도 고려해 처리 + 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; + } + + // 자막 텍스트로 자막클립인덱스 반환 + function getClipIndexByText(text) { + const crOwneUnits = xpath.select(`//CROwneUnitArr/CROwneUnit`, gmepXmlDoc); + + let subtitleClipIndex = null; + // 자막 텍스트와 일치하는 요소의 인덱스를 반환 + for (let i = 0; i < crOwneUnits.length; i++) { + const crcUnitArr = xpath.select1('.//CRCUnitArr', crOwneUnits[i]); + if (crcUnitArr && crcUnitArr.getAttribute('Name') === text) { + subtitleClipIndex = i; + break; + } + } + console.log('🟢 자막 텍스트로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); + return subtitleClipIndex; + } + + function getClipIndexByOrder(order) { + // 자막의 갯수가 2개 이상 (오프닝과 동영상 자막이 있을 경우) + // 앞은 오프닝 뒤는 동영상 자막으로 판단 + // crTrackClips[0] : 오프닝 자막 + // crTrackClips[1] : 동영상 자막 + const crOwneUnits = xpath.select(`//CROwneUnitArr/CROwneUnit`, gmepXmlDoc); + const crTrackClips = xpath.select("//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@Type='0') and not(@ClipIndex='-1')]", gmepXmlDoc); + let subtitleClipIndex = null; + if (subtitleClipIndex === null && crOwneUnits.length >= 2) { + // if (crOwneUnits.length >= 2) { + for (let i = 0; i < crTrackClips.length; i++) { + if ((order - 1) === i) { + subtitleClipIndex = parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10); + break; + } + } + } + console.log('🟡 자막 순서로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); + return subtitleClipIndex; + } + + // 자막들의 시작 시간을 가지는 리스트에서 + // 구하고자 하는 영상의 시작시간(startTime)과 일치하는 시간이 있다면 인덱스를 가져와 + // 자막의 인덱스를 구함 + function getCilpIndexByStartTime(startTime) { + const crTrackClips = xpath.select("//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip", gmepXmlDoc); + const subtitleStartTimeList = getSubtitleStartTime(); + const startTimeIndex = subtitleStartTimeList.findIndex(value => value === startTime); + + let subtitleClipIndex = null; + for (let i = 0; i < crTrackClips.length; i++) { + if (parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10) == -1) { + continue; + } + const clipIndex = parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10); + if (startTimeIndex === i) { + subtitleClipIndex = clipIndex; + break; + } + } + console.log('🟠 자막 시작시간으로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex); + return subtitleClipIndex; + } + + // 영상내 존재하는 자막과 자막사이 공백의 시작시간 리스트를 구함 + function getSubtitleStartTime() { + const trackClips = xpath.select(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip`, gmepXmlDoc); + + let cumulativeLengths = []; + let total = 0; + + for (let i = 0; i < trackClips.length; i++) { + const length = parseInt(trackClips[i].getAttribute('Length'), 10); + + cumulativeLengths.push(total); + total += length; + } + + console.log("🔵 자막 구간 시작시간 : ", cumulativeLengths); + return cumulativeLengths; + } + + function getCrtrackClipIndex(clipIndex) { + const crTrackClips = xpath.select(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/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; @@ -226,10 +332,16 @@ function getGmepScore(gmepData, scoringJson, index) { // xpath 전처리 const trackClipNode = getTrackClipNode(gmepXmlDoc, type, videoStartTime, openingStartTime); const subtitleIndex = trackClipNode ? parseInt(trackClipNode.getAttribute('ClipIndex'), 10) + 1 : null; - const clipIndex = getClipIndexBySubtitle(gmepXmlDoc, search); - // const subtitleOrder = type === 'video' ? 2 : type === 'opening' ? 1 : null; - // 2503회 문제오류 처리를 위한 임시 변경 - const subtitleOrder = type === 'video' ? 2 : type === 'opening' ? 1 : null; + const textClipIndex = getTextClipIndex(gmepXmlDoc, search); + const typeToOrderMap = { + opening: 1, + openingStartTime: 1, + openingLength: 1, + video: 2, + videoStartTime: 2, + videoLength: 2, + }; + const subtitleOrder = typeToOrderMap[type] ?? null; const startTime = type === 'video' ? videoStartTime : type === 'opening' ? openingStartTime : null; let xpathList = [ele, ele2, ele3, existEle]; @@ -237,17 +349,12 @@ function getGmepScore(gmepData, scoringJson, index) { .replace(/{subtitleIndex}/g, subtitleIndex) .replace(/{subtitleOrder}/g, subtitleOrder) .replace(/{startTime}/g, startTime) - .replace(/{clipIndex}/g, clipIndex) + .replace(/{textClipIndex}/g, textClipIndex) .replace(/{image}/g, image) : e ); [ele, ele2, ele3, existEle] = xpathList; - // [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{subtitleIndex}/g, subtitleIndex)); - // [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{subtitleOrder}/g, subtitleOrder)); - // [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{startTime}/g, startTime)); - // [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{clipIndex}/g, clipIndex)); - // search 값이 undefined 아니면 ele의 {search}부분을 search로 치환 /** * JSON파일 곰믹스 5번문항/22번 문항 @@ -260,11 +367,9 @@ function getGmepScore(gmepData, scoringJson, index) { let result = findSimilarString(gmepXmlDoc, search, 0.8); if (result !== null) { result = result.replace(/"/g, "'"); - [ele, ele2, ele3, existEle] - = [ele, ele2, ele3, existEle].map(e => e?.replace(/{search}/g, result)); + [ele, ele2, ele3, existEle] = [ele, ele2, ele3, existEle].map(e => e?.replace(/{search}/g, result)); } else { - [ele, ele2, ele3] - = [ele, ele2, ele3].map(e => e?.includes('{search}') ? null : e); + [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.includes('{search}') ? null : e); } } @@ -305,20 +410,24 @@ function getGmepScore(gmepData, scoringJson, index) { // 미디어의 인덱스 순서 const clipIndexOrder = xpath.select(ele, gmepXmlDoc); + clipIndexOrder.forEach((clipIndex) => { CRClipIndex = parseInt(clipIndex.value, 10) + 1; // XPath는 1-based index를 사용 // 인덱스 순서에 따른 CRClip 요소의 Path를 찾기 const mediaPath = xpath.select1(`//CRClipArr/CRClip[${CRClipIndex}]/@Path`, gmepXmlDoc); + // 만약 CRClip 요소가 motion clip인 경우 CRCUnitArr의 Path를 찾기 if (mediaPath == null) { const motionClipPath = xpath.select1(`//CRClipArr/CRClip[${CRClipIndex}]/CRCUnitArr/@Path`, gmepXmlDoc); if (motionClipPath !== null) { - mediaOrderList.push(motionClipPath.value); + const fileName = path.basename(motionClipPath.value); + mediaOrderList.push(fileName); } } else if (mediaPath != null) { - mediaOrderList.push(mediaPath.value); + const fileName = path.basename(mediaPath.value); + mediaOrderList.push(fileName); } }); const userAnswer = mediaOrderList; @@ -326,38 +435,99 @@ function getGmepScore(gmepData, scoringJson, index) { } else if (type == "startEnd") { - // CRClipArr/CRClip 요소의 Path속성 리스트를 구함 - // 모션 클립 이미지도 고려해 처리 - const mediaPathList = xpath.select("//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", gmepXmlDoc); + const videoClipIndex = getClipIndexByMediaPath(media); - // "동영상.mp4"의 clipIndex를 구함 - let clipIndex = mediaPathList.findIndex(mediaPath => mediaPath.value === media); - // clipIndex가 -1이면 해당 미디어가 존재하지 않는 것 - if (clipIndex === -1) { + // 해당 미디어가 없을경우 clipIndex값 -1 + if (videoClipIndex == -1) { scoringResult[key] = 0; continue; } - else { // //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='동영상.mp4'] 요소를 찾음 - const trackClipNode = xpath.select1(`//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='${clipIndex}']`, gmepXmlDoc); - if (!trackClipNode) { + const CRTrackClipNode = xpath.select1(ele, gmepXmlDoc); + if (!CRTrackClipNode) { scoringResult[key] = 0; continue; } else { // CRTrackClip 요소의 Pos(시작시간)과 Length(재생길이)를 구함 - const pos = xpath.select1('@Pos', trackClipNode); - const length = xpath.select1('@Length', trackClipNode); + 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); } } + } + else if (type == "effect") { + const videoClipIndex = getClipIndexByMediaPath(media); + if (videoClipIndex == -1) { + scoringResult[key] = 0; + continue; + } + else { + //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter + const CRFilterNode = xpath.select1(ele, gmepXmlDoc); + if (!CRFilterNode) { + scoringResult[key] = 0; + continue; + } + else { + const userAnswer = {} + const attributes = CRFilterNode.attributes; - + // rightAnswer의 key값을 순회하면서 + // CRFilterNode의 속성명과 일치하는 값을 userAnswer에 저장 + // for (const [keyName, expectedValue] of Object.keys(rightAnswer)) { + for (const keyName of Object.keys(rightAnswer)) { + const attr = attributes.getNamedItem(keyName); + userAnswer[keyName] = attr ? attr.value : null; + } + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + } + } } + else if (type === 'openingStartTime' || type === 'openingLength' + || type === 'videoStartTime' || type === 'videoLength') { + // 자막의 정보를 이용해 CROwneUnit의 인덱스를 구함 + // 1. 텍스트 + // 2. 순서 + // 3. 시작시간 + const indexByText = getClipIndexByText(ele); + const indexByOrder = getClipIndexByOrder(subtitleOrder); + + if (type.includes('opening')) time = openingStartTime; + else if (type.includes('video')) time = videoStartTime; + else time = null; + const indexByStartTime = getCilpIndexByStartTime(time); + + // 1, 2, 3순으로 자막을 찾음 + const index = indexByText ?? indexByOrder ?? indexByStartTime; + if (index != null) { + // 자막 시작시간 + if (type.includes('StartTime')) { + const crtrackClipIndex = getCrtrackClipIndex(index) + const startTimeList = getSubtitleStartTime(); + const startTime = startTimeList[crtrackClipIndex]; + userAnswer = startTime; + } + + // 자막 길이 + else if (type.includes('Length')) { + const crtrackClipIndex = getCrtrackClipIndex(index) + 1 // XML 1-based index + const clipLength = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[${crtrackClipIndex}]/@Length`, gmepXmlDoc); + + userAnswer = parseInt(clipLength.value, 10); + } + } + else { + userAnswer = null; + } + + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + } + else if (type == "color") { const result = xpath.select(ele, gmepXmlDoc); @@ -468,7 +638,7 @@ function getGmepScore(gmepData, scoringJson, index) { // 찾으려는 자막이 존재하지 않는 경우 // (2-28) 문항의 경우 오프닝 자막이 없어도 xpath구문의 sum함수 결과값이 0이 반환되는것을 방지 - if (trackClipNode === undefined && clipIndex === null) { + if (trackClipNode === undefined && textClipIndex === null) { scoringResult[key] = 0; continue; } @@ -703,7 +873,7 @@ function getTrackClipNode(xmlDoc, type, videoStartTime, openingStartTime) { * 1. 자막 텍스트의 유사도를 판별 * 2. 자막텍스트와 일치하는 자막요소(CROWneUnit)의 순서를 구함 */ -function getClipIndexBySubtitle(xmlDoc, search) { +function getTextClipIndex(xmlDoc, search) { // 1. search값이 일치하지 않는 경우 : count가 0이 되어 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [오류] // 2. search값이 일치하는 경우 // 1) search값이 CROwneUnit[1]이면 : preceding-sibling::CROwneUnit이 없어서 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [정상] diff --git a/z.xbook b/z.xbook index cf45aef..80492a9 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":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='1']"},{"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":"//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":"//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[1]/@Length"},{"kind":2,"language":"xpath","value":"//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path"}] \ No newline at end of file