parse5 2.2.2 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -19
- package/README.md +40 -40
- package/lib/common/doctype.js +156 -137
- package/lib/common/foreign_content.js +1 -1
- package/lib/common/html.js +272 -266
- package/lib/common/merge_options.js +13 -13
- package/lib/common/unicode.js +47 -47
- package/lib/index.d.ts +1077 -0
- package/lib/index.js +44 -108
- package/lib/location_info/parser_mixin.js +57 -51
- package/lib/location_info/tokenizer_mixin.js +2 -23
- package/lib/parser/formatting_element_list.js +167 -167
- package/lib/parser/index.js +2819 -2817
- package/lib/parser/open_element_stack.js +395 -395
- package/lib/parser/parser_stream.js +76 -0
- package/lib/parser/plain_text_conversion_stream.js +20 -0
- package/lib/sax/dev_null_stream.js +14 -14
- package/lib/sax/index.js +0 -117
- package/lib/serializer/index.js +162 -180
- package/lib/serializer/{stream.js → serializer_stream.js} +28 -49
- package/lib/tokenizer/index.js +63 -16
- package/lib/tokenizer/named_entity_data.js +5 -0
- package/lib/tree_adapters/default.js +209 -578
- package/lib/tree_adapters/htmlparser2.js +338 -327
- package/package.json +13 -14
- package/lib/parser/stream.js +0 -140
- package/lib/tokenizer/named_entity_trie.js +0 -6
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var WritableStream = require('stream').Writable,
|
|
4
|
+
inherits = require('util').inherits,
|
|
5
|
+
Parser = require('./index');
|
|
6
|
+
|
|
7
|
+
var ParserStream = module.exports = function (options) {
|
|
8
|
+
WritableStream.call(this);
|
|
9
|
+
|
|
10
|
+
this.parser = new Parser(options);
|
|
11
|
+
|
|
12
|
+
this.lastChunkWritten = false;
|
|
13
|
+
this.writeCallback = null;
|
|
14
|
+
this.pausedByScript = false;
|
|
15
|
+
|
|
16
|
+
this.document = this.parser.treeAdapter.createDocument();
|
|
17
|
+
|
|
18
|
+
this.pendingHtmlInsertions = [];
|
|
19
|
+
|
|
20
|
+
this._resume = this._resume.bind(this);
|
|
21
|
+
this._documentWrite = this._documentWrite.bind(this);
|
|
22
|
+
this._scriptHandler = this._scriptHandler.bind(this);
|
|
23
|
+
|
|
24
|
+
this.parser._bootstrap(this.document, null);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
inherits(ParserStream, WritableStream);
|
|
28
|
+
|
|
29
|
+
//WritableStream implementation
|
|
30
|
+
ParserStream.prototype._write = function (chunk, encoding, callback) {
|
|
31
|
+
this.writeCallback = callback;
|
|
32
|
+
this.parser.tokenizer.write(chunk.toString('utf8'), this.lastChunkWritten);
|
|
33
|
+
this._runParsingLoop();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
ParserStream.prototype.end = function (chunk, encoding, callback) {
|
|
37
|
+
this.lastChunkWritten = true;
|
|
38
|
+
WritableStream.prototype.end.call(this, chunk, encoding, callback);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
//Scriptable parser implementation
|
|
42
|
+
ParserStream.prototype._runParsingLoop = function () {
|
|
43
|
+
this.parser.runParsingLoopForCurrentChunk(this.writeCallback, this._scriptHandler);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
ParserStream.prototype._resume = function () {
|
|
47
|
+
if (!this.pausedByScript)
|
|
48
|
+
throw new Error('Parser was already resumed');
|
|
49
|
+
|
|
50
|
+
while (this.pendingHtmlInsertions.length) {
|
|
51
|
+
var html = this.pendingHtmlInsertions.pop();
|
|
52
|
+
|
|
53
|
+
this.parser.tokenizer.insertHtmlAtCurrentPos(html);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.pausedByScript = false;
|
|
57
|
+
|
|
58
|
+
//NOTE: keep parsing if we don't wait for the next input chunk
|
|
59
|
+
if (this.parser.tokenizer.active)
|
|
60
|
+
this._runParsingLoop();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
ParserStream.prototype._documentWrite = function (html) {
|
|
64
|
+
if (!this.parser.stopped)
|
|
65
|
+
this.pendingHtmlInsertions.push(html);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
ParserStream.prototype._scriptHandler = function (scriptElement) {
|
|
69
|
+
if (this.listeners('script').length) {
|
|
70
|
+
this.pausedByScript = true;
|
|
71
|
+
this.emit('script', scriptElement, this._documentWrite, this._resume);
|
|
72
|
+
}
|
|
73
|
+
else
|
|
74
|
+
this._runParsingLoop();
|
|
75
|
+
};
|
|
76
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ParserStream = require('./parser_stream'),
|
|
4
|
+
inherits = require('util').inherits,
|
|
5
|
+
$ = require('../common/html').TAG_NAMES;
|
|
6
|
+
|
|
7
|
+
var PlainTextConversionStream = module.exports = function (options) {
|
|
8
|
+
ParserStream.call(this, options);
|
|
9
|
+
|
|
10
|
+
// NOTE: see https://html.spec.whatwg.org/#read-text
|
|
11
|
+
this.parser._insertFakeElement($.HTML);
|
|
12
|
+
this.parser._insertFakeElement($.HEAD);
|
|
13
|
+
this.parser.openElements.pop();
|
|
14
|
+
this.parser._insertFakeElement($.BODY);
|
|
15
|
+
this.parser._insertFakeElement($.PRE);
|
|
16
|
+
this.parser.treeAdapter.insertText(this.parser.openElements.current, '\n');
|
|
17
|
+
this.parser.switchToPlaintextParsing();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
inherits(PlainTextConversionStream, ParserStream);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var WritableStream = require('stream').Writable,
|
|
4
|
-
util = require('util');
|
|
5
|
-
|
|
6
|
-
var DevNullStream = module.exports = function () {
|
|
7
|
-
WritableStream.call(this);
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
util.inherits(DevNullStream, WritableStream);
|
|
11
|
-
|
|
12
|
-
DevNullStream.prototype._write = function (chunk, encoding, cb) {
|
|
13
|
-
cb();
|
|
14
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var WritableStream = require('stream').Writable,
|
|
4
|
+
util = require('util');
|
|
5
|
+
|
|
6
|
+
var DevNullStream = module.exports = function () {
|
|
7
|
+
WritableStream.call(this);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
util.inherits(DevNullStream, WritableStream);
|
|
11
|
+
|
|
12
|
+
DevNullStream.prototype._write = function (chunk, encoding, cb) {
|
|
13
|
+
cb();
|
|
14
|
+
};
|
package/lib/sax/index.js
CHANGED
|
@@ -7,46 +7,10 @@ var TransformStream = require('stream').Transform,
|
|
|
7
7
|
ParserFeedbackSimulator = require('./parser_feedback_simulator'),
|
|
8
8
|
mergeOptions = require('../common/merge_options');
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {Object} SAXParserOptions
|
|
12
|
-
*
|
|
13
|
-
* @property {Boolean} [locationInfo=false] - Enables source code location information for the tokens.
|
|
14
|
-
* When enabled, each token event handler will receive {@link LocationInfo} (or {@link StartTagLocationInfo})
|
|
15
|
-
* object as its last argument.
|
|
16
|
-
*/
|
|
17
10
|
var DEFAULT_OPTIONS = {
|
|
18
11
|
locationInfo: false
|
|
19
12
|
};
|
|
20
13
|
|
|
21
|
-
/**
|
|
22
|
-
* Streaming [SAX]{@link https://en.wikipedia.org/wiki/Simple_API_for_XML}-style HTML parser.
|
|
23
|
-
* A [transform stream](https://nodejs.org/api/stream.html#stream_class_stream_transform)
|
|
24
|
-
* (which means you can pipe *through* it, see example).
|
|
25
|
-
* @class SAXParser
|
|
26
|
-
* @memberof parse5
|
|
27
|
-
* @instance
|
|
28
|
-
* @extends stream.Transform
|
|
29
|
-
* @param {SAXParserOptions} options - Parsing options.
|
|
30
|
-
* @example
|
|
31
|
-
* var parse5 = require('parse5');
|
|
32
|
-
* var http = require('http');
|
|
33
|
-
* var fs = require('fs');
|
|
34
|
-
*
|
|
35
|
-
* var file = fs.createWriteStream('/home/google.com.html');
|
|
36
|
-
* var parser = new parse5.SAXParser();
|
|
37
|
-
*
|
|
38
|
-
* parser.on('text', function(text) {
|
|
39
|
-
* // Handle page text content
|
|
40
|
-
* ...
|
|
41
|
-
* });
|
|
42
|
-
*
|
|
43
|
-
* http.get('http://google.com', function(res) {
|
|
44
|
-
* // SAXParser is the Transform stream, which means you can pipe
|
|
45
|
-
* // through it. So, you can analyze page content and, e.g., save it
|
|
46
|
-
* // to the file at the same time:
|
|
47
|
-
* res.pipe(parser).pipe(file);
|
|
48
|
-
* });
|
|
49
|
-
*/
|
|
50
14
|
var SAXParser = module.exports = function (options) {
|
|
51
15
|
TransformStream.call(this);
|
|
52
16
|
|
|
@@ -90,33 +54,6 @@ SAXParser.prototype.end = function (chunk, encoding, callback) {
|
|
|
90
54
|
TransformStream.prototype.end.call(this, chunk, encoding, callback);
|
|
91
55
|
};
|
|
92
56
|
|
|
93
|
-
/**
|
|
94
|
-
* Stops parsing. Useful if you want the parser to stop consuming CPU time once you've obtained the desired info
|
|
95
|
-
* from the input stream. Doesn't prevent piping, so that data will flow through the parser as usual.
|
|
96
|
-
*
|
|
97
|
-
* @function stop
|
|
98
|
-
* @memberof parse5#SAXParser
|
|
99
|
-
* @instance
|
|
100
|
-
* @example
|
|
101
|
-
* var parse5 = require('parse5');
|
|
102
|
-
* var http = require('http');
|
|
103
|
-
* var fs = require('fs');
|
|
104
|
-
*
|
|
105
|
-
* var file = fs.createWriteStream('/home/google.com.html');
|
|
106
|
-
* var parser = new parse5.SAXParser();
|
|
107
|
-
*
|
|
108
|
-
* parser.on('doctype', function(name, publicId, systemId) {
|
|
109
|
-
* // Process doctype info ans stop parsing
|
|
110
|
-
* ...
|
|
111
|
-
* parser.stop();
|
|
112
|
-
* });
|
|
113
|
-
*
|
|
114
|
-
* http.get('http://google.com', function(res) {
|
|
115
|
-
* // Despite the fact that parser.stop() was called whole
|
|
116
|
-
* // content of the page will be written to the file
|
|
117
|
-
* res.pipe(parser).pipe(file);
|
|
118
|
-
* });
|
|
119
|
-
*/
|
|
120
57
|
SAXParser.prototype.stop = function () {
|
|
121
58
|
this.stopped = true;
|
|
122
59
|
};
|
|
@@ -156,74 +93,20 @@ SAXParser.prototype._handleToken = function (token) {
|
|
|
156
93
|
this.currentTokenLocation = token.location;
|
|
157
94
|
|
|
158
95
|
if (token.type === Tokenizer.START_TAG_TOKEN)
|
|
159
|
-
/**
|
|
160
|
-
* Raised when the parser encounters a start tag.
|
|
161
|
-
* @event startTag
|
|
162
|
-
* @memberof parse5#SAXParser
|
|
163
|
-
* @instance
|
|
164
|
-
* @type {Function}
|
|
165
|
-
* @param {String} name - Tag name.
|
|
166
|
-
* @param {Array} attrs - List of attributes in the `{ name: String, value: String, prefix?: String }` form.
|
|
167
|
-
* @param {Boolean} selfClosing - Indicates if the tag is self-closing.
|
|
168
|
-
* @param {StartTagLocationInfo} [location] - Start tag source code location info.
|
|
169
|
-
* Available if location info is enabled in {@link SAXParserOptions}.
|
|
170
|
-
*/
|
|
171
96
|
this.emit('startTag', token.tagName, token.attrs, token.selfClosing, this.currentTokenLocation);
|
|
172
97
|
|
|
173
98
|
else if (token.type === Tokenizer.END_TAG_TOKEN)
|
|
174
|
-
/**
|
|
175
|
-
* Raised then parser encounters an end tag.
|
|
176
|
-
* @event endTag
|
|
177
|
-
* @memberof parse5#SAXParser
|
|
178
|
-
* @instance
|
|
179
|
-
* @type {Function}
|
|
180
|
-
* @param {String} name - Tag name.
|
|
181
|
-
* @param {LocationInfo} [location] - End tag source code location info.
|
|
182
|
-
* Available if location info is enabled in {@link SAXParserOptions}.
|
|
183
|
-
*/
|
|
184
99
|
this.emit('endTag', token.tagName, this.currentTokenLocation);
|
|
185
100
|
|
|
186
101
|
else if (token.type === Tokenizer.COMMENT_TOKEN)
|
|
187
|
-
/**
|
|
188
|
-
* Raised then parser encounters a comment.
|
|
189
|
-
* @event comment
|
|
190
|
-
* @memberof parse5#SAXParser
|
|
191
|
-
* @instance
|
|
192
|
-
* @type {Function}
|
|
193
|
-
* @param {String} text - Comment text.
|
|
194
|
-
* @param {LocationInfo} [location] - Comment source code location info.
|
|
195
|
-
* Available if location info is enabled in {@link SAXParserOptions}.
|
|
196
|
-
*/
|
|
197
102
|
this.emit('comment', token.data, this.currentTokenLocation);
|
|
198
103
|
|
|
199
104
|
else if (token.type === Tokenizer.DOCTYPE_TOKEN)
|
|
200
|
-
/**
|
|
201
|
-
* Raised then parser encounters a [document type declaration]{@link https://en.wikipedia.org/wiki/Document_type_declaration}.
|
|
202
|
-
* @event doctype
|
|
203
|
-
* @memberof parse5#SAXParser
|
|
204
|
-
* @instance
|
|
205
|
-
* @type {Function}
|
|
206
|
-
* @param {String} name - Document type name.
|
|
207
|
-
* @param {String} publicId - Document type public identifier.
|
|
208
|
-
* @param {String} systemId - Document type system identifier.
|
|
209
|
-
* @param {LocationInfo} [location] - Document type declaration source code location info.
|
|
210
|
-
* Available if location info is enabled in {@link SAXParserOptions}.
|
|
211
|
-
*/
|
|
212
105
|
this.emit('doctype', token.name, token.publicId, token.systemId, this.currentTokenLocation);
|
|
213
106
|
};
|
|
214
107
|
|
|
215
108
|
SAXParser.prototype._emitPendingText = function () {
|
|
216
109
|
if (this.pendingText !== null) {
|
|
217
|
-
/**
|
|
218
|
-
* Raised then parser encounters text content.
|
|
219
|
-
* @event text
|
|
220
|
-
* @memberof parse5#SAXParser
|
|
221
|
-
* @instance
|
|
222
|
-
* @type {Function}
|
|
223
|
-
* @param {String} text - Text content.
|
|
224
|
-
* @param {LocationInfo} [location] - Text content code location info.
|
|
225
|
-
* Available if location info is enabled in {@link SAXParserOptions}.
|
|
226
|
-
*/
|
|
227
110
|
this.emit('text', this.pendingText, this.currentTokenLocation);
|
|
228
111
|
this.pendingText = null;
|
|
229
112
|
}
|
package/lib/serializer/index.js
CHANGED
|
@@ -1,180 +1,162 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var defaultTreeAdapter = require('../tree_adapters/default'),
|
|
4
|
-
doctype = require('../common/doctype'),
|
|
5
|
-
mergeOptions = require('../common/merge_options'),
|
|
6
|
-
HTML = require('../common/html');
|
|
7
|
-
|
|
8
|
-
//Aliases
|
|
9
|
-
var $ = HTML.TAG_NAMES,
|
|
10
|
-
NS = HTML.NAMESPACES;
|
|
11
|
-
|
|
12
|
-
//Default serializer options
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.html += attr.name;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this.html += content;
|
|
165
|
-
|
|
166
|
-
else
|
|
167
|
-
this.html += Serializer.escapeString(content, false);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
Serializer.prototype._serializeCommentNode = function (node) {
|
|
171
|
-
this.html += '<!--' + this.treeAdapter.getCommentNodeContent(node) + '-->';
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
Serializer.prototype._serializeDocumentTypeNode = function (node) {
|
|
175
|
-
var name = this.treeAdapter.getDocumentTypeNodeName(node),
|
|
176
|
-
publicId = this.treeAdapter.getDocumentTypeNodePublicId(node),
|
|
177
|
-
systemId = this.treeAdapter.getDocumentTypeNodeSystemId(node);
|
|
178
|
-
|
|
179
|
-
this.html += '<' + doctype.serializeContent(name, publicId, systemId) + '>';
|
|
180
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var defaultTreeAdapter = require('../tree_adapters/default'),
|
|
4
|
+
doctype = require('../common/doctype'),
|
|
5
|
+
mergeOptions = require('../common/merge_options'),
|
|
6
|
+
HTML = require('../common/html');
|
|
7
|
+
|
|
8
|
+
//Aliases
|
|
9
|
+
var $ = HTML.TAG_NAMES,
|
|
10
|
+
NS = HTML.NAMESPACES;
|
|
11
|
+
|
|
12
|
+
//Default serializer options
|
|
13
|
+
var DEFAULT_OPTIONS = {
|
|
14
|
+
treeAdapter: defaultTreeAdapter
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//Escaping regexes
|
|
18
|
+
var AMP_REGEX = /&/g,
|
|
19
|
+
NBSP_REGEX = /\u00a0/g,
|
|
20
|
+
DOUBLE_QUOTE_REGEX = /"/g,
|
|
21
|
+
LT_REGEX = /</g,
|
|
22
|
+
GT_REGEX = />/g;
|
|
23
|
+
|
|
24
|
+
//Serializer
|
|
25
|
+
var Serializer = module.exports = function (node, options) {
|
|
26
|
+
this.options = mergeOptions(DEFAULT_OPTIONS, options);
|
|
27
|
+
this.treeAdapter = this.options.treeAdapter;
|
|
28
|
+
|
|
29
|
+
this.html = '';
|
|
30
|
+
this.startNode = node;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// NOTE: exported as static method for the testing purposes
|
|
34
|
+
Serializer.escapeString = function (str, attrMode) {
|
|
35
|
+
str = str
|
|
36
|
+
.replace(AMP_REGEX, '&')
|
|
37
|
+
.replace(NBSP_REGEX, ' ');
|
|
38
|
+
|
|
39
|
+
if (attrMode)
|
|
40
|
+
str = str.replace(DOUBLE_QUOTE_REGEX, '"');
|
|
41
|
+
|
|
42
|
+
else {
|
|
43
|
+
str = str
|
|
44
|
+
.replace(LT_REGEX, '<')
|
|
45
|
+
.replace(GT_REGEX, '>');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return str;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
//API
|
|
53
|
+
Serializer.prototype.serialize = function () {
|
|
54
|
+
this._serializeChildNodes(this.startNode);
|
|
55
|
+
|
|
56
|
+
return this.html;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
//Internals
|
|
61
|
+
Serializer.prototype._serializeChildNodes = function (parentNode) {
|
|
62
|
+
var childNodes = this.treeAdapter.getChildNodes(parentNode);
|
|
63
|
+
|
|
64
|
+
if (childNodes) {
|
|
65
|
+
for (var i = 0, cnLength = childNodes.length; i < cnLength; i++) {
|
|
66
|
+
var currentNode = childNodes[i];
|
|
67
|
+
|
|
68
|
+
if (this.treeAdapter.isElementNode(currentNode))
|
|
69
|
+
this._serializeElement(currentNode);
|
|
70
|
+
|
|
71
|
+
else if (this.treeAdapter.isTextNode(currentNode))
|
|
72
|
+
this._serializeTextNode(currentNode);
|
|
73
|
+
|
|
74
|
+
else if (this.treeAdapter.isCommentNode(currentNode))
|
|
75
|
+
this._serializeCommentNode(currentNode);
|
|
76
|
+
|
|
77
|
+
else if (this.treeAdapter.isDocumentTypeNode(currentNode))
|
|
78
|
+
this._serializeDocumentTypeNode(currentNode);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
Serializer.prototype._serializeElement = function (node) {
|
|
84
|
+
var tn = this.treeAdapter.getTagName(node),
|
|
85
|
+
ns = this.treeAdapter.getNamespaceURI(node);
|
|
86
|
+
|
|
87
|
+
this.html += '<' + tn;
|
|
88
|
+
this._serializeAttributes(node);
|
|
89
|
+
this.html += '>';
|
|
90
|
+
|
|
91
|
+
if (tn !== $.AREA && tn !== $.BASE && tn !== $.BASEFONT && tn !== $.BGSOUND && tn !== $.BR && tn !== $.BR &&
|
|
92
|
+
tn !== $.COL && tn !== $.EMBED && tn !== $.FRAME && tn !== $.HR && tn !== $.IMG && tn !== $.INPUT &&
|
|
93
|
+
tn !== $.KEYGEN && tn !== $.LINK && tn !== $.MENUITEM && tn !== $.META && tn !== $.PARAM && tn !== $.SOURCE &&
|
|
94
|
+
tn !== $.TRACK && tn !== $.WBR) {
|
|
95
|
+
|
|
96
|
+
var childNodesHolder = tn === $.TEMPLATE && ns === NS.HTML ?
|
|
97
|
+
this.treeAdapter.getTemplateContent(node) :
|
|
98
|
+
node;
|
|
99
|
+
|
|
100
|
+
this._serializeChildNodes(childNodesHolder);
|
|
101
|
+
this.html += '</' + tn + '>';
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
Serializer.prototype._serializeAttributes = function (node) {
|
|
106
|
+
var attrs = this.treeAdapter.getAttrList(node);
|
|
107
|
+
|
|
108
|
+
for (var i = 0, attrsLength = attrs.length; i < attrsLength; i++) {
|
|
109
|
+
var attr = attrs[i],
|
|
110
|
+
value = Serializer.escapeString(attr.value, true);
|
|
111
|
+
|
|
112
|
+
this.html += ' ';
|
|
113
|
+
|
|
114
|
+
if (!attr.namespace)
|
|
115
|
+
this.html += attr.name;
|
|
116
|
+
|
|
117
|
+
else if (attr.namespace === NS.XML)
|
|
118
|
+
this.html += 'xml:' + attr.name;
|
|
119
|
+
|
|
120
|
+
else if (attr.namespace === NS.XMLNS) {
|
|
121
|
+
if (attr.name !== 'xmlns')
|
|
122
|
+
this.html += 'xmlns:';
|
|
123
|
+
|
|
124
|
+
this.html += attr.name;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
else if (attr.namespace === NS.XLINK)
|
|
128
|
+
this.html += 'xlink:' + attr.name;
|
|
129
|
+
|
|
130
|
+
else
|
|
131
|
+
this.html += attr.namespace + ':' + attr.name;
|
|
132
|
+
|
|
133
|
+
this.html += '="' + value + '"';
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
Serializer.prototype._serializeTextNode = function (node) {
|
|
138
|
+
var content = this.treeAdapter.getTextNodeContent(node),
|
|
139
|
+
parent = this.treeAdapter.getParentNode(node),
|
|
140
|
+
parentTn = void 0;
|
|
141
|
+
|
|
142
|
+
if (parent && this.treeAdapter.isElementNode(parent))
|
|
143
|
+
parentTn = this.treeAdapter.getTagName(parent);
|
|
144
|
+
|
|
145
|
+
if (parentTn === $.STYLE || parentTn === $.SCRIPT || parentTn === $.XMP || parentTn === $.IFRAME ||
|
|
146
|
+
parentTn === $.NOEMBED || parentTn === $.NOFRAMES || parentTn === $.PLAINTEXT || parentTn === $.NOSCRIPT)
|
|
147
|
+
|
|
148
|
+
this.html += content;
|
|
149
|
+
|
|
150
|
+
else
|
|
151
|
+
this.html += Serializer.escapeString(content, false);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
Serializer.prototype._serializeCommentNode = function (node) {
|
|
155
|
+
this.html += '<!--' + this.treeAdapter.getCommentNodeContent(node) + '-->';
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
Serializer.prototype._serializeDocumentTypeNode = function (node) {
|
|
159
|
+
var name = this.treeAdapter.getDocumentTypeNodeName(node);
|
|
160
|
+
|
|
161
|
+
this.html += '<' + doctype.serializeContent(name, null, null) + '>';
|
|
162
|
+
};
|