├── example.html ├── .vscode ├── settings.json └── launch.json ├── md2html.ts ├── mod.ts ├── example.md ├── LICENSE ├── src ├── extend-regexp.ts ├── helpers.ts ├── renderer.ts ├── marked.ts ├── interfaces.ts ├── parser.ts ├── inline-lexer.ts └── block-lexer.ts └── README.md /example.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubersl0th/markdown/HEAD/example.html -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "[typescript]": { 4 | "editor.defaultFormatter": "axetroy.vscode-deno" 5 | }, 6 | "[typescriptreact]": { 7 | "editor.defaultFormatter": "axetroy.vscode-deno" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /md2html.ts: -------------------------------------------------------------------------------- 1 | import { Marked } from "./mod.ts"; 2 | 3 | const decoder = new TextDecoder("utf-8"); 4 | const filename = Deno.args[0]; 5 | const markdown = decoder.decode(await Deno.readFile(filename)); 6 | const markup = Marked.parse(markdown); 7 | console.log(markup.content); 8 | console.log(JSON.stringify(markup.meta)) 9 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/block-lexer.ts"; 2 | export * from "./src/helpers.ts"; 3 | export * from "./src/inline-lexer.ts"; 4 | export * from "./src/interfaces.ts"; 5 | export * from "./src/marked.ts"; 6 | export * from "./src/parser.ts"; 7 | export * from "./src/renderer.ts"; 8 | export * from "./src/extend-regexp.ts"; 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Deno", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeExecutable": "deno", 10 | "runtimeArgs": ["run", "--inspect", "-A", "app.ts"], 11 | "port": 9229 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /example.md: -------------------------------------------------------------------------------- 1 | --- 2 | title : Hello world! 3 | subtitle : Front-matter is supported! 4 | boolean: true 5 | list-example: 6 | - this 7 | - is 8 | - a: list 9 | --- 10 | # Hello World 11 | 12 | ## This an example for `md2html.ts` 13 | 14 | A small paragraph that will become a `
` tag 15 | 16 | --- 17 | 18 | Code Block (md2html.ts) 19 | 20 | ```typescript 21 | import { Marked } from "./mod.ts"; 22 | 23 | const decoder = new TextDecoder("utf-8"); 24 | const filename = Deno.args[0]; 25 | const markdown = decoder.decode(await Deno.readFile(filename)); 26 | const markup = Marked.parse(markdown); 27 | console.log(markup.content); 28 | console.log(JSON.stringify(markup.meta)); 29 | ``` 30 | 31 | This module is forked from [ts-stack/markdown](https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831) 32 | 33 | Made for Deno 34 |  35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eivind Furuberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/extend-regexp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @license 3 | * 4 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 5 | * https://github.com/chjj/marked 6 | * 7 | * Copyright (c) 2018, Костя Третяк. (MIT Licensed) 8 | * https://github.com/ts-stack/markdown 9 | */ 10 | 11 | export class ExtendRegexp { 12 | private source: string; 13 | private flags: string; 14 | 15 | constructor(regex: RegExp, flags: string = "") { 16 | this.source = regex.source; 17 | this.flags = flags; 18 | } 19 | 20 | /** 21 | * Extend regular expression. 22 | * 23 | * @param groupName Regular expression for search a group name. 24 | * @param groupRegexp Regular expression of named group. 25 | */ 26 | setGroup(groupName: RegExp | string, groupRegexp: RegExp | string): this { 27 | let newRegexp: string = typeof groupRegexp == "string" 28 | ? groupRegexp 29 | : groupRegexp.source; 30 | newRegexp = newRegexp.replace(/(^|[^\[])\^/g, "$1"); 31 | 32 | // Extend regexp. 33 | this.source = this.source.replace(groupName, newRegexp); 34 | return this; 35 | } 36 | 37 | /** 38 | * Returns a result of extending a regular expression. 39 | */ 40 | getRegexp(): RegExp { 41 | return new RegExp(this.source, this.flags); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * 4 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 5 | * https://github.com/chjj/marked 6 | * 7 | * Copyright (c) 2018, Костя Третяк. (MIT Licensed) 8 | * https://github.com/ts-stack/markdown 9 | */ 10 | 11 | import { Replacements } from "./interfaces.ts"; 12 | 13 | const escapeTest = /[&<>"']/; 14 | const escapeReplace = /[&<>"']/g; 15 | const replacements: Replacements = { 16 | "&": "&", 17 | "<": "<", 18 | ">": ">", 19 | '"': """, 20 | // tslint:disable-next-line:quotemark 21 | "'": "'", 22 | }; 23 | 24 | const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; 25 | const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; 26 | 27 | export function escape(html: string, encode?: boolean) { 28 | if (encode) { 29 | if (escapeTest.test(html)) { 30 | return html.replace(escapeReplace, (ch: string) => replacements[ch]); 31 | } 32 | } else { 33 | if (escapeTestNoEncode.test(html)) { 34 | return html.replace( 35 | escapeReplaceNoEncode, 36 | (ch: string) => replacements[ch], 37 | ); 38 | } 39 | } 40 | 41 | return html; 42 | } 43 | 44 | export function unescape(html: string) { 45 | // Explicitly match decimal, hex, and named HTML entities 46 | return html.replace( 47 | /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi, 48 | function (_, n) { 49 | n = n.toLowerCase(); 50 | 51 | if (n === "colon") { 52 | return ":"; 53 | } 54 | 55 | if (n.charAt(0) === "#") { 56 | return n.charAt(1) === "x" 57 | ? String.fromCharCode(parseInt(n.substring(2), 16)) 58 | : String.fromCharCode(+n.substring(1)); 59 | } 60 | 61 | return ""; 62 | }, 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown 2 | 3 | Deno Markdown module forked from https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831 4 | 5 | ### Example usage 6 | 7 | Simple md2html.ts script: 8 | 9 | ```typescript 10 | import { Marked } from "./mod.ts"; 11 | 12 | const decoder = new TextDecoder("utf-8"); 13 | const filename = Deno.args[0]; 14 | const markdown = decoder.decode(await Deno.readFile(filename)); 15 | const markup = Marked.parse(markdown); 16 | console.log(markup.content); 17 | console.log(JSON.stringify(markup.meta)) 18 | ``` 19 | 20 | Now running: 21 | 22 | ```bash 23 | deno run --allow-read md2html.ts example.md > example.html 24 | ``` 25 | 26 | Will output: 27 | 28 | ```html 29 |
md2html.ts
32 | A small paragraph that will become a <p> tag
Code Block (md2html.ts)
36 | 37 |import { Marked } from "./mod.ts";
38 |
39 | const decoder = new TextDecoder("utf-8");
40 | const filename = Deno.args[0];
41 | const markdown = decoder.decode(await Deno.readFile(filename));
42 | const markup = Marked.parse(markdown);
43 | console.log(markup.content);
44 | console.log(JSON.stringify(markup.meta))
45 |
46 | 47 | This module is forked from 48 | ts-stack/markdown 52 |
53 |Made for Deno
" + (escaped
35 | ? code
36 | : this.options.escape(code, true)) +
37 | "\n\n";
38 | }
39 |
40 | return (
41 | '\n' +
45 | (escaped ? code : this.options.escape(code, true)) +
46 | "\n\n"
47 | );
48 | }
49 |
50 | blockquote(quote: string): string {
51 | return "\n" + quote + "\n"; 52 | } 53 | 54 | html(html: string): string { 55 | return html; 56 | } 57 | 58 | heading(text: string, level: number, raw: string): string { 59 | const id: string = this.options.headerPrefix + 60 | raw.toLowerCase().replace(/[^\w]+/g, "-"); 61 | 62 | return `
" + text + "
\n"; 81 | } 82 | 83 | table(header: string, body: string): string { 84 | return ` 85 |" + text + "";
121 | }
122 |
123 | br(): string {
124 | return this.options.xhtml ? "An error occured:
" + 147 | this.options.escape(err.message + "", true) + ""; 148 | } 149 | 150 | throw err; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * 4 | * Copyright (c) 2018, Костя Третяк. (MIT Licensed) 5 | * https://github.com/ts-stack/markdown 6 | */ 7 | 8 | import { escape, unescape } from "./helpers.ts"; 9 | import { Renderer } from "./renderer.ts"; 10 | 11 | export interface Obj { 12 | [key: string]: any; 13 | } 14 | 15 | export interface RulesBlockBase { 16 | newline: RegExp; 17 | code: RegExp; 18 | hr: RegExp; 19 | heading: RegExp; 20 | lheading: RegExp; 21 | blockquote: RegExp; 22 | list: RegExp; 23 | html: RegExp; 24 | def: RegExp; 25 | paragraph: RegExp; 26 | text: RegExp; 27 | bullet: RegExp; 28 | /** 29 | * List item (
some text
' 162 | * 163 | * Marked.setOptions({isNoP: true}); 164 | * 165 | * Marked.parse('some text'); // returns 'some text' 166 | * ``` 167 | */ 168 | isNoP?: boolean; 169 | } 170 | 171 | export interface LexerReturns { 172 | tokens: Token[]; 173 | links: Links; 174 | meta: Obj; 175 | } 176 | 177 | export interface Parsed { 178 | content: string; 179 | meta: Obj; 180 | } 181 | 182 | export interface DebugReturns extends LexerReturns { 183 | result: string; 184 | } 185 | 186 | export interface Replacements { 187 | [key: string]: string; 188 | } 189 | 190 | export interface RulesInlineCallback { 191 | regexp?: RegExp; 192 | condition(): RegExp; 193 | tokenize(execArr: RegExpExecArray): void; 194 | } 195 | 196 | export type SimpleRenderer = (execArr?: RegExpExecArray) => string; 197 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * 4 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 5 | * https://github.com/chjj/marked 6 | * 7 | * Copyright (c) 2018, Костя Третяк. (MIT Licensed) 8 | * https://github.com/ts-stack/markdown 9 | */ 10 | 11 | import { InlineLexer } from "./inline-lexer.ts"; 12 | import { 13 | Links, 14 | MarkedOptions, 15 | SimpleRenderer, 16 | Token, 17 | TokenType, 18 | } from "./interfaces.ts"; 19 | import { Marked } from "./marked.ts"; 20 | import { Renderer } from "./renderer.ts"; 21 | 22 | /** 23 | * Parsing & Compiling. 24 | */ 25 | export class Parser { 26 | simpleRenderers: SimpleRenderer[] = []; 27 | protected tokens: Token[]; 28 | protected token: Token | undefined; 29 | protected inlineLexer!: InlineLexer; 30 | protected options: MarkedOptions; 31 | protected renderer: Renderer; 32 | protected line: number = 0; 33 | 34 | constructor(options?: MarkedOptions) { 35 | this.tokens = []; 36 | this.token = undefined; 37 | this.options = options || Marked.options; 38 | this.renderer = this.options.renderer || new Renderer(this.options); 39 | } 40 | 41 | static parse(tokens: Token[], links: Links, options?: MarkedOptions): string { 42 | const parser = new this(options); 43 | return parser.parse(links, tokens); 44 | } 45 | 46 | parse(links: Links, tokens: Token[]) { 47 | this.inlineLexer = new InlineLexer( 48 | InlineLexer, 49 | links, 50 | this.options, 51 | this.renderer, 52 | ); 53 | this.tokens = tokens.reverse(); 54 | 55 | let out = ""; 56 | 57 | while (this.next()) { 58 | out += this.tok(); 59 | } 60 | 61 | return out; 62 | } 63 | 64 | debug(links: Links, tokens: Token[]) { 65 | this.inlineLexer = new InlineLexer( 66 | InlineLexer, 67 | links, 68 | this.options, 69 | this.renderer, 70 | ); 71 | this.tokens = tokens.reverse(); 72 | 73 | let out = ""; 74 | 75 | while (this.next()) { 76 | const outToken: string = this.tok() || ""; 77 | if (!this.token) throw ReferenceError; 78 | this.token.line = this.line += outToken.split("\n").length - 1; 79 | out += outToken; 80 | } 81 | 82 | return out; 83 | } 84 | 85 | protected next() { 86 | return (this.token = this.tokens.pop()); 87 | } 88 | 89 | protected getNextElement() { 90 | return this.tokens[this.tokens.length - 1]; 91 | } 92 | 93 | protected parseText() { 94 | if (!this.token) throw ReferenceError; 95 | let body = this.token.text; 96 | let nextElement: Token; 97 | 98 | while ( 99 | (nextElement = this.getNextElement()) && 100 | nextElement.type == TokenType.text 101 | ) { 102 | body += "\n" + this.next()?.text; 103 | } 104 | 105 | return this.inlineLexer.output(body || ""); 106 | } 107 | 108 | protected tok() { 109 | if (!this.token) throw ReferenceError; 110 | switch (this.token.type) { 111 | case TokenType.space: { 112 | return ""; 113 | } 114 | case TokenType.paragraph: { 115 | return this.renderer.paragraph( 116 | this.inlineLexer.output(this.token.text || ""), 117 | ); 118 | } 119 | case TokenType.text: { 120 | if (this.options.isNoP) { 121 | return this.parseText(); 122 | } else { 123 | return this.renderer.paragraph(this.parseText()); 124 | } 125 | } 126 | case TokenType.heading: { 127 | return this.renderer.heading( 128 | this.inlineLexer.output(this.token.text || ""), 129 | this.token.depth || 0, 130 | this.token.text || "", 131 | ); 132 | } 133 | case TokenType.listStart: { 134 | let body = ""; 135 | const ordered = this.token.ordered; 136 | 137 | while (this.next()?.type != TokenType.listEnd) { 138 | body += this.tok(); 139 | } 140 | 141 | return this.renderer.list(body, ordered); 142 | } 143 | case TokenType.listItemStart: { 144 | let body = ""; 145 | 146 | while (this.next()?.type != TokenType.listItemEnd) { 147 | body += this.token.type == (TokenType.text as any) 148 | ? this.parseText() 149 | : this.tok(); 150 | } 151 | 152 | return this.renderer.listitem(body); 153 | } 154 | case TokenType.looseItemStart: { 155 | let body = ""; 156 | 157 | while (this.next()?.type != TokenType.listItemEnd) { 158 | body += this.tok(); 159 | } 160 | 161 | return this.renderer.listitem(body); 162 | } 163 | case TokenType.code: { 164 | return this.renderer.code( 165 | this.token.text || "", 166 | this.token.lang, 167 | this.token.escaped, 168 | ); 169 | } 170 | case TokenType.table: { 171 | let header = ""; 172 | let body = ""; 173 | let cell; 174 | 175 | if ( 176 | !this.token || !this.token.header || !this.token.align || 177 | !this.token.cells 178 | ) { 179 | throw ReferenceError; 180 | } 181 | // header 182 | cell = ""; 183 | for (let i = 0; i < this.token.header.length; i++) { 184 | const flags = { header: true, align: this.token.align[i] }; 185 | const out = this.inlineLexer.output(this.token.header[i]); 186 | 187 | cell += this.renderer.tablecell(out, flags); 188 | } 189 | 190 | header += this.renderer.tablerow(cell); 191 | 192 | for (const row of this.token.cells) { 193 | cell = ""; 194 | 195 | for (let j = 0; j < row.length; j++) { 196 | cell += this.renderer.tablecell(this.inlineLexer.output(row[j]), { 197 | header: false, 198 | align: this.token.align[j], 199 | }); 200 | } 201 | 202 | body += this.renderer.tablerow(cell); 203 | } 204 | 205 | return this.renderer.table(header, body); 206 | } 207 | case TokenType.blockquoteStart: { 208 | let body = ""; 209 | 210 | while (this.next()?.type != TokenType.blockquoteEnd) { 211 | body += this.tok(); 212 | } 213 | 214 | return this.renderer.blockquote(body); 215 | } 216 | case TokenType.hr: { 217 | return this.renderer.hr(); 218 | } 219 | case TokenType.html: { 220 | const html = !this.token.pre && !this.options.pedantic 221 | ? this.inlineLexer.output(this.token.text || "") 222 | : this.token.text; 223 | return this.renderer.html(html || ""); 224 | } 225 | default: { 226 | if (this.simpleRenderers.length) { 227 | for (let i = 0; i < this.simpleRenderers.length; i++) { 228 | if (this.token.type == "simpleRule" + (i + 1)) { 229 | return this.simpleRenderers[i].call( 230 | this.renderer, 231 | this.token.execArr, 232 | ); 233 | } 234 | } 235 | } 236 | 237 | const errMsg = `Token with "${this.token.type}" type was not found.`; 238 | 239 | if (this.options.silent) { 240 | console.log(errMsg); 241 | } else { 242 | throw new Error(errMsg); 243 | } 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/inline-lexer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * 4 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 5 | * https://github.com/chjj/marked 6 | * 7 | * Copyright (c) 2018, Костя Третяк. (MIT Licensed) 8 | * https://github.com/ts-stack/markdown 9 | */ 10 | 11 | import { ExtendRegexp } from "./extend-regexp.ts"; 12 | import { 13 | Link, 14 | Links, 15 | MarkedOptions, 16 | RulesInlineBase, 17 | RulesInlineBreaks, 18 | RulesInlineCallback, 19 | RulesInlineGfm, 20 | RulesInlinePedantic, 21 | } from "./interfaces.ts"; 22 | import { Marked } from "./marked.ts"; 23 | import { Renderer } from "./renderer.ts"; 24 | 25 | /** 26 | * Inline Lexer & Compiler. 27 | */ 28 | export class InlineLexer { 29 | protected static rulesBase: RulesInlineBase; 30 | /** 31 | * Pedantic Inline Grammar. 32 | */ 33 | protected static rulesPedantic: RulesInlinePedantic; 34 | /** 35 | * GFM Inline Grammar 36 | */ 37 | protected static rulesGfm: RulesInlineGfm; 38 | /** 39 | * GFM + Line Breaks Inline Grammar. 40 | */ 41 | protected static rulesBreaks: RulesInlineBreaks; 42 | protected rules!: 43 | | RulesInlineBase 44 | | RulesInlinePedantic 45 | | RulesInlineGfm 46 | | RulesInlineBreaks; 47 | protected renderer: Renderer; 48 | protected inLink!: boolean; 49 | protected hasRulesGfm!: boolean; 50 | protected ruleCallbacks!: RulesInlineCallback[]; 51 | 52 | constructor( 53 | protected staticThis: typeof InlineLexer, 54 | protected links: Links, 55 | protected options: MarkedOptions = Marked.options, 56 | renderer?: Renderer, 57 | ) { 58 | this.renderer = renderer || this.options.renderer || 59 | new Renderer(this.options); 60 | 61 | if (!this.links) { 62 | throw new Error(`InlineLexer requires 'links' parameter.`); 63 | } 64 | 65 | this.setRules(); 66 | } 67 | 68 | /** 69 | * Static Lexing/Compiling Method. 70 | */ 71 | static output(src: string, links: Links, options: MarkedOptions): string { 72 | const inlineLexer = new this(this, links, options); 73 | return inlineLexer.output(src); 74 | } 75 | 76 | protected static getRulesBase(): RulesInlineBase { 77 | if (this.rulesBase) { 78 | return this.rulesBase; 79 | } 80 | 81 | /** 82 | * Inline-Level Grammar. 83 | */ 84 | const base: RulesInlineBase = { 85 | escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 86 | autolink: /^<([^ <>]+(@|:\/)[^ <>]+)>/, 87 | tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^<'">])*?>/, 88 | link: /^!?\[(inside)\]\(href\)/, 89 | reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 90 | nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, 91 | strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 92 | em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, 93 | code: /^(`+)([\s\S]*?[^`])\1(?!`)/, 94 | br: /^ {2,}\n(?!\s*$)/, 95 | text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/, 98 | }; 99 | 100 | base.link = new ExtendRegexp(base.link) 101 | .setGroup("inside", base._inside) 102 | .setGroup("href", base._href) 103 | .getRegexp(); 104 | 105 | base.reflink = new ExtendRegexp(base.reflink).setGroup( 106 | "inside", 107 | base._inside, 108 | ).getRegexp(); 109 | 110 | return (this.rulesBase = base); 111 | } 112 | 113 | protected static getRulesPedantic(): RulesInlinePedantic { 114 | if (this.rulesPedantic) { 115 | return this.rulesPedantic; 116 | } 117 | 118 | return (this.rulesPedantic = { 119 | ...this.getRulesBase(), 120 | ...{ 121 | strong: 122 | /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 123 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, 124 | }, 125 | }); 126 | } 127 | 128 | protected static getRulesGfm(): RulesInlineGfm { 129 | if (this.rulesGfm) { 130 | return this.rulesGfm; 131 | } 132 | 133 | const base = this.getRulesBase(); 134 | 135 | const escape = new ExtendRegexp(base.escape).setGroup("])", "~|])") 136 | .getRegexp(); 137 | 138 | const text = new ExtendRegexp(base.text) 139 | .setGroup("]|", "~]|") 140 | .setGroup("|", "|https?://|") 141 | .getRegexp(); 142 | 143 | return (this.rulesGfm = { 144 | ...base, 145 | ...{ 146 | escape, 147 | url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, 148 | del: /^~~(?=\S)([\s\S]*?\S)~~/, 149 | text, 150 | }, 151 | }); 152 | } 153 | 154 | protected static getRulesBreaks(): RulesInlineBreaks { 155 | if (this.rulesBreaks) { 156 | return this.rulesBreaks; 157 | } 158 | 159 | const inline = this.getRulesGfm(); 160 | const gfm = this.getRulesGfm(); 161 | 162 | return (this.rulesBreaks = { 163 | ...gfm, 164 | ...{ 165 | br: new ExtendRegexp(inline.br).setGroup("{2,}", "*").getRegexp(), 166 | text: new ExtendRegexp(gfm.text).setGroup("{2,}", "*").getRegexp(), 167 | }, 168 | }); 169 | } 170 | 171 | protected setRules() { 172 | if (this.options.gfm) { 173 | if (this.options.breaks) { 174 | this.rules = this.staticThis.getRulesBreaks(); 175 | } else { 176 | this.rules = this.staticThis.getRulesGfm(); 177 | } 178 | } else if (this.options.pedantic) { 179 | this.rules = this.staticThis.getRulesPedantic(); 180 | } else { 181 | this.rules = this.staticThis.getRulesBase(); 182 | } 183 | 184 | this.hasRulesGfm = (this.rules as RulesInlineGfm).url !== undefined; 185 | } 186 | 187 | /** 188 | * Lexing/Compiling. 189 | */ 190 | output(nextPart: string): string { 191 | nextPart = nextPart; 192 | let execArr: RegExpExecArray | null; 193 | let out = ""; 194 | 195 | while (nextPart) { 196 | // escape 197 | if ((execArr = this.rules.escape.exec(nextPart))) { 198 | nextPart = nextPart.substring(execArr[0].length); 199 | out += execArr[1]; 200 | continue; 201 | } 202 | 203 | // autolink 204 | if ((execArr = this.rules.autolink.exec(nextPart))) { 205 | let text: string; 206 | let href: string; 207 | nextPart = nextPart.substring(execArr[0].length); 208 | 209 | if (!this.options.escape) throw ReferenceError; 210 | 211 | if (execArr[2] === "@") { 212 | text = this.options.escape( 213 | execArr[1].charAt(6) === ":" 214 | ? this.mangle(execArr[1].substring(7)) 215 | : this.mangle(execArr[1]), 216 | ); 217 | href = this.mangle("mailto:") + text; 218 | } else { 219 | text = this.options.escape(execArr[1]); 220 | href = text; 221 | } 222 | 223 | out += this.renderer.link(href, "", text); 224 | continue; 225 | } 226 | 227 | // url (gfm) 228 | if ( 229 | !this.inLink && this.hasRulesGfm && 230 | (execArr = (this.rules as RulesInlineGfm).url.exec(nextPart)) 231 | ) { 232 | if (!this.options.escape) throw ReferenceError; 233 | let text: string; 234 | let href: string; 235 | nextPart = nextPart.substring(execArr[0].length); 236 | text = this.options.escape(execArr[1]); 237 | href = text; 238 | out += this.renderer.link(href, "", text); 239 | continue; 240 | } 241 | 242 | // tag 243 | if ((execArr = this.rules.tag.exec(nextPart))) { 244 | if (!this.inLink && /^/i.test(execArr[0])) { 247 | this.inLink = false; 248 | } 249 | 250 | nextPart = nextPart.substring(execArr[0].length); 251 | 252 | if (!this.options.escape) throw ReferenceError; 253 | 254 | out += this.options.sanitize 255 | ? this.options.sanitizer 256 | ? this.options.sanitizer(execArr[0]) 257 | : this.options.escape(execArr[0]) 258 | : execArr[0]; 259 | continue; 260 | } 261 | 262 | // link 263 | if ((execArr = this.rules.link.exec(nextPart))) { 264 | nextPart = nextPart.substring(execArr[0].length); 265 | this.inLink = true; 266 | 267 | out += this.outputLink(execArr, { 268 | href: execArr[2], 269 | title: execArr[3], 270 | }); 271 | 272 | this.inLink = false; 273 | continue; 274 | } 275 | 276 | // reflink, nolink 277 | if ( 278 | (execArr = this.rules.reflink.exec(nextPart)) || 279 | (execArr = this.rules.nolink.exec(nextPart)) 280 | ) { 281 | nextPart = nextPart.substring(execArr[0].length); 282 | const keyLink = (execArr[2] || execArr[1]).replace(/\s+/g, " "); 283 | const link = this.links[keyLink.toLowerCase()]; 284 | 285 | if (!link || !link.href) { 286 | out += execArr[0].charAt(0); 287 | nextPart = execArr[0].substring(1) + nextPart; 288 | continue; 289 | } 290 | 291 | this.inLink = true; 292 | out += this.outputLink(execArr, link); 293 | this.inLink = false; 294 | continue; 295 | } 296 | 297 | // strong 298 | if ((execArr = this.rules.strong.exec(nextPart))) { 299 | nextPart = nextPart.substring(execArr[0].length); 300 | out += this.renderer.strong(this.output(execArr[2] || execArr[1])); 301 | continue; 302 | } 303 | 304 | // em 305 | if ((execArr = this.rules.em.exec(nextPart))) { 306 | nextPart = nextPart.substring(execArr[0].length); 307 | out += this.renderer.em(this.output(execArr[2] || execArr[1])); 308 | continue; 309 | } 310 | 311 | // code 312 | if ((execArr = this.rules.code.exec(nextPart))) { 313 | if (!this.options.escape) throw ReferenceError; 314 | nextPart = nextPart.substring(execArr[0].length); 315 | out += this.renderer.codespan( 316 | this.options.escape(execArr[2].trim(), true), 317 | ); 318 | continue; 319 | } 320 | 321 | // br 322 | if ((execArr = this.rules.br.exec(nextPart))) { 323 | nextPart = nextPart.substring(execArr[0].length); 324 | out += this.renderer.br(); 325 | continue; 326 | } 327 | 328 | // del (gfm) 329 | if ( 330 | this.hasRulesGfm && 331 | (execArr = (this.rules as RulesInlineGfm).del.exec(nextPart)) 332 | ) { 333 | nextPart = nextPart.substring(execArr[0].length); 334 | out += this.renderer.del(this.output(execArr[1])); 335 | continue; 336 | } 337 | 338 | // text 339 | if ((execArr = this.rules.text.exec(nextPart))) { 340 | if (!this.options.escape) throw ReferenceError; 341 | nextPart = nextPart.substring(execArr[0].length); 342 | out += this.renderer.text( 343 | this.options.escape(this.smartypants(execArr[0])), 344 | ); 345 | continue; 346 | } 347 | 348 | if (nextPart) { 349 | throw new Error("Infinite loop on byte: " + nextPart.charCodeAt(0)); 350 | } 351 | } 352 | 353 | return out; 354 | } 355 | 356 | /** 357 | * Compile Link. 358 | */ 359 | protected outputLink(execArr: RegExpExecArray, link: Link) { 360 | if (!this.options.escape) throw ReferenceError; 361 | const href = this.options.escape(link.href); 362 | const title = link.title ? this.options.escape(link.title) : null; 363 | 364 | return execArr[0].charAt(0) !== "!" 365 | ? this.renderer.link(href, title || "", this.output(execArr[1])) 366 | : this.renderer.image(href, title || "", this.options.escape(execArr[1])); 367 | } 368 | 369 | /** 370 | * Smartypants Transformations. 371 | */ 372 | protected smartypants(text: string) { 373 | if (!this.options.smartypants) { 374 | return text; 375 | } 376 | 377 | return ( 378 | text 379 | // em-dashes 380 | .replace(/---/g, "\u2014") 381 | // en-dashes 382 | .replace(/--/g, "\u2013") 383 | // opening singles 384 | .replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018") 385 | // closing singles & apostrophes 386 | .replace(/'/g, "\u2019") 387 | // opening doubles 388 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201c") 389 | // closing doubles 390 | .replace(/"/g, "\u201d") 391 | // ellipses 392 | .replace(/\.{3}/g, "\u2026") 393 | ); 394 | } 395 | 396 | /** 397 | * Mangle Links. 398 | */ 399 | protected mangle(text: string) { 400 | if (!this.options.mangle) { 401 | return text; 402 | } 403 | 404 | let out = ""; 405 | const length = text.length; 406 | 407 | for (let i = 0; i < length; i++) { 408 | let str: string = ""; 409 | 410 | if (Math.random() > 0.5) { 411 | str = "x" + text.charCodeAt(i).toString(16); 412 | } 413 | 414 | out += "" + str + ";"; 415 | } 416 | 417 | return out; 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/block-lexer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * 4 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 5 | * https://github.com/chjj/marked 6 | * 7 | * Copyright (c) 2018, Костя Третяк. (MIT Licensed) 8 | * https://github.com/ts-stack/markdown 9 | */ 10 | 11 | import { ExtendRegexp } from "./extend-regexp.ts"; 12 | import { 13 | Align, 14 | LexerReturns, 15 | Links, 16 | MarkedOptions, 17 | RulesBlockBase, 18 | RulesBlockGfm, 19 | RulesBlockTables, 20 | Token, 21 | TokenType, 22 | Obj 23 | } from "./interfaces.ts"; 24 | import { Marked } from "./marked.ts"; 25 | import { load } from "https://deno.land/std/yaml/_loader/loader.ts"; 26 | 27 | export class BlockLexer