import { unified } from 'unified'
import parse from 'remark-parse'
import { visit } from 'unist-util-visit'
import remarkgfm from 'remark-gfm' //是markdown支持表格、选择框、连接等相关
const compile = (text: any) => {
let content = text || ''
const re = /`\{\{(.*?)\}\}/
let exec: any;
while ((exec = re.exec(content))) {
content = content.replace(re, '`\n' + `#${exec[0].substring(1)}\n`);
}
exec = null
const reg = /#[desc]\s+/
while ((exec = reg.exec(content))) {
const s = exec[0]
content = content.replace(reg, `# -${s.substring(1)}`)
}
// content = content.replace(/\n/g, '
')
return content;
}
const code = (): any => {
return (tree: any) => {
visit(tree, 'paragraph', (node: any, index: any, parent: any) => {
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) => {
visit(tree, 'inlineCode', (node: any, index: any, parent: any) => {
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 captions = (): any => {
return (tree: any) => {
visit(tree, 'text', (_node: any, _index: any, _parent: any) => {
if ('paragraph' === _parent?.type) {
const value = _node?.value
const re = /(\[.*\].*)/g
const exsit = re.test(value)
if (!exsit) {
return
}
const children: any[] = []
const regex = /(\[.*\].*)/g
const list = value?.split(regex)
for (let s of list) {
if (!regex.test(s)) {
const n = {
type: 'text',
value: s
}
children.push(n)
continue
}
const reg = /(\[.*\])(.*)/g
const ts = (reg?.exec(s) ?? ['', '[]'])[1]?.replace(/[\[\]]/g, '')?.split('-')
const n = {
type: 'captions',
properties: {
st: ts[0] ?? '',
et: ts[1] ?? ''
},
value: s?.replace(/\[.*\]/g, '')
}
children.push(n)
}
_parent.children.splice(_index, 1, ...children)
}
});
return tree;
};
}
const heading = (): any => {
return (tree: any) => {
visit(tree, 'text', (_node: any, _index: any, _parent: any) => {
if ('heading' === _parent?.type) {
const value = _node?.value
const re = /^(-[desc])/g
if (!re.test(value)) {
return
}
const args = value?.split(re).filter((it: any) => it)
if (!args || args?.length == 0) {
return
}
const tag = args[0]?.substring(1)?.trim() ?? ''
const text = args[1]?.trim() ?? ''
_node.value = text
_parent.properties = {
tag: tag
}
}
})
return tree
}
}
const traverse = (children: any, data: any) => {
for (let i = 0; i < children?.length; i++) {
if ('paragraph' === children[i]?.type) {
traverse(children[i]?.children, data);
} else {
data.push(children[i])
}
}
}
const build = (target: any, root: any) => {
if (!root?.next) {
root.next = []
root.next.push(target)
return
}
const last = root?.next[root?.next?.length - 1]
if (last?.depth < target?.depth) {
build(target, last)
} else {
root?.next?.push(target)
}
}
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: string) => {
try {
if (!str) {
return undefined
}
if (str.endsWith('.')) {
str = str.substring(0, str.length - 1)
}
let ms: number = 0
let s: string = str
if (str.includes('.')) {
ms = Number(str.substring(str.lastIndexOf('.') + 1))
s = str.substring(0, str.lastIndexOf('.'))
}
const t = s.split(':')
let ts: number = 0
for (let i = 0; i < t.length; i++) {
ts += Number(t[t.length - 1 - i]) * Math.pow(60, i)
}
return ts + ms / 1000
} catch (error) {
console.error(error)
return undefined
}
}
const properties = (str: any) => {
const match = str?.match(/#\{\{(.*?)\}\}/);
return {
ext: match[1].trim(),
};
}
const Mdast = {
parse: (text?: string) => {
const content = compile(text || '')
const processer = unified()
.use(parse)
.use(remarkgfm)
.use(code)
.use(inlineCode)
.use(heading)
.use(captions)
const tree = processer.parse(content)
const ast = processer.runSync(tree)
processer.freeze()
return ast
},
captions: (mdast: any) => {
const _mdast = deepClone(mdast)
const data: any = [];
const processer = unified().use(() => (tree: any) => {
visit(tree, 'captions', (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 _mdast = deepClone(mdast)
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: ext?.startsWith('active ') ? timer(ext?.substring(7).trim()) : undefined,
et: undefined,
value: node?.value?.trim(),
position: node?.position
}
data.push(item)
}
})
})
processer.runSync(_mdast)
processer.freeze()
return data
},
heading: {
list: (mdast: any) => {
const _mdast = deepClone(mdast)
let curr: any = null
const children: any = []
traverse(_mdast.children, children)
children?.forEach((node: any) => {
if ('heading' === node?.type) {
node.properties = node?.properties || {}
node.properties.stlist = []
node.properties.etlist = []
curr = null
curr = node
}
if ('captions' === node?.type && curr && curr?.properties) {
curr?.properties?.stlist.push(node?.properties?.st)
curr?.properties?.etlist.push(node?.properties?.et)
}
});
children.forEach((node: any, _i: number) => {
if ('heading' === node?.type && node?.properties?.stlist?.length === 0) {
let j = _i + 1;
while (j < _mdast?.children?.length) {
const child = _mdast?.children[j]
if ('heading' === child?.type && child?.properties?.stlist?.length > 0) {
node?.properties?.stlist.push(...(child?.properties?.stlist ?? []))
node?.properties?.etlist.push(...(child?.properties?.etlist ?? []))
}
j++;
}
}
})
const arry: any = []
children.forEach((node: any) => {
if ('heading' === node?.type) {
const st = node?.properties?.stlist.map((s: any) => timer(s)).sort((a: any, b: any) => a - b)[0]
const et = node?.properties?.etlist.map((s: any) => timer(s)).sort((a: any, b: any) => b - a)[0]
node.properties.st = st
node.properties.et = et
delete node?.properties?.stlist
delete node?.properties?.etlist
arry.push(node)
}
})
return arry;
},
tree: (mdast: any) => {
const heading: Array = Mdast.heading.list(mdast)
let c: any = null
let children: any = []
heading.forEach((h: any, _i: number) => {
if (c === null || h?.depth <= c?.depth) {
c = deepClone(h)
c.next = []
children?.push(c)
} else {
if (c?.next?.length === 0) {
c?.next?.push(deepClone(h))
} else {
build(deepClone(h), c)
}
}
});
return children
}
}
}
export default Mdast