메인소스코드 정리 및 시험타입별 채점 한번에 처리 가능하도록 변경
This commit is contained in:
Binary file not shown.
BIN
250408_DIC_2503A_채점결과.xlsx
Normal file
BIN
250408_DIC_2503A_채점결과.xlsx
Normal file
Binary file not shown.
BIN
250408_DIC_2503B_채점결과.xlsx
Normal file
BIN
250408_DIC_2503B_채점결과.xlsx
Normal file
Binary file not shown.
BIN
250408_DIC_2503C_채점결과.xlsx
Normal file
BIN
250408_DIC_2503C_채점결과.xlsx
Normal file
Binary file not shown.
466
psdExport_2.js
466
psdExport_2.js
@@ -11,266 +11,123 @@ const getGpdpScore = require('./gpdpScoring.js');
|
|||||||
const getToday = require('./getToday.js');
|
const getToday = require('./getToday.js');
|
||||||
const todayDate = getToday();
|
const todayDate = getToday();
|
||||||
|
|
||||||
// --------------------------------------------------------
|
const examType = [
|
||||||
// const scoringJson = require('./DIC_2503A.json');
|
'A',
|
||||||
const scoringJson = require('./DIC_2503B.json');
|
'B',
|
||||||
// const scoringJson = require('./DIC_2503C.json');
|
'C',
|
||||||
// const scoringJson = require('./DIC_2503D.json');
|
// 'D'
|
||||||
|
];
|
||||||
|
const examRound = '2503';
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// testMode가 true일 경우 TEST 폴더에 있는 답안 파일을 읽어옴
|
||||||
// const answerFilesDir = './output/A/DIC';
|
// const testMode = true;
|
||||||
const answerFilesDir = './output/B/DIC';
|
const testMode = false;
|
||||||
// const answerFilesDir = './output/C/DIC';
|
|
||||||
// const answerFilesDir = './output/D/DIC';
|
|
||||||
|
|
||||||
// TEST
|
const outputExcelFiles = [];
|
||||||
// const answerFilesDir = './output/A/TEST';
|
|
||||||
// const answerFilesDir = './output/B/TEST';
|
|
||||||
// const answerFilesDir = './output/C/TEST';
|
|
||||||
// const answerFilesDir = './output/D/TEST';
|
|
||||||
|
|
||||||
// --------------------------------------------------------
|
examType.forEach(type => {
|
||||||
// const outputExcelFile = './'+todayDate+'_DIC_2503A_채점결과.xlsx';
|
const scoringJson = require(`./DIC_${examRound}${type}.json`);
|
||||||
const outputExcelFile = './'+todayDate+'_DIC_2503B_채점결과.xlsx';
|
const answerFilesDir = `./output/${type}/${testMode ? 'TEST' : 'DIC'}`;
|
||||||
// const outputExcelFile = './'+todayDate+'_DIC_2503C_채점결과.xlsx';
|
const outputExcelFile = `./${todayDate}_DIC_${examRound}${type}_${testMode ? 'TEST.xlsx' : '채점결과.xlsx'}`;
|
||||||
// const outputExcelFile = './'+todayDate+'_DIC_2503D_채점결과.xlsx';
|
|
||||||
|
|
||||||
// TEST
|
// 답안 폴더 내부에 디렉토리가 아닌 일반 파일이 있을 경우 디렉토리만 필터링 해서 불러옴
|
||||||
// const outputExcelFile = './'+todayDate+'_DIC_2503A_TEST.xlsx';
|
const studentDirs = fs.readdirSync(answerFilesDir).filter(file => {
|
||||||
// const outputExcelFile = './'+todayDate+'_DIC_2503B_TEST.xlsx';
|
const filePath = path.join(answerFilesDir, file);
|
||||||
// const outputExcelFile = './'+todayDate+'_DIC_2503C_TEST.xlsx';
|
return fs.statSync(filePath).isDirectory();
|
||||||
// const outputExcelFile = './'+todayDate+'_DIC_2503D_TEST.xlsx';
|
|
||||||
|
|
||||||
// --------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
// 답안 폴더 내부에 디렉토리가 아닌 일반 파일이 있을 경우 디렉토리만 필터링 해서 불러옴
|
|
||||||
const studentDirs = fs.readdirSync(answerFilesDir).filter(file => {
|
|
||||||
const filePath = path.join(answerFilesDir, file);
|
|
||||||
return fs.statSync(filePath).isDirectory();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 채점 결과 리스트
|
|
||||||
const scoringResultList = [];
|
|
||||||
const psdData = [];
|
|
||||||
|
|
||||||
studentDirs.forEach(student => {
|
|
||||||
// 맥에서 한글 디렉토리 이름을 읽어서 엑셀에 저장 할 시 자소 분리가 되어 저장되는 문제 노말라이즈해서 해결
|
|
||||||
const name = student.normalize('NFC');
|
|
||||||
const studentDir = path.join(answerFilesDir, student);
|
|
||||||
const psdFiles = fs.readdirSync(studentDir).filter(file => file.endsWith('.psd'));
|
|
||||||
// DIAT시험 프로젝트로 생성시 gmep확장자로
|
|
||||||
// 교육용 프로젝프로 생성시 gmdp확장자로 생성됨
|
|
||||||
// 두 경우 모두 처리
|
|
||||||
const gmepFile = fs.readdirSync(studentDir).filter(
|
|
||||||
file => file.endsWith('.gmep') || file.endsWith('.gmdp')
|
|
||||||
);
|
|
||||||
|
|
||||||
// 곰픽 파일 gpdp 파일 이거나 xml 파일
|
|
||||||
const gpdpFiles = fs.readdirSync(studentDir).filter(
|
|
||||||
file => file.endsWith('.xml')
|
|
||||||
);
|
|
||||||
|
|
||||||
// 학생 이름을 key로 하는 객체 생성
|
|
||||||
// 채점결과
|
|
||||||
const scoringResult = {
|
|
||||||
0: name
|
|
||||||
};
|
|
||||||
|
|
||||||
psdFiles.forEach((psdFile, index) => {
|
|
||||||
const psdPath = path.join('./', studentDir, psdFile);
|
|
||||||
console.log(`Reading ${psdPath}...`);
|
|
||||||
try {
|
|
||||||
const psdFileData = psd.fromFile(psdPath);
|
|
||||||
psdFileData.parse();
|
|
||||||
psdData[index] = psdFileData;
|
|
||||||
scoringResult[index + 1] = getScore(psdData, scoringJson, index);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error reading PSD file: ${psdPath}`, error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
gpdpFiles.forEach((gpdpFile, index) => {
|
|
||||||
const gpdpPath = path.join('./', studentDir, gpdpFile);
|
|
||||||
console.log(`Reading ${gpdpPath}...`);
|
|
||||||
|
|
||||||
const xmlString = fs.readFileSync(gpdpPath, 'utf8');
|
// 채점 결과 리스트
|
||||||
// XML 문자열을 파싱하여 XML 문서 객체로 변환
|
const scoringResultList = [];
|
||||||
const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml');
|
const psdData = [];
|
||||||
// console.log('xmlDocument:', xmlDocument);
|
|
||||||
|
|
||||||
scoringResult[index + 1] = getGpdpScore(xmlDocument, scoringJson, index + 4);
|
studentDirs.forEach(student => {
|
||||||
});
|
// 맥에서 한글 디렉토리 이름을 읽어서 엑셀에 저장 할 시 자소 분리가 되어 저장되는 문제 노말라이즈해서 해결
|
||||||
gmepFile.forEach((gmep, index) => {
|
const name = student.normalize('NFC');
|
||||||
const gmepPath = path.join('./', studentDir, gmep);
|
const studentDir = path.join(answerFilesDir, student);
|
||||||
console.log(`Reading ${gmepPath}...`);
|
const psdFiles = fs.readdirSync(studentDir).filter(file => file.endsWith('.psd'));
|
||||||
|
// DIAT시험 프로젝트로 생성시 gmep확장자로
|
||||||
|
// 교육용 프로젝프로 생성시 gmdp확장자로 생성됨
|
||||||
|
// 두 경우 모두 처리
|
||||||
|
const gmepFile = fs.readdirSync(studentDir).filter(
|
||||||
|
file => file.endsWith('.gmep') || file.endsWith('.gmdp')
|
||||||
|
);
|
||||||
|
|
||||||
const xmlString = fs.readFileSync(gmepPath, 'utf8');
|
// 곰픽 파일 gpdp 파일 이거나 xml 파일
|
||||||
// XML 문자열을 파싱하여 XML 문서 객체로 변환
|
const gpdpFiles = fs.readdirSync(studentDir).filter(
|
||||||
const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml');
|
file => file.endsWith('.xml')
|
||||||
// console.log('xmlDocument:', xmlDocument);
|
);
|
||||||
|
|
||||||
scoringResult[3] = getGmepScore(xmlDocument, scoringJson, 2);
|
// 학생 이름을 key로 하는 객체 생성
|
||||||
});
|
// 채점결과
|
||||||
scoringResultList.push(scoringResult);
|
const scoringResult = {
|
||||||
});
|
0: name
|
||||||
|
};
|
||||||
|
|
||||||
// // Flatten the resultData for better representation in Excel
|
psdFiles.forEach((psdFile, index) => {
|
||||||
// const flattenedData = scoringResultList.map(student => {
|
const psdPath = path.join('./', studentDir, psdFile);
|
||||||
// // const name = student["0"];
|
console.log(`Reading ${psdPath}...`);
|
||||||
// const flattened = { "학생": student["0"] };
|
try {
|
||||||
|
const psdFileData = psd.fromFile(psdPath);
|
||||||
// // excel에 표시하지 않을 key값들
|
psdFileData.parse();
|
||||||
// const exceptKeys = [
|
psdData[index] = psdFileData;
|
||||||
// "0", // 학생 이름 항상 제외
|
scoringResult[index + 1] = getScore(psdData, scoringJson, index);
|
||||||
// // "1", // psd1
|
} catch (error) {
|
||||||
// // "2", // psd2
|
console.error(`Error reading PSD file: ${psdPath}`, error);
|
||||||
// ]
|
|
||||||
// const exceptSubkeys = [
|
|
||||||
// "videoStartTime",
|
|
||||||
// "openingStartTime",
|
|
||||||
// ];
|
|
||||||
// Object.keys(student).forEach(key => {
|
|
||||||
// if (exceptKeys.includes(key)) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// Object.keys(student[key]).forEach(subKey => {
|
|
||||||
// if (exceptSubkeys.includes(subKey)) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// flattened[`${key}-${subKey}`] = student[key][subKey];
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// return flattened;
|
|
||||||
// });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scoringResultList 배열을 엑셀에 출력하기 위한 데이터 정리 함수
|
|
||||||
* @param {Array} scoringResultList - 학생별 채점 결과 리스트
|
|
||||||
* @returns {Array} - 엑셀에 출력할 데이터 배열
|
|
||||||
*/
|
|
||||||
function prepareExcelData(scoringResultList) {
|
|
||||||
return scoringResultList.map(student => {
|
|
||||||
// const flattened = { "학생": student["0"] }; // 학생 이름을 첫 번째 열로 설정
|
|
||||||
const flattened = { "문항": student["0"] }; // 행열을 변환 할 경우 첫 행의 제목을 "문항"으로 설정
|
|
||||||
|
|
||||||
// 제외할 키와 서브키 정의
|
|
||||||
const exceptKeys = [
|
|
||||||
"0", // 학생 이름 제외
|
|
||||||
// "1", // psd1
|
|
||||||
// "2", // psd2
|
|
||||||
];
|
|
||||||
const exceptSubkeys = ["videoStartTime", "openingStartTime"]; // 제외할 서브키
|
|
||||||
|
|
||||||
// 학생 데이터 순회
|
|
||||||
Object.keys(student).forEach(key => {
|
|
||||||
if (exceptKeys.includes(key)) {
|
|
||||||
return; // 제외할 키는 건너뜀
|
|
||||||
}
|
|
||||||
|
|
||||||
// 서브키 순회
|
|
||||||
if (typeof student[key] === "object") {
|
|
||||||
Object.keys(student[key]).forEach(subKey => {
|
|
||||||
if (exceptSubkeys.includes(subKey)) {
|
|
||||||
return; // 제외할 서브키는 건너뜀
|
|
||||||
}
|
|
||||||
flattened[`${key}-${subKey}`] = student[key][subKey];
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 서브키가 없는 경우
|
|
||||||
flattened[key] = student[key];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
gpdpFiles.forEach((gpdpFile, index) => {
|
||||||
|
const gpdpPath = path.join('./', studentDir, gpdpFile);
|
||||||
|
console.log(`Reading ${gpdpPath}...`);
|
||||||
|
|
||||||
return flattened;
|
const xmlString = fs.readFileSync(gpdpPath, 'utf8');
|
||||||
});
|
// XML 문자열을 파싱하여 XML 문서 객체로 변환
|
||||||
}
|
const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml');
|
||||||
// console.log(flattenedData);
|
// console.log('xmlDocument:', xmlDocument);
|
||||||
const flattenedData = prepareExcelData(scoringResultList);
|
|
||||||
|
|
||||||
function transposeData(data) {
|
scoringResult[index + 1] = getGpdpScore(xmlDocument, scoringJson, index + 4);
|
||||||
// 데이터가 없으면 빈 배열 반환
|
|
||||||
if (data.length === 0) return [];
|
|
||||||
|
|
||||||
// 첫 번째 객체의 키(열 제목) 가져오기
|
|
||||||
const keys = Object.keys(data[0]);
|
|
||||||
|
|
||||||
// 행과 열을 변환
|
|
||||||
const transposed = keys.map(key => {
|
|
||||||
const row = { "항목": key }; // 각 열 제목을 "항목"으로 설정
|
|
||||||
data.forEach((item, index) => {
|
|
||||||
//console.log(data[index]['문항']);
|
|
||||||
row[data[index]['문항']] = item[key]; // 각 학생의 데이터를 열로 추가
|
|
||||||
});
|
});
|
||||||
return row;
|
gmepFile.forEach((gmep, index) => {
|
||||||
|
const gmepPath = path.join('./', studentDir, gmep);
|
||||||
|
console.log(`Reading ${gmepPath}...`);
|
||||||
|
|
||||||
|
const xmlString = fs.readFileSync(gmepPath, 'utf8');
|
||||||
|
// XML 문자열을 파싱하여 XML 문서 객체로 변환
|
||||||
|
const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml');
|
||||||
|
// console.log('xmlDocument:', xmlDocument);
|
||||||
|
|
||||||
|
scoringResult[3] = getGmepScore(xmlDocument, scoringJson, 2);
|
||||||
|
});
|
||||||
|
scoringResultList.push(scoringResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
return transposed;
|
const flattenedData = prepareExcelData(scoringResultList);
|
||||||
}
|
const transposedData = transposeData(flattenedData);
|
||||||
|
|
||||||
const transposedData = transposeData(flattenedData);
|
// 엑셀 파일 생성
|
||||||
// const transposedData = transposeData(flattenedData).slice(1)
|
const worksheet = XLSX.utils.json_to_sheet(transposedData, { skipHeader: true });
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
|
||||||
// 엑셀 파일 생성
|
// 열 너비 계산
|
||||||
const worksheet = XLSX.utils.json_to_sheet(transposedData, {skipHeader: true});
|
const columnWidths = Object.keys(transposedData[0]).map(key => {
|
||||||
const workbook = XLSX.utils.book_new();
|
// 각 열의 최대 길이를 계산
|
||||||
|
const maxLength = Math.max(
|
||||||
|
// key.length, // 열 제목의 길이
|
||||||
|
// ...transposedData.map(row => (row[key] ? row[key].toString().length : 0)) // 각 셀의 데이터 길이
|
||||||
|
4 // 고정 너비
|
||||||
|
);
|
||||||
|
return { wch: maxLength + 1 }; // 여유 공간 추가
|
||||||
|
});
|
||||||
|
|
||||||
// 열 너비 계산
|
// 열 너비 설정
|
||||||
const columnWidths = Object.keys(transposedData[0]).map(key => {
|
worksheet['!cols'] = columnWidths;
|
||||||
const maxLength = Math.max(
|
// Add the worksheet to the workbook
|
||||||
// key.length, // 열 제목의 길이
|
XLSX.utils.book_append_sheet(workbook, worksheet, '채점 결과');
|
||||||
// ...transposedData.map(row => (row[key] ? row[key].toString().length : 0)) // 각 셀의 데이터 길이
|
|
||||||
4 // 고정 너비
|
// 엑셀 파일 저장
|
||||||
);
|
XLSX.writeFile(workbook, outputExcelFile);
|
||||||
return { wch: maxLength + 1 }; // 여유 공간 추가
|
outputExcelFiles.push(outputExcelFile);
|
||||||
});
|
});
|
||||||
|
console.log('채점 결과가 ' + outputExcelFiles + ' 파일에 저장되었습니다.');
|
||||||
// 열 너비 설정
|
|
||||||
worksheet['!cols'] = columnWidths;
|
|
||||||
// Add the worksheet to the workbook
|
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, '채점 결과');
|
|
||||||
|
|
||||||
// 엑셀 파일 저장
|
|
||||||
XLSX.writeFile(workbook, outputExcelFile);
|
|
||||||
console.log('채점 결과가 ' + outputExcelFile + ' 파일에 저장되었습니다.');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 자막 태그의 인덱스를 구할 때 사용
|
|
||||||
* 1. CRTrackClip 요소의 순서에 따라 그 요소에 해당하는 CROwneUnit 태그의 순서를 구함
|
|
||||||
* 2. CRTrackClip 요소의 시작시간에 따라 그 요소에 해당하는 CROwneUnit 태그의 순서를 구함
|
|
||||||
*/
|
|
||||||
function getTrackClipNode(xmlDoc, type, videoStartTime, openingStartTime) {
|
|
||||||
let trackClipNode = null;
|
|
||||||
|
|
||||||
// 동영상 자막이면 2, 오프닝 자막이면 1, 그 외는 0
|
|
||||||
const subtitleOrder = type === 'video' ? 2 : type === 'opening' ? 1 : null;
|
|
||||||
const startTime = type === 'video' ? videoStartTime : openingStartTime;
|
|
||||||
|
|
||||||
// xpath 구문을 통해 CRTrackClip 요소의 ClipIndex를 찾음
|
|
||||||
const trackClipNode1 = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][${subtitleOrder}]`, xmlDoc);
|
|
||||||
const trackClipNode2 = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = ${startTime}]`, xmlDoc);
|
|
||||||
|
|
||||||
return trackClipNode = trackClipNode1 ?? trackClipNode2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 자막텍스트를 이용해 자막 태그의 인덱스를 구할 때 사용
|
|
||||||
* 1. 자막 텍스트의 유사도를 판별
|
|
||||||
* 2. 자막텍스트와 일치하는 자막요소(CROWneUnit)의 순서를 구함
|
|
||||||
*/
|
|
||||||
function getClipIndexBySubtitle(xmlDoc, search) {
|
|
||||||
// 1. search값이 일치하지 않는 경우 : count가 0이 되어 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [오류]
|
|
||||||
// 2. search값이 일치하는 경우
|
|
||||||
// 1) search값이 CROwneUnit[1]이면 : preceding-sibling::CROwneUnit이 없어서 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [정상]
|
|
||||||
// 2) search값이 CROwneUnit[2]이면 : preceding-sibling::CROwneUnit이 한개 있으므로 @ClipIndex = 1 / CROwneUnit[2]을 가리킴 [정상] ...
|
|
||||||
if (!search) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const searchResult = search ? findSimilarString(xmlDoc, search, 0.8) : null;
|
|
||||||
const cROwneUnitPreceding = searchResult ? xpath.select(`//CROwneUnit[CRCUnitArr[@Name='${searchResult}']]/preceding-sibling::CROwneUnit`, xmlDoc) : null;
|
|
||||||
|
|
||||||
const clipIndex = cROwneUnitPreceding ? cROwneUnitPreceding.length : null;
|
|
||||||
return clipIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// xml 형식의 gmep 파일을 읽어서 점수를 계산
|
// xml 형식의 gmep 파일을 읽어서 점수를 계산
|
||||||
// scoring.json 파일 내에 있는 ele 요소는 xpath 형식으로 접근하여 요소를 탐색하고 나오는 값을 value와 비교하여 점수를 계산
|
// scoring.json 파일 내에 있는 ele 요소는 xpath 형식으로 접근하여 요소를 탐색하고 나오는 값을 value와 비교하여 점수를 계산
|
||||||
@@ -772,3 +629,134 @@ function getScore(psdData, scoring, index) {
|
|||||||
scoringResult['총점'] = totalScore;
|
scoringResult['총점'] = totalScore;
|
||||||
return scoringResult;
|
return scoringResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 자막 태그의 인덱스를 구할 때 사용
|
||||||
|
* 1. CRTrackClip 요소의 순서에 따라 그 요소에 해당하는 CROwneUnit 태그의 순서를 구함
|
||||||
|
* 2. CRTrackClip 요소의 시작시간에 따라 그 요소에 해당하는 CROwneUnit 태그의 순서를 구함
|
||||||
|
*/
|
||||||
|
function getTrackClipNode(xmlDoc, type, videoStartTime, openingStartTime) {
|
||||||
|
let trackClipNode = null;
|
||||||
|
|
||||||
|
// 동영상 자막이면 2, 오프닝 자막이면 1, 그 외는 0
|
||||||
|
const subtitleOrder = type === 'video' ? 2 : type === 'opening' ? 1 : null;
|
||||||
|
const startTime = type === 'video' ? videoStartTime : openingStartTime;
|
||||||
|
|
||||||
|
// xpath 구문을 통해 CRTrackClip 요소의 ClipIndex를 찾음
|
||||||
|
const trackClipNode1 = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][${subtitleOrder}]`, xmlDoc);
|
||||||
|
const trackClipNode2 = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[sum(preceding-sibling::CRTrackClip/@Length) = ${startTime}]`, xmlDoc);
|
||||||
|
|
||||||
|
return trackClipNode = trackClipNode1 ?? trackClipNode2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 자막텍스트를 이용해 자막 태그의 인덱스를 구할 때 사용
|
||||||
|
* 1. 자막 텍스트의 유사도를 판별
|
||||||
|
* 2. 자막텍스트와 일치하는 자막요소(CROWneUnit)의 순서를 구함
|
||||||
|
*/
|
||||||
|
function getClipIndexBySubtitle(xmlDoc, search) {
|
||||||
|
// 1. search값이 일치하지 않는 경우 : count가 0이 되어 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [오류]
|
||||||
|
// 2. search값이 일치하는 경우
|
||||||
|
// 1) search값이 CROwneUnit[1]이면 : preceding-sibling::CROwneUnit이 없어서 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [정상]
|
||||||
|
// 2) search값이 CROwneUnit[2]이면 : preceding-sibling::CROwneUnit이 한개 있으므로 @ClipIndex = 1 / CROwneUnit[2]을 가리킴 [정상] ...
|
||||||
|
if (!search) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const searchResult = search ? findSimilarString(xmlDoc, search, 0.8) : null;
|
||||||
|
const cROwneUnitPreceding = searchResult ? xpath.select(`//CROwneUnit[CRCUnitArr[@Name='${searchResult}']]/preceding-sibling::CROwneUnit`, xmlDoc) : null;
|
||||||
|
|
||||||
|
const clipIndex = cROwneUnitPreceding ? cROwneUnitPreceding.length : null;
|
||||||
|
return clipIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // Flatten the resultData for better representation in Excel
|
||||||
|
// const flattenedData = scoringResultList.map(student => {
|
||||||
|
// // const name = student["0"];
|
||||||
|
// const flattened = { "학생": student["0"] };
|
||||||
|
|
||||||
|
// // excel에 표시하지 않을 key값들
|
||||||
|
// const exceptKeys = [
|
||||||
|
// "0", // 학생 이름 항상 제외
|
||||||
|
// // "1", // psd1
|
||||||
|
// // "2", // psd2
|
||||||
|
// ]
|
||||||
|
// const exceptSubkeys = [
|
||||||
|
// "videoStartTime",
|
||||||
|
// "openingStartTime",
|
||||||
|
// ];
|
||||||
|
// Object.keys(student).forEach(key => {
|
||||||
|
// if (exceptKeys.includes(key)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// Object.keys(student[key]).forEach(subKey => {
|
||||||
|
// if (exceptSubkeys.includes(subKey)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// flattened[`${key}-${subKey}`] = student[key][subKey];
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// return flattened;
|
||||||
|
// });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* scoringResultList 배열을 엑셀에 출력하기 위한 데이터 정리 함수
|
||||||
|
* @param {Array} scoringResultList - 학생별 채점 결과 리스트
|
||||||
|
* @returns {Array} - 엑셀에 출력할 데이터 배열
|
||||||
|
*/
|
||||||
|
function prepareExcelData(scoringResultList) {
|
||||||
|
return scoringResultList.map(student => {
|
||||||
|
// const flattened = { "학생": student["0"] }; // 학생 이름을 첫 번째 열로 설정
|
||||||
|
const flattened = { "문항": student["0"] }; // 행열을 변환 할 경우 첫 행의 제목을 "문항"으로 설정
|
||||||
|
|
||||||
|
// 제외할 키와 서브키 정의
|
||||||
|
const exceptKeys = [
|
||||||
|
"0", // 학생 이름 제외
|
||||||
|
// "1", // psd1
|
||||||
|
// "2", // psd2
|
||||||
|
];
|
||||||
|
const exceptSubkeys = ["videoStartTime", "openingStartTime"]; // 제외할 서브키
|
||||||
|
|
||||||
|
// 학생 데이터 순회
|
||||||
|
Object.keys(student).forEach(key => {
|
||||||
|
if (exceptKeys.includes(key)) {
|
||||||
|
return; // 제외할 키는 건너뜀
|
||||||
|
}
|
||||||
|
|
||||||
|
// 서브키 순회
|
||||||
|
if (typeof student[key] === "object") {
|
||||||
|
Object.keys(student[key]).forEach(subKey => {
|
||||||
|
if (exceptSubkeys.includes(subKey)) {
|
||||||
|
return; // 제외할 서브키는 건너뜀
|
||||||
|
}
|
||||||
|
flattened[`${key}-${subKey}`] = student[key][subKey];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 서브키가 없는 경우
|
||||||
|
flattened[key] = student[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return flattened;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function transposeData(data) {
|
||||||
|
// 데이터가 없으면 빈 배열 반환
|
||||||
|
if (data.length === 0) return [];
|
||||||
|
|
||||||
|
// 첫 번째 객체의 키(열 제목) 가져오기
|
||||||
|
const keys = Object.keys(data[0]);
|
||||||
|
|
||||||
|
// 행과 열을 변환
|
||||||
|
const transposed = keys.map(key => {
|
||||||
|
const row = { "항목": key }; // 각 열 제목을 "항목"으로 설정
|
||||||
|
data.forEach((item, index) => {
|
||||||
|
//console.log(data[index]['문항']);
|
||||||
|
row[data[index]['문항']] = item[key]; // 각 학생의 데이터를 열로 추가
|
||||||
|
});
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
return transposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user