diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index 086a21e..fd3d918 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -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 { diff --git a/packages/parser/src/inline.ts b/packages/parser/src/inline.ts index b30b0f2..1fb146e 100644 --- a/packages/parser/src/inline.ts +++ b/packages/parser/src/inline.ts @@ -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: diff --git a/packages/parser/src/paragraph.ts b/packages/parser/src/paragraph.ts index e5446ab..75b3030 100644 --- a/packages/parser/src/paragraph.ts +++ b/packages/parser/src/paragraph.ts @@ -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 (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 (Paragraph | Table | Paragraph[])> = { code: parseFence, table: parseTable, p: parseParagraph, + ul: parseList(StyleId.ul), + ol: parseList(StyleId.ol), h1: parseHeading, h2: parseHeading, h3: parseHeading, diff --git a/packages/parser/src/utils.ts b/packages/parser/src/utils.ts index 61f1496..319425b 100644 --- a/packages/parser/src/utils.ts +++ b/packages/parser/src/utils.ts @@ -54,3 +54,5 @@ export function sliceInlineText(tokens: Token[]): SliceResult { } return sliceParagraph(tokens) } + +export const MathBlockRegExp = /^\$\$\n([^]*)\n\$\$$/