메인소스코드 정리 및 시험타입별 채점 한번에 처리 가능하도록 변경
This commit is contained in:
466
psdExport_2.js
466
psdExport_2.js
@@ -11,266 +11,123 @@ const getGpdpScore = require('./gpdpScoring.js');
|
||||
const getToday = require('./getToday.js');
|
||||
const todayDate = getToday();
|
||||
|
||||
// --------------------------------------------------------
|
||||
// const scoringJson = require('./DIC_2503A.json');
|
||||
const scoringJson = require('./DIC_2503B.json');
|
||||
// const scoringJson = require('./DIC_2503C.json');
|
||||
// const scoringJson = require('./DIC_2503D.json');
|
||||
const examType = [
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
// 'D'
|
||||
];
|
||||
const examRound = '2503';
|
||||
|
||||
// --------------------------------------------------------
|
||||
// const answerFilesDir = './output/A/DIC';
|
||||
const answerFilesDir = './output/B/DIC';
|
||||
// const answerFilesDir = './output/C/DIC';
|
||||
// const answerFilesDir = './output/D/DIC';
|
||||
// testMode가 true일 경우 TEST 폴더에 있는 답안 파일을 읽어옴
|
||||
// const testMode = true;
|
||||
const testMode = false;
|
||||
|
||||
// TEST
|
||||
// const answerFilesDir = './output/A/TEST';
|
||||
// const answerFilesDir = './output/B/TEST';
|
||||
// const answerFilesDir = './output/C/TEST';
|
||||
// const answerFilesDir = './output/D/TEST';
|
||||
const outputExcelFiles = [];
|
||||
|
||||
// --------------------------------------------------------
|
||||
// const outputExcelFile = './'+todayDate+'_DIC_2503A_채점결과.xlsx';
|
||||
const outputExcelFile = './'+todayDate+'_DIC_2503B_채점결과.xlsx';
|
||||
// const outputExcelFile = './'+todayDate+'_DIC_2503C_채점결과.xlsx';
|
||||
// const outputExcelFile = './'+todayDate+'_DIC_2503D_채점결과.xlsx';
|
||||
examType.forEach(type => {
|
||||
const scoringJson = require(`./DIC_${examRound}${type}.json`);
|
||||
const answerFilesDir = `./output/${type}/${testMode ? 'TEST' : 'DIC'}`;
|
||||
const outputExcelFile = `./${todayDate}_DIC_${examRound}${type}_${testMode ? 'TEST.xlsx' : '채점결과.xlsx'}`;
|
||||
|
||||
// TEST
|
||||
// const outputExcelFile = './'+todayDate+'_DIC_2503A_TEST.xlsx';
|
||||
// const outputExcelFile = './'+todayDate+'_DIC_2503B_TEST.xlsx';
|
||||
// const outputExcelFile = './'+todayDate+'_DIC_2503C_TEST.xlsx';
|
||||
// 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);
|
||||
}
|
||||
// 답안 폴더 내부에 디렉토리가 아닌 일반 파일이 있을 경우 디렉토리만 필터링 해서 불러옴
|
||||
const studentDirs = fs.readdirSync(answerFilesDir).filter(file => {
|
||||
const filePath = path.join(answerFilesDir, file);
|
||||
return fs.statSync(filePath).isDirectory();
|
||||
});
|
||||
gpdpFiles.forEach((gpdpFile, index) => {
|
||||
const gpdpPath = path.join('./', studentDir, gpdpFile);
|
||||
console.log(`Reading ${gpdpPath}...`);
|
||||
|
||||
const xmlString = fs.readFileSync(gpdpPath, 'utf8');
|
||||
// XML 문자열을 파싱하여 XML 문서 객체로 변환
|
||||
const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml');
|
||||
// console.log('xmlDocument:', xmlDocument);
|
||||
// 채점 결과 리스트
|
||||
const scoringResultList = [];
|
||||
const psdData = [];
|
||||
|
||||
scoringResult[index + 1] = getGpdpScore(xmlDocument, scoringJson, index + 4);
|
||||
});
|
||||
gmepFile.forEach((gmep, index) => {
|
||||
const gmepPath = path.join('./', studentDir, gmep);
|
||||
console.log(`Reading ${gmepPath}...`);
|
||||
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')
|
||||
);
|
||||
|
||||
const xmlString = fs.readFileSync(gmepPath, 'utf8');
|
||||
// XML 문자열을 파싱하여 XML 문서 객체로 변환
|
||||
const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml');
|
||||
// console.log('xmlDocument:', xmlDocument);
|
||||
// 곰픽 파일 gpdp 파일 이거나 xml 파일
|
||||
const gpdpFiles = fs.readdirSync(studentDir).filter(
|
||||
file => file.endsWith('.xml')
|
||||
);
|
||||
|
||||
scoringResult[3] = getGmepScore(xmlDocument, scoringJson, 2);
|
||||
});
|
||||
scoringResultList.push(scoringResult);
|
||||
});
|
||||
// 학생 이름을 key로 하는 객체 생성
|
||||
// 채점결과
|
||||
const scoringResult = {
|
||||
0: name
|
||||
};
|
||||
|
||||
// // 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];
|
||||
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}...`);
|
||||
|
||||
return flattened;
|
||||
});
|
||||
}
|
||||
// console.log(flattenedData);
|
||||
const flattenedData = prepareExcelData(scoringResultList);
|
||||
const xmlString = fs.readFileSync(gpdpPath, 'utf8');
|
||||
// XML 문자열을 파싱하여 XML 문서 객체로 변환
|
||||
const xmlDocument = new DOMParser().parseFromString(xmlString, 'application/xml');
|
||||
// console.log('xmlDocument:', xmlDocument);
|
||||
|
||||
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]; // 각 학생의 데이터를 열로 추가
|
||||
scoringResult[index + 1] = getGpdpScore(xmlDocument, scoringJson, index + 4);
|
||||
});
|
||||
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 workbook = XLSX.utils.book_new();
|
||||
// 열 너비 계산
|
||||
const columnWidths = Object.keys(transposedData[0]).map(key => {
|
||||
// 각 열의 최대 길이를 계산
|
||||
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 => {
|
||||
const maxLength = Math.max(
|
||||
// key.length, // 열 제목의 길이
|
||||
// ...transposedData.map(row => (row[key] ? row[key].toString().length : 0)) // 각 셀의 데이터 길이
|
||||
4 // 고정 너비
|
||||
);
|
||||
return { wch: maxLength + 1 }; // 여유 공간 추가
|
||||
// 열 너비 설정
|
||||
worksheet['!cols'] = columnWidths;
|
||||
// Add the worksheet to the workbook
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, '채점 결과');
|
||||
|
||||
// 엑셀 파일 저장
|
||||
XLSX.writeFile(workbook, outputExcelFile);
|
||||
outputExcelFiles.push(outputExcelFile);
|
||||
});
|
||||
|
||||
// 열 너비 설정
|
||||
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;
|
||||
}
|
||||
console.log('채점 결과가 ' + outputExcelFiles + ' 파일에 저장되었습니다.');
|
||||
|
||||
// xml 형식의 gmep 파일을 읽어서 점수를 계산
|
||||
// scoring.json 파일 내에 있는 ele 요소는 xpath 형식으로 접근하여 요소를 탐색하고 나오는 값을 value와 비교하여 점수를 계산
|
||||
@@ -772,3 +629,134 @@ function getScore(psdData, scoring, index) {
|
||||
scoringResult['총점'] = totalScore;
|
||||
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