├── .eslintrc ├── .gitignore ├── .travis.yml ├── index.js ├── src ├── MT.js └── transformer.js ├── test ├── index.test.js └── test.md ├── package.json └── README.md /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /npm-debug.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | - "6" 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./src/MT'); 4 | -------------------------------------------------------------------------------- /src/MT.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const remark = require('remark')(); 4 | const YFM = require('yaml-front-matter'); 5 | const transformer = require('./transformer'); 6 | 7 | module.exports = function MT(markdown) { 8 | const ret = {}; 9 | 10 | const raw = YFM.loadFront(markdown); 11 | const ast = remark.parse(raw.__content); 12 | ret.content = transformer(ast); 13 | 14 | // Get meta data 15 | delete raw.__content; 16 | ret.meta = raw; 17 | 18 | return ret; 19 | }; 20 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const fs = require('fs'); 5 | const MT = require('..'); 6 | 7 | describe('MT', () => { 8 | const md = fs.readFileSync('./test/test.md').toString(); 9 | const ret = MT(md); 10 | console.log(JSON.stringify(ret, null, 2)); 11 | 12 | const meta = ret.meta; 13 | 14 | it('should process YAML as meta data', () => { 15 | assert.strictEqual(meta.title, 'test'); 16 | assert.strictEqual( 17 | meta.description, 18 | 'a sample Markdown for test' 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mark-twain", 3 | "version": "2.0.3", 4 | "description": "Parse Markdown into JsonML.", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint ./index.js ./src ./test", 8 | "eslint-fix": "eslint --fix ./index.js ./src ./test", 9 | "test": "npm run lint && mocha" 10 | }, 11 | "keywords": [ 12 | "markdown", 13 | "yaml", 14 | "parser", 15 | "jsonml" 16 | ], 17 | "author": "Benjy Cui ", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/benjycui/mark-twain" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/benjycui/mark-twain/issues" 24 | }, 25 | "license": "MIT", 26 | "dependencies": { 27 | "jsonml.js": "^0.1.0", 28 | "remark": "^5.0.1", 29 | "yaml-front-matter": "^4.0.0" 30 | }, 31 | "devDependencies": { 32 | "eslint": "^3.0.0", 33 | "eslint-config-egg": "^4.2.0", 34 | "mocha": "^3.4.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/test.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: test 3 | description: a sample Markdown for test 4 | --- 5 | 6 | # H1 7 | ## H2 8 | ### H3 9 | #### H4 10 | ##### H5 11 | ###### H6 12 | 13 | 14 | 1. First 15 | 1. Second 16 | 1. Third 17 | 18 | 19 | * Something 20 | * [Something](www.something.com) 21 | * ![Somethong](www.comething.com/img) 22 | 23 | 24 | 1. Parent 25 | - Children 26 | 27 | 28 | | hello | world | 29 | |-------|-------| 30 | | 你好 | 世界 | 31 | 32 | This is a paragraph, including *EM* and **STRONG**. Any question? Oh, I almost forget `inline code`. 33 | 34 | ```javascript 35 | console.log('Hello world!'); 36 | ``` 37 | 38 | > Here is a blockquote 39 | 40 | --- 41 | 42 | 45 | 46 |

My blog

