/**
* 佛山大学强智教务系统适配
* @since 2026-5-14
* @description 支持课程表导入,需要校园网访问
* @author e7g
* @version 1.0
*/
function parseWeeksString(weekStr) {
const weeks = [];
if (!weekStr) return weeks;
let cleanStr = weekStr;
while (cleanStr.includes('(周)')) {
cleanStr = cleanStr.replace('(周)', '');
}
while (cleanStr.includes('周')) {
cleanStr = cleanStr.replace('周', '');
}
cleanStr = cleanStr.trim();
const parts = cleanStr.split(',');
for (const part of parts) {
const trimmed = part.trim();
if (trimmed.includes('-')) {
const nums = trimmed.split('-').map(s => parseInt(s.trim(), 10));
const start = nums[0];
const end = nums[1];
if (!isNaN(start) && !isNaN(end)) {
for (let w = start; w <= end; w++) {
weeks.push(w);
}
}
} else {
const week = parseInt(trimmed, 10);
if (!isNaN(week)) {
weeks.push(week);
}
}
}
return weeks.sort((a, b) => a - b);
}
function parseSectionFromText(text) {
const startIdx = text.indexOf('[');
const endIdx = text.indexOf(']节');
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
return null;
}
const sectionStr = text.substring(startIdx + 1, endIdx);
const sections = sectionStr.split('-').map(s => parseInt(s.trim(), 10));
if (sections.some(s => isNaN(s))) {
return null;
}
return {
start: Math.min(...sections),
end: Math.max(...sections)
};
}
function removeSectionFromText(text) {
const startIdx = text.indexOf('[');
const endIdx = text.indexOf(']节');
if (startIdx === -1 || endIdx === -1) {
return text;
}
return text.substring(0, startIdx).trim();
}
function extractFontContent(line) {
const startTag = line.indexOf('', startTag);
if (closeTag === -1) return null;
const endTag = line.indexOf('', closeTag);
if (endTag === -1) return null;
return line.substring(closeTag + 1, endTag).trim();
}
function removeHtmlTags(text) {
let result = '';
let inTag = false;
for (let i = 0; i < text.length; i++) {
if (text[i] === '<') {
inTag = true;
} else if (text[i] === '>') {
inTag = false;
} else if (!inTag) {
result += text[i];
}
}
return result.trim();
}
function parseCourseFromDiv(divContent, dayIndex, sectionIndex) {
const courses = [];
if (!divContent || divContent.includes(' ') || divContent.trim() === '') {
return courses;
}
const courseBlocks = [];
let currentBlock = '';
let dashCount = 0;
for (let i = 0; i < divContent.length; i++) {
if (divContent[i] === '-') {
dashCount++;
} else {
if (dashCount >= 10) {
if (currentBlock.trim()) {
courseBlocks.push(currentBlock);
}
currentBlock = '';
} else if (dashCount > 0) {
for (let j = 0; j < dashCount; j++) {
currentBlock += '-';
}
}
currentBlock += divContent[i];
dashCount = 0;
}
}
if (currentBlock.trim()) {
courseBlocks.push(currentBlock);
}
let pendingCourse = null;
for (const block of courseBlocks) {
const trimmedBlock = block.trim();
if (!trimmedBlock || trimmedBlock.includes(' ')) continue;
const lines = trimmedBlock.split('
').map(l => l.trim()).filter(l => l);
if (lines.length === 0) continue;
const courseName = removeHtmlTags(lines[0]);
let teacher = '';
let position = '';
let weeks = [];
let startSection = sectionIndex * 2 - 1;
let endSection = sectionIndex * 2;
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (line.includes('title="老师"')) {
const content = extractFontContent(line);
if (content) {
teacher = content;
}
} else if (line.includes('title="周次(节次)"')) {
const content = extractFontContent(line);
if (content) {
weeks = parseWeeksString(content);
}
} else if (line.includes('title="教室"')) {
const content = extractFontContent(line);
if (content) {
position = content;
const sectionMatch = parseSectionFromText(content);
if (sectionMatch) {
startSection = sectionMatch.start;
endSection = sectionMatch.end;
position = removeSectionFromText(content);
}
}
}
}
if (teacher && !weeks.length) {
pendingCourse = {
name: courseName,
teacher: teacher,
position: '',
day: dayIndex,
startSection: startSection,
endSection: endSection,
weeks: []
};
} else if (weeks.length > 0) {
if (pendingCourse && pendingCourse.name === courseName) {
pendingCourse.position = position;
pendingCourse.startSection = startSection;
pendingCourse.endSection = endSection;
pendingCourse.weeks = weeks;
courses.push(pendingCourse);
pendingCourse = null;
} else {
if (courseName && weeks.length > 0) {
courses.push({
name: courseName,
teacher: teacher,
position: position,
day: dayIndex,
startSection: startSection,
endSection: endSection,
weeks: weeks
});
}
}
}
}
return courses;
}
function findTagContent(html, tagName, startFrom) {
const openTag = '<' + tagName;
const closeTag = '' + tagName + '>';
let start = html.indexOf(openTag, startFrom || 0);
if (start === -1) return null;
const tagEnd = html.indexOf('>', start);
if (tagEnd === -1) return null;
let depth = 1;
let pos = tagEnd + 1;
while (depth > 0 && pos < html.length) {
const nextOpen = html.indexOf(openTag, pos);
const nextClose = html.indexOf(closeTag, pos);
if (nextClose === -1) return null;
if (nextOpen !== -1 && nextOpen < nextClose) {
depth++;
pos = html.indexOf('>', nextOpen) + 1;
} else {
depth--;
if (depth === 0) {
return {
content: html.substring(tagEnd + 1, nextClose),
endPos: nextClose + closeTag.length
};
}
pos = nextClose + closeTag.length;
}
}
return null;
}
function findAllTags(html, tagName) {
const results = [];
let pos = 0;
while (true) {
const result = findTagContent(html, tagName, pos);
if (!result) break;
results.push(result.content);
pos = result.endPos;
}
return results;
}
function findTagWithAttr(html, tagName, attrName, attrValue) {
const openTag = '<' + tagName;
const closeTag = '' + tagName + '>';
let pos = 0;
while (true) {
let start = html.indexOf(openTag, pos);
if (start === -1) return null;
const tagEnd = html.indexOf('>', start);
if (tagEnd === -1) return null;
const tagDecl = html.substring(start, tagEnd + 1);
const attrPattern = attrName + '="';
const attrStart = tagDecl.indexOf(attrPattern);
if (attrStart !== -1) {
const attrValueStart = attrStart + attrPattern.length;
const attrValueEnd = tagDecl.indexOf('"', attrValueStart);
if (attrValueEnd !== -1) {
const foundValue = tagDecl.substring(attrValueStart, attrValueEnd);
if (foundValue === attrValue || foundValue.includes(attrValue)) {
let depth = 1;
let searchPos = tagEnd + 1;
while (depth > 0 && searchPos < html.length) {
const nextOpen = html.indexOf(openTag, searchPos);
const nextClose = html.indexOf(closeTag, searchPos);
if (nextClose === -1) return null;
if (nextOpen !== -1 && nextOpen < nextClose) {
depth++;
searchPos = html.indexOf('>', nextOpen) + 1;
} else {
depth--;
if (depth === 0) {
return html.substring(tagEnd + 1, nextClose);
}
searchPos = nextClose + closeTag.length;
}
}
}
}
}
pos = tagEnd + 1;
}
return null;
}
function parseSemesterValue(semesterValue) {
const parts = semesterValue.split('-');
if (parts.length !== 3) return null;
const startYear = parts[0];
const endYear = parts[1];
const semesterNum = parts[2];
if (startYear.length !== 4 || endYear.length !== 4 || semesterNum.length !== 1) {
return null;
}
return {
startYear: parseInt(startYear, 10),
endYear: parseInt(endYear, 10),
semesterNum: semesterNum
};
}
function parseHtmlTable(htmlContent) {
const courses = [];
const tableContent = findTagWithAttr(htmlContent, 'table', 'id', 'kbtable');
if (!tableContent) {
console.error('未找到课程表格');
return courses;
}
const rows = findAllTags(tableContent, 'tr');
let sectionIndex = 0;
for (const row of rows) {
if (row.includes('星期一') || row.includes('备注')) {
continue;
}
const thContent = findTagContent(row, 'th', 0);
if (thContent) {
const thText = thContent.content;
const sectionNames = ['一', '二', '三', '四', '五', '六'];
for (let i = 0; i < sectionNames.length; i++) {
if (thText.includes('第' + sectionNames[i] + '大节')) {
sectionIndex = i + 1;
break;
}
}
}
if (sectionIndex === 0) continue;
const cells = findAllTags(row, 'td');
let dayIndex = 1;
for (const cell of cells) {
let kbcontentDivs = [];
let divPos = 0;
while (true) {
const divStart = cell.indexOf('