diff --git a/00_DIC_2505B_TEST.xlsx b/00_DIC_2505B_TEST.xlsx index fb415eb..94fcbaa 100644 Binary files a/00_DIC_2505B_TEST.xlsx and b/00_DIC_2505B_TEST.xlsx differ diff --git a/01_copyFiles.py b/01_copyFiles.py index 9773dc7..c3a8002 100644 --- a/01_copyFiles.py +++ b/01_copyFiles.py @@ -38,8 +38,8 @@ def copy_dic_subdirs(source_root, target_root_a, target_root_b, target_root_c, t print(f"Skipping {dir_name} under {parent_dir}, as it doesn't match '2교시' or '3교시'.") # 사용법 -exam_round = "2505" -source_directory = r"C:\Users\dra\project\data\제2505회 정기\답안파일" # 원본 디렉토리 경로 +exam_round = "2504" +source_directory = r"C:\Users\dra\project\data\제2504회 정기\답안파일" # 원본 디렉토리 경로 target_directory_a = f".\\output\\{exam_round}\\A" # '1교시'의 타겟 경로 target_directory_b = f".\\output\\{exam_round}\\B" # '2교시'의 타겟 경로 diff --git a/DIC_2504B.json b/DIC_2504B.json new file mode 100644 index 0000000..a2c535e --- /dev/null +++ b/DIC_2504B.json @@ -0,0 +1,732 @@ +{ + "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": "size", + "value": { + "width": 65, + "height": 45 + }, + "point": 4 + }, + "9": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "1": { + "1": { + "ele": "none", + "point": 0 + }, + "2": { + "ele": "none", + "point": 0 + }, + "3": { + "ele": "none", + "point": 0 + }, + "4": { + "ele": "$.children[?(@.name=='꽃잎들')].name", + "value": "꽃잎들", + "point": 4 + }, + "5": { + "ele": "none", + "point": 0 + }, + "6": { + "ele": "$.children[?(@.name=='Flower Rock')].name", + "value": "Flower Rock", + "point": 4 + }, + "7": { + "ele": "$.children[?(@.name=='Flower Rock')].text.font.names[0]", + "type": "font", + "value": "Arial", + "point": 2 + }, + "8": { + "ele": "$.children[?(@.name=='Flower Rock')].text.font.names[0]", + "value": "Arial-BoldItalicMT", + "point": 2 + }, + "9": { + "ele": "$.children[?(@.name=='Flower Rock')].text.font.sizes[0]", + "value": 48, + "point": 2 + }, + "10": { + "ele": "$.children[?(@.name=='Flower Rock')].text.font.colors[0]", + "type": "color", + "value": "d6f592", + "point": 2 + }, + "11": { + "ele": "none", + "point": 0 + }, + "12": { + "ele": "none", + "point": 0 + }, + "13": { + "ele": "none", + "point": 0 + }, + "14": { + "ele": "$.children[?(@.name=='꽃과 바위')].name", + "value": "꽃과 바위", + "point": 4 + }, + "15": { + "ele": "$.children[?(@.name=='꽃과 바위')].text.font.names[0]", + "type": "font", + "value": "GungsuhChe", + "point": 2, + "desc": { + "돋움체": "DotumChe", + "궁서체": "GungsuhChe", + "굴림체": "GulimChe", + "휴먼옛체": "YetR" + } + }, + "16": { + "ele": "$.children[?(@.name=='꽃과 바위')].text.font.sizes[0]", + "value": 36, + "point": 2 + }, + "17": { + "ele": "$.children[?(@.name=='꽃과 바위')].text.font.colors[0]", + "type": "color", + "value": "0d17d5", + "point": 2 + }, + "18": { + "ele": "none", + "point": 0 + }, + "19": { + "ele": "none", + "point": 0 + }, + "20": { + "ele": "none", + "point": 0 + }, + "21": { + "ele": "none", + "point": 0 + }, + "22": { + "ele": "$.children[?(@.name=='노랑꽃')].name", + "value": "노랑꽃", + "point": 4 + }, + "23": { + "ele": "none", + "point": 0 + }, + "24": { + "ele": "none", + "point": 0 + }, + "25": { + "ele": "none", + "point": 0 + }, + "26": { + "ele": "$[?(@.width == 65 && @.height == 35)]", + "type": "size", + "value": { + "width": 65, + "height": 35 + }, + "point": 5 + }, + "27": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "2": { + "1": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex", + "type": "mediaOrder", + "value": [ + "동영상.mp4", + "이미지3.jpg", + "이미지1.jpg", + "이미지2.jpg" + ], + "point": 4, + "desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다." + }, + "2": { + "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed", + "type": "oneAnswer", + "value": { + "speed": "140" + }, + "point": 2, + "desc": "100당 1배속 / 130 = 1.3배속" + }, + "3": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']", + "type": "startEnd", + "media": "동영상.mp4", + "value": { + "start": "0", + "end": "370" + }, + "point": 2, + "desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다." + }, + "4": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "effect", + "media": "동영상.mp4", + "value": { + "ID": "44", + "VID100": "8", + "VID103": "0.89999998" + }, + "point": 3, + "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요" + }, + "5": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr/@Name", + "search": "화단의 꽃들", + "type": "video.Text", + "value": "화단의 꽃들", + "point": 3 + }, + "6": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID102", + "search": "화단의 꽃들", + "type": "video.Text", + "value": "돋움체", + "point": 2 + }, + "7": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID101", + "search": "화단의 꽃들", + "type": "video.Text", + "value": "110", + "point": 2 + }, + "8": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4']/@VID100", + "search": "화단의 꽃들", + "type": "video.Text.Color", + "value": "ff531b", + "point": 2, + "desc": "컬러값은 RGB로 입력한다, [대소문자, #]허용 (#FFFFFF, ffffff 두 값 모두 허용)" + }, + "9": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr/@*[name()='VID600' or name()='VID601']", + "search": "화단의 꽃들", + "type": "video.Location", + "value": [ + "0.38333333", + "0.92962962" + ], + "point": 2, + "desc": "정답 파일의 자막 좌표를 기준으로 프로그램 내부적으로 0.1까지 오차를 허용한다" + }, + "10": { + "ele": "", + "search": "화단의 꽃들", + "type": "video.StartTime", + "value": 170, + "point": 2, + "desc": "내부적으로 자막의 시작시간과 길이를 계산" + }, + "11": { + "ele": "", + "search": "화단의 꽃들", + "type": "video.Length", + "value": 150, + "point": 2, + "desc": "내부적으로 자막의 시작시간과 길이를 계산" + }, + "12": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Mute", + "type": "Mute", + "media": "동영상.mp4", + "value": "1", + "point": 2 + }, + "13": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지3.jpg", + "value": "180", + "point": 2 + }, + "14": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지3.jpg", + "value": { + "ID": "67", + "VID102": "40" + }, + "point": 2 + }, + "15": { + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지3.jpg", + "value": { + "ID": "10", + "Range": "490:550", + "Type": "2" + }, + "point": 2, + "desc": "오버랩일 경우 Type속성값 16으로 변경" + }, + "16": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지1.jpg", + "value": "150", + "point": 2 + }, + "17": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지1.jpg", + "value": { + "ID": "184", + "VID102": "30" + }, + "point": 2 + }, + "18": { + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지1.jpg", + "value": { + "ID": "19", + "Range": "650:680", + "Type": "2" + }, + "point": 2, + "desc": "오버랩일 경우 Type속성값 16으로 변경" + }, + "19": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지2.jpg", + "value": "180", + "point": 2 + }, + "20": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지2.jpg", + "value": { + "ID": "67", + "VID102": "30" + }, + "point": 2 + }, + "21": { + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지2.jpg", + "value": { + "ID": "10", + "Range": "800:860", + "Type": "2" + }, + "point": 2, + "desc": "오버랩일 경우 Type속성값 16으로 변경" + }, + "22": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr/@Name", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "video.Text", + "value": "아름다운 꽃 축제 (Happy Flower Festival)", + "point": 3 + }, + "23": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID102", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "video.Text", + "value": "궁서체", + "point": 2 + }, + "24": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID101", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "video.Text", + "value": "140", + "point": 2 + }, + "25": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4']/@VID100", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "video.Text.Color", + "value": "fd5721", + "point": 2, + "desc": "컬러값은 RGB로 입력한다, [대소문자, #]허용 (#FFFFFF, ffffff 두 값 모두 허용)" + }, + "26": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='2']", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "video.Text.Outline", + "value": { + "width": "30", + "color": "fff9c4" + }, + "point": 2, + "desc": "두께는 XML에서는 소수점으로 표기되지만, 프로그램 내부적으로 변환하여 사용하므로 현재 파일에서는 정수로 작성" + }, + "27": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "opening.Text.FadeInEffect", + "value": { + "ID": "4", + "PlayTime": "2" + }, + "point": 3, + "desc": "오프닝자막의 나타나기 효과를 확인하는 문항. id속성은 VID505, playtime속성은 VID507으로 XML 내부에 표기되어 있다." + }, + "28": { + "ele": "{search}", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "opening.StartTime", + "value": 0, + "point": 2, + "desc": "오프닝자막의 시작시간 value 속성만 수정" + }, + "29": { + "ele": "{search}", + "search": "아름다운 꽃 축제 (Happy Flower Festival)", + "type": "opening.Length", + "value": 120, + "point": 2 + }, + "30": { + "ele": "", + "type": "audio.StartTime", + "media": "음악.mp3", + "value": 0, + "point": 2 + }, + "31": { + "ele": "//CRTrackList[@Name='오디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']", + "type": "audio.EndTime", + "media": "음악.mp3", + "value": 810, + "point": 2 + }, + "32": { + "ele": "//CRTrackList[@Name='오디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "audio.Effect", + "media": "음악.mp3", + "value": { + "ID": "1", + "PlayTime": "90" + }, + "point": 2, + "desc": "ID속성-페이드인:0 / 페이드아웃: 1" + }, + "33": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "4": { + "1": { + "type": "canvas.Size", + "ele": "//Document/Width/@value | //Document/Height/@value", + "value": [ + "650", + "350" + ], + "point": 5, + "desc": "캔버스 사이즈 650*350" + }, + "2": { + "type": "none", + "ele": "", + "point": 5, + "desc": "자유 변형 문항은 채점 불가" + }, + "3": { + "type": "layer.Exists", + "ele": "//Layer/Name/@value", + "value": "Flower", + "point": 5, + "desc": "Flower 레이어가 있는지 여부 체크" + }, + "4": { + "type": "layer.Effects", + "ele": "//Layer[Name[@value='{layer}']]/Effects/Item", + "layer": "Flower", + "value": { + "name": "세피아", + "option": { + "U":"75", + "V":"150" + } + }, + "point": 5, + "desc": "Flower 레이어의 효과 체크" + }, + "5": { + "ele": "none", + "point": 6, + "desc": "올가미 도구/이미지 문항은 채점 불가" + }, + "6": { + "type": "exists", + "ele": "//Layer/Effects/Item/Name/@value", + "value": "세피아", + "point": 6, + "desc": "세피아 효과가 있는지 여부 체크" + }, + "7": { + "type": "exact", + "ele": "//Layer/Shapes/Shape/shape_type/@value", + "value": "ELLIPSE", + "point": 3, + "desc": "레이어 쉐이프 타입이 타원인지 체크" + }, + "8": { + "type": "size", + "ele": "//Layer//op_points", + "value": { + "width": 120, + "height": 120 + }, + "point": 3, + "desc": "레이어 쉐이프 X, Y 좌표를 가지고 너비, 높이 계산하여 정답 채점" + }, + "9": { + "type": "color", + "ele": "//Layer//Shape[contains(draw_type/@value, 'Interior')]/secondary_color/@value", + "value": "7097bb", + "point": 6, + "desc": "색상 코드 비교 시 소문자로 입력할 것" + }, + "10": { + "type": "multiValue", + "ele": "//Layer/BlendOp/@value | //Layer/Opacity/@value", + "value": [ + "반사", + "80" + ], + "point": 6, + "desc": "혼합모드(색 회피율, 불투명도 : 80)" + }, + "11": { + "ele": "none", + "point": 0, + "desc": "기본설정" + }, + "12": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "5": { + "1": { + "type": "multi", + "ele": "//Document/Width/@value | //Document/Height/@value", + "value": [ + "650", + "450" + ], + "point": 5, + "desc": "캔버스 사이즈 650*450" + }, + "2": { + "ele": "none", + "point": 5, + "desc": "배경색 문항은 채점 불가" + }, + "3": { + "type": "exists", + "ele": "//Layer/MaskOpType/@value", + "value": "Layering", + "point": 6 + }, + "4": { + "ele": "none", + "point": 6, + "desc": "가로방향 흐릿하게 문항은 채점 불가" + }, + "5": { + "type": "exists", + "ele": "//Layer//shape_type/@value", + "value": "ROUNDED_RECTANGLE", + "point": 3 + }, + "6": { + "type": "size", + "ele": "//Layer//op_points", + "value": { + "width": 400, + "height": 60 + }, + "point": 3, + "desc": "레이어 쉐이프 X, Y 좌표를 가지고 너비, 높이 계산하여 정답 채점" + }, + "7": { + "type": "gradient", + "ele": "//Layer/Shapes/Shape", + "startColor": "gradient_start_color/@value", + "endColor": "gradient_end_color/@value", + "value": { + "startColor": "ffe000", + "endColor": "34a159" + }, + "point": 6 + }, + "8": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/lines/Item/@value", + "value": "흰 꽃 사이 노란 꽃", + "point": 5 + }, + "9": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/font/Name/@value", + "value": "맑은 고딕", + "point": 3 + }, + "10": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/font/{style}/@value", + "style": "Italic", + "value": "True", + "point": 3 + }, + "11": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/font/Size/@value", + "value": "30", + "point": 3 + }, + "12": { + "type": "color", + "ele": "//Layer//Shape[shape_type/@value='TEXT'][contains(draw_type/@value, 'Interior')]/secondary_color/@value", + "value": "b46ef8", + "point": 3, + "desc": "색상 코드 비교 시 소문자로 입력할 것" + }, + "13": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/outline_peninfo/Width/@value", + "value": "7", + "point": 3 + }, + "14": { + "type": "color", + "ele": "//Layer//Shape[shape_type/@value='TEXT'][contains(draw_type/@value, 'Outline')]/primary_color/@value", + "value": "ffffff", + "point": 3, + "desc": "색상 코드 비교 시 소문자로 입력할 것" + }, + "15": { + "ele": "//Layer[MaskOpType/@value='Clipping'][last()]", + "point": 6, + "desc": "클리핑 마스크 항목은 별도 레이어로 추가되고 해당 속성을 추가해놓은 레이어가 있는지 여부 체크 함" + }, + "16": { + "type": "exists", + "ele": "//Layer/Shapes/Shape/shape_type/@value", + "value": "RECTANGLE", + "point": 3 + }, + "17": { + "type": "size", + "ele": "//Layer//op_points", + "value": { + "width": 150, + "height": 150 + }, + "point": 3, + "desc": "레이어 쉐이프 X, Y 좌표를 가지고 너비, 높이 계산하여 정답 채점" + }, + "18": { + "type": "exists", + "ele": "//Layer//outline_peninfo/Width/@value", + "value": "7", + "point": 3 + }, + "19": { + "type": "color", + "ele": "//Layer//Shape[contains(draw_type/@value, 'Outline')]/primary_color/@value", + "value": "e8e88e", + "point": 3, + "desc": "색상 코드 비교 시 소문자로 입력할 것(채우기:secondary_color, 외곽선:primary_color)" + }, + "20": { + "type": "shadow", + "ele": { + "shadow": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]", + "width": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_width/@value", + "distance": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_distance/@value", + "blur": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_blur/@value", + "angle": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_angle/@value" + }, + "value": { + "width": "3", + "distance": "5", + "blur": "1", + "angle": "320" + }, + "point": 5, + "desc": "그림자 속성이 있는 경우 그림자 속성의 너비, 거리, 흐림 정도, 각도를 비교하여 정답 채점" + }, + "21": { + "ele": "none", + "point": 0, + "desc": "기본설정" + }, + "22": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + } +} \ No newline at end of file diff --git a/DIC_2505B copy.json b/DIC_2505B copy.json new file mode 100644 index 0000000..9095736 --- /dev/null +++ b/DIC_2505B copy.json @@ -0,0 +1,729 @@ +{ + "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": "size", + "value": { + "width": 65, + "height": 45 + }, + "point": 4 + }, + "9": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "1": { + "1": { + "ele": "none", + "point": 0 + }, + "2": { + "ele": "none", + "point": 0 + }, + "3": { + "ele": "none", + "point": 0 + }, + "4": { + "ele": "$.children[?(@.name=='한옥')].name", + "value": "한옥", + "point": 4 + }, + "5": { + "ele": "none", + "point": 0 + }, + "6": { + "ele": "$.children[?(@.name=='Traditional Park')].name", + "value": "Traditional Park", + "point": 4 + }, + "7": { + "ele": "$.children[?(@.name=='Traditional Park')].text.font.names[0]", + "type": "font", + "value": "Arial", + "point": 2 + }, + "8": { + "ele": "$.children[?(@.name=='Traditional Park')].text.font.names[0]", + "value": "Arial-BoldItalicMT", + "point": 2 + }, + "9": { + "ele": "$.children[?(@.name=='Traditional Park')].text.font.sizes[0]", + "value": 48, + "point": 2 + }, + "10": { + "ele": "$.children[?(@.name=='Traditional Park')].text.font.colors[0]", + "type": "color", + "value": "017e86", + "point": 2 + }, + "11": { + "ele": "none", + "point": 0 + }, + "12": { + "ele": "none", + "point": 0 + }, + "13": { + "ele": "none", + "point": 0 + }, + "14": { + "ele": "$.children[?(@.name=='전통 문화 공원')].name", + "value": "전통 문화 공원", + "point": 4 + }, + "15": { + "ele": "$.children[?(@.name=='전통 문화 공원')].text.font.names[0]", + "type": "font", + "value": "GungsuhChe", + "point": 2, + "desc": { + "돋움체": "DotumChe", + "궁서체": "GungsuhChe", + "굴림체": "GulimChe", + "휴먼옛체": "YetR" + } + }, + "16": { + "ele": "$.children[?(@.name=='전통 문화 공원')].text.font.sizes[0]", + "value": 36, + "point": 2 + }, + "17": { + "ele": "$.children[?(@.name=='전통 문화 공원')].text.font.colors[0]", + "type": "color", + "value": "ffeeca", + "point": 2 + }, + "18": { + "ele": "none", + "point": 0 + }, + "19": { + "ele": "none", + "point": 0 + }, + "20": { + "ele": "none", + "point": 0 + }, + "21": { + "ele": "none", + "point": 0 + }, + "22": { + "ele": "$.children[?(@.name=='맷돌')].name", + "value": "맷돌", + "point": 4 + }, + "23": { + "ele": "none", + "point": 0 + }, + "24": { + "ele": "none", + "point": 0 + }, + "25": { + "ele": "none", + "point": 0 + }, + "26": { + "ele": "$[?(@.width == 65 && @.height == 35)]", + "type": "size", + "value": { + "width": 65, + "height": 35 + }, + "point": 5 + }, + "27": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "2": { + "1": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex", + "type": "mediaOrder", + "value": [ + "동영상.mp4", + "이미지2.jpg", + "이미지3.jpg", + "이미지1.jpg" + ], + "point": 4, + "desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다." + }, + "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": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']", + "type": "startEnd", + "media": "동영상.mp4", + "value": { + "start": "0", + "end": "380" + }, + "point": 2, + "desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다." + }, + "4": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "effect", + "media": "동영상.mp4", + "value": { + "ID": "70", + "VID100": "30", + "VID101": "20" + }, + "point": 3, + "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요" + }, + "5": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr/@Name", + "search": "청량하고 시원한 폭포", + "type": "video.Text", + "value": "청량하고 시원한 폭포", + "point": 3 + }, + "6": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID102", + "search": "청량하고 시원한 폭포", + "type": "video.Text", + "value": "돋움체", + "point": 2 + }, + "7": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID101", + "search": "청량하고 시원한 폭포", + "type": "video.Text", + "value": "120", + "point": 2 + }, + "8": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4']/@VID100", + "search": "청량하고 시원한 폭포", + "type": "video.Text.Color", + "value": "1db0f1", + "point": 2, + "desc": "컬러값은 RGB로 입력한다, [대소문자, #]허용 (#FFFFFF, ffffff 두 값 모두 허용)" + }, + "9": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr/@*[name()='VID600' or name()='VID601']", + "search": "청량하고 시원한 폭포", + "type": "video.Location", + "value": [ + "0.27395833", + "0.9222222" + ], + "point": 2, + "desc": "정답 파일의 자막 좌표를 기준으로 프로그램 내부적으로 0.1까지 오차를 허용한다" + }, + "10": { + "ele": "", + "search": "청량하고 시원한 폭포", + "type": "video.StartTime", + "value": 170, + "point": 2, + "desc": "내부적으로 자막의 시작시간과 길이를 계산" + }, + "11": { + "ele": "", + "search": "청량하고 시원한 폭포", + "type": "video.Length", + "value": 150, + "point": 2, + "desc": "내부적으로 자막의 시작시간과 길이를 계산" + }, + "12": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Mute", + "type": "Mute", + "media": "동영상.mp4", + "value": "1", + "point": 2 + }, + "13": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지2.jpg", + "value": "150", + "point": 2 + }, + "14": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지2.jpg", + "value": { + "ID": "103", + "VID102": "7" + }, + "point": 2 + }, + "15": { + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지2.jpg", + "value": { + "ID": "11", + "Range": "500:530", + "Type": "2" + }, + "point": 2, + "desc": "오버랩일 경우 Type속성값 16으로 변경" + }, + "16": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지3.jpg", + "value": "150", + "point": 2 + }, + "17": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지3.jpg", + "value": { + "ID": "184", + "VID102": "30" + }, + "point": 2 + }, + "18": { + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지3.jpg", + "value": { + "ID": "19", + "Range": "650:680", + "Type": "2" + }, + "point": 2, + "desc": "오버랩일 경우 Type속성값 16으로 변경" + }, + "19": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']/@Length", + "type": "imageLength", + "media": "이미지1.jpg", + "value": "180", + "point": 2 + }, + "20": { + "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "imageOverlay", + "media": "이미지1.jpg", + "value": { + "ID": "67", + "VID102": "30" + }, + "point": 2 + }, + "21": { + "ele": "//CRTransFilter[@ClipIndex='{CRTrackClipIndex}']", + "type": "clipTransition", + "media": "이미지1.jpg", + "value": { + "ID": "10", + "Range": "800:860", + "Type": "2" + }, + "point": 2, + "desc": "오버랩일 경우 Type속성값 16으로 변경" + }, + "22": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr/@Name", + "search": "전통 공원 (Traditional Park)", + "type": "video.Text", + "value": "전통 공원 (Traditional Park)", + "point": 3 + }, + "23": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID102", + "search": "전통 공원 (Traditional Park)", + "type": "video.Text", + "value": "궁서체", + "point": 2 + }, + "24": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool[@Type='1']/GCUnit/@VID101", + "search": "전통 공원 (Traditional Park)", + "type": "video.Text", + "value": "140", + "point": 2 + }, + "25": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='4']/@VID100", + "search": "전통 공원 (Traditional Park)", + "type": "video.Text.Color", + "value": "fd5721", + "point": 2, + "desc": "컬러값은 RGB로 입력한다, [대소문자, #]허용 (#FFFFFF, ffffff 두 값 모두 허용)" + }, + "26": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr//GCUnitPool/GCUnit[@Type='2']", + "search": "전통 공원 (Traditional Park)", + "type": "video.Text.Outline", + "value": { + "width": "30", + "color": "fff9c4" + }, + "point": 2, + "desc": "두께는 XML에서는 소수점으로 표기되지만, 프로그램 내부적으로 변환하여 사용하므로 현재 파일에서는 정수로 작성" + }, + "27": { + "ele": "//CROwneUnit[{index}]/CRCUnitArr", + "search": "전통 공원 (Traditional Park)", + "type": "opening.Text.FadeInEffect", + "value": { + "ID": "4", + "PlayTime": "2" + }, + "point": 3, + "desc": "오프닝자막의 나타나기 효과를 확인하는 문항. id속성은 VID505, playtime속성은 VID507으로 XML 내부에 표기되어 있다." + }, + "28": { + "ele": "", + "search": "전통 공원 (Traditional Park)", + "type": "opening.StartTime", + "value": 0, + "point": 2, + "desc": "오프닝자막의 시작시간 value 속성만 수정" + }, + "29": { + "ele": "", + "search": "전통 공원 (Traditional Park)", + "type": "opening.Length", + "value": 120, + "point": 2 + }, + "30": { + "ele": "", + "type": "audio.StartTime", + "media": "음악.mp3", + "value": 0, + "point": 2 + }, + "31": { + "ele": "//CRTrackList[@Name='오디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']", + "type": "audio.EndTime", + "media": "음악.mp3", + "value": 810, + "point": 2 + }, + "32": { + "ele": "//CRTrackList[@Name='오디오1']/CRTrackClip[@ClipIndex='{CRClipIndex}']//CRFilter", + "type": "audio.Effect", + "media": "음악.mp3", + "value": { + "ID": "1", + "PlayTime": "90" + }, + "point": 2, + "desc": "ID속성-페이드인:0 / 페이드아웃:1" + }, + "33": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "4": { + "1": { + "type": "multi", + "ele": "//Document/Width/@value | //Document/Height/@value", + "value": [ + "650", + "350" + ], + "point": 5, + "desc": "캔버스 사이즈 650*350" + }, + "2": { + "ele": "none", + "point": 5, + "desc": "자유 변형 문항은 채점 불가" + }, + "3": { + "type": "isExist", + "ele": "//Layer/Name/@value", + "value": "Flower", + "point": 5, + "desc": "Flower 레이어가 있는지 여부 체크" + }, + "4": { + "type": "multiValue", + "ele": "//Layer[Name[@value='{layer}']]/Effects/Item[EffectData/{option}]/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value", + "layer": "Flower", + "option": "VibranceValue", + "value": [ + "39", + "생동감" + ], + "point": 5, + "desc": "Flower 레이어의 효과 체크" + }, + "5": { + "ele": "none", + "point": 6, + "desc": "올가미 도구/이미지 문항은 채점 불가" + }, + "6": { + "type": "exists", + "ele": "//Layer/Effects/Item/Name/@value", + "value": "세피아", + "point": 6, + "desc": "세피아 효과가 있는지 여부 체크" + }, + "7": { + "type": "exact", + "ele": "//Layer/Shapes/Shape/shape_type/@value", + "value": "ELLIPSE", + "point": 3, + "desc": "레이어 쉐이프 타입이 타원인지 체크" + }, + "8": { + "type": "size", + "ele": "//Layer//op_points", + "value": { + "width": 120, + "height": 120 + }, + "point": 3, + "desc": "레이어 쉐이프 X, Y 좌표를 가지고 너비, 높이 계산하여 정답 채점" + }, + "9": { + "type": "color", + "ele": "//Layer//Shape[contains(draw_type/@value, 'Interior')]/secondary_color/@value", + "value": "7097bb", + "point": 6, + "desc": "색상 코드 비교 시 소문자로 입력할 것" + }, + "10": { + "type": "multiValue", + "ele": "//Layer/BlendOp/@value | //Layer/Opacity/@value", + "value": [ + "반사", + "80" + ], + "point": 6, + "desc": "혼합모드(색 회피율, 불투명도 : 80)" + }, + "11": { + "ele": "none", + "point": 0, + "desc": "기본설정" + }, + "12": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + }, + "5": { + "1": { + "type": "multi", + "ele": "//Document/Width/@value | //Document/Height/@value", + "value": [ + "650", + "450" + ], + "point": 5, + "desc": "캔버스 사이즈 650*450" + }, + "2": { + "ele": "none", + "point": 5, + "desc": "배경색 문항은 채점 불가" + }, + "3": { + "type": "exists", + "ele": "//Layer/MaskOpType/@value", + "value": "Layering", + "point": 6 + }, + "4": { + "ele": "none", + "point": 6, + "desc": "가로방향 흐릿하게 문항은 채점 불가" + }, + "5": { + "type": "exists", + "ele": "//Layer//shape_type/@value", + "value": "ROUNDED_RECTANGLE", + "point": 3 + }, + "6": { + "type": "size", + "ele": "//Layer//op_points", + "value": { + "width": 400, + "height": 60 + }, + "point": 3, + "desc": "레이어 쉐이프 X, Y 좌표를 가지고 너비, 높이 계산하여 정답 채점" + }, + "7": { + "type": "gradient", + "ele": "//Layer/Shapes/Shape", + "startColor": "gradient_start_color/@value", + "endColor": "gradient_end_color/@value", + "value": { + "startColor": "ffe000", + "endColor": "34a159" + }, + "point": 6 + }, + "8": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/lines/Item/@value", + "value": "흰 꽃 사이 노란 꽃", + "point": 5 + }, + "9": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/font/Name/@value", + "value": "맑은 고딕", + "point": 3 + }, + "10": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/font/{style}/@value", + "style": "Italic", + "value": "True", + "point": 3 + }, + "11": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/font/Size/@value", + "value": "30", + "point": 3 + }, + "12": { + "type": "color", + "ele": "//Layer//Shape[shape_type/@value='TEXT'][contains(draw_type/@value, 'Interior')]/secondary_color/@value", + "value": "b46ef8", + "point": 3, + "desc": "색상 코드 비교 시 소문자로 입력할 것" + }, + "13": { + "type": "exists", + "ele": "//Layer//Shape[shape_type/@value='TEXT']/outline_peninfo/Width/@value", + "value": "7", + "point": 3 + }, + "14": { + "type": "color", + "ele": "//Layer//Shape[shape_type/@value='TEXT'][contains(draw_type/@value, 'Outline')]/primary_color/@value", + "value": "ffffff", + "point": 3, + "desc": "색상 코드 비교 시 소문자로 입력할 것" + }, + "15": { + "ele": "//Layer[MaskOpType/@value='Clipping'][last()]", + "point": 6, + "desc": "클리핑 마스크 항목은 별도 레이어로 추가되고 해당 속성을 추가해놓은 레이어가 있는지 여부 체크 함" + }, + "16": { + "type": "exists", + "ele": "//Layer/Shapes/Shape/shape_type/@value", + "value": "RECTANGLE", + "point": 3 + }, + "17": { + "type": "size", + "ele": "//Layer//op_points", + "value": { + "width": 150, + "height": 150 + }, + "point": 3, + "desc": "레이어 쉐이프 X, Y 좌표를 가지고 너비, 높이 계산하여 정답 채점" + }, + "18": { + "type": "exists", + "ele": "//Layer//outline_peninfo/Width/@value", + "value": "7", + "point": 3 + }, + "19": { + "type": "color", + "ele": "//Layer//Shape[contains(draw_type/@value, 'Outline')]/primary_color/@value", + "value": "e8e88e", + "point": 3, + "desc": "색상 코드 비교 시 소문자로 입력할 것(채우기:secondary_color, 외곽선:primary_color)" + }, + "20": { + "type": "shadow", + "ele": { + "shadow": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]", + "width": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_width/@value", + "distance": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_distance/@value", + "blur": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_blur/@value", + "angle": "//Layer//Shape[contains(draw_type/@value, 'Shadow')]/shadow_angle/@value" + }, + "value": { + "width": "3", + "distance": "5", + "blur": "1", + "angle": "320" + }, + "point": 5, + "desc": "그림자 속성이 있는 경우 그림자 속성의 너비, 거리, 흐림 정도, 각도를 비교하여 정답 채점" + }, + "21": { + "ele": "none", + "point": 0, + "desc": "기본설정" + }, + "22": { + "ele": "none", + "point": 0, + "desc": "파일명 확인" + } + } +} \ No newline at end of file diff --git a/DIC_2505B.json b/DIC_2505B.json index 73c20d1..b457875 100644 --- a/DIC_2505B.json +++ b/DIC_2505B.json @@ -464,7 +464,7 @@ "PlayTime": "90" }, "point": 2, - "desc": "ID속성-페이드인[0]/페이드아웃[1]" + "desc": "ID속성-페이드인:0 / 페이드아웃: 1" }, "33": { "ele": "none", diff --git a/gpdpScoring.js b/gpdpScoring.js index 9ff6ac0..0981e66 100644 --- a/gpdpScoring.js +++ b/gpdpScoring.js @@ -42,6 +42,54 @@ module.exports = getGpdpScore; // scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐 // 채점 결과를 scoringResultList 배열에 저장 function getGpdpScore(gpdpData, scoringJson, index) { + function compareAndScore(user, right, point, key, scoringResult, options = {}) { + let score = 0; + let 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 + (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('작성답안: ', user); + console.log('>⭕ 정답: ', right); + } else { + console.log('작성답안: ', user); + console.log('>❌ 오답: ', right); + } + + scoringResult[key] = score; + // totalScore += score; + return score; + } const gpdpXmlDoc = gpdpData; const scoringResult = {}; @@ -64,11 +112,13 @@ function getGpdpScore(gpdpData, scoringJson, index) { let ele = scoringData[key].ele; let ele2 = scoringData[key].ele2; let existEle = scoringData[key].existEle; - let rightAnswer = scoringData[key].value; - let point = scoringData[key].point; - let type = scoringData[key].type; + const rightAnswer = scoringData[key].value; + const point = scoringData[key].point; + const type = scoringData[key].type; let search = scoringData[key].search; + let userAnswer = null; + const layer = scoringData[key].layer; const option = scoringData[key].option; const style = scoringData[key].style; @@ -88,33 +138,151 @@ function getGpdpScore(gpdpData, scoringJson, index) { existEle = existEle.replace(/{search}/g, result); } } - console.log(`example number: ${key}`) - console.log("🚀 ~ getGpdpScore ~ ele:", ele) - // if (type == "effects") { - // // Layer/Effects/Item요소를 가져옴 - // const items = xpath.select(ele, gpdpXmlDoc); + console.log(`❔문제번호 : [4-${key}]`) - // // let isRight = false; - // // 각 Item 요소별 이름과 속성값을 구하고 정답과 비교 - // items.forEach((item) => { - // const name = xpath.select1('Name/@value', item)?.value; - // const value = xpath.select1(`EffectData/${option?.replace(/"/g, '')}/@value`, item)?.value; - // const resultArray = [name, value]; - // // if (isRight) return; - // if (JSON.stringify(resultArray) === JSON.stringify(rightAnswer)) { - // totalScore += point; - // scoringResult[key] = point; - // console.log("🚀 ~ 정답 일치:", rightAnswer); - // // isRight = true; - // return; - // } else { - // scoringResult[key] = 0; - // console.log("🚀 ~ 오답:", rightAnswer); - // } - // }); - // } - if (type == "boolean") { + if (type === "none") { + console.log("❌ 채점하지 않음"); + scoringResult[key] = "확인필요"; + continue; + } + + else if (type === "canvas.Size") { + const result = xpath.select(ele, gpdpXmlDoc); + const width = result[0].value; + const height = result[1].value; + + userAnswer = [width, height]; + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult, { type: 'number[]' }); + + continue; + } + + // [] + else if (type === "layer.Exists") { + const layerNameList = xpath.select(ele, gpdpXmlDoc); + const layerNames = layerNameList.map(layer => layer.value); + console.log("🚀 ~ getGpdpScore ~ layerNames:", layerNames); + + // userAnswer = layerNames.find(name => name === rightAnswer); + for (const layerName of layerNames) { + if (layerName.trim().toLowerCase() === rightAnswer.trim().toLowerCase()) { + userAnswer = layerName; + break; + } + } + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + continue; + } + + // [1-4] 사진1 > 조정 + else if (type === "layer.Effects") { + const effects = xpath.select(ele, gpdpXmlDoc); + + let isMatched = false; + for (const item of effects) { + const name = xpath.select1('Name/@value', item)?.value; + const effectData = xpath.select1(`EffectData`, item); + + // 동일한 이펙트 요소만 검사 + if (rightAnswer['name'] !== name) { + continue; + } + + userAnswer = { + name: name, + option: {}, + } + if (name === '흑백') { + const Intensity = xpath.select1('Intensity/@value', effectData)?.value; + + const optionKeys = Object.keys(rightAnswer['option']); + if (optionKeys.includes('강도')) userAnswer['option']['강도'] = Intensity; + } + else if (name === '밝기/대비') { + const brightness = xpath.select1('brightness/@value', effectData)?.value; + const contrast = xpath.select1('contrast/@value', effectData)?.value; + + const optionKeys = Object.keys(rightAnswer['option']); + if (optionKeys.includes('밝기')) userAnswer['option']['밝기'] = brightness; + if (optionKeys.includes('대비')) userAnswer['option']['대비'] = contrast; + } + else if (name === '노출') { + const ExposureValue = xpath.select1('ExposureValue/@value', effectData)?.value; + + const optionKeys = Object.keys(rightAnswer['option']); + if (optionKeys.includes('노출')) userAnswer['option']['노출'] = ExposureValue; + } + else if (name === '색조/채도') { + const hue = xpath.select1('hue/@value', effectData)?.value; + const saturation = xpath.select1('saturation/@value', effectData)?.value; + const lightness = xpath.select1('lightness/@value', effectData)?.value; + + const optionKeys = Object.keys(rightAnswer['option']); + if (optionKeys.includes('색조')) userAnswer['option']['색조'] = hue; + if (optionKeys.includes('채도')) userAnswer['option']['채도'] = saturation; + if (optionKeys.includes('명도')) userAnswer['option']['명도'] = lightness; + } + else if (name === '감마') { + const lift = xpath.select1('Lift/@value', effectData)?.value; + const gamma = xpath.select1('Gamma/@value', effectData)?.value; + const gain = xpath.select1('Gain/@value', effectData)?.value; + + const optionKeys = Object.keys(rightAnswer['option']); + if (optionKeys.includes('리프트')) userAnswer['option']['리프트'] = lift; + if (optionKeys.includes('감마')) userAnswer['option']['감마'] = gamma; + if (optionKeys.includes('게인')) userAnswer['option']['게인'] = gain; + } + else if (name === '세피아') { + const u = xpath.select1('U/@value', effectData)?.value; + const v = xpath.select1('V/@value', effectData)?.value; + + const optionKeys = Object.keys(rightAnswer['option']).map(key => key.toUpperCase()); + if (optionKeys.includes('U')) userAnswer['option']['U'] = u; + if (optionKeys.includes('V')) userAnswer['option']['V'] = v; + } + else if (name === '생동감') { + const vibranceValue = xpath.select1('VibranceValue/@value', effectData)?.value; + // 생동감 옵션값이 프로그램에서 적용한 값에 오차가 발생하는 경우가 있음 + // 곰픽>XML / 30>29 / 40>39 + // 설정한 값 그대로 적용되는 경우도 있어서 오차범위를 설정 + const userValue = parseInt(vibranceValue, 10); + const rightValue = parseInt(rightAnswer.option['생동감'], 10); + + if (Math.abs(rightValue - userValue) <= 2) { + const optionKeys = Object.keys(rightAnswer['option']); + if (optionKeys.includes('생동감')) { + userAnswer['option']['생동감'] = rightValue.toString(); + } + } + } + + for (const key in rightAnswer.option) { + // 속성값이 정답과 다른 경우가 있으면 오답처리 + if (rightAnswer.option[key] !== userAnswer.option[key]) { + isMatched = false; + break; + } + else { + isMatched = true; + } + } + + // 속성값이 하나라도 일치하지 않으면 오답 + if (isMatched === false) { + break; + } + } + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); + continue; + } + + + // [1-6] 세피아 + else if (type === "sepia.Exists") { + + } + else if (type == "boolean") { const items = xpath.select(ele, gpdpXmlDoc); // xpath 결과값을 반환하는 요소가 없을 경우 diff --git a/psdExport_2.js b/psdExport_2.js index 997574e..d98594c 100644 --- a/psdExport_2.js +++ b/psdExport_2.js @@ -13,9 +13,9 @@ const { userInfo } = require('os'); const { get } = require('http'); const todayDate = getToday(); -const examRound = '2505'; -const dic_or_dpi = 'DIC' -// const dic_or_dpi = 'DPI' +const examRound = '2504'; +// const dic_or_dpi = 'DIC' +const dic_or_dpi = 'DPI' const examTypes = [ // 'A', 'B', @@ -24,8 +24,8 @@ const examTypes = [ ]; // testMode가 true일 경우 TEST 폴더에 있는 답안 파일을 읽어옴 -// const testMode = false; -const testMode = true; +const testMode = false; +// const testMode = true; const outputExcelFiles = []; @@ -188,15 +188,24 @@ function getGmepScore(gmepData, scoringJson, index) { } else if (Array.isArray(right) && Array.isArray(user)) { if (right.length === user.length) { - if (type === 'number[]' || (type === 'auto' && typeof right[0] === 'number')) { + 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')) { + } + else if + (type === 'string[]' || + (type === 'auto' && typeof right[0] === 'string')) { isEqual = right.every((val, idx) => val === user[idx]); } } - } else if (type === 'object' || (type === 'auto' && typeof right === 'object' && typeof user === 'object')) { + } + else if + (type === 'object' || + (type === 'auto' && typeof right === 'object' && typeof user === 'object')) { isEqual = JSON.stringify(user) === JSON.stringify(right); - } else { + } + else { isEqual = user == right; // primitive 비교 } @@ -210,6 +219,7 @@ function getGmepScore(gmepData, scoringJson, index) { } scoringResult[key] = score; + // totalScore += score; return score; } @@ -246,6 +256,8 @@ function getGmepScore(gmepData, scoringJson, index) { // 채점기준표 문항별 분류 for (const key in scoringData) { + + // XML 요소의 index값을 구함 function getCRClipIndex(mediaName) { // CRClipArr/CRClip 요소의 Path속성 리스트를 구함 // 모션 클립 이미지도 고려해 처리 @@ -371,9 +383,6 @@ function getGmepScore(gmepData, scoringJson, 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 ? scoringData[key].type : ''; @@ -389,23 +398,7 @@ function getGmepScore(gmepData, scoringJson, index) { 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 subtitleOrder = type.includes('opening') ? 1 : type.includes('video') ? 2 : null; - const startTime = type === 'video' ? videoStartTime : type === 'opening' ? openingStartTime : null; - - 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(/{textClipIndex}/g, textClipIndex) - .replace(/{image}/g, image) - : e - ); - [ele, ele2, ele3, existEle] = xpathList; // search 값이 undefined 아니면 ele의 {search}부분을 search로 치환 /** @@ -420,9 +413,10 @@ function getGmepScore(gmepData, scoringJson, index) { if (result !== null) { result = result.replace(/"/g, "'"); search = result; - [ele, ele2, ele3, existEle] = [ele, ele2, ele3, existEle].map(e => e?.replace(/{search}/g, search)); - } else { - [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.includes('{search}') ? null : e); + ele = ele?.replace(/{search}/g, search); + } + else { + ele = ele?.includes('{search}') ? null : ele; } } @@ -432,11 +426,7 @@ function getGmepScore(gmepData, scoringJson, index) { continue; } - if (type == "boolean") { - scoringResult[key] = result.length > 0 ? point : 0; - } - - else if (type == "oneAnswer") { + if (type == "oneAnswer") { const result = xpath.select1(ele, gmepXmlDoc); // userAnswer = {}; @@ -668,7 +658,7 @@ function getGmepScore(gmepData, scoringJson, index) { const fadeoutEffect = xpath.select1(xpathQuery, gmepXmlDoc); if (!fadeoutEffect) { userAnswer = null; - } + } else { const attributes = fadeoutEffect.attributes; const id = attributes.getNamedItem('ID').value; @@ -697,6 +687,12 @@ function getGmepScore(gmepData, scoringJson, index) { const indexByStartTime = getCilpIndexByStartTime(time); // 1, 2, 3순으로 자막을 찾음 + + + // 권수혁 김재은 27, 28, 29 문제 오답 0점 채점 안되는 부분 처리 + // 06.18(목) 시작 + + const index = indexByText ?? indexByOrder ?? indexByStartTime; if (index != null) { // 자막 시작시간 [2-10] [2-28] @@ -780,6 +776,7 @@ function getGmepScore(gmepData, scoringJson, index) { } else { userAnswer = null; + totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); } } diff --git a/z.xbook b/z.xbook index a6c5450..9010b61 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":"//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 +[{"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":"//Document/Width/@value | //Document/Height/@value"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='Flower']]/Effects/Item[EffectData/VibranceValue]/(Name/@value | EffectData/VibranceValue/@value)"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='Flower']]/Effects/Item[EffectData/VibranceValue]/Name/@value | //Layer[Name[@value='Flower']]/Effects/Item/EffectData/VibranceValue/@value"}] \ No newline at end of file diff --git a/회차별채점자료/2504/excel_채점기준표/DIC_2504B.xlsx b/회차별채점자료/2504/excel_채점기준표/DIC_2504B.xlsx index 172bc18..b6cded6 100644 Binary files a/회차별채점자료/2504/excel_채점기준표/DIC_2504B.xlsx and b/회차별채점자료/2504/excel_채점기준표/DIC_2504B.xlsx differ diff --git a/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx b/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx index d9966dd..479d726 100644 Binary files a/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx and b/회차별채점자료/2505/excel_채점기준표/DIC_2505B.xlsx differ