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; /** * /Document/Layers/Layer/Shapes/Shape/draw_type 속성값 * > Interior: 내부 채우기 / Outline: 외곽선 * /Document/Layers/Layer/Shapes/Shape/interior_type 속성값 * > Fill: 채우기 / Gradient: 그라데이션 * @param {*} gpdpData * @param {*} scoringJson * @param {*} index * @returns */ // 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; const layer = scoringData[key].layer; const option = scoringData[key].option; ele = typeof ele === 'string' ? ele.replace(/{layer}/g, layer) : ele; ele = typeof ele === 'string' ? ele.replace(/{option}/g, option) : ele; 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}`) console.log("🚀 ~ getGpdpScore ~ ele:", ele) if (type == "multiValue") { if (Array.isArray(rightAnswer)) { const result = ele ? xpath.select(ele, gpdpXmlDoc) : []; const resultValues = Array.isArray(result) ? result.map(r => (typeof r === 'object' ? r.value : r)) : [result]; console.log("🚀 ~ getGpdpScore ~ resultValues:", resultValues) const groupSize = rightAnswer.length; const groupedResult = []; for (let i = 0; i < resultValues.length; i += groupSize) { groupedResult.push(resultValues.slice(i, i + groupSize)); } console.log("🚀 ~ getGpdpScore ~ groupedResult:", groupedResult) // 배열 비교 함수 function arraysEqual(arr1, arr2) { if (arr1.length !== arr2.length) return false; return arr1.every((value, index) => value === arr2[index]); } // groupedResult 내부 배열에서 rightAnswer와 일치하는 배열이 있는지 확인 const isMatch = groupedResult.some(group => arraysEqual(group, rightAnswer)); if (isMatch) { totalScore += point; scoringResult[key] = point; console.log("🚀 ~ 정답 포함"); } else { scoringResult[key] = 0; console.log("🚀 ~ 오답"); } } } else if (type == "isExist" ) { const result = xpath.select(ele, gpdpXmlDoc); const isMatch = result.some( v => { if ( v.value === rightAnswer ) { totalScore += point; scoringResult[key] = point; console.log("🚀 ~ result.forEach ~ 정답 일치:", rightAnswer) return true; } return false; }); if (!isMatch) { scoringResult[key] = 0; console.log("🚀 ~ result.forEach ~ 오답:", rightAnswer) } } else 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; }