@samitouri / QOS-React-2 / commits / 1d1b6ed07a

Move htmltojsx.js to React-Magic project.

The HTMLtoJSX file is really a part of the React-Magic project, and should belong there rather than here: - It doesn't really follow the release cycle of React at all - It is totally standalone with no dependency on React - The only usage of this script is in React-Magic and on the HTML to JSX page on the React website (http://facebook.github.io/react/html-jsx.html) I've hotlinked it from the Github site for that project since this was the easiest way to pull it in. Due to the fact that Github Pages lacks redirects (among other basic web hosting functionality), I've replaced the `html-jsx-lib.js` file with a comment explaining where the file has moved to, just in case anyone was hotlinking it from the React site.

Daniel Lo Nigro committed Sep 6, 2014 at 22:51 UTC 1d1b6ed07a25b122b5964df8fd235e3cf95060a7
2 files changed +2 -483
docs/_js/html-jsx-lib.js
+1 -482
@@ -1,482 +1 @@
1 -/**
2 - * Copyright 2013-2014 Facebook, Inc.
3 - *
4 - * Licensed under the Apache License, Version 2.0 (the "License");
5 - * you may not use this file except in compliance with the License.
6 - * You may obtain a copy of the License at
7 - *
8 - * http://www.apache.org/licenses/LICENSE-2.0
9 - *
10 - * Unless required by applicable law or agreed to in writing, software
11 - * distributed under the License is distributed on an "AS IS" BASIS,
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 - * See the License for the specific language governing permissions and
14 - * limitations under the License.
15 - */
16 -
17 -/**
18 - * This is a very simple HTML to JSX converter. It turns out that browsers
19 - * have good HTML parsers (who would have thought?) so we utilise this by
20 - * inserting the HTML into a temporary DOM node, and then do a breadth-first
21 - * traversal of the resulting DOM tree.
22 - */
23 -;(function(global) {
24 - 'use strict';
25 -
26 - // https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType
27 - var NODE_TYPE = {
28 - ELEMENT: 1,
29 - TEXT: 3,
30 - COMMENT: 8
31 - };
32 - var ATTRIBUTE_MAPPING = {
33 - 'for': 'htmlFor',
34 - 'class': 'className'
35 - };
36 -
37 - /**
38 - * Repeats a string a certain number of times.
39 - * Also: the future is bright and consists of native string repetition:
40 - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
41 - *
42 - * @param {string} string String to repeat
43 - * @param {number} times Number of times to repeat string. Integer.
44 - * @see http://jsperf.com/string-repeater/2
45 - */
46 - function repeatString(string, times) {
47 - if (times === 1) {
48 - return string;
49 - }
50 - if (times < 0) { throw new Error(); }
51 - var repeated = '';
52 - while (times) {
53 - if (times & 1) {
54 - repeated += string;
55 - }
56 - if (times >>= 1) {
57 - string += string;
58 - }
59 - }
60 - return repeated;
61 - }
62 -
63 - /**
64 - * Determine if the string ends with the specified substring.
65 - *
66 - * @param {string} haystack String to search in
67 - * @param {string} needle String to search for
68 - * @return {boolean}
69 - */
70 - function endsWith(haystack, needle) {
71 - return haystack.slice(-needle.length) === needle;
72 - }
73 -
74 - /**
75 - * Trim the specified substring off the string. If the string does not end
76 - * with the specified substring, this is a no-op.
77 - *
78 - * @param {string} haystack String to search in
79 - * @param {string} needle String to search for
80 - * @return {string}
81 - */
82 - function trimEnd(haystack, needle) {
83 - return endsWith(haystack, needle)
84 - ? haystack.slice(0, -needle.length)
85 - : haystack;
86 - }
87 -
88 - /**
89 - * Convert a hyphenated string to camelCase.
90 - */
91 - function hyphenToCamelCase(string) {
92 - return string.replace(/-(.)/g, function(match, chr) {
93 - return chr.toUpperCase();
94 - });
95 - }
96 -
97 - /**
98 - * Determines if the specified string consists entirely of whitespace.
99 - */
100 - function isEmpty(string) {
101 - return !/[^\s]/.test(string);
102 - }
103 -
104 - /**
105 - * Determines if the specified string consists entirely of numeric characters.
106 - */
107 - function isNumeric(input) {
108 - return input !== undefined
109 - && input !== null
110 - && (typeof input === 'number' || parseInt(input, 10) == input);
111 - }
112 -
113 - var HTMLtoJSX = function(config) {
114 - this.config = config || {};
115 -
116 - if (this.config.createClass === undefined) {
117 - this.config.createClass = true;
118 - }
119 - if (!this.config.indent) {
120 - this.config.indent = ' ';
121 - }
122 - if (!this.config.outputClassName) {
123 - this.config.outputClassName = 'NewComponent';
124 - }
125 - };
126 - HTMLtoJSX.prototype = {
127 - /**
128 - * Reset the internal state of the converter
129 - */
130 - reset: function() {
131 - this.output = '';
132 - this.level = 0;
133 - },
134 - /**
135 - * Main entry point to the converter. Given the specified HTML, returns a
136 - * JSX object representing it.
137 - * @param {string} html HTML to convert
138 - * @return {string} JSX
139 - */
140 - convert: function(html) {
141 - this.reset();
142 -
143 - // It turns out browsers have good HTML parsers (imagine that).
144 - // Let's take advantage of it.
145 - var containerEl = document.createElement('div');
146 - containerEl.innerHTML = '\n' + this._cleanInput(html) + '\n';
147 -
148 - if (this.config.createClass) {
149 - if (this.config.outputClassName) {
150 - this.output = 'var ' + this.config.outputClassName + ' = React.createClass({\n';
151 - } else {
152 - this.output = 'React.createClass({\n';
153 - }
154 - this.output += this.config.indent + 'render: function() {' + "\n";
155 - this.output += this.config.indent + this.config.indent + 'return (\n';
156 - }
157 -
158 - if (this._onlyOneTopLevel(containerEl)) {
159 - // Only one top-level element, the component can return it directly
160 - // No need to actually visit the container element
161 - this._traverse(containerEl);
162 - } else {
163 - // More than one top-level element, need to wrap the whole thing in a
164 - // container.
165 - this.output += this.config.indent + this.config.indent + this.config.indent;
166 - this.level++;
167 - this._visit(containerEl);
168 - }
169 - this.output = this.output.trim() + '\n';
170 - if (this.config.createClass) {
171 - this.output += this.config.indent + this.config.indent + ');\n';
172 - this.output += this.config.indent + '}\n';
173 - this.output += '});';
174 - }
175 - return this.output;
176 - },
177 -
178 - /**
179 - * Cleans up the specified HTML so it's in a format acceptable for
180 - * converting.
181 - *
182 - * @param {string} html HTML to clean
183 - * @return {string} Cleaned HTML
184 - */
185 - _cleanInput: function(html) {
186 - // Remove unnecessary whitespace
187 - html = html.trim();
188 - // Ugly method to strip script tags. They can wreak havoc on the DOM nodes
189 - // so let's not even put them in the DOM.
190 - html = html.replace(/<script(.*?)<\/script>/g, '');
191 - return html;
192 - },
193 -
194 - /**
195 - * Determines if there's only one top-level node in the DOM tree. That is,
196 - * all the HTML is wrapped by a single HTML tag.
197 - *
198 - * @param {DOMElement} containerEl Container element
199 - * @return {boolean}
200 - */
201 - _onlyOneTopLevel: function(containerEl) {
202 - // Only a single child element
203 - if (
204 - containerEl.childNodes.length === 1
205 - && containerEl.childNodes[0].nodeType === NODE_TYPE.ELEMENT
206 - ) {
207 - return true;
208 - }
209 - // Only one element, and all other children are whitespace
210 - var foundElement = false;
211 - for (var i = 0, count = containerEl.childNodes.length; i < count; i++) {
212 - var child = containerEl.childNodes[i];
213 - if (child.nodeType === NODE_TYPE.ELEMENT) {
214 - if (foundElement) {
215 - // Encountered an element after already encountering another one
216 - // Therefore, more than one element at root level
217 - return false;
218 - } else {
219 - foundElement = true;
220 - }
221 - } else if (child.nodeType === NODE_TYPE.TEXT && !isEmpty(child.textContent)) {
222 - // Contains text content
223 - return false;
224 - }
225 - }
226 - return true;
227 - },
228 -
229 - /**
230 - * Gets a newline followed by the correct indentation for the current
231 - * nesting level
232 - *
233 - * @return {string}
234 - */
235 - _getIndentedNewline: function() {
236 - return '\n' + repeatString(this.config.indent, this.level + 2);
237 - },
238 -
239 - /**
240 - * Handles processing the specified node
241 - *
242 - * @param {Node} node
243 - */
244 - _visit: function(node) {
245 - this._beginVisit(node);
246 - this._traverse(node);
247 - this._endVisit(node);
248 - },
249 -
250 - /**
251 - * Traverses all the children of the specified node
252 - *
253 - * @param {Node} node
254 - */
255 - _traverse: function(node) {
256 - this.level++;
257 - for (var i = 0, count = node.childNodes.length; i < count; i++) {
258 - this._visit(node.childNodes[i]);
259 - }
260 - this.level--;
261 - },
262 -
263 - /**
264 - * Handle pre-visit behaviour for the specified node.
265 - *
266 - * @param {Node} node
267 - */
268 - _beginVisit: function(node) {
269 - switch (node.nodeType) {
270 - case NODE_TYPE.ELEMENT:
271 - this._beginVisitElement(node);
272 - break;
273 -
274 - case NODE_TYPE.TEXT:
275 - this._visitText(node);
276 - break;
277 -
278 - case NODE_TYPE.COMMENT:
279 - this._visitComment(node);
280 - break;
281 -
282 - default:
283 - console.warn('Unrecognised node type: ' + node.nodeType);
284 - }
285 - },
286 -
287 - /**
288 - * Handles post-visit behaviour for the specified node.
289 - *
290 - * @param {Node} node
291 - */
292 - _endVisit: function(node) {
293 - switch (node.nodeType) {
294 - case NODE_TYPE.ELEMENT:
295 - this._endVisitElement(node);
296 - break;
297 - // No ending tags required for these types
298 - case NODE_TYPE.TEXT:
299 - case NODE_TYPE.COMMENT:
300 - break;
301 - }
302 - },
303 -
304 - /**
305 - * Handles pre-visit behaviour for the specified element node
306 - *
307 - * @param {DOMElement} node
308 - */
309 - _beginVisitElement: function(node) {
310 - var tagName = node.tagName.toLowerCase();
311 - var attributes = [];
312 - for (var i = 0, count = node.attributes.length; i < count; i++) {
313 - attributes.push(this._getElementAttribute(node, node.attributes[i]));
314 - }
315 -
316 - this.output += '<' + tagName;
317 - if (attributes.length > 0) {
318 - this.output += ' ' + attributes.join(' ');
319 - }
320 - if (node.firstChild) {
321 - this.output += '>';
322 - }
323 - },
324 -
325 - /**
326 - * Handles post-visit behaviour for the specified element node
327 - *
328 - * @param {Node} node
329 - */
330 - _endVisitElement: function(node) {
331 - // De-indent a bit
332 - // TODO: It's inefficient to do it this way :/
333 - this.output = trimEnd(this.output, this.config.indent);
334 - if (node.firstChild) {
335 - this.output += '</' + node.tagName.toLowerCase() + '>';
336 - } else {
337 - this.output += ' />';
338 - }
339 - },
340 -
341 - /**
342 - * Handles processing of the specified text node
343 - *
344 - * @param {TextNode} node
345 - */
346 - _visitText: function(node) {
347 - var text = node.textContent;
348 - // If there's a newline in the text, adjust the indent level
349 - if (text.indexOf('\n') > -1) {
350 - text = node.textContent.replace(/\n\s*/g, this._getIndentedNewline());
351 - }
352 - this.output += text;
353 - },
354 -
355 - /**
356 - * Handles processing of the specified text node
357 - *
358 - * @param {Text} node
359 - */
360 - _visitComment: function(node) {
361 - // Do not render the comment
362 - // Since we remove comments, we also need to remove the next line break so we
363 - // don't end up with extra whitespace after every comment
364 - //if (node.nextSibling && node.nextSibling.nodeType === NODE_TYPE.TEXT) {
365 - // node.nextSibling.textContent = node.nextSibling.textContent.replace(/\n\s*/, '');
366 - //}
367 - this.output += '{/*' + node.textContent.replace('*/', '* /') + '*/}';
368 - },
369 -
370 - /**
371 - * Gets a JSX formatted version of the specified attribute from the node
372 - *
373 - * @param {DOMElement} node
374 - * @param {object} attribute
375 - * @return {string}
376 - */
377 - _getElementAttribute: function(node, attribute) {
378 - switch (attribute.name) {
379 - case 'style':
380 - return this._getStyleAttribute(attribute.value);
381 - default:
382 - var name = ATTRIBUTE_MAPPING[attribute.name] || attribute.name;
383 - var result = name + '=';
384 - // Numeric values should be output as {123} not "123"
385 - if (isNumeric(attribute.value)) {
386 - result += '{' + attribute.value + '}';
387 - } else {
388 - result += '"' + attribute.value.replace('"', '&quot;') + '"';
389 - }
390 - return result;
391 - }
392 - },
393 -
394 - /**
395 - * Gets a JSX formatted version of the specified element styles
396 - *
397 - * @param {string} styles
398 - * @return {string}
399 - */
400 - _getStyleAttribute: function(styles) {
401 - var jsxStyles = new StyleParser(styles).toJSXString();
402 - return 'style={{' + jsxStyles + '}}';
403 - }
404 - };
405 -
406 - /**
407 - * Handles parsing of inline styles
408 - *
409 - * @param {string} rawStyle Raw style attribute
410 - * @constructor
411 - */
412 - var StyleParser = function(rawStyle) {
413 - this.parse(rawStyle);
414 - };
415 - StyleParser.prototype = {
416 - /**
417 - * Parse the specified inline style attribute value
418 - * @param {string} rawStyle Raw style attribute
419 - */
420 - parse: function(rawStyle) {
421 - this.styles = {};
422 - rawStyle.split(';').forEach(function(style) {
423 - style = style.trim();
424 - var firstColon = style.indexOf(':');
425 - var key = style.substr(0, firstColon);
426 - var value = style.substr(firstColon + 1).trim();
427 - if (key !== '') {
428 - this.styles[key] = value;
429 - }
430 - }, this);
431 - },
432 -
433 - /**
434 - * Convert the style information represented by this parser into a JSX
435 - * string
436 - *
437 - * @return {string}
438 - */
439 - toJSXString: function() {
440 - var output = [];
441 - for (var key in this.styles) {
442 - if (!this.styles.hasOwnProperty(key)) {
443 - continue;
444 - }
445 - output.push(this.toJSXKey(key) + ': ' + this.toJSXValue(this.styles[key]));
446 - }
447 - return output.join(', ');
448 - },
449 -
450 - /**
451 - * Convert the CSS style key to a JSX style key
452 - *
453 - * @param {string} key CSS style key
454 - * @return {string} JSX style key
455 - */
456 - toJSXKey: function(key) {
457 - return hyphenToCamelCase(key);
458 - },
459 -
460 - /**
461 - * Convert the CSS style value to a JSX style value
462 - *
463 - * @param {string} value CSS style value
464 - * @return {string} JSX style value
465 - */
466 - toJSXValue: function(value) {
467 - if (isNumeric(value)) {
468 - // If numeric, no quotes
469 - return value;
470 - } else if (endsWith(value, 'px')) {
471 - // "500px" -> 500
472 - return trimEnd(value, 'px');
473 - } else {
474 - // Proably a string, wrap it in quotes
475 - return '\'' + value.replace(/'/g, '"') + '\'';
476 - }
477 - }
478 - };
479 -
480 - // Expose public API
481 - global.HTMLtoJSX = HTMLtoJSX;
482 -}(window));
\ No newline at end of file
1 +// This file has moved to http://reactjs.github.io/react-magic/htmltojsx.min.js
docs/html-jsx.md
+1 -1
@@ -6,6 +6,6 @@ id: html-jsx
6 <div class="jsxCompiler">
7 <h1>HTML to JSX Compiler</h1>
8 <div id="jsxCompiler"></div>
9 - <script src="js/html-jsx-lib.js"></script>
9 + <script src="http://reactjs.github.io/react-magic/htmltojsx.min.js"></script>
10 <script src="js/html-jsx.js"></script>
11 </div>