import * as vscode from "vscode" ; const util = require ( "util" ) ; const markdownLatexPlugin = require ( "./markdown-latex-plugin" ) ; const MarkdownIt = require ( "markdown-it" ) ; const domutils = require ( "domutils" ) ; const htmlparser2 = require ( "htmlparser2" ) ; import { tex2mml } from "./load-mathjax" ; interface WLNotebookData { cells : { kind : vscode . NotebookCellKind ; languageId : string ; value : string ; outputs ? : { items : { mime : string ; data : string | Uint8Array ; } [ ] ; metadata ? : { [ key ] : any ; } ; } [ ] ; executionSummary ? : vscode . NotebookCellExecutionSummary ; metadata ? : { [ key ] : any ; } ; } [ ] ; metadata ? : { [ key ] : any ; } ; } export class WLNotebookSerializer implements vscode . NotebookSerializer { async deserializeNotebook ( content , _token ) { const decoder = new util . TextDecoder ( ) ; const encoder = new util . TextEncoder ( ) ; let notebook ; try { notebook = JSON . parse ( decoder . decode ( content ) ) as WLNotebookData ; for ( let cell of notebook . cells ) { if ( cell . executionSummary ) { delete cell . executionSummary ; } if ( cell . outputs ) { for ( let output of cell . outputs ) { for ( let item of output . items ) { item . data = encoder . encode ( item . data ) ; } } } } } catch ( e ) { notebook = { cells : [ ] } ; } return notebook as vscode . NotebookData ; } async serializeNotebook ( data , _token ) { const decoder = new util . TextDecoder ( ) ; const encoder = new util . TextEncoder ( ) ; let notebook = data as WLNotebookData ; try { for ( let cell of notebook . cells ) { if ( cell . outputs ) { for ( let output of cell . outputs ) { for ( let item of output . items ) { item . data = decoder . decode ( item . data ) ; } } } } } catch ( e ) { notebook = { cells : [ ] } ; } return encoder . encode ( JSON . stringify ( notebook , null , 1 ) ) ; } } export function deserializeMarkup ( markupText ) { const cellData = [ ] ; const md = new MarkdownIt ( { html : true } ) . use ( markdownLatexPlugin ) ; const html = md . render ( markupText ) ; const doc = htmlparser2 . parseDocument ( html ) ; const paragraphTags = new Set ( [ "blockquote" , "div" , "h1" , "h2" , "h3" , "h4" , "h5" , "h6" , "li" , "ol" , "p" , "pre" , "ul" ] ) ; const handleContent = ( element , pre = false , textOnly = false ) => { const handleChildren = ( pre , textOnly ) => { const contents = ( element ?. children || [ ] ) . map ( ( e ) => handleContent ( e , pre , textOnly ) ) ; return textOnly ? contents . join ( "" ) : contents ; } ; if ( typeof element === "string" ) { return element ; } else if ( element . type === "text" ) { return pre ? element . data . replace ( /\n$/ , " " ) : element . data . replaceAll ( "\n" , " " ) ; } else if ( element . name === "br" ) { return "\n" ; } else if ( element . name === "li" ) { return domutils . getOuterHTML ( element ) ; } else if ( ! textOnly ) { switch ( element . name ) { case "a" : return { type : "Hyperlink" , children : handleChildren ( pre , textOnly ) , link : element ?. attribs ?. href || "" } ; case "button" : return { type : "Button" , children : handleChildren ( pre , textOnly ) , link : element ?. attribs ?. onclick || "" } ; case "code" : return { type : "Code" , children : handleChildren ( pre , true ) } ; case "em" : case "i" : return { type : "Italic" , children : handleChildren ( pre , textOnly ) } ; case "strong" : return { type : "Bold" , children : handleChildren ( pre , textOnly ) } ; case "s" : return { type : "StrikeThrough" , children : handleChildren ( pre , textOnly ) } ; case "small" : return { type : "Smaller" , children : handleChildren ( pre , textOnly ) } ; case "sup" : return { type : "Superscript" , children : handleChildren ( pre , true ) } ; case "sub" : return { type : "Subscript" , children : handleChildren ( pre , true ) } ; case "img" : return { type : "Image" , children : element ?. attribs ?. title || "[Image]" , link : element ?. attribs ?. src || "" } ; case "tex" : const tex = Buffer . from ( handleChildren ( pre , true ) , "base64" ) . toString ( ) ; return { type : "LaTeX" , children : tex2mml ( tex ) } ; } return handleChildren ( pre , textOnly ) ; } else { return handleChildren ( pre , true ) ; } } ; const nonTerminalTagRules = { "" : [ "ol" , "ul" , "pre" , "blockquote" ] , "ol" : [ "li" ] , "ul" : [ "li" ] , "li" : [ "ol" , "ul" , "pre" ] , "blockquote" : [ "blockquote" , "pre" ] } ; let tagStack = [ ] ; let isItemParagraphStack = [ ] ; let ignoreNextNChildren = 0 ; const handleElement = ( element , nonTerminalTags ) => { if ( ignoreNextNChildren > 0 ) { -- ignoreNextNChildren ; return ; } if ( nonTerminalTags . indexOf ( element ?. name || "" ) >= 0 ) { tagStack . push ( element . name ) ; isItemParagraphStack . push ( false ) ; ignoreNextNChildren = 0 ; element . children . map ( ( e ) => handleElement ( e , nonTerminalTagRules [ element ?. name ] || [ ] ) ) ; ignoreNextNChildren = 0 ; tagStack . pop ( ) ; isItemParagraphStack . pop ( ) ; } else { const elementTag = element . type === "text" ? "text" : element . name ; const parentTag = tagStack [ tagStack . length - 1 ] ; if ( elementTag . match ( /h[1-6]/g ) ) { cellData . push ( { type : [ "Title" , "Chapter" , "Section" , "Subsection" , "Subsubsection" , "Subsubsubsection" ] [ parseInt ( elementTag . slice ( 1 ) ) - 1 ] , label : "" , text : handleContent ( element ) } ) ; } else if ( elementTag === "code" ) { cellData . push ( { type : "Text" , label : "" , text : handleContent ( element , true ) } ) ; } else if ( elementTag === "hr" ) { cellData . push ( { type : "HorizontalLine" , label : "" , text : "" } ) ; } else { switch ( parentTag ) { case "li" : { const listTags = tagStack . filter ( tag => ( tag === "ol" || tag === "ul" ) ) ; const isOrderedList = ( listTags [ listTags . length - 1 ] === "ol" ) ; const listLevel = Math . min ( Math . max ( listTags . length , 1 ) , 3 ) ; if ( element ?. data !== "\n" ) { const isItemParagraph = isItemParagraphStack [ isItemParagraphStack . length - 1 ] ; const type = [ "Item" , "Subitem" , "Subsubitem" ] [ listLevel - 1 ] + ( isItemParagraph ? "Paragraph" : isOrderedList ? "Numbered" : "" ) ; isItemParagraphStack [ isItemParagraphStack . length - 1 ] = true ; if ( element . type === "tag" && paragraphTags . has ( element . name ) ) { cellData . push ( { type : type , label : "" , text : handleContent ( element ) } ) ; } else { let groupedElements = [ ] ; ignoreNextNChildren = - 1 ; while ( element ?. type === "text" || ( element ?. type === "tag" && ! paragraphTags . has ( element ?. name ) ) ) { ++ ignoreNextNChildren ; groupedElements . push ( element ) ; element = element . next ; } cellData . push ( { type : type , label : "" , text : handleContent ( { type : "tag" , name : "p" , children : groupedElements } ) } ) ; } } break ; } default : { if ( element ?. data !== "\n" ) { cellData . push ( { type : "Text" , label : "" , text : handleContent ( element ) } ) ; } } } } } } ; doc . children . map ( ( element ) => { try { handleElement ( element , nonTerminalTagRules [ "" ] ) ; } catch ( _ ) { } } ) ; return cellData ; }