const xpath = require('xpath'); const { DOMParser } = require('xmldom'); function parseColorToHex(colorString) { // 정규식을 사용하여 B, G, R, A 값 추출 const regex = /B:\s*(\d+),\s*G:\s*(\d+),\s*R:\s*(\d+),\s*A:\s*(\d+)/; const matches = colorString.match(regex); if (!matches) { throw new Error('Invalid color string format'); } // matches[1]은 B, matches[2]는 G, matches[3]은 R, matches[4]는 A const [_, b, g, r, a] = matches; // 각 값을 16진수로 변환하고 2자리로 패딩 const rHex = parseInt(r).toString(16).padStart(2, '0'); const gHex = parseInt(g).toString(16).padStart(2, '0'); const bHex = parseInt(b).toString(16).padStart(2, '0'); const aHex = parseInt(a).toString(16).padStart(2, '0'); // #RRGGBBAA 형식으로 반환 return `${rHex}${gHex}${bHex}`; } module.exports = getGpdpScore; // xml 형식의 GPDP 파일을 읽어서 점수를 계산 // scoring.json 파일 내에 있는 ele 요소는 xpath 형식으로 접근하여 요소를 탐색하고 나오는 값을 value와 비교하여 점수를 계산 // scoring.json 파일 내에 있는 type은 비교할 값의 타입을 의미하며, boolean, array 등이 있음 // scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐 // 채점 결과를 scoringResultList 배열에 저장 function getGpdpScore(gpdpData, scoringJson, index) { const gpdpXmlDoc = gpdpData; const scoringResult = {}; const scoringData = scoringJson[index]; // console.log(scoringData); let totalScore = 0; // 채점기준표 문항별 분류 for (const key in scoringData) { 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; let search = scoringData[key].search; // search 값이 undefined 아니면 ele의 {search}부분을 search로 치환 /** * JSON파일 곰믹스 5번문항/22번 문항 * type : "subtitle" 인 항목들 * GPString태그 VID7속성 찾는 xpath구문 * CRCUnitArr태그 Name속성 찾는 구문으로 변환 * > 멀티라인 텍스트 유사도 판별하기 어려움 */ if (search !== undefined) { let result = findSimilarString(gpdpXmlDoc, search, 0.8) // xpath 내부 "(큰따옴표) 필터링 if (result !== null) { result = result.replace(/"/g, "'"); } ele = ele.replace(/{search}/g, result); if (existEle !== undefined) { existEle = existEle.replace(/{search}/g, result); } } console.log(`example number: ${key}`) if (type == "exact") { let result = xpath.select(ele, gpdpXmlDoc); if (result.length == 0) { scoringResult[key] = 0; console.log('ele not found'); continue; } if (result[0].value === rightAnswer) { totalScore += point; scoringResult[key] = point; } else { scoringResult[key] = 0; console.log('ele not matched, ' + result[0].value); } } else if (type == "color") { let result = xpath.select(ele, gpdpXmlDoc); if (result.length == 0) { scoringResult[key] = 0; console.log('ele not found'); continue; } const hexColor = parseColorToHex(result[0].value); if (hexColor === rightAnswer) { totalScore += point; scoringResult[key] = point; console.log('color matched, ' + hexColor); } else { scoringResult[key] = 0; console.log('color not matched, ' + hexColor); } } else if (type == "multi") { try { const result = xpath.select(ele, gpdpXmlDoc); let isSame = true; // console.log(`ele: ${ele}, value: ${value} result: ${result}`); if (result.length == 0) { console.log('result length 0'); scoringResult[key] = 0; continue; } result.forEach((v, i) => { // value[i] 값이 정수형인 경우에는 float로 변환하여 비교 // 정수형 v값을 float 형으로 변환하고 소수점 3자리까지 버림 let temp = v.value; let answer = rightAnswer[i]; if (Number.isFinite(rightAnswer[i]) && !Number.isInteger(rightAnswer[i])) { temp = parseFloat(v.value); answer = parseFloat(rightAnswer[i]); // 소수점 3자리까지 버림 temp = Math.floor(temp * 1000) / 1000; } // answer 문자열 중 : 가 포함되어 있다면 각각 분리하고 그 값의 차이를 구함 if (typeof answer == "string" && answer.indexOf(':') > -1) { const [answerStart, answerEnd] = answer.split(':').map(Number); const [tempStart, tempEnd] = temp.split(':').map(Number); answer = answerEnd - answerStart; temp = tempEnd - tempStart; } console.log(`temp: ${temp} answer: ${answer}`); if (answer !== temp) { console.log(`answer !== temp`); isSame = false; } }); totalScore += isSame ? point : 0; scoringResult[key] = isSame ? point : 0; } catch (e) { console.log('err :', e); scoringResult[key] = 0; } } else if (type == "size") { let posX = scoringData[key].posX; let posY = scoringData[key].posY; let answerWidth = rightAnswer["width"]; let answerHeight = rightAnswer["height"]; let width = xpath.select(posX, gpdpXmlDoc); let height = xpath.select(posY, gpdpXmlDoc); width = Math.round(width); height = Math.round(height); console.log(`width:${answerWidth},${width}, height: ${answerHeight},${height}`); if (answerWidth === width && answerHeight === height) { totalScore += point; scoringResult[key] = point; console.log("same size"); } else { scoringResult[key] = 0; console.log("different size"); } } else if (type == "gradient") { let startColor = scoringData[key].startColor; let endColor = scoringData[key].endColor; let answerStartColor = rightAnswer["startColor"]; let answerEndColor = rightAnswer["endColor"]; let start = xpath.select(startColor, gpdpXmlDoc); let end = xpath.select(endColor, gpdpXmlDoc); // console.log(start[0].value, end[0].value); if (start.length == 0 || end.length == 0) { console.log("gradient color not found"); scoringResult[key] = 0; continue; } const startHexColor = parseColorToHex(start[0].value); const endHexColor = parseColorToHex(end[0].value); console.log(startHexColor + ":" + answerStartColor, endHexColor + ":" + answerEndColor); if (startHexColor === answerStartColor && endHexColor === answerEndColor) { totalScore += point; scoringResult[key] = point; console.log("same color"); } else { scoringResult[key] = 0; console.log("different color"); } } // 그림자 속성이 있는지 여부 파악해서 그림자 속성 별로 점수 1 점씩 부여 else if(type == "shadow"){ let result = xpath.select(ele["shadow"], gpdpXmlDoc); let shadowScore = 0; if (result.length == 0) { scoringResult[key] = 0; console.log('shadow not found'); continue; } shadowScore += 1; let width = xpath.select(ele["width"], gpdpXmlDoc); let distance = xpath.select(ele["distance"], gpdpXmlDoc); let blur = xpath.select(ele["blur"], gpdpXmlDoc); let angle = xpath.select(ele["angle"], gpdpXmlDoc); if(width.length !== 0 && width[0].value == rightAnswer["width"]){ shadowScore += 1; console.log('width matched'); } if(distance.length !== 0 && distance[0].value == rightAnswer["distance"]){ shadowScore += 1; console.log('distance matched'); } if(blur.length !== 0 && blur[0].value == rightAnswer["blur"]){ shadowScore += 1; console.log('blur matched'); } if(angle.length !== 0 && angle[0].value == rightAnswer["angle"]){ shadowScore += 1; console.log('angle matched'); } totalScore += shadowScore; scoringResult[key] = shadowScore; } else { let result = xpath.select(ele, gpdpXmlDoc); let result2 = null; let isCheck = false; if (ele === 'none') { scoringResult[key] = "확인필요"; continue; } if (result.length == 0) { isCheck = true; } if (isCheck && ele2) { result2 = xpath.select(ele2, gpdpXmlDoc); if (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; } } scoringResult['총점'] = totalScore; return scoringResult; }