根据业务需求,需要有一个批量添加题目的功能。
如上图所示,左边是纯文本输入的题目,右边需要解析成一个个对应的题目。
右边的题目就是一个组件,根据不同的类型进行显示,这个组件这里不做谈论。这里要做的就是根据左边的文本内容,解析成一个个对象的形式,传入右边的组件进行渲染。
这篇文章,就是如何把左边的纯文本,解析成一个个的对象,这就考验文本的拆分能力了。
第一步当然是指定规则,不然没法知道以什么的方式进行解析。
这里为了方便处理,对每个题目类型的格式加以限定,每个题目之间也用空行进行分隔。
1、每个题目之间必须以空行分割,题干中间不得换行。
2、所有题型必须含有 “答案:”字段,且不能为空。
3、【选择题】最多支持10个选项A,B,C,D,E,F,G,H,I,J,且必须按顺序输入。
4、【选择题】选项号A-H与内容之间必须用 ”、“ 或 ”.“ 分隔开。
5、【选择题】答案中不能加空格。
6、【判断题】答案仅支持 “正确”,“错误” 或者 “对”,“错”。
7、【填空题】每个空使用至少三个短下划线 “___” 作为空的位置。
8、【填空题】多个填空的答案用 “|” 分割。每个填空有多个答案的话用 “&&” 分隔。单个答案不用添加。
9、【填空题】答案如果多于题目中需要填空的个数,多于的答案将被忽略。
10、【问答题】的答案可以为空。
11、【文件上传题】的答案必须为 “[文件]” 。
下一步就是拆分每个题目。
以为核心就是一句正则表达式 /\n\s*\n\s*/g
,所以直接上代码:
// 将批量题目分成一个个题目字符串
function getEachSub(whole) {
let me = this;
me.previewSubjects = [];
// 主要就是这句话
let eachSourceSubs = whole.trim().split(/\n\s*\n\s*/g);
if(eachSourceSubs.length) {
eachSourceSubs.forEach(item=>{
// assembleSub就是把每个题目的字符串转换成题目对象,保存到previewSubjects数组里
me.previewSubjects.push(me.assembleSub(item.trim()));
});
}
}
题目的类型总共分为:
每个题目的对象为:
{
inpttype:'', // 类型
subject:'', // 题干
answer: '', // 正确答案
items:[], // 题目选项(单选,多选,填空使用)
err:'', // 题目解析有问题时的报错信息
}
解析的思路:
具体的代码如下:
// 将一个个题目字符串拆解/组合成题目对象
function assembleSub(eachSub) {
let me = this;
let subObj = {
inpttype:'', // 类型
subject:'', // 题干
answer: '', // 正确答案
items:[], // 题目选项
err:'' // 错误提示
};
let ansArr = eachSub.match(/\n\s*答案[::]/g);
if(ansArr) {
if(ansArr.length>1) { // 匹配到多个答案
subObj.err = '每道题只能有一个答案';
} else {
/**
* 单选题和多选题
*/
if (eachSub.search(/\n\s*[A-Z][\.、]/ig) > -1) {
let selReg = /\n\s*答案[::]\s*[A-Z]+/i; // 单选题和多选题
let selectAns = eachSub.match(selReg);
if(selectAns) {
let ans = selectAns[0].trim().replace(/^答案[::]\s*/g, '').toUpperCase();
subObj.answer = ans;
// 单选题与多选题
subObj.inpttype = ans.length===1 ? 'radio' : 'chk';
let sourceTimu = eachSub.replace(selReg,'');
// 拆分题干与选项
let sourceTimuArr = sourceTimu.split(/[A-Z][、\.]/ig);
if(sourceTimuArr.length == 1){
subObj.err = '选项不能为空';
}else if(sourceTimuArr.length > 11){
subObj.err = '选项数量不能大于10个';
}
let valArr = [];
sourceTimuArr.map((item,i)=>{
sourceTimuArr[i] = item.trim().replace(/\s+/g, ' ');
if(i===0) {
// 题干
subObj.subject = sourceTimuArr[i];
} else {
// 选项
let obj = {
pic:'',
text: sourceTimuArr[i],
val: String.fromCharCode(65+i-1) // ascii转字母
};
subObj.items.push(obj);
valArr.push(obj.val);
}
});
// 单选题
if(subObj.answer.length === 1) {
if(!valArr.includes(subObj.answer)) {
subObj.err= '答案选项不正确';
}
} else { // 多选题
for (let a of subObj.answer) {
if(!valArr.includes(a)) {
subObj.err= '答案选项不正确';
break;
}
}
}
} else {
subObj.err = '选择题答案不正确';
}
}
else {
let reg = /\n\s*答案[::]\s*/g;
let regArr = eachSub.split(reg);
subObj.subject = regArr[0];
/**
* 填空题
*/
if(regArr[0].includes('___')) {
subObj.inpttype = 'fillin';
let len = regArr[0].match(/_{3,}/g).length;
let fillinAns = regArr[1].split(/\s*\|\s*/g);
if(len>0 && len <= fillinAns.length) {
for(let i=0; i<len; i++) {
if(fillinAns[i].includes('&&')) {
subObj.items.push({
matchtype: "equal",
vals: fillinAns[i].split('&&').filter(item=>{return item})
});
} else {
subObj.items.push({
matchtype: "equal",
vals: [fillinAns[i]]
});
}
}
} else {
subObj.err = '填空题答案个数错误';
}
}
/**
* 判断题
*/
else if(regArr[1].trim() === '对'
|| regArr[1].trim() === '错'
|| regArr[1].trim() ==='正确'
|| regArr[1].trim() ==='错误') {
subObj.inpttype = 'judge';
subObj.answer = (regArr[1].trim() === '对'|| regArr[1].trim() === '正确') ? '1' : '0';
}
/**
* 文件上传题
*/
else if(regArr[1].trim() === '[文件]'){
subObj.inpttype = 'file';
}
/**
* 问答题
*/
else {
subObj.inpttype = 'textarea';
subObj.answer = regArr[1].trim();
}
}
}
} else { // 未匹配到则为null
subObj.err = '题目缺少答案'
}
return subObj;
}
(完)