feat(parser): add list and math support
This commit is contained in:
parent
4042f292cc
commit
de303bf064
|
@ -1,7 +1,7 @@
|
|||
import type Token from 'markdown-it/lib/token'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import type { ISectionOptions, IStylesOptions, Table, TableOfContents } from 'docx'
|
||||
import { BorderStyle, Document, Footer, Header, Packer, PageNumber, Paragraph, SectionType, TextRun, convertInchesToTwip } from 'docx'
|
||||
import { AlignmentType, BorderStyle, Document, Footer, Header, LevelFormat, Packer, PageNumber, Paragraph, SectionType, TextRun, convertInchesToTwip } from 'docx'
|
||||
import type { IMarkdownReportConfig } from '@md-report/types'
|
||||
import { StyleId } from '@md-report/types'
|
||||
import { sliceParagraph, sliceSection } from './utils'
|
||||
|
@ -35,6 +35,119 @@ export function parseDocument(tokens: Token[], styles: IStylesOptions): Document
|
|||
pos += offset
|
||||
}
|
||||
return new Document({
|
||||
numbering: {
|
||||
config: [
|
||||
{
|
||||
reference: StyleId.ul,
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: LevelFormat.BULLET,
|
||||
text: '\u2022',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(0.25), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 1,
|
||||
format: LevelFormat.BULLET,
|
||||
text: '\u25E6',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
format: LevelFormat.BULLET,
|
||||
text: '\u25AA',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(0.75), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 3,
|
||||
format: LevelFormat.BULLET,
|
||||
text: '\u25AB',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(1), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 4,
|
||||
format: LevelFormat.BULLET,
|
||||
text: '\u2022',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(1.25), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
reference: StyleId.ol,
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: LevelFormat.DECIMAL,
|
||||
text: '%1.',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(0.25), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 1,
|
||||
format: LevelFormat.LOWER_LETTER,
|
||||
text: '%2)',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
format: LevelFormat.LOWER_ROMAN,
|
||||
text: '%3.',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(0.75), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 3,
|
||||
format: LevelFormat.DECIMAL,
|
||||
text: '(%4)',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(1), hanging: convertInchesToTwip(0.25) },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
evenAndOddHeaderAndFooters: true,
|
||||
styles,
|
||||
sections,
|
||||
|
@ -49,7 +162,11 @@ export function parseSection(tokens: Token[]): ISectionOptions {
|
|||
while (pos < tokens.length) {
|
||||
const { tokens: paragraph, offset } = sliceParagraph(tokens.slice(pos))
|
||||
const parser = paragraphParser[paragraph[0].tag]
|
||||
children.push(parser(paragraph))
|
||||
const parseResult = parser(paragraph)
|
||||
if (Array.isArray(parseResult))
|
||||
children.push(...parseResult)
|
||||
else
|
||||
children.push(parseResult)
|
||||
pos += offset
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -7,25 +7,43 @@ import type Token from 'markdown-it/lib/token'
|
|||
import { StyleId } from '@md-report/types'
|
||||
import { sliceInlineText } from './utils'
|
||||
|
||||
export function parseInline(props: { tokens: Token[]; style?: StyleId; headingLevel?: number }): Paragraph {
|
||||
// Variables.
|
||||
const { tokens, style = StyleId.normal, headingLevel = 0 } = props
|
||||
const { children: childrenTokens } = tokens[0]
|
||||
export function parseInline(props: { tokens: Token[]; style?: StyleId; headingLevel?: number; isUL?: boolean; isOL?: boolean }): Paragraph {
|
||||
// Props.
|
||||
const { tokens, style = StyleId.normal, headingLevel = 0, isOL = false, isUL = false } = props
|
||||
const { children: childrenTokens, level } = tokens[0]
|
||||
if (!childrenTokens)
|
||||
return new Paragraph({})
|
||||
// Numbering.
|
||||
let numbering: {
|
||||
reference: string
|
||||
level: number
|
||||
} | undefined
|
||||
if (isOL) {
|
||||
numbering = {
|
||||
reference: StyleId.ol,
|
||||
level: (level - 1) / 2 - 1,
|
||||
}
|
||||
}
|
||||
else if (isUL) {
|
||||
numbering = {
|
||||
reference: StyleId.ul,
|
||||
level: (level - 1) / 2 - 1,
|
||||
}
|
||||
}
|
||||
const children: ParagraphChild[] = []
|
||||
let pos = 0
|
||||
// Parse inline children.
|
||||
while (pos < childrenTokens.length) {
|
||||
const { tokens: paragraphChild, offset: nextPos } = sliceInlineText(childrenTokens.slice(pos))
|
||||
const { tokens: paragraphChild, offset } = sliceInlineText(childrenTokens.slice(pos))
|
||||
if (paragraphChild[0].tag === 'img')
|
||||
children.push(parseImage(paragraphChild))
|
||||
else
|
||||
children.push(parseText(paragraphChild))
|
||||
pos += nextPos
|
||||
pos += offset
|
||||
}
|
||||
return new Paragraph({
|
||||
style,
|
||||
numbering,
|
||||
heading: headingLevel ? HeadingLevel[`HEADING_${headingLevel}` as keyof typeof HeadingLevel] : undefined,
|
||||
children,
|
||||
})
|
||||
|
@ -65,7 +83,15 @@ export function parseText(tokens: Token[]): TextRun {
|
|||
// Inline code.
|
||||
case 'code':
|
||||
// TODO: Replace code font with env data.
|
||||
options = { ...options, font: {}, text: token.content }
|
||||
options = {
|
||||
...options,
|
||||
size: 20,
|
||||
font: {
|
||||
ascii: 'Monaco',
|
||||
eastAsia: 'KaiTi',
|
||||
},
|
||||
text: token.content,
|
||||
}
|
||||
break
|
||||
// Normal text.
|
||||
default:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import type Token from 'markdown-it/lib/token'
|
||||
import type { IBorderOptions } from 'docx'
|
||||
import { BorderStyle, Paragraph, Table, TableCell, TableRow, TextRun } from 'docx'
|
||||
import { BorderStyle, Math, MathRun, Paragraph, Table, TableCell, TableRow, TextRun } from 'docx'
|
||||
import { StyleId } from '@md-report/types'
|
||||
import { sliceTableRow } from './utils'
|
||||
import { MathBlockRegExp, sliceTableRow } from './utils'
|
||||
import { parseInline } from './inline'
|
||||
|
||||
export function parseFence(tokens: Token[]): Paragraph {
|
||||
|
@ -59,7 +59,19 @@ export function parseParagraph(tokens: Token[]): Paragraph {
|
|||
let style = StyleId.normal
|
||||
if (inline[0].children?.length === 1 && inline[0].children[0].tag === 'img')
|
||||
style = StyleId.image
|
||||
|
||||
// Math blocks.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_, mathBlock] = inline[0].content.match(MathBlockRegExp) || []
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(mathBlock)
|
||||
if (mathBlock) {
|
||||
return new Paragraph({
|
||||
style: StyleId.image,
|
||||
children: [new Math({
|
||||
children: [new MathRun(mathBlock)],
|
||||
})],
|
||||
})
|
||||
}
|
||||
return parseInline({
|
||||
tokens: inline,
|
||||
style,
|
||||
|
@ -78,10 +90,27 @@ export function parseHeading(tokens: Token[]): Paragraph {
|
|||
})
|
||||
}
|
||||
|
||||
export const paragraphParser: Record<string, (tokens: Token[]) => (Paragraph|Table)> = {
|
||||
export function parseList(type: StyleId.ol | StyleId.ul): (tokens: Token[]) => Paragraph[] {
|
||||
const parser = (tokens: Token[]) => {
|
||||
let pos = 0
|
||||
const children: Paragraph[] = []
|
||||
while (pos < tokens.length) {
|
||||
if (tokens[pos].type === 'inline')
|
||||
children.push(parseInline({ tokens: [tokens[pos]], style: StyleId.list, isUL: type === StyleId.ul, isOL: type === StyleId.ol }))
|
||||
pos++
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
return parser
|
||||
}
|
||||
|
||||
export const paragraphParser: Record<string, (tokens: Token[]) => (Paragraph | Table | Paragraph[])> = {
|
||||
code: parseFence,
|
||||
table: parseTable,
|
||||
p: parseParagraph,
|
||||
ul: parseList(StyleId.ul),
|
||||
ol: parseList(StyleId.ol),
|
||||
h1: parseHeading,
|
||||
h2: parseHeading,
|
||||
h3: parseHeading,
|
||||
|
|
|
@ -54,3 +54,5 @@ export function sliceInlineText(tokens: Token[]): SliceResult {
|
|||
}
|
||||
return sliceParagraph(tokens)
|
||||
}
|
||||
|
||||
export const MathBlockRegExp = /^\$\$\n([^]*)\n\$\$$/
|
||||
|
|
Loading…
Reference in New Issue