forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhtml.js
268 lines (212 loc) · 9.39 KB
/
html.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
HTML.Tag = function () {};
HTML.Tag.prototype.tagName = ''; // this will be set per Tag subclass
HTML.Tag.prototype.attrs = null;
HTML.Tag.prototype.children = Object.freeze ? Object.freeze([]) : [];
HTML.Tag.prototype.htmljsType = HTML.Tag.htmljsType = ['Tag'];
// Given "p" create the function `HTML.P`.
var makeTagConstructor = function (tagName) {
// HTMLTag is the per-tagName constructor of a HTML.Tag subclass
var HTMLTag = function (/*arguments*/) {
// Work with or without `new`. If not called with `new`,
// perform instantiation by recursively calling this constructor.
// We can't pass varargs, so pass no args.
var instance = (this instanceof HTML.Tag) ? this : new HTMLTag;
var i = 0;
var attrs = arguments.length && arguments[0];
if (attrs && (typeof attrs === 'object')) {
// Treat vanilla JS object as an attributes dictionary.
if (! HTML.isConstructedObject(attrs)) {
instance.attrs = attrs;
i++;
} else if (attrs instanceof HTML.Attrs) {
var array = attrs.value;
if (array.length === 1) {
instance.attrs = array[0];
} else if (array.length > 1) {
instance.attrs = array;
}
i++;
}
}
// If no children, don't create an array at all, use the prototype's
// (frozen, empty) array. This way we don't create an empty array
// every time someone creates a tag without `new` and this constructor
// calls itself with no arguments (above).
if (i < arguments.length)
instance.children = SLICE.call(arguments, i);
return instance;
};
HTMLTag.prototype = new HTML.Tag;
HTMLTag.prototype.constructor = HTMLTag;
HTMLTag.prototype.tagName = tagName;
return HTMLTag;
};
// Not an HTMLjs node, but a wrapper to pass multiple attrs dictionaries
// to a tag (for the purpose of implementing dynamic attributes).
var Attrs = HTML.Attrs = function (/*attrs dictionaries*/) {
// Work with or without `new`. If not called with `new`,
// perform instantiation by recursively calling this constructor.
// We can't pass varargs, so pass no args.
var instance = (this instanceof Attrs) ? this : new Attrs;
instance.value = SLICE.call(arguments);
return instance;
};
////////////////////////////// KNOWN ELEMENTS
HTML.getTag = function (tagName) {
var symbolName = HTML.getSymbolName(tagName);
if (symbolName === tagName) // all-caps tagName
throw new Error("Use the lowercase or camelCase form of '" + tagName + "' here");
if (! HTML[symbolName])
HTML[symbolName] = makeTagConstructor(tagName);
return HTML[symbolName];
};
HTML.ensureTag = function (tagName) {
HTML.getTag(tagName); // don't return it
};
HTML.isTagEnsured = function (tagName) {
return HTML.isKnownElement(tagName);
};
HTML.getSymbolName = function (tagName) {
// "foo-bar" -> "FOO_BAR"
return tagName.toUpperCase().replace(/-/g, '_');
};
HTML.knownElementNames = 'a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br button canvas caption center cite code col colgroup command data datagrid datalist dd del details dfn dir div dl dt em embed eventsource fieldset figcaption figure font footer form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins isindex kbd keygen label legend li link main map mark menu meta meter nav noframes noscript object ol optgroup option output p param pre progress q rp rt ruby s samp script section select small source span strike strong style sub summary sup table tbody td textarea tfoot th thead time title tr track tt u ul var video wbr'.split(' ');
// (we add the SVG ones below)
HTML.knownSVGElementNames = 'altGlyph altGlyphDef altGlyphItem animate animateColor animateMotion animateTransform circle clipPath color-profile cursor defs desc ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font font-face font-face-format font-face-name font-face-src font-face-uri foreignObject g glyph glyphRef hkern image line linearGradient marker mask metadata missing-glyph path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view vkern'.split(' ');
// Append SVG element names to list of known element names
HTML.knownElementNames = HTML.knownElementNames.concat(HTML.knownSVGElementNames);
HTML.voidElementNames = 'area base br col command embed hr img input keygen link meta param source track wbr'.split(' ');
// Speed up search through lists of known elements by creating internal "sets"
// of strings.
var YES = {yes:true};
var makeSet = function (array) {
var set = {};
for (var i = 0; i < array.length; i++)
set[array[i]] = YES;
return set;
};
var voidElementSet = makeSet(HTML.voidElementNames);
var knownElementSet = makeSet(HTML.knownElementNames);
var knownSVGElementSet = makeSet(HTML.knownSVGElementNames);
HTML.isKnownElement = function (tagName) {
return knownElementSet[tagName] === YES;
};
HTML.isKnownSVGElement = function (tagName) {
return knownSVGElementSet[tagName] === YES;
};
HTML.isVoidElement = function (tagName) {
return voidElementSet[tagName] === YES;
};
// Ensure tags for all known elements
for (var i = 0; i < HTML.knownElementNames.length; i++)
HTML.ensureTag(HTML.knownElementNames[i]);
var CharRef = HTML.CharRef = function (attrs) {
if (! (this instanceof CharRef))
// called without `new`
return new CharRef(attrs);
if (! (attrs && attrs.html && attrs.str))
throw new Error(
"HTML.CharRef must be constructed with ({html:..., str:...})");
this.html = attrs.html;
this.str = attrs.str;
};
CharRef.prototype.htmljsType = CharRef.htmljsType = ['CharRef'];
var Comment = HTML.Comment = function (value) {
if (! (this instanceof Comment))
// called without `new`
return new Comment(value);
if (typeof value !== 'string')
throw new Error('HTML.Comment must be constructed with a string');
this.value = value;
// Kill illegal hyphens in comment value (no way to escape them in HTML)
this.sanitizedValue = value.replace(/^-|--+|-$/g, '');
};
Comment.prototype.htmljsType = Comment.htmljsType = ['Comment'];
var Raw = HTML.Raw = function (value) {
if (! (this instanceof Raw))
// called without `new`
return new Raw(value);
if (typeof value !== 'string')
throw new Error('HTML.Raw must be constructed with a string');
this.value = value;
};
Raw.prototype.htmljsType = Raw.htmljsType = ['Raw'];
HTML.isArray = function (x) {
// could change this to use the more convoluted Object.prototype.toString
// approach that works when objects are passed between frames, but does
// it matter?
return (x instanceof Array);
};
HTML.isConstructedObject = function (x) {
// Figure out if `x` is "an instance of some class" or just a plain
// object literal. It correctly treats an object literal like
// `{ constructor: ... }` as an object literal. It won't detect
// instances of classes that lack a `constructor` property (e.g.
// if you assign to a prototype when setting up the class as in:
// `Foo = function () { ... }; Foo.prototype = { ... }`, then
// `(new Foo).constructor` is `Object`, not `Foo`).
return (x && (typeof x === 'object') &&
(x.constructor !== Object) &&
(typeof x.constructor === 'function') &&
(x instanceof x.constructor));
};
HTML.isNully = function (node) {
if (node == null)
// null or undefined
return true;
if (HTML.isArray(node)) {
// is it an empty array or an array of all nully items?
for (var i = 0; i < node.length; i++)
if (! HTML.isNully(node[i]))
return false;
return true;
}
return false;
};
HTML.isValidAttributeName = function (name) {
return /^[:_A-Za-z][:_A-Za-z0-9.\-]*/.test(name);
};
// If `attrs` is an array of attributes dictionaries, combines them
// into one. Removes attributes that are "nully."
HTML.flattenAttributes = function (attrs) {
if (! attrs)
return attrs;
var isArray = HTML.isArray(attrs);
if (isArray && attrs.length === 0)
return null;
var result = {};
for (var i = 0, N = (isArray ? attrs.length : 1); i < N; i++) {
var oneAttrs = (isArray ? attrs[i] : attrs);
if ((typeof oneAttrs !== 'object') ||
HTML.isConstructedObject(oneAttrs))
throw new Error("Expected plain JS object as attrs, found: " + oneAttrs);
for (var name in oneAttrs) {
if (! HTML.isValidAttributeName(name))
throw new Error("Illegal HTML attribute name: " + name);
var value = oneAttrs[name];
if (! HTML.isNully(value))
result[name] = value;
}
}
return result;
};
////////////////////////////// TOHTML
HTML.toHTML = function (content) {
return (new HTML.ToHTMLVisitor).visit(content);
};
// Escaping modes for outputting text when generating HTML.
HTML.TEXTMODE = {
STRING: 1,
RCDATA: 2,
ATTRIBUTE: 3
};
HTML.toText = function (content, textMode) {
if (! textMode)
throw new Error("textMode required for HTML.toText");
if (! (textMode === HTML.TEXTMODE.STRING ||
textMode === HTML.TEXTMODE.RCDATA ||
textMode === HTML.TEXTMODE.ATTRIBUTE))
throw new Error("Unknown textMode: " + textMode);
var visitor = new HTML.ToTextVisitor({textMode: textMode});;
return visitor.visit(content);
};