From 1ec36b3e85a083045f6823b526c6753262299229 Mon Sep 17 00:00:00 2001 From: dragdra Date: Tue, 17 Jun 2025 18:01:02 +0900 Subject: [PATCH] =?UTF-8?q?[2-13~21]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=81=B4=EB=A6=BD=EA=B8=B8=EC=9D=B4,=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4,=20=ED=8A=B8=EB=9E=9C?= =?UTF-8?q?=EC=A7=80=EC=85=98=20=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 00_DIC_2505B_TEST.xlsx | Bin 28109 -> 21849 bytes DIC_2505B.json | 148 +++++---- psdExport_2.js | 284 ++++++++++++------ z.xbook | 2 +- .../2505/excel_채점기준표/DIC_2505B.xlsx | Bin 18911 -> 18916 bytes 5 files changed, 269 insertions(+), 165 deletions(-) diff --git a/00_DIC_2505B_TEST.xlsx b/00_DIC_2505B_TEST.xlsx index 1b0f55a30eadfff6f452f034412e12456137745c..fb76c92d67641781615d17fa586e10c8237a5194 100644 GIT binary patch delta 1664 zcmZ8iO>7%Q6!xxZqo7Joy=%u=9I0_-Y(o=gcm1;~|0s#qyNyjn)6x{tlVyq!*oZ)> zA~+ybT#%4R@(D-}pn^a^FEm8Z69NuMfYbv=RDo0^5F)wI0|Ie?_hxKF?ZbS2v+vvQ z&3tcWZ`@SAzp7k&HGTIh{&u%A6$(|Rb}y$7DB-|zci+BuTiMEvkC#(N`knP!s*Ed{ z4BYJ5@%`<0_db4U@2wB415WJg?|>3sNR=AaWSgcxYdZ!sI2g~Var6d-PC6H-E zv9U5Kb4yq|8Rx%>Pm?h#et{~})i?Z~au)3`oo==Q8TvQl%N zwNwJyP6paf33}KUp&kwhxxB~o2G0AMYMP=x6Uv!b*rSLzxRxKor%r@O95cxjq@Rg& zC?Jw0g)Av#Ng)Onk{A%O(ChOCc9|orW)Q{Myfls^M`A$n%7aM+rh!L}6Q>|`$Z%c| zn>_&55#Cd6Y>+tx#B>9W|{Cj5w=cfswIsl5EOPg zPUl;ANRzaYbgM|;kChA@Jw+7hJHe(;D#CZ7@VWkxQoiLBX)^)6m7XcBShz_MD{qty ztew{IS$Pa^v?6@9kiTKiDvGlqBK%e!mRl74Gwv8jJfxxI#F;?GsurK-7*052$h0Hq zIzvWDFfKX9UG}VP;fj-H3OPy|<$(qp`-3wumS(v}|0a#c&QS3V%u0r~QKC1%roim* zaX?|GJdfwxDfCt+U$op*y?U`;9J#C-!+Gn@qwNjm?U5tj9Ojqq%=d@+Bk!RrU+Ag@ z3;U}Ni%&ps_8a|w-v$`H)yVC7D74q1=jx?60f){a?SX z_wTCCTzJ2B{@dCIuPts*AAa%d*>qxp|IJKHOwh$UXXxX_U+bcZiS{uu60DurV#(94|$y?by5 z(ODF2G+N;p8hvb;qfHc@YqY{KG(ix`J@k5K*Hqf-N1xAyef z^S#+=T)X0>@omU_8-)BE4I1`$u_Eq6=KJ93gsM2FintM(Z-l24s^a*H+#~Kprkyyy zvW1#<8~wb(&#?b0R8vrIu!sk@@^Eht^x-ZHX8EF}zXpq#$@L+oIO8dvqt}P>-0^FY z_2|RybS#O-aolxrUnC^mKes00x{OQ$&r%#{4sf`iJiC-e) zUm{N@^m)>dCynxC-N;HN)p|Ug(C5iQo-EE2_uly4dpe=dV?rJi=ZPI*jK25E1dHiY z*<8ouAw?dih%I8gMLeC*mr{g0MVu!#j*&IDUU@p9&tpR#8|R5%Z{uHYPbZW-_nR{0 zDdRlxTQ9*IqbPm#|EHaiRiSNq;SRm{4zZC9FZD^Kz|{LCf4Av}B>l}vc(Kmp`o_py zQ5x$uMrqU`XJmV5WYI!Od=k!6hsiWyyPV2q7MjkAkXY|nWJYHI?hq$3~>{)p_5F1$|4;Ic1%^t$N z44pZ|Lgm=A@^s+w7KFjLc}`!lF!w^`7-O!W^5-20VJi<##o>P!19Qc8fJ|mOSz|Qu zpggDqb(a^kqm43xUvo>nHp;L%S6KU&r|M%2v4rv29m@(<1$X8p{l=B;jrYf-au}Vz^s7iLidL?7XQt?L-d{t!hW;S0d=wc5&QH&K8 zqnRK=3Jjhq3>`DLmEi&*-M&0sA7jM33Xh;MstI-~a=Ibs`_#DiX!A-_2dGUfmEdFX z;_{8x1tS#&eIpXFOCfgQztfN)0XpfJ?LD0$_yvcXTbRbHMxj;$bcWPQJd}(ePi*i~ zQPN!&Ppe)6R90#`#8O=Q_V!$(2v#b3w5^0n4Fjl3w_Zf^*2`!4UcoR$pU$&N4Fjl3 z4Z}kzQ5|E0V{DgR>CGnqI|dLL$d2J5%b1ue8!Usv&2yE-+ndElI4>KXV~}%Io({x% z)3(7+IE->etLb5@Ruk06R#&ph(}Bp?vB7OPj55YMpH(ATHexci^g|(`RG#^zY@sTN*>O4PCfYyeieB7k&`D+kLlIXVfiWH>(Lie zTf>hH^xoa~CJ!H_OLq^F-S^Sp$RIg$oSr>WCRaZ|A08PdPps1)M^=-+9;Eg?i&qaH J$4?Uz{{ou>cCP>c 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 4c7609120662e5bb5412836a0d3724e2ea025004..d9966dd08a5ec7bb267c81f2c60943aa8307f437 100644 GIT binary patch delta 9380 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 Wb`0VrF9Q1&(kf4bXe;xdo&N_|719g< delta 9412 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