Files
dic/gpdpScoring.js

442 lines
15 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;
const style = scoringData[key].style;
ele = typeof ele === 'string' ? ele.replace(/{layer}/g, layer) : ele;
ele = typeof ele === 'string' ? ele.replace(/{option}/g, option) : ele;
ele = typeof ele === 'string' ? ele.replace(/{style}/g, style) : 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 == "boolean") {
const items = xpath.select(ele, gpdpXmlDoc);
// xpath 결과값을 반환하는 요소가 없을 경우
if (!items) {
scoringResult[key] = 0;
console.log("❌ 찾는 요소 없음");
}
else {
totalScore += point;
scoringResult[key] = point;
console.log("✅ 찾는 요소 존재함");
}
}
// 이펙트 효과의 이름과 속성값을 비교
else 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 attr = xpath.select1(`EffectData/${option?.replace(/"/g, '')}/@value`, item)?.value;
if (name === rightAnswer[0] && attr === rightAnswer[1]) {
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 == "exists") {
const result = xpath.select(ele, gpdpXmlDoc);
const isMatch = result.some(v => {
// 문자열 앞뒤 공백 제거
v.value = typeof v.value === 'string' ? v.value.trim() : v.value
rightAnswer = typeof rightAnswer === 'string' ? rightAnswer.trim() : rightAnswer
if (v.value === rightAnswer) {
totalScore += point;
scoringResult[key] = point;
console.log(`✅ 정답 일치 > [작성답안]:${v.value} [정답]:${rightAnswer}`);
return true;
}
return false;
});
if (!isMatch) {
scoringResult[key] = 0;
console.log(`❌ 정답 없음 > [정답]:${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") {
const items = xpath.select(ele, gpdpXmlDoc);
let matched = false;
for (const item of items) {
const color = parseColorToHex(item.value);
// console.log("🚀 ~ getGpdpScore ~ color:", color);
// console.log("🚀 ~ getGpdpScore ~ rightColor:", rightAnswer);
if (color === rightAnswer) {
totalScore += point;
scoringResult[key] = point;
matched = true;
console.log(`✅ 정답 일치 > [작성답안]:${color} [정답]:${rightAnswer}`);
break;
}
}
if (!matched) {
scoringResult[key] = 0;
console.log(`❌ 정답 없음 > [정답]:${rightAnswer}`);
}
}
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") {
const items = xpath.select(ele, gpdpXmlDoc);
let matched = false;
// 각 Item 요소별 x,y 좌표 시작점과 끝점의 거리를 계산해 정답과 비교
for (const item of items) {
const x1 = Number(xpath.select1('Item[1]/X/@value', item)?.value);
const x2 = Number(xpath.select1('Item[last()]/X/@value', item)?.value);
const y1 = Number(xpath.select1('Item[1]/Y/@value', item)?.value);
const y2 = Number(xpath.select1('Item[last()]/Y/@value', item)?.value);
const width = Math.round(Math.abs(x2 - x1));
const height = Math.round(Math.abs(y2 - y1));
if (width === rightAnswer["width"] && height === rightAnswer["height"]) {
totalScore += point;
scoringResult[key] = point;
matched = true;
console.log("✅ 정답 일치:", rightAnswer);
break;
}
}
if (!matched) {
scoringResult[key] = 0;
console.log("❌ 정답 없음:", rightAnswer);
}
}
else if (type == "gradient") {
const items = xpath.select(ele, gpdpXmlDoc);
const startColorXpath = scoringData[key].startColor;
const endColorXpath = scoringData[key].endColor;
let matched = false;
for (const item of items) {
const startColor = parseColorToHex(xpath.select1(startColorXpath, item)?.value);
const endColor = parseColorToHex(xpath.select1(endColorXpath, item)?.value);
console.log(startColor + ":" + rightAnswer["startColor"], endColor + ":" + rightAnswer["endColor"]);
if (startColor === rightAnswer["startColor"] && endColor === rightAnswer["endColor"]) {
totalScore += point;
scoringResult[key] = point;
matched = true;
console.log("✅ 정답 일치:", rightAnswer);
break;
}
}
if (!matched) {
scoringResult[key] = 0;
console.log("❌ 정답 없음:", rightAnswer);
}
}
// 그림자 속성이 있는지 여부 파악해서 그림자 속성 별로 점수 1 점씩 부여
else if (type == "shadow") {
const result = xpath.select(ele["shadow"], gpdpXmlDoc);
let shadowScore = 0;
if (result.length == 0) {
scoringResult[key] = 0;
console.log('shadow not found');
continue;
}
shadowScore += 1;
const width = xpath.select(ele["width"], gpdpXmlDoc);
const distance = xpath.select(ele["distance"], gpdpXmlDoc);
const blur = xpath.select(ele["blur"], gpdpXmlDoc);
const 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 {
const result = xpath.select(ele, gpdpXmlDoc);
const 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;
}