UNPKG

md-to-adf

Version:

Translate Markdown (Github) into Atlassian Document Format (ADF)

201 lines (174 loc) 6.24 kB
/********************************************************************************************************************** * * Markdown Parser * * @author bruno.morel@b-yond.com *--------------------------------------------------------------------------------------------------------------------- * * This translate all markdown to an intermediate representation composed as an array with * each item containing and object with the follow properties: * adfType : the ADF type of the line (heading, paragraph, orderedList, ... * textToEmphasis: the actuel text (if any) attached to the element * typeParam: any extra parameter for special types (language for codeBlock) * nodeAttached: element to manage the special case of a codeBlock attached to a list * textPosition: the actual start position of the text (used later for level identication) * **********************************************************************************************************************/ /** * @typedef {Object} IRElement * @property {number} adfType - ADF type of the expression * @property {number} textPosition - the actual start of the text (adfType dependent) * @property {string} textToEmphasis - actual text of the element (adfType dependent) * @property {string} typeParam - extra parameters adfType dependent * @property {IRElement} nodeAttached - an attached code block to a list */ /** * Parse markdown into an Intermediate representation * * @param markdownLineTextWithTabs an array of markdown expression to process * @returns {IRElement} an intermediate representation of the markdown element */ function parseMarkdownLinetoIR( markdownLineTextWithTabs ){ //to simplify tab management we replace them with spaces const markdownLine = markdownLineTextWithTabs.replace( /\t/g, ' ' ) //we try to match each line to match with a markdown expression // or we push an empty paragraph const headerNode = matchHeader( markdownLine ) if( headerNode ) return headerNode const divider = matchDivider( markdownLine ) if( divider ) return divider const listNode = matchList( markdownLine ) if( listNode ) return listNode const blockQuoteNode = matchBlockQuote( markdownLine ) if( blockQuoteNode ) return blockQuoteNode const codeBlockNode = matchCodeBlock( markdownLine ) if( codeBlockNode ) return codeBlockNode const paragraphNode = matchParagraph( markdownLine ) if( paragraphNode ) return paragraphNode //this is a line break then return { adfType : "paragraph", textToEmphasis: "", textPosition: markdownLine.length } } /** * Matching of the markdown header * * @param lineToMatch actual expression to match * * @returns {IRElement} | null if the expression doesn't match */ function matchHeader( lineToMatch ){ const headerType = lineToMatch.match( /^(?<headerNumber>[#]{1,6}) (?<headerText>.*)$/i ) if( headerType && headerType.groups && headerType.groups.headerNumber && headerType.groups.headerText ){ return { adfType : "heading",//adfRoot.heading( headerType.groups.headerNumber.length ), textToEmphasis: headerType.groups.headerText, typeParam: headerType.groups.headerNumber.length, textPosition: 0 } } return null } /** * Matching of a markdown list * * @param lineToMatch actual expression to match * * @returns {IRElement} | null if the expression doesn't match */ function matchList( lineToMatch ){ const list = lineToMatch.match( /^(?:[\s])*(?:[*\-+] |(?<orderedNumber>[0-9]+)[.)] )+(?<listText>.*)$/i ) if( list && list.groups && list.groups.listText ){ // adfDescription.bulletList( ) // .textItem( ) const textIsCodeBlock = matchCodeBlock( list.groups.listText ) if( textIsCodeBlock ) textIsCodeBlock.textPosition = lineToMatch.indexOf( list.groups.listText ) return { adfType : list.groups.orderedNumber ? "orderedList" : "bulletList", typeParam: list.groups.orderedNumber, textToEmphasis: textIsCodeBlock ? '': list.groups.listText, textPosition: lineToMatch.indexOf( list.groups.listText ) - 2, nodeAttached: textIsCodeBlock } } return null } /** * Match a markdown code block * * @param lineToMatch actual expression to match * * @returns {IRElement} | null if the expression doesn't match */ function matchCodeBlock( lineToMatch ){ const codeBlock = lineToMatch.match( /^(?:[\s]*```)(?<Language>[^\s]*)$/i ) if( codeBlock && codeBlock.groups ){ return { adfType: "codeBlock", typeParam: codeBlock.groups.Language, textPosition: lineToMatch.indexOf( '```' ), textToEmphasis: '' } } return null } /** * Match a markdown blockquote * * @param lineToMatch actual expression to match * * @returns {IRElement} | null if the expression doesn't match */ function matchBlockQuote( lineToMatch ){ const blockquote = lineToMatch.match( /^(?:[\s])*> (?<quoteText>.*)$/i ) if( blockquote && blockquote.groups && blockquote.groups.quoteText ){ return { adfType : "blockQuote", textToEmphasis: blockquote.groups.quoteText, textPosition: lineToMatch.indexOf( '> ' ) } } return null } /** * Match a markdown paragraph * * @param lineToMatch actual expression to match * * @returns {IRElement} | null if the expression doesn't match */ function matchParagraph( lineToMatch ){ const paragraph = lineToMatch.match( /^(?:[\s]*)(?<paragraphText>[^\n]+)$/ ) if( paragraph && paragraph.groups && paragraph.groups.paragraphText ){ return { adfType : "paragraph", textToEmphasis: paragraph.groups.paragraphText, textPosition: !paragraph.groups.paragraphText.match( /^(?:[\s]*)$/ ) ? lineToMatch.indexOf( paragraph.groups.paragraphText ) : lineToMatch.length } } return null } /** * Match a markdown divider * * @param lineToMatch actual expression to match * * @returns {IRElement} | null if the expression doesn't match */ function matchDivider( lineToMatch ){ const divider = lineToMatch.match( /^(\s*-{3,}\s*|\s*\*{3,}\s*|\s*_{3,}\s*)$/ ) if( divider ){ return { adfType : "divider", textToEmphasis: '', textPosition: 0 } } return null } module.exports = parseMarkdownLinetoIR