Skip to content

Commit c2f34d9

Browse files
committed
chore(core): own parser/render
1 parent 8f8c435 commit c2f34d9

File tree

4 files changed

+324
-4
lines changed

4 files changed

+324
-4
lines changed

lib/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ var objectAssign = require('object-assign')
33
var pkg = require('../package.json')
44
var Api = require('./api.js')
55

6-
var parser = require('posthtml-parser')
7-
var render = require('posthtml-render')
6+
var parser = require('./parser.js')
7+
var render = require('./render.js')
88

99
/**
1010
* @author Ivan Voischev (@voischev),

lib/parser.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
'use strict'
2+
3+
var Parser = require('htmlparser2/lib/Parser')
4+
var objectAssign = require('object-assign')
5+
6+
/**
7+
* @see https://github.com/fb55/htmlparser2/wiki/Parser-options
8+
*/
9+
var defaultOptions = {lowerCaseTags: false, lowerCaseAttributeNames: false}
10+
11+
var defaultDirectives = [{name: '!doctype', start: '<', end: '>'}]
12+
13+
/**
14+
* Parse html to PostHTMLTree
15+
* @param {String} html
16+
* @param {Object} [options=defaultOptions]
17+
* @return {PostHTMLTree}
18+
*/
19+
function postHTMLParser (html, options) {
20+
var bufArray = []
21+
var results = []
22+
23+
bufArray.last = function () {
24+
return this[this.length - 1]
25+
}
26+
27+
function isDirective (directive, tag) {
28+
if (directive.name instanceof RegExp) {
29+
var regex = RegExp(directive.name.source, 'i')
30+
31+
return regex.test(tag)
32+
}
33+
34+
if (tag !== directive.name) {
35+
return false
36+
}
37+
38+
return true
39+
}
40+
41+
function parserDirective (name, data) {
42+
var directives = [].concat(defaultDirectives, options.directives || [])
43+
var last = bufArray.last()
44+
45+
for (var i = 0; i < directives.length; i++) {
46+
var directive = directives[i]
47+
var directiveText = directive.start + data + directive.end
48+
49+
name = name.toLowerCase()
50+
if (isDirective(directive, name)) {
51+
if (!last) {
52+
results.push(directiveText)
53+
return
54+
}
55+
56+
last.content || (last.content = [])
57+
last.content.push(directiveText)
58+
}
59+
}
60+
}
61+
62+
function normalizeArributes (attrs) {
63+
var result = {}
64+
Object.keys(attrs).forEach(function (key) {
65+
var obj = {}
66+
obj[key] = attrs[key].replace(/&quot/g, '"')
67+
objectAssign(result, obj)
68+
})
69+
70+
return result
71+
}
72+
73+
var parser = new Parser({
74+
onprocessinginstruction: parserDirective,
75+
oncomment: function (data) {
76+
var comment = '<!--' + data + '-->'
77+
var last = bufArray.last()
78+
79+
if (!last) {
80+
results.push(comment)
81+
return
82+
}
83+
84+
last.content || (last.content = [])
85+
last.content.push(comment)
86+
},
87+
onopentag: function (tag, attrs) {
88+
var buf = { tag: tag }
89+
90+
if (Object.keys(attrs).length) {
91+
buf.attrs = normalizeArributes(attrs)
92+
}
93+
94+
bufArray.push(buf)
95+
},
96+
onclosetag: function () {
97+
var buf = bufArray.pop()
98+
99+
if (!bufArray.length) {
100+
results.push(buf)
101+
return
102+
}
103+
104+
var last = bufArray.last()
105+
if (!Array.isArray(last.content)) {
106+
last.content = []
107+
}
108+
109+
last.content.push(buf)
110+
},
111+
ontext: function (text) {
112+
var last = bufArray.last()
113+
if (!last) {
114+
results.push(text)
115+
return
116+
}
117+
118+
last.content || (last.content = [])
119+
last.content.push(text)
120+
}
121+
}, options || defaultOptions)
122+
123+
parser.write(html)
124+
parser.end()
125+
126+
return results
127+
}
128+
129+
function parserWrapper () {
130+
var option
131+
132+
function parser (html) {
133+
var opt = objectAssign(defaultOptions, option)
134+
return postHTMLParser(html, opt)
135+
}
136+
137+
if (
138+
arguments.length === 1 &&
139+
Boolean(arguments[0]) &&
140+
arguments[0].constructor.name === 'Object'
141+
) {
142+
option = arguments[0]
143+
return parser
144+
}
145+
146+
option = arguments[1]
147+
return parser(arguments[0])
148+
}
149+
150+
module.exports = parserWrapper
151+
module.exports.defaultOptions = defaultOptions
152+
module.exports.defaultDirectives = defaultDirectives

