记录使用自定义编辑器做试题识别功能
习惯了将解析写在代码注释,这里就直接上代码啦,里面用到的bxm-ui3组件库是博主基于element-Plus做的,可以通过npm i bxm-ui3自行安装使用
// 识别方法:
// dom 当前识别数据所在区域, questionType 当前点击编辑选择的题目类型(论述题、简答题要用)
export const recognitionMethod = (inputText, dom, questionType) => {
// 存一份
let newInputText = inputText.trim()
let data = {
questionContent: '',
questionType: '',
questionAnalysis: '',
answerList: []
}
// 解析答案
let { result, newText } = recognitionResult(newInputText)
data.questionAnalysis = result || ''
// 单选多选题匹配
const regx1 = /(?:^\d+、)?(.*?)\s*[\((]\s*([A-Za-z]*)\s*[\))]\s*([\s\S]+)/
// 填空题匹配 若下划线上无答案,则三个下划线为一个空
const regx2 = /(?:^\d+、)?(.*?)[\_]+\s*/g
// const regx2 = /(?:^\d+、)?(.*?)(_{3})+\s*/g
// 判断题匹配 含有(√|×|对|错|正确|错误)
const regx3 = /(?:^\d+、)?(.*?)\(([√×对错正确错误])\)\s*/
let match = newText.match(regx1)
let match2 = newText.match(regx2)
let match3 = newText.match(regx3)
// 填空题:去根据dom获取出来有下划线的部分即为答案
let underLineList = getUnderlineList(dom, newText)
if (match) { // 基本的单选多选
let answer = match[2] || ''
let optionsStr = match[3]
// 没有答案或者只有一个答案识别为单选,多个答案为多选
if (answer.length === 1 || !answer.length) {
data.questionType = '00'
} else {
data.questionType = '01'
}
// 单选/多选,有选项
if (optionsStr) {
let options = []
let regexOption = /[A-Za-z][.、.]\s*(?:.*?)(\([^)]*\))?(?=[A-Za-z][.、.]|$)/gsu
let matchOption = null
while((matchOption = regexOption.exec(optionsStr)) !== null) {
options.push(matchOption[0].replace(/[A-Za-z][\.、.]\s*/, '') + (matchOption[1] ? matchOption[1] : ''));
}
if (!options.length) {
// 选项
let optionRegx1 = /[A-Za-z](\.|、)/
options = optionsStr.split(optionRegx1).filter(option => { return !['', '.', '、', '.'].includes(option) })
}
if (options.length) {
options.map((item, index) => {
let obj = {
answerContent: item,
answerOrd: `${index + 1}`,
answerRight: false,
answerTitle: checkIndex(index)
}
// 单选
if (data.questionType === '00') {
obj.answerRight = (checkIndex(index) === answer || checkIndex(index).toLocaleLowerCase() === answer) ? '0' : false
} else { // 多选
let answers = answer.split('')
obj.answerRight = (answers.includes(checkIndex(index)) || answers.includes(checkIndex(index).toLocaleLowerCase())) ? '0' : '1'
}
data.answerList.push(obj)
})
}
}
handleQuestionContent(match[1], newText, data)
} else if (match3) { // 判断题
data.questionType = '03'
data.questionContent = match3[1] + '()'
let answer = match3[2]
for(let i = 0; i = 8) {
data.questionContent = arr[0].trim()
data.questionAnalysis = arr[7].trim()
} else {
data.questionContent = newInputText
data.questionAnalysis = ''
}
} else {
data.questionAnalysis = ''
data.questionContent = newStr.trim()
}
}
return data
}
// 序号A~Z-----AA~AZ
export const checkIndex = (index) => {
let imn = Math.floor((index + 1)/26)
let remainder = (index + 1) % 26
if(imn === 0 || (imn === 1 && remainder === 0)) {
// A~Z
return String.fromCharCode(65 + index)
}else if((imn > 1 || (imn === 1 && remainder > 0)) && imn
// AA、AB...BA...CA~ZZ
return (String.fromCharCode(65 + (remainder ? (imn - 1) : (imn - 2))) + String.fromCharCode(65 + (remainder ? (remainder - 1) : 25)))
}
}
// 解析答案
export const recognitionResult = (inputText) = {
let result = ''
let newText = inputText
// 一共6种可以解读为答案的内容
let resultRegx = /(答:)|(答案:)|(解析:)|(分析:)|(解答:)|(回答:)]/g
// 给了解析
if (resultRegx.test(inputText)) {
// ['题干', '第一种', '第二种'.....'最后一个是根据前面某一种分割出来的答案']如果有解析就是正常的8个项
let arr = inputText.split(resultRegx)
newText = arr[0].trim()
if (arr.length >= 8) {
result = arr[7]
} else {
result = ''
}
}
return { result, newText }
}
// 以下为填空题识别相关方法
// 填空题识别
export const recognitionPack = (inputText, underLineList) => {
let questionContent = ''
let answerList = []
let newStr = /^\d+、/.test(inputText) ? inputText.replace(/^\d+、/, '') : inputText
// 这是下划线上有内容
if (underLineList.length) {
underLineList.map((item, index) => {
let obj = {
answerOrd: index + 1,
answerMoreSelect: item.answerMoreSelect,
answerTitle: `第${index + 1}空答案`,
inputVisible: false,
inputValue: '',
}
answerList.push(obj)
// 将答案替换成'___'
let end = item.underLineStart + item.answerLength
// 这里加了三个_,underLineList中剩余的项的unserLineStart都要处理,否则会错位
newStr = newStr.substring(0, item.underLineStart) + '___' + newStr.substring(end)
// 处理下一个的unserLineStart
if (index {
let allTextNodes = findDeepestNodes(dom)
let list = []
let fullText = ''
// 找到下划线标签进行数据处理
for(let index = 0; index 0 && list[i].underLineStart === list[i - 1].underLineStart + list[i - 1].answerLength) {
list[i - 1] = {
answerMoreSelect: list[i - 1].answerMoreSelect + list[i].answerMoreSelect, // 上一个的文本与当前文本组合
answerTitle: `第${i}空答案`, // 只留前一个,所以下标是前一个的
answerLength: list[i - 1].answerLength + list[i].answerLength, // 上一个的文本长度与当前文本长度之和
underLineStart: list[i - 1].underLineStart // 上一个文本的起始位置就是最终的起始位置
}
list.splice(i, 1)
i--
}
}
}
return list
}
// 获取增加或减少了多少长度
export const getChangeLen = (curUnderIndex, underList) => {
let addLen = 0
// 遍历当前以及之前的
for(let i = 0; i
// 当前下划线文本超出了下划线3个字符的长度,替换成3个下划线之后会少了 answerLength-3 的长度,后面的都需要往前移动answerLength-3个位置
// 当前下划线文本少于下划线3个字符的长度,替换成3个下划线之后会多了 3-answerLength 的长度,后面的都需要往后移动3-answerLength个位置
if (underList[i].answerLength !== 3) {
addLen += 3 - underList[i].answerLength // 变化的量可能正可能负
}
}
return addLen
}
// 处理下划线起始位置
export const handleCheckUnderStart = (curUnderIndex, underList) = {
if (curUnderIndex >= underList.length - 1) return
// 获取需要变动的数量
let changeLen = getChangeLen(curUnderIndex, underList)
// 处理当前的后一个即可
underList[curUnderIndex + 1].underLineStart += changeLen
}
// 处理选择题的题干,获取到答案并更新选项(题干中有多处为答案或者由多处括号,括号里是字母但不一定是答案的情况)
export const handleQuestionContent = (content, allText, data) => {
if (!content || !allText) return ''
let successContent = ''
// 去掉数字开头
let newTextAll = allText.replace(/^\d+[.、.]\s*/, '')
// 找到传入的题干在所有字符串中的位置
let contentIndex = newTextAll.indexOf(content)
// 截取选项之前的内容比对
let regx1 = /^(.*?)(?=\s*[A-Za-z]\.?[.、.])/s
let regx2 = /^(.*?)(?=[A-Za-z](?:(?:\s*\.\s*)|(?:\s*,\s*)|$))/s
let matchArr1 = newTextAll.match(regx1)
let matchArr2 = newTextAll.match(regx2)
let matchArr = []
if (matchArr1 && matchArr2) { // 两个都匹配比较谁匹配更接近
matchArr = matchArr1[0].length > matchArr2[0].length ? matchArr1 : matchArr2
} else if (matchArr1 || matchArr2) { // 有一个不能匹配直接获取能匹配那个
matchArr = matchArr1 ? matchArr1 : matchArr2
} else {
matchArr = null
}
// 已有的题干和真正的不同,需要对已有信息进行修改
if (matchArr && matchArr.length > 0 && matchArr[0] !== content) {
// 选项之前的内容
successContent = matchArr[0]
// 去掉空行
successContent = successContent.replace(/(\r?\n\s*)+/g, '\n')
let answers = data.answerList.map(item => { return item.answerTitle })
// 从括号中找到真正的答案
let answerKeyRegex = /[\((]\s*([A-Z]+)\s*[\))]/g
let contentArr = successContent.split(answerKeyRegex)
let resultContent = ''
let successAnswerArr = []
contentArr.map(item => {
let regxAnswer = /^[A-Za-z]+$/g
// 仅为大小写字母
if (regxAnswer.test(item)) {
// 只有一个字母,并且字母在已生成的选项中,说明是其中的一个答案
if (item.length === 1 && answers.includes(item.toLocaleUpperCase())) {
// 替换成括号
resultContent += '()'
// 记录出真正的答案,在最后去编辑选项设置选中
!successAnswerArr.includes(item.toLocaleUpperCase()) && successAnswerArr.push(item.toLocaleUpperCase())
} else if (item.length > 1) {
/**
* 多个字母需要判断:
* 1.字母有重复说明不是答案,直接还原
* 2.字母不重复但是有字母不在已生成的选项中,直接还原
* 3.字母不重复并且都在选项中为答案,同时将data中的试题类型修改为多选,选项默认选中项需要更改
*/
let itemArr = item.split('').filter(val => { return val !== '' })
let newArr = [...new Set(JSON.parse(JSON.stringify(itemArr)))]
if (itemArr.length !== newArr.length) { // 条件1
resultContent += `(${item})`
} else {
let isInner = true
for(let i = 0; i 1) {
data.questionType = '01'
} else {
data.questionType = '00'
}
// 处理选项
data.answerList.map((item, index) => {
// 当前项为答案要默认选中
if (successAnswerArr.includes(item.answerTitle)) {
item.answerRight = data.questionType === '00' ? index : '0'
} else {
item.answerRight = data.questionType === '00' ? false : '1'
}
})
} else {
data.questionContent = content + '()'
}
}
这是我简单自定义的一个编辑器,其实是一个contenteditable的div,对里面内容进行简单处理了之后就可以使用了
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!