47 | 48 |
49 | 50 |
51 | 52 |
53 | 54 | [LINK REFERENCE] 55 | 56 | 正确示例 57 | 错误示例 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mark Twain 2 | 3 | [![](https://img.shields.io/travis/benjycui/mark-twain.svg?style=flat-square)](https://travis-ci.org/benjycui/mark-twain) 4 | [![npm package](https://img.shields.io/npm/v/mark-twain.svg?style=flat-square)](https://www.npmjs.org/package/mark-twain) 5 | [![NPM downloads](http://img.shields.io/npm/dm/mark-twain.svg?style=flat-square)](https://npmjs.org/package/mark-twain) 6 | [![Dependency Status](https://david-dm.org/benjycui/mark-twain.svg?style=flat-square)](https://david-dm.org/benjycui/mark-twain) 7 | 8 | It is not easy to process Markdown directly. However, we can use `mark-twain` to parse a Markdown file(and YAML/HTML which in it) into [JsonML](http://www.jsonml.org/) which is easier to process. 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm install mark-twain 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```js 19 | const MT = require('mark-twain'); 20 | const fs = require('fs'); 21 | const jsonML = MT(fs.readFileSync('something.md').toString()); 22 | ``` 23 | 24 | The returned value of `MT` would be JsonML, something looks like this: 25 | 26 | ```js 27 | { 28 | // YAML will be parsed as meta data. 29 | meta: { 30 | title: 'Title', 31 | ... 32 | }, 33 | 34 | // Others will be parsed as JsonML. 35 | content: [ 36 | "article", 37 | ["h1", "Here is a heading"], 38 | [ 39 | "ol", 40 | [ 41 | "li", 42 | [ 43 | "p", 44 | "First" 45 | ] 46 | ], 47 | ... 48 | ], 49 | [ 50 | "p", 51 | "This is a paragraph, including ", 52 | [ 53 | "em", 54 | "EM" 55 | ], 56 | " and ", 57 | [ 58 | "strong", 59 | "STRONG" 60 | ], 61 | ". Any question? Oh, I almost forget ", 62 | [ 63 | "code", 64 | "inline code" 65 | ], 66 | "." 67 | ], 68 | ... 69 | ] 70 | } 71 | ``` 72 | 73 | ## Relative 74 | 75 | * [jsonml-to-react-component](https://github.com/benjycui/jsonml-to-react-component) To convert JsonML to React Component. 76 | * [jsonml.js](https://github.com/benjycui/jsonml.js) A collection of JsonML tools. 77 | 78 | ## Liscence 79 | 80 | MIT 81 | -------------------------------------------------------------------------------- /src/transformer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JsonML = require('jsonml.js/lib/dom'); 4 | 5 | let isTHead = false; 6 | function transformTHead(node) { 7 | const transformedNode = transformer(node); 8 | isTHead = false; 9 | return transformedNode; 10 | } 11 | 12 | function transformer(node) { 13 | if (node == null) return; 14 | 15 | if (Array.isArray(node)) { 16 | return node.map(transformer); 17 | } 18 | 19 | const transformedChildren = node.type === 'table' ? 20 | transformer(node.children.slice(1)) : 21 | transformer(node.children); 22 | switch (node.type) { 23 | case 'root': 24 | return [ 'article' ].concat(transformedChildren); 25 | case 'heading': 26 | return [ `h${node.depth}` ].concat(transformedChildren); 27 | case 'text': 28 | return node.value; 29 | case 'list': 30 | return [ node.ordered ? 'ol' : 'ul' ].concat(transformedChildren); 31 | case 'listItem': 32 | return [ 'li' ].concat(transformedChildren); 33 | case 'paragraph': 34 | return [ 'p' ].concat(transformedChildren); 35 | case 'link': 36 | return [ 'a', { 37 | title: node.title, 38 | href: node.url, 39 | }].concat(transformedChildren); 40 | case 'image': 41 | return [ 'img', { 42 | title: node.title, 43 | src: node.url, 44 | alt: node.alt, 45 | }]; 46 | case 'table': 47 | isTHead = true; 48 | return [ 49 | 'table', 50 | [ 'thead', transformTHead(node.children[0]) ], 51 | [ 'tbody' ].concat(transformedChildren), 52 | ]; 53 | case 'tableRow': 54 | return [ 'tr' ].concat(transformedChildren); 55 | case 'tableCell': 56 | return [ isTHead ? 'th' : 'td' ].concat(transformedChildren); 57 | case 'emphasis': 58 | return [ 'em' ].concat(transformedChildren); 59 | case 'strong': 60 | return [ 'strong' ].concat(transformedChildren); 61 | case 'inlineCode': 62 | return [ 'code', node.value ]; 63 | case 'code': 64 | return [ 'pre', { lang: node.lang }, [ 'code', node.value ]]; 65 | case 'blockquote': 66 | return [ 'blockquote' ].concat(transformedChildren); 67 | case 'break': 68 | return [ 'br' ]; 69 | case 'thematicBreak': 70 | return [ 'hr' ]; 71 | case 'html': 72 | return JsonML.fromHTMLText(node.value); 73 | case 'linkReference': 74 | return [ 'span' ].concat(transformedChildren); 75 | default: 76 | return node; 77 | } 78 | } 79 | 80 | module.exports = transformer; 81 | --------------------------------------------------------------------------------