Files
dic/gpdpScoring.js

419 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
const layerList = xpath.select('/Document/Layers/Layer', gpdpXmlDoc);
layerList.forEach((layer, layerIndex) => {
const childNodes = Array.from(layer.childNodes);
childNodes.forEach(child => {
// console.log("🚀 ~ child node:", child);
});
});
// gpdp 필요한 데이터 필터링
function getLayerData(gpdpXmlDoc) {
/**
* 3)레이어 마스크 여부
* 도형
* 5) 모양 (모서리가 둥근 사각형:ROUNDED_RECTANGLE / 사각형:RECTANGLE )
* 6) 크기
* 7) 그라데이션 (색상)
* 텍스트
* 8) 어린이 과학관
* 9) 글꼴(돋움)
* 10) 글꼴 스타일(기울임꼴)
* 11) 크기(32pt)
* 12) 채우기(색상 : F04DA5)
* 13) 외곽선(두께 : 3.00px)
* 14) 외곽선(색상 : FFF000)
* 15) 클리핑 마스크 설정
* 16) 원형/타원형
* 17) 크기 : 150 × 150
* 18) 외곽선(두께 : 7.00px)
* 19) 외곽선(색상 : 008878)
* 20) 그림자(두께 : 5.00px, 거리 : 3.00px, 분산도 : 1px, 각도 : 320°)
* */
}
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 == "effects") {
// // Layer/Effects/Item요소를 가져옴
// const items = xpath.select(ele, gpdpXmlDoc);
// // 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 == "effects") {
const items = xpath.select(ele, gpdpXmlDoc);
let matched = false;
// 각 Item 요소별 이름과 속성값을 구하고 정답과 비교
for (const item of items) {
const name = xpath.select1('Name/@value', item)?.value;
const value = xpath.select1(`EffectData/${option?.replace(/"/g, '')}/@value`, item)?.value;
const resultArray = [name, value];
if (JSON.stringify(resultArray) === JSON.stringify(rightAnswer)) {
totalScore += point;
scoringResult[key] = point;
matched = true;
console.log("✅ 정답 일치:", rightAnswer);
break;
}
}
if (!matched) {
scoringResult[key] = 0;
console.log("❌ 정답 없음:", rightAnswer);
}
}
else 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;
}