import { unified } from 'unified'; import parse from 'remark-parse'; import { visit } from 'unist-util-visit'; const deepClone = (obj: any) => { if (obj === null || typeof obj !== 'object') { return obj; } let newObj: any = Array.isArray(obj) ? [] : {}; for (let key in obj) { newObj[key] = deepClone(obj[key]); } return newObj; }; const timer = (str: any) => { const re = /(\d{2}):(\d{2}):(\d{2})\.(\d{3})/; if (re.test(str)) { const match = str.match(re); const h = match[1] ? Number(match[1]) * 3600 : 0; const m = match[2] ? Number(match[2]) * 60 : 0; const s = match[3] ? Number(match[3]) : 0; const ms = match[3] ? Number(match[4]) / 1000 : 0; return h + m + s + ms; } return null; }; const format = (text: any) => { let content = text || ''; const t = /\`\{\{(.*?)\}\}/; let e: any; while ((e = t.exec(content))) { content = content.replace(t, '`\n' + `#${e[0].substring(1)}\n`); } return content; }; const properties = (str: any) => { const match = str.match(/#\{\{(.*?)\}\}/); return { ext: match[1].trim(), }; }; const linkToText = (): any => { return (tree: any) => { // console.log('code =>', deepClone(tree)); visit(tree, 'link', (node: any, index: any, parent: any) => { console.debug(node, index, parent); console.log(node) node.type = 'text' node.value = node.children[0].value delete node.children }); return tree; }; } const code = (): any => { return (tree: any) => { // console.log('code =>', deepClone(tree)); visit(tree, 'paragraph', (node: any, index: any, parent: any) => { console.debug(node, index, parent); const str = node.children[0].value; if (/#\{\{(.*?)\}\}/.test(str)) { const code = parent.children[index - 1]; if (code) { code.properties = properties(str); parent.children.splice(index, 1); return; } } }); return tree; }; }; const inlineCode = (): any => { return (tree: any) => { // console.log('inlineCode =>', deepClone(tree)); visit(tree, 'inlineCode', (node: any, index: any, parent: any) => { console.debug(node, index, parent); const next = index < parent.children.length - 1 ? parent.children[index + 1] : null; if (next) { const str = next.value; if (/#\{\{(.*?)\}\}/.test(str)) { node.properties = properties(str); parent.children.splice(index + 1, 1); } } }); return tree; }; }; const tag = (): any => { return (tree: any) => { console.log('taging =>', deepClone(tree)); visit(tree, 'paragraph', (node: any, index: any, parent: any) => { console.debug(node, index, parent) node.children.forEach((child: any) => { const re = /(#[doctiwer]\s\S+)/g; const str = child.value if ('text' === child.type && re.test(str)) { const array = str.split(re).filter((i: any) => i.trim()) const data: any = [] array.forEach((line: any) => { if (/#([doctiwer])\s(\S+)/.test(line)) { const match = line.match(/#([doctiwer])\s(\S+)/); const el = { type: match[1], value: match[2], }; data.push(el); } else { let type = 'text'; let value = line.trim(); const el = { type: type, value: value, }; data.push(el); } }); child.type = 'paragraph' child.children = data delete child.value } }); }); return tree; }; } const subtitle = (): any => { return (tree: any) => { // console.log('subtitle =>', deepClone(tree)); visit(tree, 'text', (node: any, index: any, parent: any) => { if ('paragraph' !== parent.type) { return; } // console.log(node) console.debug(node, index, parent); const re = /\[(\d{2}:\d{2}:\d{2}\.\d{3})(?:-(\d{2}:\d{2}:\d{2}\.\d{3}))?\](.*)/i; const str = node.value.trim(); if (re.test(str)) { const values = str.match(re); node.type = 'subtitle'; node.properties = { st: values[1], et: values[2], }; node.value = values[3]; } }); return tree; }; }; const paragraph = (): any => { return (tree: any) => { // console.log('refresh =>', deepClone(tree)); visit(tree, 'paragraph', (node: any, index: any, parent: any) => { if ('paragraph' !== parent.type) { return } const children = node.children parent.children.splice(index, 1, ...children) console.debug(node, index, parent); }); return tree; }; }; const Parser = { mdast: (text?: string) => { const content = format(text || ''); const processer = unified() .use(parse) .use(linkToText) .use(code) .use(inlineCode) .use(tag) .use(subtitle) .use(paragraph); const tree = processer.parse(content); const ast = processer.runSync(tree); processer.freeze(); console.log('ast =>', ast); return deepClone(ast); }, subtitle: (mdast: any) => { const data: any = []; const processer = unified().use(() => (tree: any) => { visit(tree, 'subtitle', (node: any) => { const properties = node.properties; const item = { st: timer(properties.st), et: timer(properties.et), value: node.value, }; data.push(item); }); }); processer.runSync(mdast); processer.freeze(); return data; }, actives: (mdast: any) => { const data: any = [] const processer = unified() .use(() => (tree: any) => { visit(tree, 'code', (node: any) => { const ext: string = node.properties?.ext if (ext.startsWith('active ')) { const item = { lang: node.lang, st: timer(ext.substring(7).trim()), et: undefined, value: node.value.trim() } data.push(item) } }) }) processer.runSync(mdast) processer.freeze() return data } } export default Parser;