diff --git a/00_DIC_2505A_TEST.xlsx b/00_DIC_2505A_TEST.xlsx new file mode 100644 index 0000000..3806715 Binary files /dev/null and b/00_DIC_2505A_TEST.xlsx differ diff --git a/copyFiles.py b/01_copyFiles.py similarity index 95% rename from copyFiles.py rename to 01_copyFiles.py index e33e311..9773dc7 100644 --- a/copyFiles.py +++ b/01_copyFiles.py @@ -39,7 +39,7 @@ def copy_dic_subdirs(source_root, target_root_a, target_root_b, target_root_c, t # 사용법 exam_round = "2505" -source_directory = r"C:\Users\dra\project\GOM\DIC\회차별채점자료\2505\정답" # 원본 디렉토리 경로 +source_directory = r"C:\Users\dra\project\data\제2505회 정기\답안파일" # 원본 디렉토리 경로 target_directory_a = f".\\output\\{exam_round}\\A" # '1교시'의 타겟 경로 target_directory_b = f".\\output\\{exam_round}\\B" # '2교시'의 타겟 경로 diff --git a/250529_DIC_2505A_채점결과.xlsx b/250529_DIC_2505A_채점결과.xlsx new file mode 100644 index 0000000..aa83a1c Binary files /dev/null and b/250529_DIC_2505A_채점결과.xlsx differ diff --git a/250529_DIC_2505B_채점결과.xlsx b/250529_DIC_2505B_채점결과.xlsx new file mode 100644 index 0000000..088ea1a Binary files /dev/null and b/250529_DIC_2505B_채점결과.xlsx differ diff --git a/250529_DIC_2505C_채점결과.xlsx b/250529_DIC_2505C_채점결과.xlsx new file mode 100644 index 0000000..2e10673 Binary files /dev/null and b/250529_DIC_2505C_채점결과.xlsx differ diff --git a/DIC_2505A.json b/DIC_2505A.json index 40a0027..3c08431 100644 --- a/DIC_2505A.json +++ b/DIC_2505A.json @@ -184,26 +184,36 @@ "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", "이미지3.jpg", "이미지1.jpg", "이미지2.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", - "start": "0", - "end": "360", - "point": 2 + "ele": "//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", + "type": "startEnd", + "media": "동영상.mp4", + "value": { + "start": "0", + "end": "360" + }, + "point": 2, + "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']", diff --git a/psdExport_2.js b/psdExport_2.js index af154e3..88708db 100644 --- a/psdExport_2.js +++ b/psdExport_2.js @@ -15,15 +15,15 @@ const examRound = '2505'; const dic_or_dpi = 'DIC' // const dic_or_dpi = 'DPI' const examTypes = [ - // 'A', - 'B', - 'C', + 'A', + // 'B', + // 'C', // 'D' ]; // testMode가 true일 경우 TEST 폴더에 있는 답안 파일을 읽어옴 -// const testMode = true; -const testMode = false; +// const testMode = false; +const testMode = true; const outputExcelFiles = []; @@ -39,7 +39,6 @@ examTypes.forEach(type => { // let outputExcelFile = `./${todayDate}_DIC_${examRound}${type}_채점결과.xlsx`; // if (testMode) { // outputExcelFile = `./00_DIC_${examRound}${type}_TEST.xlsx`; - // } // 답안 폴더 내부에 디렉토리가 아닌 일반 파일이 있을 경우 디렉토리만 필터링 해서 불러옴 @@ -81,7 +80,9 @@ examTypes.forEach(type => { psdFiles.forEach((psdFile, index) => { const psdPath = path.join('./', studentDir, psdFile); - console.log(`Reading ${psdPath}...`); + + console.log(''); + console.log(`➡️ Reading ${psdPath}...`); try { const psdFileData = psd.fromFile(psdPath); psdFileData.parse(); @@ -116,7 +117,9 @@ examTypes.forEach(type => { else { gmepFile.forEach((gmep, index) => { const gmepPath = path.join('./', studentDir, gmep); - console.log(`Reading ${gmepPath}...`); + + console.log(''); + console.log(`➡️ Reading ${gmepPath}...`); const xmlString = fs.readFileSync(gmepPath, 'utf8'); // XML 문자열을 파싱하여 XML 문서 객체로 변환 @@ -169,6 +172,30 @@ outputExcelFiles.forEach((outputFile, index) => { // scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐 // 채점 결과를 scoringResultList 배열에 저장 function getGmepScore(gmepData, scoringJson, index) { + + function compareAndScore(userAnswer, rightAnswer, point, key, scoringResult) { + let score = 0; + + const isEqual = (typeof rightAnswer === "object" && typeof userAnswer === "object") + ? JSON.stringify(userAnswer) === JSON.stringify(rightAnswer) + : userAnswer == rightAnswer; + + if (isEqual) { + score = point; + console.log('작성답안: ', userAnswer); + console.log('>⭕ 정답: ', rightAnswer); + } else { + console.log('작성답안: ', userAnswer); + console.log('>❌ 오답: ', rightAnswer); + } + + scoringResult[key] = score; + return score; + } + function getMediaOrderbyClipIndex(gmepXmlDoc, clipIndex) { + + } + const gmepXmlDoc = gmepData; const scoringResult = {}; @@ -188,6 +215,7 @@ function getGmepScore(gmepData, scoringJson, index) { const point = scoringData[key].point; const type = scoringData[key].type; const search = scoringData[key].search; + const media = scoringData[key].media; const videoStartTime = scoringData.videoStartTime; const openingStartTime = scoringData.openingStartTime; @@ -204,14 +232,14 @@ function getGmepScore(gmepData, scoringJson, index) { const subtitleOrder = type === 'video' ? 2 : type === 'opening' ? 1 : null; const startTime = type === 'video' ? videoStartTime : type === 'opening' ? openingStartTime : null; - let xpathList = [ele, ele2, ele3, existEle]; - xpathList = xpathList.map(e => e?e + let xpathList = [ele, ele2, ele3, existEle]; + xpathList = xpathList.map(e => e ? e .replace(/{subtitleIndex}/g, subtitleIndex) .replace(/{subtitleOrder}/g, subtitleOrder) .replace(/{startTime}/g, startTime) .replace(/{clipIndex}/g, clipIndex) - .replace(/{image}/g, image) - :e + .replace(/{image}/g, image) + : e ); [ele, ele2, ele3, existEle] = xpathList; @@ -253,92 +281,83 @@ function getGmepScore(gmepData, scoringJson, index) { if (type == "boolean") { scoringResult[key] = result.length > 0 ? point : 0; } - else if (type == "array") { - // result: Path="동영상.mp4", Path="음악.mp3", Path="이미지2.jpg", Path="이미지1.jpg" - // value: 동영상.mp4,이미지1.jpg,이미지3.jpg,이미지2.jpg - // result 와 value를 순서대로 비교하여 모두 같으면 점수 point 부여 - // XPath를 사용하여 CRTrackList Name="비디오1" 요소 찾기 - const trackListNode = xpath.select1('//CRVideoTrackArr/CRTrackList[@Name="비디오1"]', gmepXmlDoc); - const values = []; - let isSame = true; + else if (type == "oneAnswer") { + const result = xpath.select1(ele, gmepXmlDoc); - if (trackListNode) { - // CRTrackClip 요소의 ClipIndex를 참조하여 CRClip 요소의 Path와 Type 출력 - // @Length(클립재생길이) 5프레임 이하, @ClipLength -1인 항목은 제외 - // 10프레임은 타임라인상 눈에 잘 보여서 5프레임으로 우선 수정 - const clipIndexes = xpath.select('CRTrackClip[not(@Length<="5" and @ClipLength="-1")]/@ClipIndex' - , trackListNode); - clipIndexes.forEach(indexNode => { - const clipIndex = parseInt(indexNode.value, 10) + 1; // XPath는 1-based index를 사용 - console.log(`clipIndex: ${clipIndex}`); - if (clipIndex === 0) { - return; - } - const clipPathNode = xpath.select1(`//CRClipArr/CRClip[${clipIndex}]/@Path`, gmepXmlDoc); - const motionClipPathNode = xpath.select1(`//CRClipArr/CRClip[${clipIndex}]/CRCUnitArr/@Path`, gmepXmlDoc); - const notUndefinedClipNode = clipPathNode ?? motionClipPathNode; - - if (notUndefinedClipNode === undefined) { - return; - } - values.push(notUndefinedClipNode.value); - }); - // values에 값이 있는지 확인 - if (values.length == 0 || values.length < 4) { - console.log('values length 0'); - scoringResult[key] = 0; - continue; - } - values.forEach((v, i) => { - console.log(`values: ${v} value: ${rightAnswer[i]}`); - if (rightAnswer[i] !== v) { - isSame = false; - } - }); - totalScore += isSame ? point : 0; - scoringResult[key] = isSame ? point : 0; - } else { - console.log('CRTrackList with Name="비디오1" not found.'); - scoringResult[key] = 0; + let userAnswer = {}; + if ("speed" in rightAnswer) { + userAnswer = { + "speed": result ? result.value : null, + }; } + else { + userAnswer = result ? result.value : null; + } + + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); } - else if (type == "startend") { - console.log('type:', type); - const start = scoringData[key].start; - const end = scoringData[key].end; - // XPath를 사용하여 CRClip 요소 중 Path가 '동영상.mp4'인 요소의 위치 찾기 - const clipIndexNode = xpath.select1(ele, gmepXmlDoc); - console.log(`clipIndexNode: ${clipIndexNode}`); + // [3-1] 문항 - // XPath를 사용하여 해당 ClipIndex를 사용하는 CRTrackClip 요소 찾기 - const trackClipNodes = xpath.select1(`//CRTrackClip[@ClipIndex='${clipIndexNode}']`, gmepXmlDoc); + else if (type == "mediaOrder") { + // 미디어 순서를 저장할 배열 + const mediaOrderList = []; - if (!trackClipNodes) { + // 미디어의 인덱스 순서 + 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); + } + } + else if (mediaPath != null) { + mediaOrderList.push(mediaPath.value); + } + }); + const userAnswer = mediaOrderList; + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + } + + else if (type == "startEnd") { + // CRClipArr/CRClip 요소의 Path속성 리스트를 구함 + // 모션 클립 이미지도 고려해 처리 + const mediaPathList = xpath.select("//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", gmepXmlDoc); + + // "동영상.mp4"의 clipIndex를 구함 + let clipIndex = mediaPathList.findIndex(mediaPath => mediaPath.value === media); + // clipIndex가 -1이면 해당 미디어가 존재하지 않는 것 + if (clipIndex === -1) { scoringResult[key] = 0; continue; } - const posNode = xpath.select1('@Pos', trackClipNodes); - const lengthNode = xpath.select1('@Length', trackClipNodes); - console.log(`Pos: ${posNode.value}, Length: ${lengthNode.value}`); - scoringResult[key] = posNode.value === start && lengthNode.value === end ? point : 0; - totalScore += posNode.value === start && lengthNode.value === end ? point : 0; + else { + // //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='동영상.mp4'] 요소를 찾음 + const trackClipNode = xpath.select1(`//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='${clipIndex}']`, gmepXmlDoc); + if (!trackClipNode) { + scoringResult[key] = 0; + continue; + } + else { + // CRTrackClip 요소의 Pos(시작시간)과 Length(재생길이)를 구함 + const pos = xpath.select1('@Pos', trackClipNode); + const length = xpath.select1('@Length', trackClipNode); + const userAnswer = { start: pos.value, end: length.value } + + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + } + } + } - // else if (type == "video") { - // const result = xpath.select(ele, gmepXmlDoc); - // const length = scoringData[key].length; - // // 결과는 배열로 나오는데 2개 일 경우가 있음 - // if (result.length !== length) { - // scoringResult[key] = 0; - // continue; - // } - - // scoringResult[key] = point; - // totalScore += point; - // } else if (type == "color") { const result = xpath.select(ele, gmepXmlDoc); diff --git a/z.xbook b/z.xbook index 8b16f75..cf45aef 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":"sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][2]/preceding-sibling::CRTrackClip/@Length)"},{"kind":2,"language":"xpath","value":"sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex=0]/preceding-sibling::CRTrackClip/@Length)"}] \ 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='비디오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 diff --git a/회차별채점자료/2505/excel_채점기준표/DIC_2505C.xlsx b/회차별채점자료/2505/excel_채점기준표/DIC_2505C.xlsx index e33b522..741afe3 100644 Binary files a/회차별채점자료/2505/excel_채점기준표/DIC_2505C.xlsx and b/회차별채점자료/2505/excel_채점기준표/DIC_2505C.xlsx differ diff --git a/DIC_2505B_layer.json b/회차별채점자료/2505/json_채점기준표/0528/DIC_2505B_layer.json similarity index 100% rename from DIC_2505B_layer.json rename to 회차별채점자료/2505/json_채점기준표/0528/DIC_2505B_layer.json diff --git a/DIC_2505C_clipping.json b/회차별채점자료/2505/json_채점기준표/0528/DIC_2505C_clipping.json similarity index 100% rename from DIC_2505C_clipping.json rename to 회차별채점자료/2505/json_채점기준표/0528/DIC_2505C_clipping.json