diff --git a/.gitignore b/.gitignore index aad8ac2..96c31ef 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,4 @@ output # 열려있는 xlsx파일 ~*.xlsx +psdExport_3.js diff --git a/250306_DIC_2502C_TEST.xlsx b/250306_DIC_2502C_TEST.xlsx new file mode 100644 index 0000000..2270ddc Binary files /dev/null and b/250306_DIC_2502C_TEST.xlsx differ diff --git a/250306_DIC_2502C_채점결과.xlsx b/250306_DIC_2502C_채점결과.xlsx new file mode 100644 index 0000000..8f14021 Binary files /dev/null and b/250306_DIC_2502C_채점결과.xlsx differ diff --git a/250307_DIC_2502C_TEST.xlsx b/250307_DIC_2502C_TEST.xlsx new file mode 100644 index 0000000..94864cc Binary files /dev/null and b/250307_DIC_2502C_TEST.xlsx differ diff --git a/250307_DIC_2502C_채점결과.xlsx b/250307_DIC_2502C_채점결과.xlsx new file mode 100644 index 0000000..085e04f Binary files /dev/null and b/250307_DIC_2502C_채점결과.xlsx differ diff --git a/250310_DIC_2502C_TEST.xlsx b/250310_DIC_2502C_TEST.xlsx new file mode 100644 index 0000000..a724c6b Binary files /dev/null and b/250310_DIC_2502C_TEST.xlsx differ diff --git a/250312_DIC_2502C_TEST.xlsx b/250312_DIC_2502C_TEST.xlsx new file mode 100644 index 0000000..f4a6c93 Binary files /dev/null and b/250312_DIC_2502C_TEST.xlsx differ diff --git a/250313_DIC_2502C_TEST.xlsx b/250313_DIC_2502C_TEST.xlsx new file mode 100644 index 0000000..6951b02 Binary files /dev/null and b/250313_DIC_2502C_TEST.xlsx differ diff --git a/250314_DIC_2502C_TEST.xlsx b/250314_DIC_2502C_TEST.xlsx new file mode 100644 index 0000000..eae3626 Binary files /dev/null and b/250314_DIC_2502C_TEST.xlsx differ diff --git a/250317_DIC_2502C_TEST.xlsx b/250317_DIC_2502C_TEST.xlsx new file mode 100644 index 0000000..0e1274c Binary files /dev/null and b/250317_DIC_2502C_TEST.xlsx differ diff --git a/DIC_2502C copy.json b/DIC_2502C copy.json new file mode 100644 index 0000000..7bab7a8 --- /dev/null +++ b/DIC_2502C copy.json @@ -0,0 +1,378 @@ +{ + "0": { + "1": { + "ele": "none", + "point": 0 + }, + "2": { + "ele": "none", + "point": 0 + }, + "3": { + "ele": "none", + "point": 0 + }, + "4": { + "ele": "none", + "point": 0 + }, + "5": { + "ele": "none", + "point": 0 + }, + "6": { + "ele": "none", + "point": 0 + }, + "7": { + "ele": "none", + "point": 0 + }, + "8": { + "ele": "$[?(@.width == 65 && @.height == 45)]", + "type": "boolean", + "point": 10 + } + }, + "1": { + "1": { + "ele": "none", + "point": 0 + }, + "2": { + "ele": "none", + "point": 0 + }, + "3": { + "ele": "none", + "point": 0 + }, + "4": { + "ele": "none", + "point": 0 + }, + "5": { + "ele": "$.children[?(@.name=='Mountains of Cheorwon')].name", + "value": "Mountains of Cheorwon", + "point": 10 + }, + "6": { + "ele": "$.children[?(@.name=='Mountains of Cheorwon')].text.font.names[0]", + "type": "font", + "value": "Arial", + "point": 10 + }, + "7": { + "ele": "$.children[?(@.name=='Mountains of Cheorwon')].text.font.names[0]", + "value": "Arial-BoldItalicMT", + "point": 10 + }, + "8": { + "ele": "$.children[?(@.name=='Mountains of Cheorwon')].text.font.sizes[0]", + "value": 40, + "point": 10 + }, + "9": { + "ele": "$.children[?(@.name=='Mountains of Cheorwon')].text.font.colors[0]", + "type": "color", + "value": "f1eb4a", + "point": 10 + }, + "10": { + "ele": "none", + "point": 0 + }, + "11": { + "ele": "none", + "point": 0 + }, + "12": { + "ele": "none", + "point": 0 + }, + "13": { + "ele": "$.children[?(@.name=='철원 금학산 등산')].name", + "value": "철원 금학산 등산", + "point": 10 + }, + "14": { + "ele": "$.children[?(@.name=='철원 금학산 등산')].text.font.names[0]", + "type": "font", + "value": "GulimChe", + "point": 10 + }, + "15": { + "ele": "$.children[?(@.name=='철원 금학산 등산')].text.font.sizes[0]", + "value": 30, + "point": 10 + }, + "16": { + "ele": "$.children[?(@.name=='철원 금학산 등산')].text.font.colors[0]", + "type": "color", + "value": "f1eb4a", + "point": 10 + }, + "17": { + "ele": "none", + "point": 0 + }, + "18": { + "ele": "none", + "point": 0 + }, + "19": { + "ele": "none", + "point": 0 + }, + "20": { + "ele": "none", + "point": 0 + }, + "21": { + "ele": "none", + "point": 0 + }, + "22": { + "ele": "none", + "point": 0 + }, + "23": { + "ele": "none", + "point": 0 + }, + "24": { + "ele": "none", + "point": 0 + }, + "25": { + "ele": "none", + "point": 0 + }, + "26": { + "ele": "none", + "point": 0 + }, + "27": { + "ele": "$[?(@.width == 65 && @.height == 45)]", + "type": "boolean", + "point": 10 + } + }, + "2": { + "1": { + "ele": "//CRClipArr/CRClip[position() = //CRTrackList[1]/CRTrackClip/@ClipIndex]/@Path", + "type": "array", + "value": [ + "동영상.mp4", + "이미지1.jpg", + "이미지3.jpg", + "이미지2.jpg" + ], + "point": 4 + }, + "2": { + "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1][@Speed='120']", + "point": 2 + }, + "3": { + "ele": "count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)", + "type": "startend", + "start": "0", + "end": "385", + "point": 2 + }, + "4": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]//CRFilter[@Type='1'][@ID='47'][@VID100='6'][@VID103='1']", + "point": 3 + }, + "5": { + "ele": "//CRCUnitArr[@Name='{search}']", + "search": "금학산 정상에서", + "point": 3 + }, + "6": { + "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit[@VID102='바탕체']", + "ele2": "//CROwneUnit[1]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit[@VID102='바탕체']", + "search": "금학산 정상에서", + "point": 2 + }, + "7": { + "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit[@VID101='170']", + "ele2": "//CROwneUnit[1]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit[@VID101='170']", + "search": "금학산 정상에서", + "point": 2 + }, + "8": { + "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool/GCUnit[@Type='4'][@VID100='-15081004']", + "ele2": "//CROwneUnit[1]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4'][@VID100='-15081004']", + "search": "금학산 정상에서", + "point": 2 + }, + "9": { + "existEle": "//CRCUnitArr[@Name='{search}']", + "ele": "sum(//CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex=count(//CROwneUnit/CRCUnitArr[@Name='{search}']/parent::CROwneUnit/preceding-sibling::CROwneUnit))]/@Length)", + "type": "searchIndex", + "value": 170, + "search": "금학산 정상에서", + "point": 2 + }, + "10": { + "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[(@ClipIndex=count(//CROwneUnit[.//CRCUnitArr[@Name='금학산 정상에서']]/preceding::CROwneUnit))][@Length='150']", + "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[(@ClipIndex=count(//CROwneUnit[1]/preceding-sibling::CROwneUnit))][@Length='150']", + "point": 2 + }, + "11": { + "ele": "//CRCUnitArr[@Name='{search}']/@VID600 | //CRCUnitArr[@Name='{search}']/@VID601", + "ele2": "//CROwneUnit[1]/CRCUnitArr/@VID600 | //CROwneUnit[1]/CRCUnitArr/@VID601", + "type": "range", + "search": "금학산 정상에서", + "start": [ 0.200, 0.800 ], + "end": [ 0.666, 0.999 ], + "point": 2 + }, + "12": { + "existEle": "//CRClip[@Path='동영상.mp4']", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]/@Mute", + "type": "searchIndex", + "value": "1", + "point": 2 + }, + "13": { + "existEle": "//CRClip[@Path='이미지1.jpg'] | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지1.jpg']", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지1.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지1.jpg']/../preceding-sibling::CRClip)][1]/@Length", + "type": "searchIndex", + "value": "150", + "point": 2 + }, + "14": { + "type": "multi", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지1.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지1.jpg']/../preceding-sibling::CRClip)][1]//CRFilter/@*[name()='ID' or name()='VID103']", + "value": [ + "67", + "8" + ], + "point": 2 + }, + "15": { + "type": "multi", + "ele": "//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지1.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지1.jpg']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)]/@*[name()='ID' or name()='Range' or name()='Type']", + "value": [ + "10", + "475:535", + "2" + ], + "point": 2 + }, + "16": { + "existEle": "//CRClip[@Path='이미지3.jpg'] | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)][1]/@Length", + "type": "searchIndex", + "value": "120", + "point": 2 + }, + "17": { + "type": "multi", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)][1]//CRFilter/@*[name()='ID' or name()='VID102']", + "value": [ + "102", + "9" + ], + "point": 2 + }, + "18": { + "type": "multi", + "ele": "//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)]/@*[name()='ID' or name()='Range' or name()='Type']", + "value": [ + "13", + "595:655", + "2" + ], + "point": 2 + }, + "19": { + "existEle": "//CRClip[@Path='이미지2.jpg'] | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지2.jpg']", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지2.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지2.jpg']/../preceding-sibling::CRClip)][1]/@Length", + "type": "searchIndex", + "value": "180", + "point": 2 + }, + "20": { + "type": "multi", + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지2.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지2.jpg']/../preceding-sibling::CRClip)][1]//CRFilter/@*[name()='ID' or name()='VID102']", + "value": [ + "184", + "25" + ], + "point": 2 + }, + "21": { + "type": "multi", + "ele": "//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지2.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지2.jpg']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)]/@*[name()='ID' or name()='Range' or name()='Type']", + "value": [ + "19", + "805:835", + "2" + ], + "point": 2 + }, + "22": { + "ele": "//CRCUnitArr[@Name='{search}']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 3 + }, + "23": { + "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit[@VID102='돋움체']", + "ele2": "//CROwneUnit[2]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit[@VID102='돋움체']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 2 + }, + "24": { + "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit[@VID101='150']", + "ele2": "//CROwneUnit[2]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit[@VID101='150']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 2 + }, + "25": { + "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool/GCUnit[@Type='4'][@VID100='-4077760']", + "ele2": "//CROwneUnit[2]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4'][@VID100='-4077760']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 2 + }, + "26": { + "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool/GCUnit[@Type='2'][@VID100='0.30000001'][@VID101='-16777216']", + "ele2": "//CROwneUnit[2]/CRCUnitArr//GCUnitPool/GCUnit[@Type='2'][@VID100='0.30000001'][@VID101='-16777216']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 2 + }, + "27": { + "ele": "//CRCUnitArr[@Name='{search}'][@VID505='1'][@VID507='2']", + "ele2": "//CROwneUnit[2]/CRCUnitArr[@VID505='1'][@VID507='2']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 3 + }, + "28": { + "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[.//CRCUnitArr[@Name='{search}']]/preceding::CROwneUnit)][@Pos='0']", + "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[2]/CRCUnitArr/preceding::CROwneUnit)][@Pos='0']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 2 + }, + "29": { + "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[(@ClipIndex=count(//CROwneUnit[.//CRCUnitArr[@Name='{search}']]/preceding::CROwneUnit))][@Length='120']", + "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[2]/CRCUnitArr/preceding::CROwneUnit)][@Length='120']", + "search": "금학산의 기운 (Energy of a Mountain)", + "point": 2 + }, + "30": { + "ele": "//CRTrackList[@Name='오디오1'][@Count>='1']/CRTrackClip[1][not(@ClipIndex='-1')]", + "point": 2 + }, + "31": { + "ele": "//CRTrackArr/CRAudioTrackArr/CRTrackList[@Name='오디오1']/CRTrackClip[@Length='830']", + "point": 2 + }, + "32": { + "ele": "//CRTrackArr/CRAudioTrackArr/CRTrackList[@Name='오디오1']//CRFilter[@Type='2'][@ID='1'][@VID8='90']", + "point": 2 + } + } +} \ No newline at end of file diff --git a/DIC_2502C.json b/DIC_2502C.json index 3d9bec5..48f1f44 100644 --- a/DIC_2502C.json +++ b/DIC_2502C.json @@ -159,6 +159,8 @@ } }, "2": { + "subtitleStartTime": 170, + "openingStartTime": 0, "1": { "ele": "//CRClipArr/CRClip[position() = //CRTrackList[1]/CRTrackClip/@ClipIndex]/@Path", "type": "array", @@ -186,48 +188,59 @@ "point": 3 }, "5": { - "ele": "//CRCUnitArr[@Name='{search}']", - "search": "금학산 정상에서", + "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", + "ele2": "", + "type": "subtitle", + "value": "금학산 정상에서", "point": 3 }, "6": { - "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit[@VID102='바탕체']", - "ele2": "//CROwneUnit[1]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit[@VID102='바탕체']", - "search": "금학산 정상에서", + "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID102", + "ele2": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit/@VID102", + "search" : "금학산 정상에서", + "type": "subtitle", + "value": "바탕체", "point": 2 }, "7": { - "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit[@VID101='170']", - "ele2": "//CROwneUnit[1]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit[@VID101='170']", + "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID101", + "ele2": "//CRCUnitArr[@Name='{search}']//GCUnitPool[@Type='1']/GCUnit/@VID101", "search": "금학산 정상에서", + "type": "subtitle", + "value": "170", "point": 2 }, "8": { - "ele": "//CRCUnitArr[@Name='{search}']//GCUnitPool/GCUnit[@Type='4'][@VID100='-15081004']", - "ele2": "//CROwneUnit[1]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4'][@VID100='-15081004']", + "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4']/@VID100", + "ele2": "//CRCUnitArr[@Name='{search}']//GCUnitPool/GCUnit[@Type='4']/@VID100", "search": "금학산 정상에서", + "type": "subtitle", + "value": "-15081004", "point": 2 }, "9": { - "existEle": "//CRCUnitArr[@Name='{search}']", - "ele": "sum(//CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex=count(//CROwneUnit[1]/preceding-sibling::CROwneUnit))]/@Length)", - "type": "searchIndex", - "value": 170, + "ele": "sum(//CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", + "ele2": "sum(//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[CRCUnitArr[@Name='{search}']]/preceding-sibling::CROwneUnit)]/preceding-sibling::CRTrackClip/@Length)", "search": "금학산 정상에서", + "type": "subtitle", + "value": 170, "point": 2 }, "10": { - "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[(@ClipIndex=count(//CROwneUnit[.//CRCUnitArr[@Name='금학산 정상에서']]/preceding::CROwneUnit))][@Length='150']", - "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[(@ClipIndex=count(//CROwneUnit[1]/preceding-sibling::CROwneUnit))][@Length='150']", + "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", + "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = {startTime}]/@Length", + "ele3": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[CRCUnitArr[@Name='{search}']]/preceding-sibling::CROwneUnit)]/@Length", + "search" : "금학산 정상에서", + "type": "subtitle", + "value": "150", "point": 2 }, "11": { - "ele": "//CRCUnitArr[@Name='{search}']/@VID600 | //CRCUnitArr[@Name='{search}']/@VID601", - "ele2": "//CROwneUnit[1]/CRCUnitArr/@VID600 | //CROwneUnit[1]/CRCUnitArr/@VID601", - "type": "range", + "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@VID600 | //CROwneUnit[{subtitleIndex}]/CRCUnitArr/@VID601", + "ele2": "//CROwneUnit[position() = (//CRTrackList[@Name='텍스트']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = 170]/@ClipIndex) + 1]/CRCUnitArr/@VID600 | //CROwneUnit[position() = (//CRTrackList[@Name='텍스트']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = 170]/@ClipIndex) + 1]/CRCUnitArr/@VID601", + "type": "subtitle", "search": "금학산 정상에서", - "start": [ 0.200, 0.800 ], - "end": [ 0.666, 0.999 ], + "value": ["0.260", "0.888"], "point": 2 }, "12": { @@ -351,14 +364,17 @@ "point": 3 }, "28": { - "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[.//CRCUnitArr[@Name='{search}']]/preceding::CROwneUnit)][@Pos='0']", - "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[2]/CRCUnitArr/preceding::CROwneUnit)][@Pos='0']", + "existEle": "//CRCUnitArr[@Name='{search}']", + "ele": "sum(//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[CRCUnitArr[@Name='{search}']]/preceding-sibling::CROwneUnit)]/preceding-sibling::CRTrackClip/@Length)", + "ele2": "sum(//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[not(CRCUnitArr[@Name='기본자막'])][2]/preceding-sibling::CROwneUnit)]/preceding-sibling::CRTrackClip/@Length)", + "type": "searchIndex", + "value": 0, "search": "금학산의 기운 (Energy of a Mountain)", "point": 2 }, "29": { - "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[(@ClipIndex=count(//CROwneUnit[.//CRCUnitArr[@Name='{search}']]/preceding::CROwneUnit))][@Length='120']", - "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[2]/CRCUnitArr/preceding::CROwneUnit)][@Length='120']", + "ele": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit/CRCUnitArr[@Name='금학산의 기운 (Energy of a Mountain)']/parent::CROwneUnit/preceding-sibling::CROwneUnit)][@Length=120]", + "ele2": "//CRTrackList[@Name='텍스트']/CRTrackClip[@ClipIndex=count(//CROwneUnit[not(CRCUnitArr[@Name='기본자막'])][2]/preceding::CROwneUnit)][@Length='120']", "search": "금학산의 기운 (Energy of a Mountain)", "point": 2 }, diff --git a/README.md b/README.md index f159290..4fbe980 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,11 @@ xpath 테스트 용 ```xpath //CRTrackList[@Name='오디오1'][@Count>='1']/CRTrackClip[1][not(@ClipIndex='-1')] ``` + +### 5. 자막 순서 검색 문제 + +#### 자막 기준 + +#### 자막 텍스트가 일치하지 않은 상황에서 xml문서 내부 태그의 순서가 바뀌어있는 경우 + +##### 1. 시작시간을 기준으로 찾는다 diff --git a/findSimilarString.js b/findSimilarString.js index 6bee1fe..ef7b911 100644 --- a/findSimilarString.js +++ b/findSimilarString.js @@ -29,13 +29,12 @@ function findSimilarString(xmlDoc, targetString, threshold = 0.8) { stringList.forEach(text => { const similarity = stringSimilarity.compareTwoStrings(targetString, text); - console.log("🚀 ~ findSimilarString ~ text:", text) - console.log("🚀 ~ findSimilarString ~ targetString:", targetString) - console.log("🚀 ~ findSimilarString ~ similarity:", similarity) + // console.log("🚀 ~ findSimilarString ~ text:", text) + // console.log("🚀 ~ findSimilarString ~ targetString:", targetString) + // console.log("🚀 ~ findSimilarString ~ similarity:", similarity) if (similarity > highestSimilarity && similarity >= threshold) { highestSimilarity = similarity; - console.log("🚀 ~ findSimilarString ~ highestSimilarity:", highestSimilarity) bestMatch = text; } }); diff --git a/psdExport_2.js b/psdExport_2.js index 03bfe4a..3c264db 100644 --- a/psdExport_2.js +++ b/psdExport_2.js @@ -13,8 +13,8 @@ const todayDate = getToday(); // -------------------------------------------------------- // const scoringJson = require('./DIC_2502A.json'); // const scoringJson = require('./DIC_2502B.json'); -// const scoringJson = require('./DIC_2502C.json'); -const scoringJson = require('./DIC_2502D.json'); +const scoringJson = require('./DIC_2502C.json'); +// const scoringJson = require('./DIC_2502D.json'); // TEST // const scoringJson = require('./DIC_2502A_TEST.json'); @@ -25,24 +25,24 @@ const scoringJson = require('./DIC_2502D.json'); // const answerFilesDir = './output/A/DIC'; // const answerFilesDir = './output/B/DIC'; // const answerFilesDir = './output/C/DIC'; -const answerFilesDir = './output/D/DIC'; +// const answerFilesDir = './output/D/DIC'; // TEST // const answerFilesDir = './output/A/TEST'; // const answerFilesDir = './output/B/TEST'; -// const answerFilesDir = './output/C/TEST'; +const answerFilesDir = './output/C/TEST'; // const answerFilesDir = './output/D/TEST'; // -------------------------------------------------------- // const outputExcelFile = './'+todayDate+'_DIC_2502A_채점결과.xlsx'; // const outputExcelFile = './'+todayDate+'_DIC_2502B_채점결과.xlsx'; // const outputExcelFile = './'+todayDate+'_DIC_2502C_채점결과.xlsx'; -const outputExcelFile = './'+todayDate+'_DIC_2502D_채점결과.xlsx'; +// const outputExcelFile = './'+todayDate+'_DIC_2502D_채점결과.xlsx'; // TEST // const outputExcelFile = './'+todayDate+'_DIC_2502A_TEST.xlsx'; // const outputExcelFile = './'+todayDate+'_DIC_2502B_TEST.xlsx'; -// const outputExcelFile = './'+todayDate+'_DIC_2502C_TEST.xlsx'; +const outputExcelFile = './' + todayDate + '_DIC_2502C_TEST.xlsx'; // const outputExcelFile = './'+todayDate+'_DIC_2502D_TEST.xlsx'; // -------------------------------------------------------- @@ -52,7 +52,6 @@ const studentDirs = fs.readdirSync(answerFilesDir).filter(file => { return fs.statSync(filePath).isDirectory(); }); - // 채점 결과 리스트 const scoringResultList = []; const psdData = []; @@ -90,7 +89,7 @@ studentDirs.forEach(student => { gmepFile.forEach((gmep, index) => { const gmepPath = path.join('./', studentDir, gmep); console.log(`Reading ${gmepPath}...`); - + const xmlString = fs.readFileSync(gmepPath, 'utf8'); // XML 문자열을 파싱하여 XML 문서 객체로 변환 const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml'); @@ -125,8 +124,28 @@ XLSX.utils.book_append_sheet(workbook, worksheet, '채점 결과'); // 엑셀 파일 저장 XLSX.writeFile(workbook, outputExcelFile); -console.log('채점 결과가 '+outputExcelFile+' 파일에 저장되었습니다.'); +console.log('채점 결과가 ' + outputExcelFile + ' 파일에 저장되었습니다.'); +/** + * 자막 태그의 인덱스를 구할 때 사용 + * 1. CRTrackClip 요소의 순서에 따라 그 요소에 해당하는 CROwneUnit 태그의 순서를 구함 + * 2. CRTrackClip 요소의 시작시간에 따라 그 요소에 해당하는 CROwneUnit 태그의 순서를 구함 + * @returns subtitle index + * + */ +function getTrackClipNode(xmlDoc, type, subtitleStartTime, openingStartTime) { + let trackClipNode = null; + + // 동영상 자막이면 2, 오프닝 자막이면 1, 그 외는 0 + const subtitleOrder = type === 'subtitle' ? 2 : type === 'opening' ? 1 : 0; + const startTime = type === 'subtitle' ? subtitleStartTime : openingStartTime; + + // xpath 구문을 통해 CRTrackClip 요소의 ClipIndex를 찾음 + const trackClipNode1 = xpath.select1(`//CRTrackList[@Name="텍스트"]/CRTrackClip[not(@ClipIndex='-1')][${subtitleOrder}]`, xmlDoc); + const trackClipNode2 = xpath.select1(`//CRTrackList[@Name='텍스트']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = ${startTime}]`, xmlDoc); + + return trackClipNode = trackClipNode1 ?? trackClipNode2; +} // xml 형식의 gmep 파일을 읽어서 점수를 계산 // scoring.json 파일 내에 있는 ele 요소는 xpath 형식으로 접근하여 요소를 탐색하고 나오는 값을 value와 비교하여 점수를 계산 @@ -140,17 +159,36 @@ function getGmepScore(gmepData, scoringJson, index) { const scoringData = scoringJson[index]; // console.log(scoringData); + let totalScore = 0; - + // 채점기준표 문항별 분류 for (const key in scoringData) { let ele = scoringData[key].ele; - const ele2 = scoringData[key].ele2; + 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 search = scoringData[key].search; + const subtitleStartTime = scoringData.subtitleStartTime; + const openingStartTime = scoringData.openingStartTime; + + // xpath 전처리 + const trackClipNode = getTrackClipNode(gmepXmlDoc, type, subtitleStartTime, + openingStartTime); + const subtitleIndex = trackClipNode ? parseInt(trackClipNode.getAttribute('ClipIndex'), 10) + 1 : null; + const subtitleOrder = type === 'subtitle' ? 2 : type === 'opening' ? 1 : null; + const startTime = type === 'subtitle' ? subtitleStartTime + : type === 'opening' ? openingStartTime : null; + + [ele, ele2] = [ele, ele2].map(e => e?.replace(/{subtitleIndex}/g, subtitleIndex)); + [ele, ele2] = [ele, ele2].map(e => e?.replace(/{subtitleOrder}/g, subtitleOrder)); + [ele, ele2] = [ele, ele2].map(e => e?.replace(/{startTime}/g, startTime)); + + console.log("🚀 ~ getGmepScore ~ ele:", ele) + console.log("🚀 ~ getGmepScore ~ ele2:", ele2) // search 값이 undefined 아니면 ele의 {search}부분을 search로 치환 /** @@ -161,16 +199,17 @@ function getGmepScore(gmepData, scoringJson, index) { * > 멀티라인 텍스트 유사도 판별하기 어려움 */ if (search !== undefined) { - let result = findSimilarString(gmepXmlDoc, search, 0.8) - // xpath 내부 "(큰따옴표) 필터링 + let result = findSimilarString(gmepXmlDoc, search, 0.8); if (result !== null) { result = result.replace(/"/g, "'"); - } - ele = ele.replace(/{search}/g, result); - if ( existEle !== undefined ){ - existEle = existEle.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); } } + console.log(`example number: ${key}`) // xpath @@ -208,11 +247,9 @@ function getGmepScore(gmepData, scoringJson, index) { const motionClipPathNode = xpath.select1(`//CRClipArr/CRClip[${clipIndex}]/CRCUnitArr/@Path`, gmepXmlDoc); const notUndefinedClipNode = clipPathNode ?? motionClipPathNode; - if ( notUndefinedClipNode === undefined ) { - console.log("🚀 ~ getGmepScore ~ notUndefinedClipNode:", notUndefinedClipNode) + if (notUndefinedClipNode === undefined) { return; } - console.log("🚀 ~ getGmepScore ~ notUndefinedClipNode:", notUndefinedClipNode.value) values.push(notUndefinedClipNode.value); }); // values에 값이 있는지 확인 @@ -260,7 +297,7 @@ function getGmepScore(gmepData, scoringJson, index) { // else if (type == "subtitle") { // const result = xpath.select(ele, gmepXmlDoc); // const length = scoringData[key].length; - + // // 결과는 배열로 나오는데 2개 일 경우가 있음 // if (result.length !== length) { // scoringResult[key] = 0; @@ -293,19 +330,19 @@ function getGmepScore(gmepData, scoringJson, index) { const start = scoringData[key].start; const end = scoringData[key].end; - try{ + try { let result = xpath.select(ele, gmepXmlDoc); - if (result.length == 0) { + if (!result) { result = xpath.select(ele2, gmepXmlDoc); - if (result.length == 0 ) { + if (!result) { scoringResult[key] = 0; continue; } } // 수험자 답안 자막 좌표 (x,y) - const x = parseFloat(result[0].value); - const y = parseFloat(result[1].value); + const x = parseFloat(result[0]?.value); + const y = parseFloat(result[1]?.value); // 최소 좌표 (x1, y1) const x1 = parseFloat(start[0]); const y1 = parseFloat(start[1]); @@ -314,10 +351,10 @@ function getGmepScore(gmepData, scoringJson, index) { const y2 = parseFloat(end[1]); // (x1,y1) <= (x,y) <= (x2,y2) 이면 true - const isPointInRange = (x, y, x1, y1, x2, y2) => + const isPointInRange = (x, y, x1, y1, x2, y2) => (x >= x1 && x <= x2) && (y >= y1 && y <= y2); - - if (isPointInRange(x, y, x1, y1, x2, y2) === true ) { + + if (isPointInRange(x, y, x1, y1, x2, y2) === true) { totalScore += point; scoringResult[key] = point; } @@ -401,11 +438,16 @@ function getGmepScore(gmepData, scoringJson, index) { scoringResult[key] = 0; } - } else { + } + else { console.log(`not found. ${existEle} `); - const result = xpath.select1(ele, gmepXmlDoc); - console.log("🚀 ~ getGmepScore ~ result:", result) - if ( result == rightAnswer ) { + let result; + + if (ele2 !== undefined) { + result = xpath.select1(ele2, gmepXmlDoc); + } + + if (result == rightAnswer) { totalScore += point; scoringResult[key] = point; } @@ -414,34 +456,169 @@ function getGmepScore(gmepData, scoringJson, index) { } } } - else { - console.log('Unknown type:', ele); - let result = xpath.select(ele, gmepXmlDoc); - let result2 = null; - let isCheck = false; - - if (result.length == 0) { - isCheck = true; - } - if (isCheck && ele2) { - result2 = xpath.select(ele2, gmepXmlDoc); - if (result2.length == 0) { - scoringResult[key] = 0; - continue; + /* 현재 문제점 **************************************************** + * ele2 xpath구문을 수행했을때 + * /CROwneUnit[position() = //CRTrackList/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = 170]/@ClipIndex + 1]/CRCUnitArr/@Name + * position() = //CRTrackList/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = 170 부분에서 + * 시작시간이 170이 아닌 경우 false값이 반환되고 0으로 인식되어 + * //CROwneUnit[0]/CRCUnitArr/@Name 의 값이 반환됨 + ****************************************************************/ + else if (type == "subtitle" || type == "opening") { + const result = ele ? xpath.select(ele, gmepXmlDoc) : []; + const result2 = ele2 ? xpath.select(ele2, gmepXmlDoc) : []; + const result3 = ele3 ? xpath.select(ele3, gmepXmlDoc) : []; + + const resultValues = Array.isArray(result) ? result.map(r => (typeof r === 'object' ? r.value : r)) : [result]; + const resultValues2 = Array.isArray(result2) ? result2.map(r => (typeof r === 'object' ? r.value : r)) : [result2]; + const resultValues3 = Array.isArray(result3) ? result3.map(r => (typeof r === 'object' ? r.value : r)) : [result3]; + + const allResults = [...[resultValues], ...[resultValues2], ...[resultValues3]]; + console.log("🚀 ~ allResults:", allResults) + + // rightAnswer가 배열일 경우 배열로 변환 + const rightAnswerArray = Array.isArray(rightAnswer) ? rightAnswer : [rightAnswer]; + + // 결과값이 범위값인 경우 소수점 3자리까지 비교 + const formattedResults = allResults.map(result => { + // result의 길이가 1이상인 조건은 result값이 [x, y] 좌표값인 경우를 말한다 + if (Array.isArray(result) && result.length > 1) { + return result.map(r => { + if (typeof r === 'string') { + return (Math.floor(parseFloat(r) * 1000) / 1000).toFixed(3); + } + return r; + }); + } + return result; + }); + console.log("🚀 ~ formattedResults:", formattedResults) + + // 배열 비교 함수 + function arraysEqual(arr1, arr2) { + if (arr1.length !== arr2.length) return false; + if (arr2.length === 1) { + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) return false; + } + return true; + } + else if (arr2.length > 1) { + for (let i = 0; i < arr1.length; i++) { + if (Math.abs(arr1[i] - arr2[i]) > 0.1) return false; + } + return true; } - result = result2; - // console.log(`1st isChecked: ${isCheck}, result: ${result}`) } - // value와 result[0].value를 비교하여 같으면 점수 point 부여 - // console.log(`${(value === result[0].value)}, ${result.length > 0 && value === result[0].value} `) - // console.log(`2nd isChecked: ${isCheck}, result: ${result}`) - totalScore += result.length > 0 ? point : 0; - scoringResult[key] = result.length > 0 ? point : 0; + + // allResults에 rightAnswerArray와 일치하는 배열이 있는지 확인 + const isIncluded = formattedResults.some(arr => arraysEqual(arr, rightAnswerArray)); + + if (isIncluded) { + console.log("🚀 ~ getGmepScore ~ 정답:", rightAnswerArray); + totalScore += point; + scoringResult[key] = point; + } else { + console.log("🚀 ~ getGmepScore ~ 오답:", rightAnswerArray); + scoringResult[key] = 0; + } + } + // else if (type == "subtitle" || type == "opening") { + // const result = ele && xpath.select(ele, gmepXmlDoc); + // const result2 = ele2 && xpath.select(ele2, gmepXmlDoc); + // const result3 = ele3 && xpath.select(ele3, gmepXmlDoc); + + // /** + // * 1. result가 배열이 아닌 경우 배열로 변환 + // * (2-9)는 xpath구문을 통해 클립(자막) 시작시간(number자료형)을 반환받으므로 배열로 변환하여 비교 + // * 2. result가 배열이고 배열의 요소가 객체인 경우 value값만 추출하여 배열로 변환 + // * 3. result가 배열이고 배열의 요소가 객체가 아닌 문자열이나 숫자일 경우 그대로 배열로 변환 + // */ + // const resultValues = Array.isArray(result) ? result.map(r => (typeof r === 'object' ? r.value : r)) : [result]; + // const resultValues2 = Array.isArray(result2) ? result2.map(r => (typeof r === 'object' ? r.value : r)) : [result2]; + // const resultValues3 = Array.isArray(result3) ? result3.map(r => (typeof r === 'object' ? r.value : r)) : [result3]; + + // /** + // * 정답과 일치하는 답안이 있는지 비교를 위해 배열로 변환 + // * 정답 rightAnswer의 값이 2개 이상인 경우 배열로 변환하여 비교 + // */ + // let allResults = [...[resultValues], ...[resultValues2], ...[resultValues3]]; + // console.log("🚀 ~ getGmepScore ~ allResults:", allResults) + + // // allResults.forEach((result, i) => { + // // if (result.length > 1) { + // // result.forEach((r, j) => { + // // if (Math.abs(r - rightAnswer[j]) <= 0.1) { + // // true; + // // } else { + // // scoringResult[key] = 0; + // // } + // // }); + // // }); + + // // (2-11) 자막의 위치 좌표값 비교시 소수점 3자리까지 비교 + // // rightAnswer값이 배열일 경우 + // const rightAnswerArray = Array.isArray(rightAnswer) ? rightAnswer : [rightAnswer]; + // console.log("🚀 ~ getGmepScore ~ rightAnswerArray:", rightAnswerArray); + + // // 결과값이 범위값인 경우 소수점 3자리까지 비교 + // allResults = allResults.map(result => { + // if (result.length > 1) { + // return result.map(r => { + // if (typeof r === 'string') { + // return parseFloat(r).toFixed(3); + // } + // return r; + // }); + // } + // }); + // console.log("🚀 ~ //allResults.forEach ~ allResults:", allResults) + + // const isIncluded = allResults.some(arr => + // arr.length === rightAnswerArray.length && arr.every((val, index) => val === rightAnswerArray[index]) + // ); + // if (isIncluded) { + // console.log("🚀 ~ getGmepScore ~ 정답:", rightAnswerArray); + // totalScore += point; + // scoringResult[key] = point; + // } + // else { + // console.log("🚀 ~ getGmepScore ~ 오답:"); + // scoringResult[key] = 0; + // } + // } + + else { + try { + console.log('Unknown type:', ele); + let result = ele ? xpath.select(ele, gmepXmlDoc) : null; + let result2 = null; + let isCheck = false; + + if (!result || result.length === 0) { + isCheck = true; + } + if (isCheck && ele2) { + result2 = ele2 ? xpath.select(ele2, gmepXmlDoc) : null; + + if (!result2 || result2.length == 0) { + scoringResult[key] = 0; + continue; + } + result = result2; + // console.log(`1st isChecked: ${isCheck}, result: ${result}`) + } + // value와 result[0].value를 비교하여 같으면 점수 point 부여 + // console.log(`${(value === result[0].value)}, ${result.length > 0 && value === result[0].value} `) + // console.log(`2nd isChecked: ${isCheck}, result: ${result}`) + totalScore += result?.length > 0 ? point : 0; + scoringResult[key] = result?.length > 0 ? point : 0; + } catch (error) { + console.error(`Error processing XPath query for ele: ${ele}`, error); + scoringResult[key] = 0; + } } } - - scoringResult['총점'] = totalScore; return scoringResult; } @@ -479,7 +656,7 @@ function getScore(psdData, scoring, index) { continue; } if (type == "boolean") { - // console.log(`result ${result.length}`); + // console.log(`result ${result.length}`); scoringResult[key] = result.length > 0 ? point : 0; } @@ -488,9 +665,9 @@ function getScore(psdData, scoring, index) { // result: [255,162,0,255] // 255,162,0,255 -> ffa200 else if (type == "color") { - // console.log(`result ${result}`); // result 255,162,0,255 + // console.log(`result ${result}`); // result 255,162,0,255 const temp = result[0].slice(0, 3).join(','); // 255,162,0 - + // RGB의 각 색상값이 한자리수 일 경우 0을 채워 두자리로 만듬 color = temp.split(',').map(v => parseInt(v).toString(16).padStart(2, '0')).join(''); // ffa200 // ffa20 -> ffa200 @@ -498,16 +675,16 @@ function getScore(psdData, scoring, index) { // color = color + '0'; // } - // console.log(`color: ${color}`); + // console.log(`color: ${color}`); scoringResult[key] = result.length > 0 && value === color ? point : 0; } // type이 font인 경우 font의 이름만 추출하여 비교 // value: "Arial" // result: ["Arial-BoldItalicMT"] else if (type == "font") { - // console.log(`result ${result}`); + // console.log(`result ${result}`); const font = result[0].split('-')[0]; - // console.log(`font: ${font}`); + // console.log(`font: ${font}`); scoringResult[key] = result.length > 0 && value === font ? point : 0; } @@ -525,4 +702,4 @@ function getScore(psdData, scoring, index) { scoringResult['총점'] = totalScore; return scoringResult; -} \ No newline at end of file +} diff --git a/z.xbook b/z.xbook index 4ef959e..b654d7d 100644 --- a/z.xbook +++ b/z.xbook @@ -1 +1 @@ -[{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::*)]/@Length"},{"kind":2,"language":"xpath","value":"//CRClip[@Path='이미지3.jpg']/preceding-sibling::*"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지1.jpg']/preceding-sibling::*)]/@Length"},{"kind":2,"language":"xpath","value":"//CRClip[not(@Type='11') and @Path='이미지1.jpg']"},{"kind":2,"language":"xpath","value":"count(//CRClip[(@Type!='11' and @Path='이미지3.jpg') or CRCUnitArr[@Path='이미지3.jpg']])\r\n"},{"kind":2,"language":"xpath","value":"//CRClip[@Type!='11' and @Path='이미지2.jpg']/preceding-sibling::CRClip"},{"kind":2,"language":"xpath","value":"//CRClip[@Type='11'] CRCUnitArr[@Path='이미지3.jpg']"},{"kind":2,"language":"xpath","value":"//CRClip[@Path='이미지3.jpg'] | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']"},{"kind":2,"language":"xpath","value":"count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/preceding-sibling::CRClip)"},{"kind":2,"language":"xpath","value":"count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)"},{"kind":2,"language":"xpath","value":"//CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)]/@Length"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)]//CRFilter/@*[name()='ID' or name()='VID101']"},{"kind":2,"language":"xpath","value":"//CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip"},{"kind":2,"language":"xpath","value":"//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@Type='2' and @ClipIndex=count(//CRClip[@Path='이미지2.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지2.jpg']/../preceding-sibling::CRClip)]/preceding-sibling::CRTrackClip)]/@*[name()='ID' or name()='Range' or name()='Type']"},{"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)]/@*[name()='ID' or name()='Range' or name()='Type']"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=3][1]/preceding-sibling::CRTrackClip"},{"kind":2,"language":"xpath","value":"count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=4]/@Length"},{"kind":2,"language":"xpath","value":"count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)]/@Length"},{"kind":2,"language":"xpath","value":"count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=4]/preceding-sibling::CRTrackClip)"},{"kind":2,"language":"xpath","value":"//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=4]/preceding-sibling::CRTrackClip)]/@*[name()='ID' or name()='Range' or name()='Type']"},{"kind":2,"language":"xpath","value":"//CROwneUnit/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit[@VID102='바탕체']"}] \ No newline at end of file +[{"kind":2,"language":"xpath","value":"//CROwneUnit[position() = //CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex='-1')][2]/@ClipIndex + 1]/CRCUnitArr/@VID600 | //CROwneUnit[position() = //CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex='-1')][2]/@ClipIndex + 1]/CRCUnitArr/@VID601"},{"kind":2,"language":"xpath","value":"//CROwneUnit[position() = (//CRTrackList[@Name='텍스트']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = 170]/@ClipIndex) + 1]/CRCUnitArr/@VID600 | //CROwneUnit[position() = (//CRTrackList[@Name='텍스트']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = 170]/@ClipIndex) + 1]/CRCUnitArr/@VID601"},{"kind":2,"language":"xpath","value":"position() = (//CRTrackList[@Name='텍스트']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = 110]/@ClipIndex) + 1"},{"kind":2,"language":"xpath","value":"//CROwneUnit[//CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex='-1')][2]/@ClipIndex]"},{"kind":2,"language":"xpath","value":"//CROwneUnit[position() = //CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex='-1')][1]/@ClipIndex + 1]/CRCUnitArr/@Name"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='텍스트']/CRTrackClip[not(@ClipIndex='-1')][1]/@ClipIndex"}] \ No newline at end of file diff --git a/회차별채점자료/2502/excel_채점결과/250305_DIC_2502C_채점결과_비교시트.xlsx b/회차별채점자료/2502/excel_채점결과/250305_DIC_2502C_채점결과_비교시트.xlsx index c8a2974..adb6b1e 100644 Binary files a/회차별채점자료/2502/excel_채점결과/250305_DIC_2502C_채점결과_비교시트.xlsx and b/회차별채점자료/2502/excel_채점결과/250305_DIC_2502C_채점결과_비교시트.xlsx differ diff --git a/회차별채점자료/2502/excel_채점기준표/DIC_2502C.xlsx b/회차별채점자료/2502/excel_채점기준표/DIC_2502C.xlsx index bdb876a..174a1c7 100644 Binary files a/회차별채점자료/2502/excel_채점기준표/DIC_2502C.xlsx and b/회차별채점자료/2502/excel_채점기준표/DIC_2502C.xlsx differ