lib/render.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
var SINGLE_TAGS = [
2+
'area',
3+
'base',
4+
'br',
5+
'col',
6+
'command',
7+
'embed',
8+
'hr',
9+
'img',
10+
'input',
11+
'keygen',
12+
'link',
13+
'menuitem',
14+
'meta',
15+
'param',
16+
'source',
17+
'track',
18+
'wbr',
19+
// Custom (PostHTML)
20+
'import',
21+
'include',
22+
'extend',
23+
'component'
24+
]
25+
26+
/**
27+
* Render PostHTML Tree to HTML
28+
*
29+
* @param {Array|Object} tree PostHTML Tree
30+
* @param {Object} options Options
31+
*
32+
* @return {String} HTML
33+
*/
34+
function render (tree, options) {
35+
/**
36+
* Options
37+
*
38+
* @type {Object}
39+
*
40+
* @prop {Array<String|RegExp>} singleTags Custom single tags (selfClosing)
41+
* @prop {String} closingSingleTag Closing format for single tag
42+
*
43+
* Formats:
44+
*
45+
* ``` tag: `<br></br>` ```, slash: `<br />` ```, ```default: `<br>` ```
46+
*/
47+
options = options || {}
48+
49+
var singleTags = SINGLE_TAGS.concat(options.singleTags || [])
50+
var singleRegExp = singleTags.filter(function (tag) {
51+
return tag instanceof RegExp ? tag : false
52+
})
53+
54+
var closingSingleTag = options.closingSingleTag
55+
56+
return html(tree)
57+
58+
/**
59+
* HTML Stringifier
60+
*
61+
* @param {Array|Object} tree PostHTML Tree
62+
*
63+
* @return {String} result HTML
64+
*/
65+
function html (tree) {
66+
var result = ''
67+
68+
traverse([].concat(tree), function (node) {
69+
if (!node) return
70+
71+
if (typeof node === 'string' || typeof node === 'number') {
72+
result += node
73+
74+
return
75+
}
76+
77+
if (typeof node.tag === 'boolean' && !node.tag) {
78+
typeof node.content !== 'object' && (result += node.content)
79+
80+
return node.content
81+
}
82+
83+
// treat as new root tree if node is an array
84+
if (Array.isArray(node)) {
85+
result += html(node)
86+
87+
return
88+
}
89+
90+
var tag = node.tag || 'div'
91+
92+
if (isSingleTag(tag, singleTags, singleRegExp)) {
93+
result += '<' + tag + attrs(node.attrs)
94+
95+
switch (closingSingleTag) {
96+
case 'tag':
97+
result += '></' + tag + '>'
98+
99+
break
100+
case 'slash':
101+
result += ' />'
102+
103+
break
104+
default:
105+
result += '>'
106+
}
107+
} else {
108+
result += '<' + tag + (node.attrs ? attrs(node.attrs) : '') + '>' + (node.content ? html(node.content) : '') + '</' + tag + '>'
109+
}
110+
})
111+
112+
return result
113+
}
114+
}
115+
116+
/**
117+
* @module posthtml-render
118+
*
119+
* @version 1.0.7
120+
* @license MIT
121+
*/
122+
module.exports = render
123+
124+
/** @private */
125+
function attrs (obj) {
126+
var attr = ''
127+
128+
for (var key in obj) {
129+
if (typeof obj[key] === 'boolean' && obj[key]) {
130+
attr += ' ' + key
131+
} else if (
132+
typeof obj[key] === 'string' ||
133+
typeof obj[key] === 'number'
134+
) {
135+
attr += ' ' + key + '="' + obj[key] + '"'
136+
}
137+
}
138+
139+
return attr
140+
}
141+
142+
/** @private */
143+
function traverse (tree, cb) {
144+
if (Array.isArray(tree)) {
145+
for (var i = 0, length = tree.length; i < length; i++) {
146+
traverse(cb(tree[i]), cb)
147+
}
148+
} else if (typeof tree === 'object' && tree.hasOwnProperty('content')) {
149+
traverse(tree.content, cb)
150+
}
151+
152+
return tree
153+
}
154+
155+
/** @private */
156+
function isSingleTag (tag, singleTags, singleRegExp) {
157+
if (singleRegExp.length) {
158+
for (var i = 0; i < singleRegExp.length; i++) {
159+
return !!tag.match(singleRegExp[i])
160+
}
161+
}
162+
163+
if (singleTags.indexOf(tag) === -1) {
164+
return false
165+
}
166+
167+
return true
168+
}

test/parser.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ var it = require('mocha').it
55
var expect = require('chai').expect
66
var describe = require('mocha').describe
77

8-
var parser = require('posthtml-parser')
9-
var render = require('posthtml-render')
8+
var parser = require('../lib/parser')
9+
var render = require('../lib/render')
1010

1111
var html = fs.readFileSync(
1212
path.resolve(__dirname, 'templates/parser.html'), 'utf8'

0 commit comments

Comments
 (0)