├── .gitignore
├── .travis.yml
├── Gruntfile.coffee
├── LICENSE
├── README.md
├── SpecRunner.html
├── bower.json
├── build
├── html-string.js
└── html-string.min.js
├── external
└── fsm.js
├── npm-debug.log
├── package.json
├── spec
├── html-string-spec.js
└── spec-helper.js
└── src
├── characters.coffee
├── namespace.coffee
├── spec
├── html-string-spec.coffee
└── spec-helper.coffee
├── strings.coffee
└── tags.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | jasmine
2 | node_modules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | before_install:
5 | - npm install -g grunt-cli
--------------------------------------------------------------------------------
/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (grunt) ->
2 |
3 | # Project configuration
4 | grunt.initConfig({
5 |
6 | pkg: grunt.file.readJSON('package.json')
7 |
8 | coffee:
9 | options:
10 | join: true
11 |
12 | build:
13 | files:
14 | 'src/tmp/html-string.js': [
15 | 'src/namespace.coffee'
16 | 'src/strings.coffee'
17 | 'src/tags.coffee'
18 | 'src/characters.coffee'
19 | ]
20 |
21 | spec:
22 | files:
23 | 'spec/spec-helper.js': 'src/spec/spec-helper.coffee'
24 | 'spec/html-string-spec.js': 'src/spec/html-string-spec.coffee'
25 |
26 | uglify:
27 | options:
28 | banner: '/*! <%= pkg.name %> v<%= pkg.version %> by <%= pkg.author.name %> <<%= pkg.author.email %>> (<%= pkg.author.url %>) */\n'
29 | mangle: false
30 |
31 | build:
32 | src: 'build/html-string.js'
33 | dest: 'build/html-string.min.js'
34 |
35 | concat:
36 | build:
37 | src: [
38 | 'external/fsm.js'
39 | 'src/tmp/html-string.js'
40 | ]
41 | dest: 'build/html-string.js'
42 |
43 | clean:
44 | build: ['src/tmp']
45 |
46 | jasmine:
47 | build:
48 | src: ['build/html-string.js']
49 | options:
50 | specs: 'spec/html-string-spec.js'
51 | helpers: 'spec/spec-helper.js'
52 |
53 | watch:
54 | build:
55 | files: ['src/*.coffee']
56 | tasks: ['build']
57 |
58 | spec:
59 | files: ['src/spec/*.coffee']
60 | tasks: ['spec']
61 | })
62 |
63 | # Plug-ins
64 | grunt.loadNpmTasks 'grunt-contrib-clean'
65 | grunt.loadNpmTasks 'grunt-contrib-coffee'
66 | grunt.loadNpmTasks 'grunt-contrib-concat'
67 | grunt.loadNpmTasks 'grunt-contrib-jasmine'
68 | grunt.loadNpmTasks 'grunt-contrib-uglify'
69 | grunt.loadNpmTasks 'grunt-contrib-watch'
70 |
71 | # Tasks
72 | grunt.registerTask 'build', [
73 | 'coffee:build'
74 | 'concat:build'
75 | 'uglify:build'
76 | 'clean:build'
77 | ]
78 |
79 | grunt.registerTask 'spec', [
80 | 'coffee:spec'
81 | ]
82 |
83 | grunt.registerTask 'watch-build', ['watch:build']
84 | grunt.registerTask 'watch-spec', ['watch:spec']
85 |
86 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Getme Limited (http://getme.co.uk)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HTMLString
2 |
3 | [](https://travis-ci.org/GetmeUK/HTMLString)
4 |
5 | > An HTML parser written in JavaScript that's probably not what you're looking for.
6 |
7 | ## Install
8 |
9 | **Using bower**
10 |
11 | ```
12 | bower install --save HTMLString
13 | ```
14 |
15 | **Using npm**
16 |
17 | ```
18 | npm install --save HTMLString
19 | ```
20 |
21 | ## Building
22 | To build the library you'll need to use Grunt. First install the required node modules ([grunt-cli](http://gruntjs.com/getting-started) must be installed):
23 | ```
24 | git clone https://github.com/GetmeUK/HTMLString.git
25 | cd HTMLString
26 | npm install
27 | ```
28 |
29 | Then run `grunt build` to build the project.
30 |
31 | ## Testing
32 | To test the library you'll need to use Jasmine. First install Jasmine:
33 | ```
34 | git clone https://github.com/pivotal/jasmine.git
35 | mkdir HTMLString/jasmine
36 | mv jasmine/dist/jasmine-standalone-2.0.3.zip HTMLString/jasmine
37 | cd HTMLString/jasmine
38 | unzip jasmine-standalone-2.0.3.zip
39 | ```
40 |
41 | Then open `HTMLString/SpecRunner.html` in a browser to run the tests.
42 |
43 | Alternatively you can use `grunt jasmine` to run the tests from the command line.
44 |
45 | ## Documentation
46 | Full documentation is available at http://getcontenttools.com/api/html-string
47 |
48 | ## Browser support
49 | - Chrome
50 | - Firefox
51 | - IE9+
52 |
--------------------------------------------------------------------------------
/SpecRunner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTMLString spec runner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HTMLString",
3 | "description": "An HTML parser written in JavaScript that's probably not what you're looking for.",
4 | "main": "build/html-string.js",
5 | "authors": [
6 | {
7 | "name": "Anthony Blackshaw",
8 | "email": "ant@getme.co.uk",
9 | "url": "https://github.com/anthonyjb"
10 | }
11 | ],
12 | "license": "MIT",
13 | "keywords": [
14 | "html",
15 | "parser"
16 | ],
17 | "homepage": "http://getcontenttools.com/api/html-string",
18 | "repository": {
19 | "type": "git",
20 | "url": "git@github.com:GetmeUK/HTMLString.git"
21 | },
22 | "moduleType": [
23 | "globals"
24 | ],
25 | "ignore": [
26 | "**/.*",
27 | "node_modules",
28 | "bower_components",
29 | "test",
30 | "tests"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/build/html-string.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var FSM, exports;
3 |
4 | FSM = {};
5 |
6 | FSM.Machine = (function() {
7 | function Machine(context) {
8 | this.context = context;
9 | this._stateTransitions = {};
10 | this._stateTransitionsAny = {};
11 | this._defaultTransition = null;
12 | this._initialState = null;
13 | this._currentState = null;
14 | }
15 |
16 | Machine.prototype.addTransition = function(action, state, nextState, callback) {
17 | if (!nextState) {
18 | nextState = state;
19 | }
20 | return this._stateTransitions[[action, state]] = [nextState, callback];
21 | };
22 |
23 | Machine.prototype.addTransitions = function(actions, state, nextState, callback) {
24 | var action, _i, _len, _results;
25 | if (!nextState) {
26 | nextState = state;
27 | }
28 | _results = [];
29 | for (_i = 0, _len = actions.length; _i < _len; _i++) {
30 | action = actions[_i];
31 | _results.push(this.addTransition(action, state, nextState, callback));
32 | }
33 | return _results;
34 | };
35 |
36 | Machine.prototype.addTransitionAny = function(state, nextState, callback) {
37 | if (!nextState) {
38 | nextState = state;
39 | }
40 | return this._stateTransitionsAny[state] = [nextState, callback];
41 | };
42 |
43 | Machine.prototype.setDefaultTransition = function(state, callback) {
44 | return this._defaultTransition = [state, callback];
45 | };
46 |
47 | Machine.prototype.getTransition = function(action, state) {
48 | if (this._stateTransitions[[action, state]]) {
49 | return this._stateTransitions[[action, state]];
50 | } else if (this._stateTransitionsAny[state]) {
51 | return this._stateTransitionsAny[state];
52 | } else if (this._defaultTransition) {
53 | return this._defaultTransition;
54 | }
55 | throw new Error("Transition is undefined: (" + action + ", " + state + ")");
56 | };
57 |
58 | Machine.prototype.getCurrentState = function() {
59 | return this._currentState;
60 | };
61 |
62 | Machine.prototype.setInitialState = function(state) {
63 | this._initialState = state;
64 | if (!this._currentState) {
65 | return this.reset();
66 | }
67 | };
68 |
69 | Machine.prototype.reset = function() {
70 | return this._currentState = this._initialState;
71 | };
72 |
73 | Machine.prototype.process = function(action) {
74 | var result;
75 | result = this.getTransition(action, this._currentState);
76 | if (result[1]) {
77 | result[1].call(this.context || (this.context = this), action);
78 | }
79 | return this._currentState = result[0];
80 | };
81 |
82 | return Machine;
83 |
84 | })();
85 |
86 | if (typeof window !== 'undefined') {
87 | window.FSM = FSM;
88 | }
89 |
90 | if (typeof module !== 'undefined' && module.exports) {
91 | exports = module.exports = FSM;
92 | }
93 |
94 | }).call(this);
95 |
96 | (function() {
97 | var ALPHA_CHARS, ALPHA_NUMERIC_CHARS, ATTR_DELIM, ATTR_ENTITY_DOUBLE_DELIM, ATTR_ENTITY_NO_DELIM, ATTR_ENTITY_SINGLE_DELIM, ATTR_NAME, ATTR_NAME_CHARS, ATTR_NAME_FIND_VALUE, ATTR_OR_TAG_END, ATTR_VALUE_DOUBLE_DELIM, ATTR_VALUE_NO_DELIM, ATTR_VALUE_SINGLE_DELIM, CHAR_OR_ENTITY_OR_TAG, CLOSING_TAG, ENTITY, ENTITY_CHARS, HTMLString, OPENING_TAG, OPENNING_OR_CLOSING_TAG, TAG_NAME_CHARS, TAG_NAME_CLOSING, TAG_NAME_MUST_CLOSE, TAG_NAME_OPENING, TAG_OPENING_SELF_CLOSING, exports, _Parser,
98 | __slice = [].slice,
99 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
100 |
101 | HTMLString = {};
102 |
103 | if (typeof window !== 'undefined') {
104 | window.HTMLString = HTMLString;
105 | }
106 |
107 | if (typeof module !== 'undefined' && module.exports) {
108 | exports = module.exports = HTMLString;
109 | }
110 |
111 | HTMLString.String = (function() {
112 | String._parser = null;
113 |
114 | function String(html, preserveWhitespace) {
115 | if (preserveWhitespace == null) {
116 | preserveWhitespace = false;
117 | }
118 | this._preserveWhitespace = preserveWhitespace;
119 | if (html) {
120 | if (HTMLString.String._parser === null) {
121 | HTMLString.String._parser = new _Parser();
122 | }
123 | this.characters = HTMLString.String._parser.parse(html, this._preserveWhitespace).characters;
124 | } else {
125 | this.characters = [];
126 | }
127 | }
128 |
129 | String.prototype.isWhitespace = function() {
130 | var c, _i, _len, _ref;
131 | _ref = this.characters;
132 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
133 | c = _ref[_i];
134 | if (!c.isWhitespace()) {
135 | return false;
136 | }
137 | }
138 | return true;
139 | };
140 |
141 | String.prototype.length = function() {
142 | return this.characters.length;
143 | };
144 |
145 | String.prototype.preserveWhitespace = function() {
146 | return this._preserveWhitespace;
147 | };
148 |
149 | String.prototype.capitalize = function() {
150 | var c, newString;
151 | newString = this.copy();
152 | if (newString.length()) {
153 | c = newString.characters[0]._c.toUpperCase();
154 | newString.characters[0]._c = c;
155 | }
156 | return newString;
157 | };
158 |
159 | String.prototype.charAt = function(index) {
160 | return this.characters[index].copy();
161 | };
162 |
163 | String.prototype.concat = function() {
164 | var c, indexChar, inheritFormat, inheritedTags, newString, string, strings, tail, _i, _j, _k, _l, _len, _len1, _len2, _ref, _ref1;
165 | strings = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), inheritFormat = arguments[_i++];
166 | if (!(typeof inheritFormat === 'undefined' || typeof inheritFormat === 'boolean')) {
167 | strings.push(inheritFormat);
168 | inheritFormat = true;
169 | }
170 | newString = this.copy();
171 | for (_j = 0, _len = strings.length; _j < _len; _j++) {
172 | string = strings[_j];
173 | if (string.length === 0) {
174 | continue;
175 | }
176 | tail = string;
177 | if (typeof string === 'string') {
178 | tail = new HTMLString.String(string, this._preserveWhitespace);
179 | }
180 | if (inheritFormat && newString.length()) {
181 | indexChar = newString.charAt(newString.length() - 1);
182 | inheritedTags = indexChar.tags();
183 | if (indexChar.isTag()) {
184 | inheritedTags.shift();
185 | }
186 | if (typeof string !== 'string') {
187 | tail = tail.copy();
188 | }
189 | _ref = tail.characters;
190 | for (_k = 0, _len1 = _ref.length; _k < _len1; _k++) {
191 | c = _ref[_k];
192 | c.addTags.apply(c, inheritedTags);
193 | }
194 | }
195 | _ref1 = tail.characters;
196 | for (_l = 0, _len2 = _ref1.length; _l < _len2; _l++) {
197 | c = _ref1[_l];
198 | newString.characters.push(c);
199 | }
200 | }
201 | return newString;
202 | };
203 |
204 | String.prototype.contains = function(substring) {
205 | var c, found, from, i, _i, _len, _ref;
206 | if (typeof substring === 'string') {
207 | return this.text().indexOf(substring) > -1;
208 | }
209 | from = 0;
210 | while (from <= (this.length() - substring.length())) {
211 | found = true;
212 | _ref = substring.characters;
213 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
214 | c = _ref[i];
215 | if (!c.eq(this.characters[i + from])) {
216 | found = false;
217 | break;
218 | }
219 | }
220 | if (found) {
221 | return true;
222 | }
223 | from++;
224 | }
225 | return false;
226 | };
227 |
228 | String.prototype.endsWith = function(substring) {
229 | var c, characters, i, _i, _len, _ref;
230 | if (typeof substring === 'string') {
231 | return substring === '' || this.text().slice(-substring.length) === substring;
232 | }
233 | characters = this.characters.slice().reverse();
234 | _ref = substring.characters.slice().reverse();
235 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
236 | c = _ref[i];
237 | if (!c.eq(characters[i])) {
238 | return false;
239 | }
240 | }
241 | return true;
242 | };
243 |
244 | String.prototype.format = function() {
245 | var c, from, i, newString, tags, to, _i;
246 | from = arguments[0], to = arguments[1], tags = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
247 | if (to < 0) {
248 | to = this.length() + to + 1;
249 | }
250 | if (from < 0) {
251 | from = this.length() + from;
252 | }
253 | newString = this.copy();
254 | for (i = _i = from; from <= to ? _i < to : _i > to; i = from <= to ? ++_i : --_i) {
255 | c = newString.characters[i];
256 | c.addTags.apply(c, tags);
257 | }
258 | return newString;
259 | };
260 |
261 | String.prototype.hasTags = function() {
262 | var c, found, strict, tags, _i, _j, _len, _ref;
263 | tags = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), strict = arguments[_i++];
264 | if (!(typeof strict === 'undefined' || typeof strict === 'boolean')) {
265 | tags.push(strict);
266 | strict = false;
267 | }
268 | found = false;
269 | _ref = this.characters;
270 | for (_j = 0, _len = _ref.length; _j < _len; _j++) {
271 | c = _ref[_j];
272 | if (c.hasTags.apply(c, tags)) {
273 | found = true;
274 | } else {
275 | if (strict) {
276 | return false;
277 | }
278 | }
279 | }
280 | return found;
281 | };
282 |
283 | String.prototype.html = function() {
284 | var c, closingTag, closingTags, head, html, openHeads, openTag, openTags, tag, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3;
285 | html = '';
286 | openTags = [];
287 | openHeads = [];
288 | closingTags = [];
289 | _ref = this.characters;
290 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
291 | c = _ref[_i];
292 | closingTags = [];
293 | _ref1 = openTags.slice().reverse();
294 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
295 | openTag = _ref1[_j];
296 | closingTags.push(openTag);
297 | if (!c.hasTags(openTag)) {
298 | for (_k = 0, _len2 = closingTags.length; _k < _len2; _k++) {
299 | closingTag = closingTags[_k];
300 | html += closingTag.tail();
301 | openTags.pop();
302 | openHeads.pop();
303 | }
304 | closingTags = [];
305 | }
306 | }
307 | _ref2 = c._tags;
308 | for (_l = 0, _len3 = _ref2.length; _l < _len3; _l++) {
309 | tag = _ref2[_l];
310 | if (openHeads.indexOf(tag.head()) === -1) {
311 | if (!tag.selfClosing()) {
312 | head = tag.head();
313 | html += head;
314 | openTags.push(tag);
315 | openHeads.push(head);
316 | }
317 | }
318 | }
319 | if (c._tags.length > 0 && c._tags[0].selfClosing()) {
320 | html += c._tags[0].head();
321 | }
322 | html += c.c();
323 | }
324 | _ref3 = openTags.reverse();
325 | for (_m = 0, _len4 = _ref3.length; _m < _len4; _m++) {
326 | tag = _ref3[_m];
327 | html += tag.tail();
328 | }
329 | return html;
330 | };
331 |
332 | String.prototype.indexOf = function(substring, from) {
333 | var c, found, i, _i, _len, _ref;
334 | if (from == null) {
335 | from = 0;
336 | }
337 | if (from < 0) {
338 | from = 0;
339 | }
340 | if (typeof substring === 'string') {
341 | return this.text().indexOf(substring, from);
342 | }
343 | while (from <= (this.length() - substring.length())) {
344 | found = true;
345 | _ref = substring.characters;
346 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
347 | c = _ref[i];
348 | if (!c.eq(this.characters[i + from])) {
349 | found = false;
350 | break;
351 | }
352 | }
353 | if (found) {
354 | return from;
355 | }
356 | from++;
357 | }
358 | return -1;
359 | };
360 |
361 | String.prototype.insert = function(index, substring, inheritFormat) {
362 | var c, head, indexChar, inheritedTags, middle, newString, tail, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2;
363 | if (inheritFormat == null) {
364 | inheritFormat = true;
365 | }
366 | head = this.slice(0, index);
367 | tail = this.slice(index);
368 | if (index < 0) {
369 | index = this.length() + index;
370 | }
371 | middle = substring;
372 | if (typeof substring === 'string') {
373 | middle = new HTMLString.String(substring, this._preserveWhitespace);
374 | }
375 | if (inheritFormat && index > 0) {
376 | indexChar = this.charAt(index - 1);
377 | inheritedTags = indexChar.tags();
378 | if (indexChar.isTag()) {
379 | inheritedTags.shift();
380 | }
381 | if (typeof substring !== 'string') {
382 | middle = middle.copy();
383 | }
384 | _ref = middle.characters;
385 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
386 | c = _ref[_i];
387 | c.addTags.apply(c, inheritedTags);
388 | }
389 | }
390 | newString = head;
391 | _ref1 = middle.characters;
392 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
393 | c = _ref1[_j];
394 | newString.characters.push(c);
395 | }
396 | _ref2 = tail.characters;
397 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
398 | c = _ref2[_k];
399 | newString.characters.push(c);
400 | }
401 | return newString;
402 | };
403 |
404 | String.prototype.lastIndexOf = function(substring, from) {
405 | var c, characters, found, i, skip, _i, _j, _len, _len1;
406 | if (from == null) {
407 | from = 0;
408 | }
409 | if (from < 0) {
410 | from = 0;
411 | }
412 | characters = this.characters.slice(from).reverse();
413 | from = 0;
414 | if (typeof substring === 'string') {
415 | if (!this.contains(substring)) {
416 | return -1;
417 | }
418 | substring = substring.split('').reverse();
419 | while (from <= (characters.length - substring.length)) {
420 | found = true;
421 | skip = 0;
422 | for (i = _i = 0, _len = substring.length; _i < _len; i = ++_i) {
423 | c = substring[i];
424 | if (characters[i + from].isTag()) {
425 | skip += 1;
426 | }
427 | if (c !== characters[skip + i + from].c()) {
428 | found = false;
429 | break;
430 | }
431 | }
432 | if (found) {
433 | return from;
434 | }
435 | from++;
436 | }
437 | return -1;
438 | }
439 | substring = substring.characters.slice().reverse();
440 | while (from <= (characters.length - substring.length)) {
441 | found = true;
442 | for (i = _j = 0, _len1 = substring.length; _j < _len1; i = ++_j) {
443 | c = substring[i];
444 | if (!c.eq(characters[i + from])) {
445 | found = false;
446 | break;
447 | }
448 | }
449 | if (found) {
450 | return from;
451 | }
452 | from++;
453 | }
454 | return -1;
455 | };
456 |
457 | String.prototype.optimize = function() {
458 | var c, closingTag, closingTags, head, lastC, len, openHeads, openTag, openTags, runLength, runLengthSort, runLengths, run_length, t, tag, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _results;
459 | openTags = [];
460 | openHeads = [];
461 | lastC = null;
462 | _ref = this.characters.slice().reverse();
463 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
464 | c = _ref[_i];
465 | c._runLengthMap = {};
466 | c._runLengthMapSize = 0;
467 | closingTags = [];
468 | _ref1 = openTags.slice().reverse();
469 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
470 | openTag = _ref1[_j];
471 | closingTags.push(openTag);
472 | if (!c.hasTags(openTag)) {
473 | for (_k = 0, _len2 = closingTags.length; _k < _len2; _k++) {
474 | closingTag = closingTags[_k];
475 | openTags.pop();
476 | openHeads.pop();
477 | }
478 | closingTags = [];
479 | }
480 | }
481 | _ref2 = c._tags;
482 | for (_l = 0, _len3 = _ref2.length; _l < _len3; _l++) {
483 | tag = _ref2[_l];
484 | if (openHeads.indexOf(tag.head()) === -1) {
485 | if (!tag.selfClosing()) {
486 | openTags.push(tag);
487 | openHeads.push(tag.head());
488 | }
489 | }
490 | }
491 | for (_m = 0, _len4 = openTags.length; _m < _len4; _m++) {
492 | tag = openTags[_m];
493 | head = tag.head();
494 | if (!lastC) {
495 | c._runLengthMap[head] = [tag, 1];
496 | continue;
497 | }
498 | if (!c._runLengthMap[head]) {
499 | c._runLengthMap[head] = [tag, 0];
500 | }
501 | run_length = 0;
502 | if (lastC._runLengthMap[head]) {
503 | run_length = lastC._runLengthMap[head][1];
504 | }
505 | c._runLengthMap[head][1] = run_length + 1;
506 | }
507 | lastC = c;
508 | }
509 | runLengthSort = function(a, b) {
510 | return b[1] - a[1];
511 | };
512 | _ref3 = this.characters;
513 | _results = [];
514 | for (_n = 0, _len5 = _ref3.length; _n < _len5; _n++) {
515 | c = _ref3[_n];
516 | len = c._tags.length;
517 | if ((len > 0 && c._tags[0].selfClosing() && len < 3) || len < 2) {
518 | continue;
519 | }
520 | runLengths = [];
521 | _ref4 = c._runLengthMap;
522 | for (tag in _ref4) {
523 | runLength = _ref4[tag];
524 | runLengths.push(runLength);
525 | }
526 | runLengths.sort(runLengthSort);
527 | _ref5 = c._tags.slice();
528 | for (_o = 0, _len6 = _ref5.length; _o < _len6; _o++) {
529 | tag = _ref5[_o];
530 | if (!tag.selfClosing()) {
531 | c.removeTags(tag);
532 | }
533 | }
534 | _results.push(c.addTags.apply(c, (function() {
535 | var _len7, _p, _results1;
536 | _results1 = [];
537 | for (_p = 0, _len7 = runLengths.length; _p < _len7; _p++) {
538 | t = runLengths[_p];
539 | _results1.push(t[0]);
540 | }
541 | return _results1;
542 | })()));
543 | }
544 | return _results;
545 | };
546 |
547 | String.prototype.slice = function(from, to) {
548 | var c, newString;
549 | newString = new HTMLString.String('', this._preserveWhitespace);
550 | newString.characters = (function() {
551 | var _i, _len, _ref, _results;
552 | _ref = this.characters.slice(from, to);
553 | _results = [];
554 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
555 | c = _ref[_i];
556 | _results.push(c.copy());
557 | }
558 | return _results;
559 | }).call(this);
560 | return newString;
561 | };
562 |
563 | String.prototype.split = function(separator, limit) {
564 | var count, end, i, index, indexes, lastIndex, start, substrings, _i, _ref;
565 | if (separator == null) {
566 | separator = '';
567 | }
568 | if (limit == null) {
569 | limit = 0;
570 | }
571 | lastIndex = 0;
572 | count = 0;
573 | indexes = [0];
574 | while (true) {
575 | if (limit > 0 && count > limit) {
576 | break;
577 | }
578 | index = this.indexOf(separator, lastIndex);
579 | if (index === -1) {
580 | break;
581 | }
582 | indexes.push(index);
583 | lastIndex = index + 1;
584 | }
585 | indexes.push(this.length());
586 | substrings = [];
587 | for (i = _i = 0, _ref = indexes.length - 2; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
588 | start = indexes[i];
589 | if (i > 0) {
590 | start += 1;
591 | }
592 | end = indexes[i + 1];
593 | substrings.push(this.slice(start, end));
594 | }
595 | return substrings;
596 | };
597 |
598 | String.prototype.startsWith = function(substring) {
599 | var c, i, _i, _len, _ref;
600 | if (typeof substring === 'string') {
601 | return this.text().slice(0, substring.length) === substring;
602 | }
603 | _ref = substring.characters;
604 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
605 | c = _ref[i];
606 | if (!c.eq(this.characters[i])) {
607 | return false;
608 | }
609 | }
610 | return true;
611 | };
612 |
613 | String.prototype.substr = function(from, length) {
614 | if (length <= 0) {
615 | return new HTMLString.String('', this._preserveWhitespace);
616 | }
617 | if (from < 0) {
618 | from = this.length() + from;
619 | }
620 | if (length === void 0) {
621 | length = this.length() - from;
622 | }
623 | return this.slice(from, from + length);
624 | };
625 |
626 | String.prototype.substring = function(from, to) {
627 | if (to === void 0) {
628 | to = this.length();
629 | }
630 | return this.slice(from, to);
631 | };
632 |
633 | String.prototype.text = function() {
634 | var c, text, _i, _len, _ref;
635 | text = '';
636 | _ref = this.characters;
637 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
638 | c = _ref[_i];
639 | if (c.isTag()) {
640 | if (c.isTag('br')) {
641 | text += '\n';
642 | }
643 | continue;
644 | }
645 | if (c.c() === ' ') {
646 | text += c.c();
647 | continue;
648 | }
649 | text += c.c();
650 | }
651 | return this.constructor.decode(text);
652 | };
653 |
654 | String.prototype.toLowerCase = function() {
655 | var c, newString, _i, _len, _ref;
656 | newString = this.copy();
657 | _ref = newString.characters;
658 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
659 | c = _ref[_i];
660 | if (c._c.length === 1) {
661 | c._c = c._c.toLowerCase();
662 | }
663 | }
664 | return newString;
665 | };
666 |
667 | String.prototype.toUpperCase = function() {
668 | var c, newString, _i, _len, _ref;
669 | newString = this.copy();
670 | _ref = newString.characters;
671 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
672 | c = _ref[_i];
673 | if (c._c.length === 1) {
674 | c._c = c._c.toUpperCase();
675 | }
676 | }
677 | return newString;
678 | };
679 |
680 | String.prototype.trim = function() {
681 | var c, from, newString, to, _i, _j, _len, _len1, _ref, _ref1;
682 | _ref = this.characters;
683 | for (from = _i = 0, _len = _ref.length; _i < _len; from = ++_i) {
684 | c = _ref[from];
685 | if (!c.isWhitespace()) {
686 | break;
687 | }
688 | }
689 | _ref1 = this.characters.slice().reverse();
690 | for (to = _j = 0, _len1 = _ref1.length; _j < _len1; to = ++_j) {
691 | c = _ref1[to];
692 | if (!c.isWhitespace()) {
693 | break;
694 | }
695 | }
696 | to = this.length() - to - 1;
697 | newString = new HTMLString.String('', this._preserveWhitespace);
698 | newString.characters = (function() {
699 | var _k, _len2, _ref2, _results;
700 | _ref2 = this.characters.slice(from, +to + 1 || 9e9);
701 | _results = [];
702 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
703 | c = _ref2[_k];
704 | _results.push(c.copy());
705 | }
706 | return _results;
707 | }).call(this);
708 | return newString;
709 | };
710 |
711 | String.prototype.trimLeft = function() {
712 | var c, from, newString, to, _i, _len, _ref;
713 | to = this.length() - 1;
714 | _ref = this.characters;
715 | for (from = _i = 0, _len = _ref.length; _i < _len; from = ++_i) {
716 | c = _ref[from];
717 | if (!c.isWhitespace()) {
718 | break;
719 | }
720 | }
721 | newString = new HTMLString.String('', this._preserveWhitespace);
722 | newString.characters = (function() {
723 | var _j, _len1, _ref1, _results;
724 | _ref1 = this.characters.slice(from, +to + 1 || 9e9);
725 | _results = [];
726 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
727 | c = _ref1[_j];
728 | _results.push(c.copy());
729 | }
730 | return _results;
731 | }).call(this);
732 | return newString;
733 | };
734 |
735 | String.prototype.trimRight = function() {
736 | var c, from, newString, to, _i, _len, _ref;
737 | from = 0;
738 | _ref = this.characters.slice().reverse();
739 | for (to = _i = 0, _len = _ref.length; _i < _len; to = ++_i) {
740 | c = _ref[to];
741 | if (!c.isWhitespace()) {
742 | break;
743 | }
744 | }
745 | to = this.length() - to - 1;
746 | newString = new HTMLString.String('', this._preserveWhitespace);
747 | newString.characters = (function() {
748 | var _j, _len1, _ref1, _results;
749 | _ref1 = this.characters.slice(from, +to + 1 || 9e9);
750 | _results = [];
751 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
752 | c = _ref1[_j];
753 | _results.push(c.copy());
754 | }
755 | return _results;
756 | }).call(this);
757 | return newString;
758 | };
759 |
760 | String.prototype.unformat = function() {
761 | var c, from, i, newString, tags, to, _i;
762 | from = arguments[0], to = arguments[1], tags = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
763 | if (to < 0) {
764 | to = this.length() + to + 1;
765 | }
766 | if (from < 0) {
767 | from = this.length() + from;
768 | }
769 | newString = this.copy();
770 | for (i = _i = from; from <= to ? _i < to : _i > to; i = from <= to ? ++_i : --_i) {
771 | c = newString.characters[i];
772 | c.removeTags.apply(c, tags);
773 | }
774 | return newString;
775 | };
776 |
777 | String.prototype.copy = function() {
778 | var c, stringCopy;
779 | stringCopy = new HTMLString.String('', this._preserveWhitespace);
780 | stringCopy.characters = (function() {
781 | var _i, _len, _ref, _results;
782 | _ref = this.characters;
783 | _results = [];
784 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
785 | c = _ref[_i];
786 | _results.push(c.copy());
787 | }
788 | return _results;
789 | }).call(this);
790 | return stringCopy;
791 | };
792 |
793 | String.decode = function(string) {
794 | var textarea;
795 | textarea = document.createElement('textarea');
796 | textarea.innerHTML = string;
797 | return textarea.textContent;
798 | };
799 |
800 | String.encode = function(string) {
801 | var textarea;
802 | textarea = document.createElement('textarea');
803 | textarea.textContent = string;
804 | return textarea.innerHTML;
805 | };
806 |
807 | String.join = function(separator, strings) {
808 | var joined, s, _i, _len;
809 | joined = strings.shift();
810 | for (_i = 0, _len = strings.length; _i < _len; _i++) {
811 | s = strings[_i];
812 | joined = joined.concat(separator, s);
813 | }
814 | return joined;
815 | };
816 |
817 | return String;
818 |
819 | })();
820 |
821 | ALPHA_CHARS = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz-_$'.split('');
822 |
823 | ALPHA_NUMERIC_CHARS = ALPHA_CHARS.concat('1234567890'.split(''));
824 |
825 | ATTR_NAME_CHARS = ALPHA_NUMERIC_CHARS.concat([':']);
826 |
827 | ENTITY_CHARS = ALPHA_NUMERIC_CHARS.concat(['#']);
828 |
829 | TAG_NAME_CHARS = ALPHA_NUMERIC_CHARS.concat([':']);
830 |
831 | CHAR_OR_ENTITY_OR_TAG = 1;
832 |
833 | ENTITY = 2;
834 |
835 | OPENNING_OR_CLOSING_TAG = 3;
836 |
837 | OPENING_TAG = 4;
838 |
839 | CLOSING_TAG = 5;
840 |
841 | TAG_NAME_OPENING = 6;
842 |
843 | TAG_NAME_CLOSING = 7;
844 |
845 | TAG_OPENING_SELF_CLOSING = 8;
846 |
847 | TAG_NAME_MUST_CLOSE = 9;
848 |
849 | ATTR_OR_TAG_END = 10;
850 |
851 | ATTR_NAME = 11;
852 |
853 | ATTR_NAME_FIND_VALUE = 12;
854 |
855 | ATTR_DELIM = 13;
856 |
857 | ATTR_VALUE_SINGLE_DELIM = 14;
858 |
859 | ATTR_VALUE_DOUBLE_DELIM = 15;
860 |
861 | ATTR_VALUE_NO_DELIM = 16;
862 |
863 | ATTR_ENTITY_NO_DELIM = 17;
864 |
865 | ATTR_ENTITY_SINGLE_DELIM = 18;
866 |
867 | ATTR_ENTITY_DOUBLE_DELIM = 19;
868 |
869 | _Parser = (function() {
870 | function _Parser() {
871 | this.fsm = new FSM.Machine(this);
872 | this.fsm.setInitialState(CHAR_OR_ENTITY_OR_TAG);
873 | this.fsm.addTransitionAny(CHAR_OR_ENTITY_OR_TAG, null, function(c) {
874 | return this._pushChar(c);
875 | });
876 | this.fsm.addTransition('<', CHAR_OR_ENTITY_OR_TAG, OPENNING_OR_CLOSING_TAG);
877 | this.fsm.addTransition('&', CHAR_OR_ENTITY_OR_TAG, ENTITY);
878 | this.fsm.addTransition('END', CHAR_OR_ENTITY_OR_TAG, null);
879 | this.fsm.addTransitions(ENTITY_CHARS, ENTITY, null, function(c) {
880 | return this.entity += c;
881 | });
882 | this.fsm.addTransition(';', ENTITY, CHAR_OR_ENTITY_OR_TAG, function() {
883 | this._pushChar("&" + this.entity + ";");
884 | return this.entity = '';
885 | });
886 | this.fsm.addTransitionAny(ENTITY, CHAR_OR_ENTITY_OR_TAG, function(c) {
887 | var _i, _len, _ref;
888 | this._pushChar('&');
889 | _ref = this.entity.split('');
890 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
891 | c = _ref[_i];
892 | this._pushChar(c);
893 | }
894 | this.entity = '';
895 | return this._back();
896 | });
897 | this.fsm.addTransition('END', ENTITY, null, function() {
898 | var c, _i, _len, _ref;
899 | this._pushChar('&');
900 | _ref = this.entity.split('');
901 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
902 | c = _ref[_i];
903 | this._pushChar(c);
904 | }
905 | return this.entity = '';
906 | });
907 | this.fsm.addTransitions([' ', '\n'], OPENNING_OR_CLOSING_TAG);
908 | this.fsm.addTransitions(ALPHA_CHARS, OPENNING_OR_CLOSING_TAG, OPENING_TAG, function() {
909 | return this._back();
910 | });
911 | this.fsm.addTransition('/', OPENNING_OR_CLOSING_TAG, CLOSING_TAG);
912 | this.fsm.addTransitions([' ', '\n'], OPENING_TAG);
913 | this.fsm.addTransitions(ALPHA_CHARS, OPENING_TAG, TAG_NAME_OPENING, function() {
914 | return this._back();
915 | });
916 | this.fsm.addTransitions([' ', '\n'], CLOSING_TAG);
917 | this.fsm.addTransitions(ALPHA_CHARS, CLOSING_TAG, TAG_NAME_CLOSING, function() {
918 | return this._back();
919 | });
920 | this.fsm.addTransitions(TAG_NAME_CHARS, TAG_NAME_OPENING, null, function(c) {
921 | return this.tagName += c;
922 | });
923 | this.fsm.addTransitions([' ', '\n'], TAG_NAME_OPENING, ATTR_OR_TAG_END);
924 | this.fsm.addTransition('/', TAG_NAME_OPENING, TAG_OPENING_SELF_CLOSING, function() {
925 | return this.selfClosing = true;
926 | });
927 | this.fsm.addTransition('>', TAG_NAME_OPENING, CHAR_OR_ENTITY_OR_TAG, function() {
928 | return this._pushTag();
929 | });
930 | this.fsm.addTransitions([' ', '\n'], TAG_OPENING_SELF_CLOSING);
931 | this.fsm.addTransition('>', TAG_OPENING_SELF_CLOSING, CHAR_OR_ENTITY_OR_TAG, function() {
932 | return this._pushTag();
933 | });
934 | this.fsm.addTransitions([' ', '\n'], ATTR_OR_TAG_END);
935 | this.fsm.addTransition('/', ATTR_OR_TAG_END, TAG_OPENING_SELF_CLOSING, function() {
936 | return this.selfClosing = true;
937 | });
938 | this.fsm.addTransition('>', ATTR_OR_TAG_END, CHAR_OR_ENTITY_OR_TAG, function() {
939 | return this._pushTag();
940 | });
941 | this.fsm.addTransitions(ALPHA_CHARS, ATTR_OR_TAG_END, ATTR_NAME, function() {
942 | return this._back();
943 | });
944 | this.fsm.addTransitions(TAG_NAME_CHARS, TAG_NAME_CLOSING, null, function(c) {
945 | return this.tagName += c;
946 | });
947 | this.fsm.addTransitions([' ', '\n'], TAG_NAME_CLOSING, TAG_NAME_MUST_CLOSE);
948 | this.fsm.addTransition('>', TAG_NAME_CLOSING, CHAR_OR_ENTITY_OR_TAG, function() {
949 | return this._popTag();
950 | });
951 | this.fsm.addTransitions([' ', '\n'], TAG_NAME_MUST_CLOSE);
952 | this.fsm.addTransition('>', TAG_NAME_MUST_CLOSE, CHAR_OR_ENTITY_OR_TAG, function() {
953 | return this._popTag();
954 | });
955 | this.fsm.addTransitions(ATTR_NAME_CHARS, ATTR_NAME, null, function(c) {
956 | return this.attributeName += c;
957 | });
958 | this.fsm.addTransitions([' ', '\n'], ATTR_NAME, ATTR_NAME_FIND_VALUE);
959 | this.fsm.addTransition('=', ATTR_NAME, ATTR_DELIM);
960 | this.fsm.addTransitions([' ', '\n'], ATTR_NAME_FIND_VALUE);
961 | this.fsm.addTransition('=', ATTR_NAME_FIND_VALUE, ATTR_DELIM);
962 | this.fsm.addTransitions('>', ATTR_NAME, ATTR_OR_TAG_END, function() {
963 | this._pushAttribute();
964 | return this._back();
965 | });
966 | this.fsm.addTransitionAny(ATTR_NAME_FIND_VALUE, ATTR_OR_TAG_END, function() {
967 | this._pushAttribute();
968 | return this._back();
969 | });
970 | this.fsm.addTransitions([' ', '\n'], ATTR_DELIM);
971 | this.fsm.addTransition('\'', ATTR_DELIM, ATTR_VALUE_SINGLE_DELIM);
972 | this.fsm.addTransition('"', ATTR_DELIM, ATTR_VALUE_DOUBLE_DELIM);
973 | this.fsm.addTransitions(ALPHA_NUMERIC_CHARS.concat(['&'], ATTR_DELIM, ATTR_VALUE_NO_DELIM, function() {
974 | return this._back();
975 | }));
976 | this.fsm.addTransition(' ', ATTR_VALUE_NO_DELIM, ATTR_OR_TAG_END, function() {
977 | return this._pushAttribute();
978 | });
979 | this.fsm.addTransitions(['/', '>'], ATTR_VALUE_NO_DELIM, ATTR_OR_TAG_END, function() {
980 | this._back();
981 | return this._pushAttribute();
982 | });
983 | this.fsm.addTransition('&', ATTR_VALUE_NO_DELIM, ATTR_ENTITY_NO_DELIM);
984 | this.fsm.addTransitionAny(ATTR_VALUE_NO_DELIM, null, function(c) {
985 | return this.attributeValue += c;
986 | });
987 | this.fsm.addTransition('\'', ATTR_VALUE_SINGLE_DELIM, ATTR_OR_TAG_END, function() {
988 | return this._pushAttribute();
989 | });
990 | this.fsm.addTransition('&', ATTR_VALUE_SINGLE_DELIM, ATTR_ENTITY_SINGLE_DELIM);
991 | this.fsm.addTransitionAny(ATTR_VALUE_SINGLE_DELIM, null, function(c) {
992 | return this.attributeValue += c;
993 | });
994 | this.fsm.addTransition('"', ATTR_VALUE_DOUBLE_DELIM, ATTR_OR_TAG_END, function() {
995 | return this._pushAttribute();
996 | });
997 | this.fsm.addTransition('&', ATTR_VALUE_DOUBLE_DELIM, ATTR_ENTITY_DOUBLE_DELIM);
998 | this.fsm.addTransitionAny(ATTR_VALUE_DOUBLE_DELIM, null, function(c) {
999 | return this.attributeValue += c;
1000 | });
1001 | this.fsm.addTransitions(ENTITY_CHARS, ATTR_ENTITY_NO_DELIM, null, function(c) {
1002 | return this.entity += c;
1003 | });
1004 | this.fsm.addTransitions(ENTITY_CHARS, ATTR_ENTITY_SINGLE_DELIM, null, function(c) {
1005 | return this.entity += c;
1006 | });
1007 | this.fsm.addTransitions(ENTITY_CHARS, ATTR_ENTITY_DOUBLE_DELIM, null, function(c) {
1008 | return this.entity += c;
1009 | });
1010 | this.fsm.addTransition(';', ATTR_ENTITY_NO_DELIM, ATTR_VALUE_NO_DELIM, function() {
1011 | this.attributeValue += "&" + this.entity + ";";
1012 | return this.entity = '';
1013 | });
1014 | this.fsm.addTransition(';', ATTR_ENTITY_SINGLE_DELIM, ATTR_VALUE_SINGLE_DELIM, function() {
1015 | this.attributeValue += "&" + this.entity + ";";
1016 | return this.entity = '';
1017 | });
1018 | this.fsm.addTransition(';', ATTR_ENTITY_DOUBLE_DELIM, ATTR_VALUE_DOUBLE_DELIM, function() {
1019 | this.attributeValue += "&" + this.entity + ";";
1020 | return this.entity = '';
1021 | });
1022 | this.fsm.addTransitionAny(ATTR_ENTITY_NO_DELIM, ATTR_VALUE_NO_DELIM, function(c) {
1023 | this.attributeValue += '&' + this.entity;
1024 | this.entity = '';
1025 | return this._back();
1026 | });
1027 | this.fsm.addTransitionAny(ATTR_ENTITY_SINGLE_DELIM, ATTR_VALUE_SINGLE_DELIM, function(c) {
1028 | this.attributeValue += '&' + this.entity;
1029 | this.entity = '';
1030 | return this._back();
1031 | });
1032 | this.fsm.addTransitionAny(ATTR_ENTITY_DOUBLE_DELIM, ATTR_VALUE_DOUBLE_DELIM, function(c) {
1033 | this.attributeValue += '&' + this.entity;
1034 | this.entity = '';
1035 | return this._back();
1036 | });
1037 | }
1038 |
1039 | _Parser.prototype._back = function() {
1040 | return this.head--;
1041 | };
1042 |
1043 | _Parser.prototype._pushAttribute = function() {
1044 | this.attributes[this.attributeName] = this.attributeValue;
1045 | this.attributeName = '';
1046 | return this.attributeValue = '';
1047 | };
1048 |
1049 | _Parser.prototype._pushChar = function(c) {
1050 | var character, lastCharacter;
1051 | character = new HTMLString.Character(c, this.tags);
1052 | if (this._preserveWhitespace) {
1053 | this.string.characters.push(character);
1054 | return;
1055 | }
1056 | if (this.string.length() && !character.isTag() && !character.isEntity() && character.isWhitespace()) {
1057 | lastCharacter = this.string.characters[this.string.length() - 1];
1058 | if (lastCharacter.isWhitespace() && !lastCharacter.isTag() && !lastCharacter.isEntity()) {
1059 | return;
1060 | }
1061 | }
1062 | return this.string.characters.push(character);
1063 | };
1064 |
1065 | _Parser.prototype._pushTag = function() {
1066 | var tag, _ref;
1067 | tag = new HTMLString.Tag(this.tagName, this.attributes);
1068 | this.tags.push(tag);
1069 | if (tag.selfClosing()) {
1070 | this._pushChar('');
1071 | this.tags.pop();
1072 | if (!this.selfClosed && (_ref = this.tagName, __indexOf.call(HTMLString.Tag.SELF_CLOSING, _ref) >= 0)) {
1073 | this.fsm.reset();
1074 | }
1075 | }
1076 | this.tagName = '';
1077 | this.selfClosed = false;
1078 | return this.attributes = {};
1079 | };
1080 |
1081 | _Parser.prototype._popTag = function() {
1082 | var character, tag;
1083 | while (true) {
1084 | tag = this.tags.pop();
1085 | if (this.string.length()) {
1086 | character = this.string.characters[this.string.length() - 1];
1087 | if (!character.isTag() && !character.isEntity() && character.isWhitespace()) {
1088 | character.removeTags(tag);
1089 | }
1090 | }
1091 | if (tag.name() === this.tagName.toLowerCase()) {
1092 | break;
1093 | }
1094 | }
1095 | return this.tagName = '';
1096 | };
1097 |
1098 | _Parser.prototype.parse = function(html, preserveWhitespace) {
1099 | var character, error;
1100 | this._preserveWhitespace = preserveWhitespace;
1101 | this.reset();
1102 | html = this.preprocess(html);
1103 | this.fsm.parser = this;
1104 | while (this.head < html.length) {
1105 | character = html[this.head];
1106 | try {
1107 | this.fsm.process(character);
1108 | } catch (_error) {
1109 | error = _error;
1110 | throw new Error("Error at char " + this.head + " >> " + error);
1111 | }
1112 | this.head++;
1113 | }
1114 | this.fsm.process('END');
1115 | return this.string;
1116 | };
1117 |
1118 | _Parser.prototype.preprocess = function(html) {
1119 | html = html.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
1120 | html = html.replace(//g, '');
1121 | if (!this._preserveWhitespace) {
1122 | html = html.replace(/\s+/g, ' ');
1123 | }
1124 | return html;
1125 | };
1126 |
1127 | _Parser.prototype.reset = function() {
1128 | this.fsm.reset();
1129 | this.head = 0;
1130 | this.string = new HTMLString.String();
1131 | this.entity = '';
1132 | this.tags = [];
1133 | this.tagName = '';
1134 | this.selfClosing = false;
1135 | this.attributes = {};
1136 | this.attributeName = '';
1137 | return this.attributeValue = '';
1138 | };
1139 |
1140 | return _Parser;
1141 |
1142 | })();
1143 |
1144 | HTMLString.Tag = (function() {
1145 | function Tag(name, attributes) {
1146 | var k, v;
1147 | this._name = name.toLowerCase();
1148 | this._selfClosing = HTMLString.Tag.SELF_CLOSING[this._name] === true;
1149 | this._head = null;
1150 | this._attributes = {};
1151 | for (k in attributes) {
1152 | v = attributes[k];
1153 | this._attributes[k] = v;
1154 | }
1155 | }
1156 |
1157 | Tag.SELF_CLOSING = {
1158 | 'area': true,
1159 | 'base': true,
1160 | 'br': true,
1161 | 'hr': true,
1162 | 'img': true,
1163 | 'input': true,
1164 | 'link meta': true,
1165 | 'wbr': true
1166 | };
1167 |
1168 | Tag.prototype.head = function() {
1169 | var components, k, v, _ref;
1170 | if (!this._head) {
1171 | components = [];
1172 | _ref = this._attributes;
1173 | for (k in _ref) {
1174 | v = _ref[k];
1175 | if (v) {
1176 | components.push("" + k + "=\"" + v + "\"");
1177 | } else {
1178 | components.push("" + k);
1179 | }
1180 | }
1181 | components.sort();
1182 | components.unshift(this._name);
1183 | this._head = "<" + (components.join(' ')) + ">";
1184 | }
1185 | return this._head;
1186 | };
1187 |
1188 | Tag.prototype.name = function() {
1189 | return this._name;
1190 | };
1191 |
1192 | Tag.prototype.selfClosing = function() {
1193 | return this._selfClosing;
1194 | };
1195 |
1196 | Tag.prototype.tail = function() {
1197 | if (this._selfClosing) {
1198 | return '';
1199 | }
1200 | return "" + this._name + ">";
1201 | };
1202 |
1203 | Tag.prototype.attr = function(name, value) {
1204 | if (value === void 0) {
1205 | return this._attributes[name];
1206 | }
1207 | this._attributes[name] = value;
1208 | return this._head = null;
1209 | };
1210 |
1211 | Tag.prototype.removeAttr = function(name) {
1212 | if (this._attributes[name] === void 0) {
1213 | return;
1214 | }
1215 | delete this._attributes[name];
1216 | return this._head = null;
1217 | };
1218 |
1219 | Tag.prototype.copy = function() {
1220 | return new HTMLString.Tag(this._name, this._attributes);
1221 | };
1222 |
1223 | return Tag;
1224 |
1225 | })();
1226 |
1227 | HTMLString.Character = (function() {
1228 | function Character(c, tags) {
1229 | this._c = c;
1230 | if (c.length > 1) {
1231 | this._c = c.toLowerCase();
1232 | }
1233 | this._tags = [];
1234 | this.addTags.apply(this, tags);
1235 | }
1236 |
1237 | Character.prototype.c = function() {
1238 | return this._c;
1239 | };
1240 |
1241 | Character.prototype.isEntity = function() {
1242 | return this._c.length > 1;
1243 | };
1244 |
1245 | Character.prototype.isTag = function(tagName) {
1246 | if (this._tags.length === 0 || !this._tags[0].selfClosing()) {
1247 | return false;
1248 | }
1249 | if (tagName && this._tags[0].name() !== tagName) {
1250 | return false;
1251 | }
1252 | return true;
1253 | };
1254 |
1255 | Character.prototype.isWhitespace = function() {
1256 | var _ref;
1257 | return ((_ref = this._c) === ' ' || _ref === '\n' || _ref === ' ') || this.isTag('br');
1258 | };
1259 |
1260 | Character.prototype.tags = function() {
1261 | var t;
1262 | return (function() {
1263 | var _i, _len, _ref, _results;
1264 | _ref = this._tags;
1265 | _results = [];
1266 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1267 | t = _ref[_i];
1268 | _results.push(t.copy());
1269 | }
1270 | return _results;
1271 | }).call(this);
1272 | };
1273 |
1274 | Character.prototype.addTags = function() {
1275 | var tag, tags, _i, _len, _results;
1276 | tags = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1277 | _results = [];
1278 | for (_i = 0, _len = tags.length; _i < _len; _i++) {
1279 | tag = tags[_i];
1280 | if (Array.isArray(tag)) {
1281 | continue;
1282 | }
1283 | if (tag.selfClosing()) {
1284 | if (!this.isTag()) {
1285 | this._tags.unshift(tag.copy());
1286 | }
1287 | continue;
1288 | }
1289 | _results.push(this._tags.push(tag.copy()));
1290 | }
1291 | return _results;
1292 | };
1293 |
1294 | Character.prototype.eq = function(c) {
1295 | var tag, tags, _i, _j, _len, _len1, _ref, _ref1;
1296 | if (this.c() !== c.c()) {
1297 | return false;
1298 | }
1299 | if (this._tags.length !== c._tags.length) {
1300 | return false;
1301 | }
1302 | tags = {};
1303 | _ref = this._tags;
1304 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1305 | tag = _ref[_i];
1306 | tags[tag.head()] = true;
1307 | }
1308 | _ref1 = c._tags;
1309 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
1310 | tag = _ref1[_j];
1311 | if (!tags[tag.head()]) {
1312 | return false;
1313 | }
1314 | }
1315 | return true;
1316 | };
1317 |
1318 | Character.prototype.hasTags = function() {
1319 | var tag, tagHeads, tagNames, tags, _i, _j, _len, _len1, _ref;
1320 | tags = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1321 | tagNames = {};
1322 | tagHeads = {};
1323 | _ref = this._tags;
1324 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1325 | tag = _ref[_i];
1326 | tagNames[tag.name()] = true;
1327 | tagHeads[tag.head()] = true;
1328 | }
1329 | for (_j = 0, _len1 = tags.length; _j < _len1; _j++) {
1330 | tag = tags[_j];
1331 | if (typeof tag === 'string') {
1332 | if (tagNames[tag] === void 0) {
1333 | return false;
1334 | }
1335 | } else {
1336 | if (tagHeads[tag.head()] === void 0) {
1337 | return false;
1338 | }
1339 | }
1340 | }
1341 | return true;
1342 | };
1343 |
1344 | Character.prototype.removeTags = function() {
1345 | var heads, names, newTags, tag, tags, _i, _len;
1346 | tags = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1347 | if (tags.length === 0) {
1348 | this._tags = [];
1349 | return;
1350 | }
1351 | names = {};
1352 | heads = {};
1353 | for (_i = 0, _len = tags.length; _i < _len; _i++) {
1354 | tag = tags[_i];
1355 | if (typeof tag === 'string') {
1356 | names[tag] = tag;
1357 | } else {
1358 | heads[tag.head()] = tag;
1359 | }
1360 | }
1361 | newTags = [];
1362 | return this._tags = this._tags.filter(function(tag) {
1363 | if (!heads[tag.head()] && !names[tag.name()]) {
1364 | return tag;
1365 | }
1366 | });
1367 | };
1368 |
1369 | Character.prototype.copy = function() {
1370 | var t;
1371 | return new HTMLString.Character(this._c, (function() {
1372 | var _i, _len, _ref, _results;
1373 | _ref = this._tags;
1374 | _results = [];
1375 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1376 | t = _ref[_i];
1377 | _results.push(t.copy());
1378 | }
1379 | return _results;
1380 | }).call(this));
1381 | };
1382 |
1383 | return Character;
1384 |
1385 | })();
1386 |
1387 | }).call(this);
1388 |
--------------------------------------------------------------------------------
/build/html-string.min.js:
--------------------------------------------------------------------------------
1 | /*! HTMLString v1.0.6 by Anthony Blackshaw (https://github.com/anthonyjb) */
2 | (function(){var FSM,exports;FSM={},FSM.Machine=function(){function Machine(context){this.context=context,this._stateTransitions={},this._stateTransitionsAny={},this._defaultTransition=null,this._initialState=null,this._currentState=null}return Machine.prototype.addTransition=function(action,state,nextState,callback){return nextState||(nextState=state),this._stateTransitions[[action,state]]=[nextState,callback]},Machine.prototype.addTransitions=function(actions,state,nextState,callback){var action,_i,_len,_results;for(nextState||(nextState=state),_results=[],_i=0,_len=actions.length;_len>_i;_i++)action=actions[_i],_results.push(this.addTransition(action,state,nextState,callback));return _results},Machine.prototype.addTransitionAny=function(state,nextState,callback){return nextState||(nextState=state),this._stateTransitionsAny[state]=[nextState,callback]},Machine.prototype.setDefaultTransition=function(state,callback){return this._defaultTransition=[state,callback]},Machine.prototype.getTransition=function(action,state){if(this._stateTransitions[[action,state]])return this._stateTransitions[[action,state]];if(this._stateTransitionsAny[state])return this._stateTransitionsAny[state];if(this._defaultTransition)return this._defaultTransition;throw new Error("Transition is undefined: ("+action+", "+state+")")},Machine.prototype.getCurrentState=function(){return this._currentState},Machine.prototype.setInitialState=function(state){return this._initialState=state,this._currentState?void 0:this.reset()},Machine.prototype.reset=function(){return this._currentState=this._initialState},Machine.prototype.process=function(action){var result;return result=this.getTransition(action,this._currentState),result[1]&&result[1].call(this.context||(this.context=this),action),this._currentState=result[0]},Machine}(),"undefined"!=typeof window&&(window.FSM=FSM),"undefined"!=typeof module&&module.exports&&(exports=module.exports=FSM)}).call(this),function(){var ALPHA_CHARS,ALPHA_NUMERIC_CHARS,ATTR_DELIM,ATTR_ENTITY_DOUBLE_DELIM,ATTR_ENTITY_NO_DELIM,ATTR_ENTITY_SINGLE_DELIM,ATTR_NAME,ATTR_NAME_CHARS,ATTR_NAME_FIND_VALUE,ATTR_OR_TAG_END,ATTR_VALUE_DOUBLE_DELIM,ATTR_VALUE_NO_DELIM,ATTR_VALUE_SINGLE_DELIM,CHAR_OR_ENTITY_OR_TAG,CLOSING_TAG,ENTITY,ENTITY_CHARS,HTMLString,OPENING_TAG,OPENNING_OR_CLOSING_TAG,TAG_NAME_CHARS,TAG_NAME_CLOSING,TAG_NAME_MUST_CLOSE,TAG_NAME_OPENING,TAG_OPENING_SELF_CLOSING,exports,_Parser,__slice=[].slice,__indexOf=[].indexOf||function(item){for(var i=0,l=this.length;l>i;i++)if(i in this&&this[i]===item)return i;return-1};HTMLString={},"undefined"!=typeof window&&(window.HTMLString=HTMLString),"undefined"!=typeof module&&module.exports&&(exports=module.exports=HTMLString),HTMLString.String=function(){function String(html,preserveWhitespace){null==preserveWhitespace&&(preserveWhitespace=!1),this._preserveWhitespace=preserveWhitespace,html?(null===HTMLString.String._parser&&(HTMLString.String._parser=new _Parser),this.characters=HTMLString.String._parser.parse(html,this._preserveWhitespace).characters):this.characters=[]}return String._parser=null,String.prototype.isWhitespace=function(){var c,_i,_len,_ref;for(_ref=this.characters,_i=0,_len=_ref.length;_len>_i;_i++)if(c=_ref[_i],!c.isWhitespace())return!1;return!0},String.prototype.length=function(){return this.characters.length},String.prototype.preserveWhitespace=function(){return this._preserveWhitespace},String.prototype.capitalize=function(){var c,newString;return newString=this.copy(),newString.length()&&(c=newString.characters[0]._c.toUpperCase(),newString.characters[0]._c=c),newString},String.prototype.charAt=function(index){return this.characters[index].copy()},String.prototype.concat=function(){var c,indexChar,inheritFormat,inheritedTags,newString,string,strings,tail,_i,_j,_k,_l,_len,_len1,_len2,_ref,_ref1;for(strings=2<=arguments.length?__slice.call(arguments,0,_i=arguments.length-1):(_i=0,[]),inheritFormat=arguments[_i++],"undefined"!=typeof inheritFormat&&"boolean"!=typeof inheritFormat&&(strings.push(inheritFormat),inheritFormat=!0),newString=this.copy(),_j=0,_len=strings.length;_len>_j;_j++)if(string=strings[_j],0!==string.length){if(tail=string,"string"==typeof string&&(tail=new HTMLString.String(string,this._preserveWhitespace)),inheritFormat&&newString.length())for(indexChar=newString.charAt(newString.length()-1),inheritedTags=indexChar.tags(),indexChar.isTag()&&inheritedTags.shift(),"string"!=typeof string&&(tail=tail.copy()),_ref=tail.characters,_k=0,_len1=_ref.length;_len1>_k;_k++)c=_ref[_k],c.addTags.apply(c,inheritedTags);for(_ref1=tail.characters,_l=0,_len2=_ref1.length;_len2>_l;_l++)c=_ref1[_l],newString.characters.push(c)}return newString},String.prototype.contains=function(substring){var c,found,from,i,_i,_len,_ref;if("string"==typeof substring)return this.text().indexOf(substring)>-1;for(from=0;from<=this.length()-substring.length();){for(found=!0,_ref=substring.characters,i=_i=0,_len=_ref.length;_len>_i;i=++_i)if(c=_ref[i],!c.eq(this.characters[i+from])){found=!1;break}if(found)return!0;from++}return!1},String.prototype.endsWith=function(substring){var c,characters,i,_i,_len,_ref;if("string"==typeof substring)return""===substring||this.text().slice(-substring.length)===substring;for(characters=this.characters.slice().reverse(),_ref=substring.characters.slice().reverse(),i=_i=0,_len=_ref.length;_len>_i;i=++_i)if(c=_ref[i],!c.eq(characters[i]))return!1;return!0},String.prototype.format=function(){var c,from,i,newString,tags,to,_i;for(from=arguments[0],to=arguments[1],tags=3<=arguments.length?__slice.call(arguments,2):[],0>to&&(to=this.length()+to+1),0>from&&(from=this.length()+from),newString=this.copy(),i=_i=from;to>=from?to>_i:_i>to;i=to>=from?++_i:--_i)c=newString.characters[i],c.addTags.apply(c,tags);return newString},String.prototype.hasTags=function(){var c,found,strict,tags,_i,_j,_len,_ref;for(tags=2<=arguments.length?__slice.call(arguments,0,_i=arguments.length-1):(_i=0,[]),strict=arguments[_i++],"undefined"!=typeof strict&&"boolean"!=typeof strict&&(tags.push(strict),strict=!1),found=!1,_ref=this.characters,_j=0,_len=_ref.length;_len>_j;_j++)if(c=_ref[_j],c.hasTags.apply(c,tags))found=!0;else if(strict)return!1;return found},String.prototype.html=function(){var c,closingTag,closingTags,head,html,openHeads,openTag,openTags,tag,_i,_j,_k,_l,_len,_len1,_len2,_len3,_len4,_m,_ref,_ref1,_ref2,_ref3;for(html="",openTags=[],openHeads=[],closingTags=[],_ref=this.characters,_i=0,_len=_ref.length;_len>_i;_i++){for(c=_ref[_i],closingTags=[],_ref1=openTags.slice().reverse(),_j=0,_len1=_ref1.length;_len1>_j;_j++)if(openTag=_ref1[_j],closingTags.push(openTag),!c.hasTags(openTag)){for(_k=0,_len2=closingTags.length;_len2>_k;_k++)closingTag=closingTags[_k],html+=closingTag.tail(),openTags.pop(),openHeads.pop();closingTags=[]}for(_ref2=c._tags,_l=0,_len3=_ref2.length;_len3>_l;_l++)tag=_ref2[_l],-1===openHeads.indexOf(tag.head())&&(tag.selfClosing()||(head=tag.head(),html+=head,openTags.push(tag),openHeads.push(head)));c._tags.length>0&&c._tags[0].selfClosing()&&(html+=c._tags[0].head()),html+=c.c()}for(_ref3=openTags.reverse(),_m=0,_len4=_ref3.length;_len4>_m;_m++)tag=_ref3[_m],html+=tag.tail();return html},String.prototype.indexOf=function(substring,from){var c,found,i,_i,_len,_ref;if(null==from&&(from=0),0>from&&(from=0),"string"==typeof substring)return this.text().indexOf(substring,from);for(;from<=this.length()-substring.length();){for(found=!0,_ref=substring.characters,i=_i=0,_len=_ref.length;_len>_i;i=++_i)if(c=_ref[i],!c.eq(this.characters[i+from])){found=!1;break}if(found)return from;from++}return-1},String.prototype.insert=function(index,substring,inheritFormat){var c,head,indexChar,inheritedTags,middle,newString,tail,_i,_j,_k,_len,_len1,_len2,_ref,_ref1,_ref2;if(null==inheritFormat&&(inheritFormat=!0),head=this.slice(0,index),tail=this.slice(index),0>index&&(index=this.length()+index),middle=substring,"string"==typeof substring&&(middle=new HTMLString.String(substring,this._preserveWhitespace)),inheritFormat&&index>0)for(indexChar=this.charAt(index-1),inheritedTags=indexChar.tags(),indexChar.isTag()&&inheritedTags.shift(),"string"!=typeof substring&&(middle=middle.copy()),_ref=middle.characters,_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],c.addTags.apply(c,inheritedTags);for(newString=head,_ref1=middle.characters,_j=0,_len1=_ref1.length;_len1>_j;_j++)c=_ref1[_j],newString.characters.push(c);for(_ref2=tail.characters,_k=0,_len2=_ref2.length;_len2>_k;_k++)c=_ref2[_k],newString.characters.push(c);return newString},String.prototype.lastIndexOf=function(substring,from){var c,characters,found,i,skip,_i,_j,_len,_len1;if(null==from&&(from=0),0>from&&(from=0),characters=this.characters.slice(from).reverse(),from=0,"string"==typeof substring){if(!this.contains(substring))return-1;for(substring=substring.split("").reverse();from<=characters.length-substring.length;){for(found=!0,skip=0,i=_i=0,_len=substring.length;_len>_i;i=++_i)if(c=substring[i],characters[i+from].isTag()&&(skip+=1),c!==characters[skip+i+from].c()){found=!1;break}if(found)return from;from++}return-1}for(substring=substring.characters.slice().reverse();from<=characters.length-substring.length;){for(found=!0,i=_j=0,_len1=substring.length;_len1>_j;i=++_j)if(c=substring[i],!c.eq(characters[i+from])){found=!1;break}if(found)return from;from++}return-1},String.prototype.optimize=function(){var c,closingTag,closingTags,head,lastC,len,openHeads,openTag,openTags,runLength,runLengthSort,runLengths,run_length,t,tag,_i,_j,_k,_l,_len,_len1,_len2,_len3,_len4,_len5,_len6,_m,_n,_o,_ref,_ref1,_ref2,_ref3,_ref4,_ref5,_results;for(openTags=[],openHeads=[],lastC=null,_ref=this.characters.slice().reverse(),_i=0,_len=_ref.length;_len>_i;_i++){for(c=_ref[_i],c._runLengthMap={},c._runLengthMapSize=0,closingTags=[],_ref1=openTags.slice().reverse(),_j=0,_len1=_ref1.length;_len1>_j;_j++)if(openTag=_ref1[_j],closingTags.push(openTag),!c.hasTags(openTag)){for(_k=0,_len2=closingTags.length;_len2>_k;_k++)closingTag=closingTags[_k],openTags.pop(),openHeads.pop();closingTags=[]}for(_ref2=c._tags,_l=0,_len3=_ref2.length;_len3>_l;_l++)tag=_ref2[_l],-1===openHeads.indexOf(tag.head())&&(tag.selfClosing()||(openTags.push(tag),openHeads.push(tag.head())));for(_m=0,_len4=openTags.length;_len4>_m;_m++)tag=openTags[_m],head=tag.head(),lastC?(c._runLengthMap[head]||(c._runLengthMap[head]=[tag,0]),run_length=0,lastC._runLengthMap[head]&&(run_length=lastC._runLengthMap[head][1]),c._runLengthMap[head][1]=run_length+1):c._runLengthMap[head]=[tag,1];lastC=c}for(runLengthSort=function(a,b){return b[1]-a[1]},_ref3=this.characters,_results=[],_n=0,_len5=_ref3.length;_len5>_n;_n++)if(c=_ref3[_n],len=c._tags.length,!(len>0&&c._tags[0].selfClosing()&&3>len||2>len)){runLengths=[],_ref4=c._runLengthMap;for(tag in _ref4)runLength=_ref4[tag],runLengths.push(runLength);for(runLengths.sort(runLengthSort),_ref5=c._tags.slice(),_o=0,_len6=_ref5.length;_len6>_o;_o++)tag=_ref5[_o],tag.selfClosing()||c.removeTags(tag);_results.push(c.addTags.apply(c,function(){var _len7,_p,_results1;for(_results1=[],_p=0,_len7=runLengths.length;_len7>_p;_p++)t=runLengths[_p],_results1.push(t[0]);return _results1}()))}return _results},String.prototype.slice=function(from,to){var c,newString;return newString=new HTMLString.String("",this._preserveWhitespace),newString.characters=function(){var _i,_len,_ref,_results;for(_ref=this.characters.slice(from,to),_results=[],_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],_results.push(c.copy());return _results}.call(this),newString},String.prototype.split=function(separator,limit){var count,end,i,index,indexes,lastIndex,start,substrings,_i,_ref;for(null==separator&&(separator=""),null==limit&&(limit=0),lastIndex=0,count=0,indexes=[0];;){if(limit>0&&count>limit)break;if(index=this.indexOf(separator,lastIndex),-1===index)break;indexes.push(index),lastIndex=index+1}for(indexes.push(this.length()),substrings=[],i=_i=0,_ref=indexes.length-2;_ref>=0?_ref>=_i:_i>=_ref;i=_ref>=0?++_i:--_i)start=indexes[i],i>0&&(start+=1),end=indexes[i+1],substrings.push(this.slice(start,end));return substrings},String.prototype.startsWith=function(substring){var c,i,_i,_len,_ref;if("string"==typeof substring)return this.text().slice(0,substring.length)===substring;for(_ref=substring.characters,i=_i=0,_len=_ref.length;_len>_i;i=++_i)if(c=_ref[i],!c.eq(this.characters[i]))return!1;return!0},String.prototype.substr=function(from,length){return 0>=length?new HTMLString.String("",this._preserveWhitespace):(0>from&&(from=this.length()+from),void 0===length&&(length=this.length()-from),this.slice(from,from+length))},String.prototype.substring=function(from,to){return void 0===to&&(to=this.length()),this.slice(from,to)},String.prototype.text=function(){var c,text,_i,_len,_ref;for(text="",_ref=this.characters,_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],c.isTag()?c.isTag("br")&&(text+="\n"):text+=(" "!==c.c(),c.c());return this.constructor.decode(text)},String.prototype.toLowerCase=function(){var c,newString,_i,_len,_ref;for(newString=this.copy(),_ref=newString.characters,_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],1===c._c.length&&(c._c=c._c.toLowerCase());return newString},String.prototype.toUpperCase=function(){var c,newString,_i,_len,_ref;for(newString=this.copy(),_ref=newString.characters,_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],1===c._c.length&&(c._c=c._c.toUpperCase());return newString},String.prototype.trim=function(){var c,from,newString,to,_i,_j,_len,_len1,_ref,_ref1;for(_ref=this.characters,from=_i=0,_len=_ref.length;_len>_i&&(c=_ref[from],c.isWhitespace());from=++_i);for(_ref1=this.characters.slice().reverse(),to=_j=0,_len1=_ref1.length;_len1>_j&&(c=_ref1[to],c.isWhitespace());to=++_j);return to=this.length()-to-1,newString=new HTMLString.String("",this._preserveWhitespace),newString.characters=function(){var _k,_len2,_ref2,_results;for(_ref2=this.characters.slice(from,+to+1||9e9),_results=[],_k=0,_len2=_ref2.length;_len2>_k;_k++)c=_ref2[_k],_results.push(c.copy());return _results}.call(this),newString},String.prototype.trimLeft=function(){var c,from,newString,to,_i,_len,_ref;for(to=this.length()-1,_ref=this.characters,from=_i=0,_len=_ref.length;_len>_i&&(c=_ref[from],c.isWhitespace());from=++_i);return newString=new HTMLString.String("",this._preserveWhitespace),newString.characters=function(){var _j,_len1,_ref1,_results;for(_ref1=this.characters.slice(from,+to+1||9e9),_results=[],_j=0,_len1=_ref1.length;_len1>_j;_j++)c=_ref1[_j],_results.push(c.copy());return _results}.call(this),newString},String.prototype.trimRight=function(){var c,from,newString,to,_i,_len,_ref;for(from=0,_ref=this.characters.slice().reverse(),to=_i=0,_len=_ref.length;_len>_i&&(c=_ref[to],c.isWhitespace());to=++_i);return to=this.length()-to-1,newString=new HTMLString.String("",this._preserveWhitespace),newString.characters=function(){var _j,_len1,_ref1,_results;for(_ref1=this.characters.slice(from,+to+1||9e9),_results=[],_j=0,_len1=_ref1.length;_len1>_j;_j++)c=_ref1[_j],_results.push(c.copy());return _results}.call(this),newString},String.prototype.unformat=function(){var c,from,i,newString,tags,to,_i;for(from=arguments[0],to=arguments[1],tags=3<=arguments.length?__slice.call(arguments,2):[],0>to&&(to=this.length()+to+1),0>from&&(from=this.length()+from),newString=this.copy(),i=_i=from;to>=from?to>_i:_i>to;i=to>=from?++_i:--_i)c=newString.characters[i],c.removeTags.apply(c,tags);return newString},String.prototype.copy=function(){var c,stringCopy;return stringCopy=new HTMLString.String("",this._preserveWhitespace),stringCopy.characters=function(){var _i,_len,_ref,_results;for(_ref=this.characters,_results=[],_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],_results.push(c.copy());return _results}.call(this),stringCopy},String.decode=function(string){var textarea;return textarea=document.createElement("textarea"),textarea.innerHTML=string,textarea.textContent},String.encode=function(string){var textarea;return textarea=document.createElement("textarea"),textarea.textContent=string,textarea.innerHTML},String.join=function(separator,strings){var joined,s,_i,_len;for(joined=strings.shift(),_i=0,_len=strings.length;_len>_i;_i++)s=strings[_i],joined=joined.concat(separator,s);return joined},String}(),ALPHA_CHARS="AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz-_$".split(""),ALPHA_NUMERIC_CHARS=ALPHA_CHARS.concat("1234567890".split("")),ATTR_NAME_CHARS=ALPHA_NUMERIC_CHARS.concat([":"]),ENTITY_CHARS=ALPHA_NUMERIC_CHARS.concat(["#"]),TAG_NAME_CHARS=ALPHA_NUMERIC_CHARS.concat([":"]),CHAR_OR_ENTITY_OR_TAG=1,ENTITY=2,OPENNING_OR_CLOSING_TAG=3,OPENING_TAG=4,CLOSING_TAG=5,TAG_NAME_OPENING=6,TAG_NAME_CLOSING=7,TAG_OPENING_SELF_CLOSING=8,TAG_NAME_MUST_CLOSE=9,ATTR_OR_TAG_END=10,ATTR_NAME=11,ATTR_NAME_FIND_VALUE=12,ATTR_DELIM=13,ATTR_VALUE_SINGLE_DELIM=14,ATTR_VALUE_DOUBLE_DELIM=15,ATTR_VALUE_NO_DELIM=16,ATTR_ENTITY_NO_DELIM=17,ATTR_ENTITY_SINGLE_DELIM=18,ATTR_ENTITY_DOUBLE_DELIM=19,_Parser=function(){function _Parser(){this.fsm=new FSM.Machine(this),this.fsm.setInitialState(CHAR_OR_ENTITY_OR_TAG),this.fsm.addTransitionAny(CHAR_OR_ENTITY_OR_TAG,null,function(c){return this._pushChar(c)}),this.fsm.addTransition("<",CHAR_OR_ENTITY_OR_TAG,OPENNING_OR_CLOSING_TAG),this.fsm.addTransition("&",CHAR_OR_ENTITY_OR_TAG,ENTITY),this.fsm.addTransition("END",CHAR_OR_ENTITY_OR_TAG,null),this.fsm.addTransitions(ENTITY_CHARS,ENTITY,null,function(c){return this.entity+=c}),this.fsm.addTransition(";",ENTITY,CHAR_OR_ENTITY_OR_TAG,function(){return this._pushChar("&"+this.entity+";"),this.entity=""}),this.fsm.addTransitionAny(ENTITY,CHAR_OR_ENTITY_OR_TAG,function(c){var _i,_len,_ref;for(this._pushChar("&"),_ref=this.entity.split(""),_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],this._pushChar(c);return this.entity="",this._back()}),this.fsm.addTransition("END",ENTITY,null,function(){var c,_i,_len,_ref;for(this._pushChar("&"),_ref=this.entity.split(""),_i=0,_len=_ref.length;_len>_i;_i++)c=_ref[_i],this._pushChar(c);return this.entity=""}),this.fsm.addTransitions([" ","\n"],OPENNING_OR_CLOSING_TAG),this.fsm.addTransitions(ALPHA_CHARS,OPENNING_OR_CLOSING_TAG,OPENING_TAG,function(){return this._back()}),this.fsm.addTransition("/",OPENNING_OR_CLOSING_TAG,CLOSING_TAG),this.fsm.addTransitions([" ","\n"],OPENING_TAG),this.fsm.addTransitions(ALPHA_CHARS,OPENING_TAG,TAG_NAME_OPENING,function(){return this._back()}),this.fsm.addTransitions([" ","\n"],CLOSING_TAG),this.fsm.addTransitions(ALPHA_CHARS,CLOSING_TAG,TAG_NAME_CLOSING,function(){return this._back()}),this.fsm.addTransitions(TAG_NAME_CHARS,TAG_NAME_OPENING,null,function(c){return this.tagName+=c}),this.fsm.addTransitions([" ","\n"],TAG_NAME_OPENING,ATTR_OR_TAG_END),this.fsm.addTransition("/",TAG_NAME_OPENING,TAG_OPENING_SELF_CLOSING,function(){return this.selfClosing=!0}),this.fsm.addTransition(">",TAG_NAME_OPENING,CHAR_OR_ENTITY_OR_TAG,function(){return this._pushTag()}),this.fsm.addTransitions([" ","\n"],TAG_OPENING_SELF_CLOSING),this.fsm.addTransition(">",TAG_OPENING_SELF_CLOSING,CHAR_OR_ENTITY_OR_TAG,function(){return this._pushTag()}),this.fsm.addTransitions([" ","\n"],ATTR_OR_TAG_END),this.fsm.addTransition("/",ATTR_OR_TAG_END,TAG_OPENING_SELF_CLOSING,function(){return this.selfClosing=!0}),this.fsm.addTransition(">",ATTR_OR_TAG_END,CHAR_OR_ENTITY_OR_TAG,function(){return this._pushTag()}),this.fsm.addTransitions(ALPHA_CHARS,ATTR_OR_TAG_END,ATTR_NAME,function(){return this._back()}),this.fsm.addTransitions(TAG_NAME_CHARS,TAG_NAME_CLOSING,null,function(c){return this.tagName+=c}),this.fsm.addTransitions([" ","\n"],TAG_NAME_CLOSING,TAG_NAME_MUST_CLOSE),this.fsm.addTransition(">",TAG_NAME_CLOSING,CHAR_OR_ENTITY_OR_TAG,function(){return this._popTag()}),this.fsm.addTransitions([" ","\n"],TAG_NAME_MUST_CLOSE),this.fsm.addTransition(">",TAG_NAME_MUST_CLOSE,CHAR_OR_ENTITY_OR_TAG,function(){return this._popTag()}),this.fsm.addTransitions(ATTR_NAME_CHARS,ATTR_NAME,null,function(c){return this.attributeName+=c}),this.fsm.addTransitions([" ","\n"],ATTR_NAME,ATTR_NAME_FIND_VALUE),this.fsm.addTransition("=",ATTR_NAME,ATTR_DELIM),this.fsm.addTransitions([" ","\n"],ATTR_NAME_FIND_VALUE),this.fsm.addTransition("=",ATTR_NAME_FIND_VALUE,ATTR_DELIM),this.fsm.addTransitions(">",ATTR_NAME,ATTR_OR_TAG_END,function(){return this._pushAttribute(),this._back()}),this.fsm.addTransitionAny(ATTR_NAME_FIND_VALUE,ATTR_OR_TAG_END,function(){return this._pushAttribute(),this._back()}),this.fsm.addTransitions([" ","\n"],ATTR_DELIM),this.fsm.addTransition("'",ATTR_DELIM,ATTR_VALUE_SINGLE_DELIM),this.fsm.addTransition('"',ATTR_DELIM,ATTR_VALUE_DOUBLE_DELIM),this.fsm.addTransitions(ALPHA_NUMERIC_CHARS.concat(["&"],ATTR_DELIM,ATTR_VALUE_NO_DELIM,function(){return this._back()})),this.fsm.addTransition(" ",ATTR_VALUE_NO_DELIM,ATTR_OR_TAG_END,function(){return this._pushAttribute()}),this.fsm.addTransitions(["/",">"],ATTR_VALUE_NO_DELIM,ATTR_OR_TAG_END,function(){return this._back(),this._pushAttribute()}),this.fsm.addTransition("&",ATTR_VALUE_NO_DELIM,ATTR_ENTITY_NO_DELIM),this.fsm.addTransitionAny(ATTR_VALUE_NO_DELIM,null,function(c){return this.attributeValue+=c}),this.fsm.addTransition("'",ATTR_VALUE_SINGLE_DELIM,ATTR_OR_TAG_END,function(){return this._pushAttribute()}),this.fsm.addTransition("&",ATTR_VALUE_SINGLE_DELIM,ATTR_ENTITY_SINGLE_DELIM),this.fsm.addTransitionAny(ATTR_VALUE_SINGLE_DELIM,null,function(c){return this.attributeValue+=c}),this.fsm.addTransition('"',ATTR_VALUE_DOUBLE_DELIM,ATTR_OR_TAG_END,function(){return this._pushAttribute()}),this.fsm.addTransition("&",ATTR_VALUE_DOUBLE_DELIM,ATTR_ENTITY_DOUBLE_DELIM),this.fsm.addTransitionAny(ATTR_VALUE_DOUBLE_DELIM,null,function(c){return this.attributeValue+=c}),this.fsm.addTransitions(ENTITY_CHARS,ATTR_ENTITY_NO_DELIM,null,function(c){return this.entity+=c}),this.fsm.addTransitions(ENTITY_CHARS,ATTR_ENTITY_SINGLE_DELIM,null,function(c){return this.entity+=c}),this.fsm.addTransitions(ENTITY_CHARS,ATTR_ENTITY_DOUBLE_DELIM,null,function(c){return this.entity+=c}),this.fsm.addTransition(";",ATTR_ENTITY_NO_DELIM,ATTR_VALUE_NO_DELIM,function(){return this.attributeValue+="&"+this.entity+";",this.entity=""}),this.fsm.addTransition(";",ATTR_ENTITY_SINGLE_DELIM,ATTR_VALUE_SINGLE_DELIM,function(){return this.attributeValue+="&"+this.entity+";",this.entity=""}),this.fsm.addTransition(";",ATTR_ENTITY_DOUBLE_DELIM,ATTR_VALUE_DOUBLE_DELIM,function(){return this.attributeValue+="&"+this.entity+";",this.entity=""}),this.fsm.addTransitionAny(ATTR_ENTITY_NO_DELIM,ATTR_VALUE_NO_DELIM,function(){return this.attributeValue+="&"+this.entity,this.entity="",this._back()}),this.fsm.addTransitionAny(ATTR_ENTITY_SINGLE_DELIM,ATTR_VALUE_SINGLE_DELIM,function(){return this.attributeValue+="&"+this.entity,this.entity="",this._back()}),this.fsm.addTransitionAny(ATTR_ENTITY_DOUBLE_DELIM,ATTR_VALUE_DOUBLE_DELIM,function(){return this.attributeValue+="&"+this.entity,this.entity="",this._back()})}return _Parser.prototype._back=function(){return this.head--},_Parser.prototype._pushAttribute=function(){return this.attributes[this.attributeName]=this.attributeValue,this.attributeName="",this.attributeValue=""},_Parser.prototype._pushChar=function(c){var character,lastCharacter;return character=new HTMLString.Character(c,this.tags),this._preserveWhitespace?void this.string.characters.push(character):!this.string.length()||character.isTag()||character.isEntity()||!character.isWhitespace()||(lastCharacter=this.string.characters[this.string.length()-1],!lastCharacter.isWhitespace()||lastCharacter.isTag()||lastCharacter.isEntity())?this.string.characters.push(character):void 0},_Parser.prototype._pushTag=function(){var tag,_ref;return tag=new HTMLString.Tag(this.tagName,this.attributes),this.tags.push(tag),tag.selfClosing()&&(this._pushChar(""),this.tags.pop(),!this.selfClosed&&(_ref=this.tagName,__indexOf.call(HTMLString.Tag.SELF_CLOSING,_ref)>=0)&&this.fsm.reset()),this.tagName="",this.selfClosed=!1,this.attributes={}},_Parser.prototype._popTag=function(){for(var character,tag;;)if(tag=this.tags.pop(),this.string.length()&&(character=this.string.characters[this.string.length()-1],character.isTag()||character.isEntity()||!character.isWhitespace()||character.removeTags(tag)),tag.name()===this.tagName.toLowerCase())break;return this.tagName=""},_Parser.prototype.parse=function(html,preserveWhitespace){var character,error;for(this._preserveWhitespace=preserveWhitespace,this.reset(),html=this.preprocess(html),this.fsm.parser=this;this.head> "+error)}this.head++}return this.fsm.process("END"),this.string},_Parser.prototype.preprocess=function(html){return html=html.replace(/\r\n/g,"\n").replace(/\r/g,"\n"),html=html.replace(//g,""),this._preserveWhitespace||(html=html.replace(/\s+/g," ")),html},_Parser.prototype.reset=function(){return this.fsm.reset(),this.head=0,this.string=new HTMLString.String,this.entity="",this.tags=[],this.tagName="",this.selfClosing=!1,this.attributes={},this.attributeName="",this.attributeValue=""},_Parser}(),HTMLString.Tag=function(){function Tag(name,attributes){var k,v;this._name=name.toLowerCase(),this._selfClosing=HTMLString.Tag.SELF_CLOSING[this._name]===!0,this._head=null,this._attributes={};for(k in attributes)v=attributes[k],this._attributes[k]=v}return Tag.SELF_CLOSING={area:!0,base:!0,br:!0,hr:!0,img:!0,input:!0,"link meta":!0,wbr:!0},Tag.prototype.head=function(){var components,k,v,_ref;if(!this._head){components=[],_ref=this._attributes;for(k in _ref)v=_ref[k],components.push(v?""+k+'="'+v+'"':""+k);components.sort(),components.unshift(this._name),this._head="<"+components.join(" ")+">"}return this._head},Tag.prototype.name=function(){return this._name},Tag.prototype.selfClosing=function(){return this._selfClosing},Tag.prototype.tail=function(){return this._selfClosing?"":""+this._name+">"},Tag.prototype.attr=function(name,value){return void 0===value?this._attributes[name]:(this._attributes[name]=value,this._head=null)},Tag.prototype.removeAttr=function(name){return void 0!==this._attributes[name]?(delete this._attributes[name],this._head=null):void 0},Tag.prototype.copy=function(){return new HTMLString.Tag(this._name,this._attributes)},Tag}(),HTMLString.Character=function(){function Character(c,tags){this._c=c,c.length>1&&(this._c=c.toLowerCase()),this._tags=[],this.addTags.apply(this,tags)}return Character.prototype.c=function(){return this._c},Character.prototype.isEntity=function(){return this._c.length>1},Character.prototype.isTag=function(tagName){return 0!==this._tags.length&&this._tags[0].selfClosing()?tagName&&this._tags[0].name()!==tagName?!1:!0:!1},Character.prototype.isWhitespace=function(){var _ref;return" "===(_ref=this._c)||"\n"===_ref||" "===_ref||this.isTag("br")},Character.prototype.tags=function(){var t;return function(){var _i,_len,_ref,_results;for(_ref=this._tags,_results=[],_i=0,_len=_ref.length;_len>_i;_i++)t=_ref[_i],_results.push(t.copy());return _results}.call(this)},Character.prototype.addTags=function(){var tag,tags,_i,_len,_results;for(tags=1<=arguments.length?__slice.call(arguments,0):[],_results=[],_i=0,_len=tags.length;_len>_i;_i++)tag=tags[_i],Array.isArray(tag)||(tag.selfClosing()?this.isTag()||this._tags.unshift(tag.copy()):_results.push(this._tags.push(tag.copy())));return _results},Character.prototype.eq=function(c){var tag,tags,_i,_j,_len,_len1,_ref,_ref1;if(this.c()!==c.c())return!1;if(this._tags.length!==c._tags.length)return!1;for(tags={},_ref=this._tags,_i=0,_len=_ref.length;_len>_i;_i++)tag=_ref[_i],tags[tag.head()]=!0;for(_ref1=c._tags,_j=0,_len1=_ref1.length;_len1>_j;_j++)if(tag=_ref1[_j],!tags[tag.head()])return!1;return!0},Character.prototype.hasTags=function(){var tag,tagHeads,tagNames,tags,_i,_j,_len,_len1,_ref;for(tags=1<=arguments.length?__slice.call(arguments,0):[],tagNames={},tagHeads={},_ref=this._tags,_i=0,_len=_ref.length;_len>_i;_i++)tag=_ref[_i],tagNames[tag.name()]=!0,tagHeads[tag.head()]=!0;for(_j=0,_len1=tags.length;_len1>_j;_j++)if(tag=tags[_j],"string"==typeof tag){if(void 0===tagNames[tag])return!1}else if(void 0===tagHeads[tag.head()])return!1;return!0},Character.prototype.removeTags=function(){var heads,names,newTags,tag,tags,_i,_len;if(tags=1<=arguments.length?__slice.call(arguments,0):[],0===tags.length)return void(this._tags=[]);for(names={},heads={},_i=0,_len=tags.length;_len>_i;_i++)tag=tags[_i],"string"==typeof tag?names[tag]=tag:heads[tag.head()]=tag;return newTags=[],this._tags=this._tags.filter(function(tag){return heads[tag.head()]||names[tag.name()]?void 0:tag})},Character.prototype.copy=function(){var t;return new HTMLString.Character(this._c,function(){var _i,_len,_ref,_results;for(_ref=this._tags,_results=[],_i=0,_len=_ref.length;_len>_i;_i++)t=_ref[_i],_results.push(t.copy());return _results}.call(this))},Character}()}.call(this);
--------------------------------------------------------------------------------
/external/fsm.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var FSM, exports;
3 |
4 | FSM = {};
5 |
6 | FSM.Machine = (function() {
7 | function Machine(context) {
8 | this.context = context;
9 | this._stateTransitions = {};
10 | this._stateTransitionsAny = {};
11 | this._defaultTransition = null;
12 | this._initialState = null;
13 | this._currentState = null;
14 | }
15 |
16 | Machine.prototype.addTransition = function(action, state, nextState, callback) {
17 | if (!nextState) {
18 | nextState = state;
19 | }
20 | return this._stateTransitions[[action, state]] = [nextState, callback];
21 | };
22 |
23 | Machine.prototype.addTransitions = function(actions, state, nextState, callback) {
24 | var action, _i, _len, _results;
25 | if (!nextState) {
26 | nextState = state;
27 | }
28 | _results = [];
29 | for (_i = 0, _len = actions.length; _i < _len; _i++) {
30 | action = actions[_i];
31 | _results.push(this.addTransition(action, state, nextState, callback));
32 | }
33 | return _results;
34 | };
35 |
36 | Machine.prototype.addTransitionAny = function(state, nextState, callback) {
37 | if (!nextState) {
38 | nextState = state;
39 | }
40 | return this._stateTransitionsAny[state] = [nextState, callback];
41 | };
42 |
43 | Machine.prototype.setDefaultTransition = function(state, callback) {
44 | return this._defaultTransition = [state, callback];
45 | };
46 |
47 | Machine.prototype.getTransition = function(action, state) {
48 | if (this._stateTransitions[[action, state]]) {
49 | return this._stateTransitions[[action, state]];
50 | } else if (this._stateTransitionsAny[state]) {
51 | return this._stateTransitionsAny[state];
52 | } else if (this._defaultTransition) {
53 | return this._defaultTransition;
54 | }
55 | throw new Error("Transition is undefined: (" + action + ", " + state + ")");
56 | };
57 |
58 | Machine.prototype.getCurrentState = function() {
59 | return this._currentState;
60 | };
61 |
62 | Machine.prototype.setInitialState = function(state) {
63 | this._initialState = state;
64 | if (!this._currentState) {
65 | return this.reset();
66 | }
67 | };
68 |
69 | Machine.prototype.reset = function() {
70 | return this._currentState = this._initialState;
71 | };
72 |
73 | Machine.prototype.process = function(action) {
74 | var result;
75 | result = this.getTransition(action, this._currentState);
76 | if (result[1]) {
77 | result[1].call(this.context || (this.context = this), action);
78 | }
79 | return this._currentState = result[0];
80 | };
81 |
82 | return Machine;
83 |
84 | })();
85 |
86 | if (typeof window !== 'undefined') {
87 | window.FSM = FSM;
88 | }
89 |
90 | if (typeof module !== 'undefined' && module.exports) {
91 | exports = module.exports = FSM;
92 | }
93 |
94 | }).call(this);
95 |
--------------------------------------------------------------------------------
/npm-debug.log:
--------------------------------------------------------------------------------
1 | 0 info it worked if it ends with ok
2 | 1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'publish' ]
3 | 2 info using npm@1.4.28
4 | 3 info using node@v0.10.33
5 | 4 verbose publish [ '.' ]
6 | 5 verbose cache add [ '.', null ]
7 | 6 verbose cache add name=undefined spec="." args=[".",null]
8 | 7 verbose parsed url { protocol: null,
9 | 7 verbose parsed url slashes: null,
10 | 7 verbose parsed url auth: null,
11 | 7 verbose parsed url host: null,
12 | 7 verbose parsed url port: null,
13 | 7 verbose parsed url hostname: null,
14 | 7 verbose parsed url hash: null,
15 | 7 verbose parsed url search: null,
16 | 7 verbose parsed url query: null,
17 | 7 verbose parsed url pathname: '.',
18 | 7 verbose parsed url path: '.',
19 | 7 verbose parsed url href: '.' }
20 | 8 silly lockFile 3a52ce78- .
21 | 9 verbose lock . /home/anthony/.npm/3a52ce78-.lock
22 | 10 verbose tar pack [ '/home/anthony/.npm/HTMLString/1.0.6/package.tgz', '.' ]
23 | 11 verbose tarball /home/anthony/.npm/HTMLString/1.0.6/package.tgz
24 | 12 verbose folder .
25 | 13 info prepublish HTMLString@1.0.6
26 | 14 silly lockFile 1f1177db-tar tar://.
27 | 15 verbose lock tar://. /home/anthony/.npm/1f1177db-tar.lock
28 | 16 silly lockFile 423837ce-npm-HTMLString-1-0-6-package-tgz tar:///home/anthony/.npm/HTMLString/1.0.6/package.tgz
29 | 17 verbose lock tar:///home/anthony/.npm/HTMLString/1.0.6/package.tgz /home/anthony/.npm/423837ce-npm-HTMLString-1-0-6-package-tgz.lock
30 | 18 silly lockFile 1f1177db-tar tar://.
31 | 19 silly lockFile 1f1177db-tar tar://.
32 | 20 silly lockFile 423837ce-npm-HTMLString-1-0-6-package-tgz tar:///home/anthony/.npm/HTMLString/1.0.6/package.tgz
33 | 21 silly lockFile 423837ce-npm-HTMLString-1-0-6-package-tgz tar:///home/anthony/.npm/HTMLString/1.0.6/package.tgz
34 | 22 silly lockFile b99c44c8-ony-npm-HTMLString-1-0-6-package /home/anthony/.npm/HTMLString/1.0.6/package
35 | 23 verbose lock /home/anthony/.npm/HTMLString/1.0.6/package /home/anthony/.npm/b99c44c8-ony-npm-HTMLString-1-0-6-package.lock
36 | 24 silly lockFile b99c44c8-ony-npm-HTMLString-1-0-6-package /home/anthony/.npm/HTMLString/1.0.6/package
37 | 25 silly lockFile b99c44c8-ony-npm-HTMLString-1-0-6-package /home/anthony/.npm/HTMLString/1.0.6/package
38 | 26 silly lockFile 3a52ce78- .
39 | 27 silly lockFile 3a52ce78- .
40 | 28 silly publish { name: 'HTMLString',
41 | 28 silly publish description: 'An HTML parser written in JavaScript that\'s probably not what you\'re looking for.',
42 | 28 silly publish version: '1.0.6',
43 | 28 silly publish keywords: [ 'html', 'parser' ],
44 | 28 silly publish author:
45 | 28 silly publish { name: 'Anthony Blackshaw',
46 | 28 silly publish email: 'ant@getme.co.uk',
47 | 28 silly publish url: 'https://github.com/anthonyjb' },
48 | 28 silly publish main: 'build/html-string.js',
49 | 28 silly publish devDependencies:
50 | 28 silly publish { grunt: '~0.4.5',
51 | 28 silly publish 'grunt-contrib-clean': '^0.6.0',
52 | 28 silly publish 'grunt-contrib-coffee': '^0.11.1',
53 | 28 silly publish 'grunt-contrib-concat': '^0.5.0',
54 | 28 silly publish 'grunt-contrib-jasmine': '^0.9.2',
55 | 28 silly publish 'grunt-contrib-uglify': '^0.5.1',
56 | 28 silly publish 'grunt-contrib-watch': '^0.6.1' },
57 | 28 silly publish scripts: { test: 'grunt jasmine --verbose' },
58 | 28 silly publish repository:
59 | 28 silly publish { type: 'git',
60 | 28 silly publish url: 'https://github.com/GetmeUK/HTMLString.git' },
61 | 28 silly publish license: 'MIT',
62 | 28 silly publish readme: '# HTMLString\n\n[](https://travis-ci.org/GetmeUK/HTMLString)\n\n> An HTML parser written in JavaScript that\'s probably not what you\'re looking for.\n\n## Install\n\n**Using bower**\n\n```\nbower install --save HTMLString\n```\n\n**Using npm**\n\n```\nnpm install --save HTMLString\n```\n\n## Building\nTo build the library you\'ll need to use Grunt. First install the required node modules ([grunt-cli](http://gruntjs.com/getting-started) must be installed):\n```\ngit clone https://github.com/GetmeUK/HTMLString.git\ncd HTMLString\nnpm install\n```\n\nThen run `grunt build` to build the project.\n\n## Testing\nTo test the library you\'ll need to use Jasmine. First install Jasmine:\n```\ngit clone https://github.com/pivotal/jasmine.git\nmkdir HTMLString/jasmine\nmv jasmine/dist/jasmine-standalone-2.0.3.zip HTMLString/jasmine\ncd HTMLString/jasmine\nunzip jasmine-standalone-2.0.3.zip\n```\n\nThen open `HTMLString/SpecRunner.html` in a browser to run the tests.\n\nAlternatively you can use `grunt jasmine` to run the tests from the command line.\n\n## Documentation\nFull documentation is available at http://getcontenttools.com/api/html-string\n\n## Browser support\n- Chrome\n- Firefox\n- IE9+\n',
63 | 28 silly publish readmeFilename: 'README.md',
64 | 28 silly publish gitHead: '8ca1028b8f00c0ae20e25887e57999c7eb6fb03b',
65 | 28 silly publish bugs: { url: 'https://github.com/GetmeUK/HTMLString/issues' },
66 | 28 silly publish homepage: 'https://github.com/GetmeUK/HTMLString',
67 | 28 silly publish _id: 'HTMLString@1.0.6',
68 | 28 silly publish _shasum: '0dbd2b90ad9ff5634d21f366ecbf9c4954477425',
69 | 28 silly publish _from: '.' }
70 | 29 verbose request where is /HTMLString
71 | 30 verbose request registry https://registry.npmjs.org/
72 | 31 verbose request id 9915c72d0522b64e
73 | 32 verbose url raw /HTMLString
74 | 33 verbose url resolving [ 'https://registry.npmjs.org/', './HTMLString' ]
75 | 34 verbose url resolved https://registry.npmjs.org/HTMLString
76 | 35 verbose request where is https://registry.npmjs.org/HTMLString
77 | 36 info trying registry request attempt 1 at 21:57:55
78 | 37 http PUT https://registry.npmjs.org/HTMLString
79 | 38 http 401 https://registry.npmjs.org/HTMLString
80 | 39 verbose headers { 'content-type': 'application/json',
81 | 39 verbose headers 'cache-control': 'max-age=300',
82 | 39 verbose headers 'content-length': '42',
83 | 39 verbose headers 'accept-ranges': 'bytes',
84 | 39 verbose headers date: 'Sun, 23 Oct 2016 20:57:49 GMT',
85 | 39 verbose headers via: '1.1 varnish',
86 | 39 verbose headers connection: 'keep-alive',
87 | 39 verbose headers 'x-served-by': 'cache-lcy1130-LCY',
88 | 39 verbose headers 'x-cache': 'MISS',
89 | 39 verbose headers 'x-cache-hits': '0',
90 | 39 verbose headers 'x-timer': 'S1477256268.696056,VS0,VE1166',
91 | 39 verbose headers vary: 'Accept-Encoding' }
92 | 40 error publish Failed PUT 401
93 | 41 error Error: Could not authenticate getmeuk : HTMLString
94 | 41 error at RegClient. (/usr/local/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:308:14)
95 | 41 error at Request._callback (/usr/local/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:246:65)
96 | 41 error at Request.self.callback (/usr/local/lib/node_modules/npm/node_modules/request/request.js:236:22)
97 | 41 error at Request.emit (events.js:98:17)
98 | 41 error at Request. (/usr/local/lib/node_modules/npm/node_modules/request/request.js:1142:14)
99 | 41 error at Request.emit (events.js:117:20)
100 | 41 error at IncomingMessage. (/usr/local/lib/node_modules/npm/node_modules/request/request.js:1096:12)
101 | 41 error at IncomingMessage.emit (events.js:117:20)
102 | 41 error at _stream_readable.js:943:16
103 | 41 error at process._tickCallback (node.js:419:13)
104 | 42 error If you need help, you may report this *entire* log,
105 | 42 error including the npm and node versions, at:
106 | 42 error
107 | 43 error System Linux 3.13.0-100-generic
108 | 44 error command "/usr/local/bin/node" "/usr/local/bin/npm" "publish"
109 | 45 error cwd /home/anthony/Desktop/Work/Public/CoffeeScript/github/HTMLString
110 | 46 error node -v v0.10.33
111 | 47 error npm -v 1.4.28
112 | 48 verbose exit [ 1, true ]
113 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HTMLString",
3 | "description": "An HTML parser written in JavaScript that's probably not what you're looking for.",
4 | "version": "1.0.7",
5 | "keywords": [
6 | "html",
7 | "parser"
8 | ],
9 | "author": {
10 | "name": "Anthony Blackshaw",
11 | "email": "ant@getme.co.uk",
12 | "url": "https://github.com/anthonyjb"
13 | },
14 | "main": "build/html-string.js",
15 | "devDependencies": {
16 | "grunt": "~0.4.5",
17 | "grunt-contrib-clean": "^0.6.0",
18 | "grunt-contrib-coffee": "^0.11.1",
19 | "grunt-contrib-concat": "^0.5.0",
20 | "grunt-contrib-jasmine": "^0.9.2",
21 | "grunt-contrib-uglify": "^0.5.1",
22 | "grunt-contrib-watch": "^0.6.1"
23 | },
24 | "scripts": {
25 | "test": "grunt jasmine --verbose"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/GetmeUK/HTMLString.git"
30 | },
31 | "license": "MIT"
32 | }
33 |
--------------------------------------------------------------------------------
/spec/html-string-spec.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var quotes;
3 |
4 | quotes = {
5 | Gates: 'We all
need people who will give us feedback. That\'s how we improve. 
',
6 | Kernighan: ' \n Everyone knows that debugging is twice as hard as writing a program in\n the first place. So if you\'re as clever as you can be when you write\n it, how will you ever debug it?\n
',
7 | Ritchie: 'I can\'t recall any difficulty in making the C language definition completely\nopen - any discussion on the matter tended to mention languages whose\ninventors tried to keep tight control, and consequent ill fate.',
8 | Turing: 'Machines take me by
surprise with great frequency.
',
9 | Wozniak: 'all the best people in life seem to like LINUX.',
10 | WozniakNamespaced: 'all the best people in life seem to like LINUX.',
11 | WozniakWhitespace: 'all the best people in life seem to like LINUX.',
12 | AmbiguousAmpersand: '& & &foo && && &end'
13 | };
14 |
15 | describe('HTMLString.String()', function() {
16 | it('should parse and render text (no HTML)', function() {
17 | var string;
18 | string = new HTMLString.String(quotes.Wozniak);
19 | return expect(string.text()).toBe(quotes.Wozniak);
20 | });
21 | it('should parse and render text (no HTML with whitespace preserved)', function() {
22 | var string;
23 | string = new HTMLString.String(quotes.WozniakWhitespace, true);
24 | return expect(string.text()).toBe(quotes.WozniakWhitespace);
25 | });
26 | it('should parse and render a string (HTML)', function() {
27 | var string;
28 | string = new HTMLString.String(quotes.Turing);
29 | expect(string.html()).toBe(quotes.Turing);
30 | string = new HTMLString.String(quotes.WozniakNamespaced);
31 | return expect(string.html()).toBe(quotes.WozniakNamespaced);
32 | });
33 | return it('should parse and render a string (HTML with ambiguous ampersands)', function() {
34 | var string;
35 | string = new HTMLString.String(quotes.AmbiguousAmpersand);
36 | expect(string.html()).toBe(quotes.AmbiguousAmpersand);
37 | return console.log(string.html());
38 | });
39 | });
40 |
41 | describe('HTMLString.String.isWhitespace()', function() {
42 | it('should return true if a string consists entirely of whitespace characters', function() {
43 | var string;
44 | string = new HTMLString.String("
");
45 | return expect(string.isWhitespace()).toBe(true);
46 | });
47 | return it('should return false if a string contains any non-whitespace character', function() {
48 | var string;
49 | string = new HTMLString.String(" a
");
50 | return expect(string.isWhitespace()).toBe(false);
51 | });
52 | });
53 |
54 | describe('HTMLString.String.length()', function() {
55 | return it('should return the length of a string', function() {
56 | var string;
57 | string = new HTMLString.String(quotes.Turing);
58 | return expect(string.length()).toBe(52);
59 | });
60 | });
61 |
62 | describe('HTMLString.String.preserveWhitespace()', function() {
63 | return it('should return the true if whitespace is reserved for the string', function() {
64 | var string;
65 | string = new HTMLString.String(quotes.Turing);
66 | expect(string.preserveWhitespace()).toBe(false);
67 | string = new HTMLString.String(quotes.Turing, true);
68 | return expect(string.preserveWhitespace()).toBe(true);
69 | });
70 | });
71 |
72 | describe('HTMLString.String.capitalize()', function() {
73 | return it('should capitalize the first character of a string', function() {
74 | var newString, string;
75 | string = new HTMLString.String(quotes.Wozniak);
76 | newString = string.capitalize();
77 | return expect(newString.charAt(0).c()).toBe('A');
78 | });
79 | });
80 |
81 | describe('HTMLString.String.charAt()', function() {
82 | return it('should return a character from a string at the specified index', function() {
83 | var string;
84 | string = new HTMLString.String(quotes.Turing);
85 | return expect(string.charAt(18).c()).toBe('y');
86 | });
87 | });
88 |
89 | describe('HTMLString.String.concat()', function() {
90 | return it('should combine 2 or more strings and return a new string', function() {
91 | var newString, stringA, stringB;
92 | stringA = new HTMLString.String(quotes.Turing);
93 | stringB = new HTMLString.String(quotes.Wozniak);
94 | newString = stringA.concat(stringB);
95 | return expect(newString.html()).toBe('Machines take me by
surprise with great frequency.all the best people in life seem to like LINUX.
');
96 | });
97 | });
98 |
99 | describe('HTMLString.String.contain()', function() {
100 | return it('should return true if a string contains a substring', function() {
101 | var string, substring;
102 | string = new HTMLString.String(quotes.Turing);
103 | substring = new HTMLString.String('take me
');
104 | expect(string.contains('take me')).toBe(true);
105 | return expect(string.contains(substring)).toBe(true);
106 | });
107 | });
108 |
109 | describe('HTMLString.String.endswith()', function() {
110 | return it('should return true if a string ends with a substring.`', function() {
111 | var string, substring;
112 | string = new HTMLString.String(quotes.Turing);
113 | substring = new HTMLString.String('great frequency.
');
114 | expect(string.endsWith("great" + (HTMLString.String.decode(' ')) + "frequency.")).toBe(true);
115 | return expect(string.endsWith(substring)).toBe(true);
116 | });
117 | });
118 |
119 | describe('HTMLString.String.format()', function() {
120 | return it('should format a selection of characters in a string (add tags)', function() {
121 | var string;
122 | string = new HTMLString.String(quotes.Ritchie);
123 | string = string.format(0, -1, new HTMLString.Tag('q'));
124 | string = string.format(19, 29, new HTMLString.Tag('a', {
125 | href: 'http://www.getme.co.uk'
126 | }), new HTMLString.Tag('b'));
127 | return expect(string.html()).toBe("I can't recall any difficulty in making the C language definition completely open - any discussion on the matter tended to mention languages whose inventors tried to keep tight control, and consequent ill fate.
".trim());
128 | });
129 | });
130 |
131 | describe('HTMLString.String.hasTags()', function() {
132 | return it('should return true if the string has and characters formatted with the specifed tags', function() {
133 | var string;
134 | string = new HTMLString.String(quotes.Kernighan).trim();
135 | expect(string.hasTags(new HTMLString.Tag('q'), 'b')).toBe(true);
136 | return expect(string.hasTags(new HTMLString.Tag('q')), true).toBe(true);
137 | });
138 | });
139 |
140 | describe('HTMLString.String.indexOf()', function() {
141 | return it('should return the first position of an substring in another string, or -1 if there is no match', function() {
142 | var string, substring;
143 | string = new HTMLString.String(quotes.Kernighan).trim();
144 | substring = new HTMLString.String('debugging
');
145 | expect(string.indexOf('debugging')).toBe(20);
146 | expect(string.indexOf(substring)).toBe(20);
147 | return expect(string.indexOf(substring, 30)).toBe(-1);
148 | });
149 | });
150 |
151 | describe('HTMLString.String.insert()', function() {
152 | return it('should insert a string into another string and return a new string', function() {
153 | var newString, stringA, stringB;
154 | stringA = new HTMLString.String(quotes.Kernighan).trim();
155 | stringB = new HTMLString.String(quotes.Turing);
156 | newString = stringA.insert(9, stringB);
157 | newString = newString.insert(9 + stringB.length(), ' - new string inserted - ');
158 | expect(newString.html()).toBe('Everyone Machines take me by
surprise with great frequency. - new string inserted -
knows that debugging is twice as hard as writing a program in the first place. So if you\'re as clever as you can be when you write it, how will you ever debug it?
');
159 | newString = stringA.insert(9, ' - insert unformatted string - ', false);
160 | return expect(newString.html()).toBe('Everyone
- insert unformatted string - knows that debugging is twice as hard as writing a program in the first place. So if you\'re as clever as you can be when you write it, how will you ever debug it?
');
161 | });
162 | });
163 |
164 | describe('HTMLString.String.lastIndexOf()', function() {
165 | return it('should return the last position of an substring in another string, or -1 if there is no match', function() {
166 | var string, substring;
167 | string = new HTMLString.String(quotes.Kernighan).trim();
168 | substring = new HTMLString.String('debugging
');
169 | expect(string.lastIndexOf('debugging')).toBe(142);
170 | expect(string.lastIndexOf(substring)).toBe(142);
171 | return expect(string.lastIndexOf(substring, 30)).toBe(-1);
172 | });
173 | });
174 |
175 | describe('HTMLString.String.optimize()', function() {
176 | return it('should optimize the string (in place) so that tags are restacked in order of longest running', function() {
177 | var string;
178 | string = new HTMLString.String(quotes.Gates);
179 | string.optimize();
180 | return expect(string.html()).toBe('We all need people who will give us feedback. That\'s how we improve. 
');
181 | });
182 | });
183 |
184 | describe('HTMLString.String.slice()', function() {
185 | return it('should extract a section of a string and return a new string', function() {
186 | var newString, string;
187 | string = new HTMLString.String(quotes.Kernighan).trim();
188 | newString = string.slice(10, 19);
189 | return expect(newString.html()).toBe('nows that
');
190 | });
191 | });
192 |
193 | describe('HTMLString.String.split()', function() {
194 | return it('should split a string by the separator and return a list of sub-strings', function() {
195 | var string, substrings;
196 | string = new HTMLString.String(' ', true);
197 | substrings = string.split(' ');
198 | expect(substrings.length).toBe(2);
199 | expect(substrings[0].length()).toBe(0);
200 | expect(substrings[1].length()).toBe(0);
201 | string = new HTMLString.String(quotes.Kernighan).trim();
202 | substrings = string.split('a');
203 | expect(substrings.length).toBe(11);
204 | substrings = string.split('@@');
205 | expect(substrings.length).toBe(1);
206 | substrings = string.split(new HTMLString.String('e
'));
207 | return expect(substrings.length).toBe(2);
208 | });
209 | });
210 |
211 | describe('HTMLString.String.startsWith()', function() {
212 | return it('should return true if a string starts with a substring.`', function() {
213 | var string, substring;
214 | string = new HTMLString.String(quotes.Turing);
215 | substring = new HTMLString.String('Machines take
');
216 | expect(string.startsWith("Machines take")).toBe(true);
217 | return expect(string.startsWith(substring)).toBe(true);
218 | });
219 | });
220 |
221 | describe('HTMLString.String.substr()', function() {
222 | return it('should return a subset of a string from an offset for a specified length`', function() {
223 | var newString, string;
224 | string = new HTMLString.String(quotes.Kernighan).trim();
225 | newString = string.substr(10, 9);
226 | return expect(newString.html()).toBe('nows that
');
227 | });
228 | });
229 |
230 | describe('HTMLString.String.substring()', function() {
231 | return it('should return a subset of a string between 2 indexes`', function() {
232 | var newString, string;
233 | string = new HTMLString.String(quotes.Kernighan).trim();
234 | newString = string.substring(10, 19);
235 | return expect(newString.html()).toBe('nows that
');
236 | });
237 | });
238 |
239 | describe('HTMLString.String.toLowerCase()', function() {
240 | return it('should return a copy of a string converted to lower case.`', function() {
241 | var newString, string;
242 | string = new HTMLString.String(quotes.Turing);
243 | newString = string.toLowerCase();
244 | return expect(newString.html()).toBe('machines take me by
surprise with great frequency.
');
245 | });
246 | });
247 |
248 | describe('HTMLString.String.toUpperCase()', function() {
249 | return it('should return a copy of a string converted to upper case.`', function() {
250 | var newString, string;
251 | string = new HTMLString.String(quotes.Turing);
252 | newString = string.toUpperCase();
253 | return expect(newString.html()).toBe('MACHINES TAKE ME BY
SURPRISE WITH GREAT FREQUENCY.
');
254 | });
255 | });
256 |
257 | describe('HTMLString.String.trim()', function() {
258 | return it('should return a copy of a string converted with whitespaces trimmed from both ends.`', function() {
259 | var newString, string;
260 | string = new HTMLString.String(quotes.Kernighan);
261 | newString = string.trim();
262 | expect(newString.characters[0].isWhitespace()).toBe(false);
263 | return expect(newString.characters[newString.length() - 1].isWhitespace()).toBe(false);
264 | });
265 | });
266 |
267 | describe('HTMLString.String.trimLeft()', function() {
268 | return it('should return a copy of a string converted with whitespaces trimmed from the left.`', function() {
269 | var newString, string;
270 | string = new HTMLString.String(quotes.Kernighan);
271 | newString = string.trimLeft();
272 | expect(newString.characters[0].isWhitespace()).toBe(false);
273 | return expect(newString.characters[newString.length() - 1].isWhitespace()).toBe(true);
274 | });
275 | });
276 |
277 | describe('HTMLString.String.trimRight)', function() {
278 | return it('should return a copy of a string converted with whitespaces trimmed from the left.`', function() {
279 | var newString, string;
280 | string = new HTMLString.String(quotes.Kernighan);
281 | newString = string.trimRight();
282 | expect(newString.characters[0].isWhitespace()).toBe(true);
283 | return expect(newString.characters[newString.length() - 1].isWhitespace()).toBe(false);
284 | });
285 | });
286 |
287 | describe('HTMLString.String.unformat()', function() {
288 | return it('should return a sting unformatted (no tags)', function() {
289 | var clearAllString, clearEachString, string;
290 | string = new HTMLString.String(quotes.Kernighan).trim();
291 | clearEachString = string.unformat(0, -1, new HTMLString.Tag('q'), new HTMLString.Tag('span', {
292 | "class": 'question'
293 | }), new HTMLString.Tag('b'));
294 | expect(clearEachString.html()).toBe(string.text());
295 | clearEachString = string.unformat(0, -1, 'q', 'span', 'b');
296 | expect(clearEachString.html()).toBe(string.text());
297 | clearAllString = string.unformat(0, -1);
298 | return expect(clearAllString.html()).toBe(string.text());
299 | });
300 | });
301 |
302 | }).call(this);
303 |
--------------------------------------------------------------------------------
/spec/spec-helper.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 |
4 | }).call(this);
5 |
--------------------------------------------------------------------------------
/src/characters.coffee:
--------------------------------------------------------------------------------
1 | class HTMLString.Character
2 |
3 | # A HTML character
4 |
5 | constructor: (c, tags) ->
6 | @_c = c
7 |
8 | # Entities are stored lower case
9 | if c.length > 1
10 | @_c = c.toLowerCase()
11 |
12 | # Add the tags
13 | @_tags = []
14 | @addTags.apply(@, tags)
15 |
16 | # Read-only properties
17 |
18 | c: () ->
19 | # Return the native character string
20 | return @_c
21 |
22 | isEntity: () ->
23 | # Return true if the character is an entity
24 | return @_c.length > 1
25 |
26 | isTag: (tagName) ->
27 | # Return true if the character is a self-closing tag (e.g br, img),
28 | # optionally a tagName can be specified to match against.
29 |
30 | if @_tags.length == 0 or not @_tags[0].selfClosing()
31 | return false
32 |
33 | if tagName and @_tags[0].name() != tagName
34 | return false
35 |
36 | return true
37 |
38 | isWhitespace: () ->
39 | # Return true if the character represents a whitespace character
40 | return @_c in [' ', '\n', ' '] or @isTag('br')
41 |
42 | tags: () ->
43 | # Return the tags for this character
44 | return (t.copy() for t in @_tags)
45 |
46 | # Methods
47 |
48 | addTags: (tags...) ->
49 | # Add tag(s) to the character
50 | for tag in tags
51 |
52 | # HACK: Fix for IE edge (see issue:
53 | # https://github.com/GetmeUK/ContentTools/issues/258#issuecomment-228931486
54 | #
55 | # ~ Anthony Blackshaw , 28th June 2016
56 | if Array.isArray(tag)
57 | continue;
58 |
59 | # Any self closing tag has to be inserted as the first tag
60 | if tag.selfClosing()
61 | # You can't add a self closing tag to a character that is a tag
62 | if not @isTag()
63 | @_tags.unshift(tag.copy())
64 |
65 | continue
66 |
67 | # Add the tag to the stack
68 | @_tags.push(tag.copy())
69 |
70 | eq: (c) ->
71 | # Return true if the specified character is equal to this character
72 |
73 | # Check characters are the same
74 | if @c() != c.c()
75 | return false
76 |
77 | # Check the number of tags are the same
78 | if @_tags.length != c._tags.length
79 | return false
80 |
81 | # Check tags are the same
82 | tags = {}
83 | for tag in @_tags
84 | tags[tag.head()] = true
85 |
86 | for tag in c._tags
87 | if not tags[tag.head()]
88 | return false
89 |
90 | return true
91 |
92 | hasTags: (tags...) ->
93 | # Return true if the tags specified format this character
94 | tagNames = {}
95 | tagHeads = {}
96 | for tag in @_tags
97 | tagNames[tag.name()] = true
98 | tagHeads[tag.head()] = true
99 |
100 | # If a tag is supplied as a string we test if a tag with that name is
101 | # characters tags, if a tag instance is supplied then we check for
102 | # exact tag.
103 | for tag in tags
104 | if typeof tag == 'string'
105 | if tagNames[tag] == undefined
106 | return false
107 | else
108 | if tagHeads[tag.head()] == undefined
109 | return false
110 |
111 | return true
112 |
113 | removeTags: (tags...) ->
114 | # Remove tag(s) from the character
115 |
116 | # If no tags are provide we remove all tags
117 | if tags.length == 0
118 | @_tags = []
119 | return
120 |
121 | names = {}
122 | heads = {}
123 | for tag in tags
124 | if typeof tag == 'string'
125 | names[tag] = tag
126 | else
127 | heads[tag.head()] = tag
128 |
129 | newTags = []
130 | @_tags = @_tags.filter (tag) ->
131 | if not heads[tag.head()] and not names[tag.name()]
132 | return tag
133 |
134 | copy: () ->
135 | # Return a copy of the character
136 | return new HTMLString.Character(@_c, (t.copy() for t in @_tags))
--------------------------------------------------------------------------------
/src/namespace.coffee:
--------------------------------------------------------------------------------
1 | HTMLString = {}
2 |
3 |
4 | # Export the namespace
5 |
6 | # Browser (via window)
7 | if typeof window != 'undefined'
8 | window.HTMLString = HTMLString
9 |
10 | # Node/Browserify
11 | if typeof module != 'undefined' and module.exports
12 | exports = module.exports = HTMLString
--------------------------------------------------------------------------------
/src/spec/html-string-spec.coffee:
--------------------------------------------------------------------------------
1 | quotes = {
2 | Gates: '''We all
need people who will give us feedback. That's how we improve. 
'''
3 | Kernighan: '''
4 | Everyone knows that debugging is twice as hard as writing a program in
5 | the first place. So if you're as clever as you can be when you write
6 | it, how will you ever debug it?
7 |
'''
8 | Ritchie: '''
9 | I can't recall any difficulty in making the C language definition completely
10 | open - any discussion on the matter tended to mention languages whose
11 | inventors tried to keep tight control, and consequent ill fate.'''
12 | Turing: '''
13 | Machines take me by
surprise with great frequency.
14 | '''
15 | Wozniak: '''
16 | all the best people in life seem to like LINUX.
17 | '''
18 | WozniakNamespaced: '''
19 | all the best people in life seem to like LINUX.
20 | '''
21 | WozniakWhitespace: '''
22 | all the best people in life seem to like LINUX.
23 | '''
24 | AmbiguousAmpersand: '''
25 | & & &foo && && &end
26 | '''
27 | }
28 |
29 | describe 'HTMLString.String()', () ->
30 |
31 | it 'should parse and render text (no HTML)', () ->
32 | string = new HTMLString.String(quotes.Wozniak)
33 | expect(string.text()).toBe quotes.Wozniak
34 |
35 | it 'should parse and render text (no HTML with whitespace preserved)', () ->
36 | string = new HTMLString.String(quotes.WozniakWhitespace, true)
37 | expect(string.text()).toBe quotes.WozniakWhitespace
38 |
39 | it 'should parse and render a string (HTML)', () ->
40 | string = new HTMLString.String(quotes.Turing)
41 | expect(string.html()).toBe quotes.Turing
42 |
43 | # Check names spaced tags are supported
44 | string = new HTMLString.String(quotes.WozniakNamespaced)
45 | expect(string.html()).toBe quotes.WozniakNamespaced
46 |
47 | it 'should parse and render a string (HTML with ambiguous ampersands)', () ->
48 | string = new HTMLString.String(quotes.AmbiguousAmpersand)
49 | expect(string.html()).toBe quotes.AmbiguousAmpersand
50 |
51 | console.log string.html()
52 |
53 |
54 | describe 'HTMLString.String.isWhitespace()', () ->
55 |
56 | it 'should return true if a string consists entirely of whitespace \
57 | characters', () ->
58 |
59 | string = new HTMLString.String("
")
60 | expect(string.isWhitespace()).toBe true
61 |
62 | it 'should return false if a string contains any non-whitespace \
63 | character', () ->
64 |
65 | string = new HTMLString.String(" a
")
66 | expect(string.isWhitespace()).toBe false
67 |
68 |
69 | describe 'HTMLString.String.length()', () ->
70 |
71 | it 'should return the length of a string', () ->
72 | string = new HTMLString.String(quotes.Turing)
73 | expect(string.length()).toBe 52
74 |
75 | describe 'HTMLString.String.preserveWhitespace()', () ->
76 |
77 | it 'should return the true if whitespace is reserved for the string', () ->
78 |
79 | # Not preserved
80 | string = new HTMLString.String(quotes.Turing)
81 | expect(string.preserveWhitespace()).toBe false
82 |
83 | # Preserved
84 | string = new HTMLString.String(quotes.Turing, true)
85 | expect(string.preserveWhitespace()).toBe true
86 |
87 |
88 | describe 'HTMLString.String.capitalize()', () ->
89 |
90 | it 'should capitalize the first character of a string', () ->
91 | string = new HTMLString.String(quotes.Wozniak)
92 | newString = string.capitalize()
93 | expect(newString.charAt(0).c()).toBe 'A'
94 |
95 |
96 | describe 'HTMLString.String.charAt()', () ->
97 |
98 | it 'should return a character from a string at the specified index', () ->
99 | string = new HTMLString.String(quotes.Turing)
100 | expect(string.charAt(18).c()).toBe 'y'
101 |
102 |
103 | describe 'HTMLString.String.concat()', () ->
104 |
105 | it 'should combine 2 or more strings and return a new string', () ->
106 | stringA = new HTMLString.String(quotes.Turing)
107 | stringB = new HTMLString.String(quotes.Wozniak)
108 | newString = stringA.concat(stringB)
109 |
110 | expect(newString.html()).toBe '''Machines take me by
surprise with great frequency.all the best people in life seem to like LINUX.
'''
111 |
112 | describe 'HTMLString.String.contain()', () ->
113 |
114 | it 'should return true if a string contains a substring', () ->
115 | string = new HTMLString.String(quotes.Turing)
116 | substring = new HTMLString.String('take me
')
117 |
118 | expect(string.contains('take me')).toBe true
119 | expect(string.contains(substring)).toBe true
120 |
121 |
122 | describe 'HTMLString.String.endswith()', () ->
123 |
124 | it 'should return true if a string ends with a substring.`', () ->
125 | string = new HTMLString.String(quotes.Turing)
126 | substring = new HTMLString.String(
127 | 'great frequency.
'
128 | )
129 |
130 | expect(
131 | string.endsWith(
132 | "great#{ HTMLString.String.decode(' ') }frequency."
133 | )
134 | ).toBe true
135 | expect(string.endsWith(substring)).toBe true
136 |
137 |
138 | describe 'HTMLString.String.format()', () ->
139 |
140 | it 'should format a selection of characters in a string (add tags)', () ->
141 | string = new HTMLString.String(quotes.Ritchie)
142 | string = string.format(0, -1, new HTMLString.Tag('q'))
143 | string = string.format(
144 | 19,
145 | 29,
146 | new HTMLString.Tag('a', {href: 'http://www.getme.co.uk'}),
147 | new HTMLString.Tag('b')
148 | )
149 |
150 | expect(string.html()).toBe """
151 | I can't recall any difficulty in making the C language definition completely open - any discussion on the matter tended to mention languages whose inventors tried to keep tight control, and consequent ill fate.
152 | """.trim()
153 |
154 |
155 | describe 'HTMLString.String.hasTags()', () ->
156 |
157 | it 'should return true if the string has and characters formatted with the \
158 | specifed tags', () ->
159 |
160 | string = new HTMLString.String(quotes.Kernighan).trim()
161 |
162 | expect(
163 | string.hasTags(
164 | new HTMLString.Tag('q'),
165 | 'b'
166 | )
167 | ).toBe true
168 | expect(string.hasTags(new HTMLString.Tag('q')), true).toBe true
169 |
170 |
171 | describe 'HTMLString.String.indexOf()', () ->
172 |
173 | it 'should return the first position of an substring in another string, \
174 | or -1 if there is no match', () ->
175 |
176 | string = new HTMLString.String(quotes.Kernighan).trim()
177 | substring = new HTMLString.String('debugging
')
178 |
179 | expect(string.indexOf('debugging')).toBe 20
180 | expect(string.indexOf(substring)).toBe 20
181 | expect(string.indexOf(substring, 30)).toBe -1
182 |
183 |
184 | describe 'HTMLString.String.insert()', () ->
185 |
186 | it 'should insert a string into another string and return a new \
187 | string', () ->
188 |
189 | stringA = new HTMLString.String(quotes.Kernighan).trim()
190 | stringB = new HTMLString.String(quotes.Turing)
191 |
192 | # Inherit formatting
193 | newString = stringA.insert(9, stringB)
194 | newString = newString.insert(9 + stringB.length(), ' - new string inserted - ')
195 | expect(newString.html()).toBe '''Everyone Machines take me by
surprise with great frequency. - new string inserted -
knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?
'''
196 |
197 | # Do not inherit formatting
198 | newString = stringA.insert(9, ' - insert unformatted string - ', false)
199 | expect(newString.html()).toBe '''Everyone
- insert unformatted string - knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?
'''
200 |
201 |
202 | describe 'HTMLString.String.lastIndexOf()', () ->
203 |
204 | it 'should return the last position of an substring in another string, \
205 | or -1 if there is no match', () ->
206 |
207 | string = new HTMLString.String(quotes.Kernighan).trim()
208 | substring = new HTMLString.String('debugging
')
209 |
210 | expect(string.lastIndexOf('debugging')).toBe 142
211 | expect(string.lastIndexOf(substring)).toBe 142
212 | expect(string.lastIndexOf(substring, 30)).toBe -1
213 |
214 |
215 | describe 'HTMLString.String.optimize()', () ->
216 |
217 | it 'should optimize the string (in place) so that tags are restacked in \
218 | order of longest running', () ->
219 |
220 | string = new HTMLString.String(quotes.Gates)
221 | string.optimize()
222 |
223 | expect(string.html()).toBe '''We all need people who will give us feedback. That's how we improve. 
'''
224 |
225 |
226 | describe 'HTMLString.String.slice()', () ->
227 |
228 | it 'should extract a section of a string and return a new string', () ->
229 | string = new HTMLString.String(quotes.Kernighan).trim()
230 | newString = string.slice(10, 19)
231 |
232 | expect(newString.html()).toBe '''nows that
'''
233 |
234 |
235 | describe 'HTMLString.String.split()', () ->
236 |
237 | it 'should split a string by the separator and return a list of \
238 | sub-strings', () ->
239 | string = new HTMLString.String(' ', true)
240 | substrings = string.split(' ')
241 | expect(substrings.length).toBe 2
242 | expect(substrings[0].length()).toBe 0
243 | expect(substrings[1].length()).toBe 0
244 |
245 | string = new HTMLString.String(quotes.Kernighan).trim()
246 |
247 | substrings = string.split('a')
248 | expect(substrings.length).toBe 11
249 |
250 | substrings = string.split('@@')
251 | expect(substrings.length).toBe 1
252 |
253 | substrings = string.split(
254 | new HTMLString.String(
255 | 'e
'
256 | )
257 | )
258 | expect(substrings.length).toBe 2
259 |
260 |
261 | describe 'HTMLString.String.startsWith()', () ->
262 |
263 | it 'should return true if a string starts with a substring.`', () ->
264 | string = new HTMLString.String(quotes.Turing)
265 | substring = new HTMLString.String(
266 | 'Machines take
'
267 | )
268 |
269 | expect(string.startsWith("Machines take")).toBe true
270 | expect(string.startsWith(substring)).toBe true
271 |
272 |
273 | describe 'HTMLString.String.substr()', () ->
274 |
275 | it 'should return a subset of a string from an offset for a specified \
276 | length`', () ->
277 |
278 | string = new HTMLString.String(quotes.Kernighan).trim()
279 | newString = string.substr(10, 9)
280 |
281 | expect(newString.html()).toBe '''nows that
'''
282 |
283 |
284 | describe 'HTMLString.String.substring()', () ->
285 |
286 | it 'should return a subset of a string between 2 indexes`', () ->
287 | string = new HTMLString.String(quotes.Kernighan).trim()
288 | newString = string.substring(10, 19)
289 |
290 | expect(newString.html()).toBe '''nows that
'''
291 |
292 |
293 | describe 'HTMLString.String.toLowerCase()', () ->
294 |
295 | it 'should return a copy of a string converted to lower case.`', () ->
296 | string = new HTMLString.String(quotes.Turing)
297 | newString = string.toLowerCase()
298 |
299 | expect(newString.html()).toBe '''machines take me by
surprise with great frequency.
'''
300 |
301 |
302 | describe 'HTMLString.String.toUpperCase()', () ->
303 |
304 | it 'should return a copy of a string converted to upper case.`', () ->
305 | string = new HTMLString.String(quotes.Turing)
306 | newString = string.toUpperCase()
307 |
308 | expect(newString.html()).toBe '''MACHINES TAKE ME BY
SURPRISE WITH GREAT FREQUENCY.
'''
309 |
310 |
311 | describe 'HTMLString.String.trim()', () ->
312 |
313 | it 'should return a copy of a string converted with whitespaces trimmed \
314 | from both ends.`', () ->
315 |
316 | string = new HTMLString.String(quotes.Kernighan)
317 | newString = string.trim()
318 |
319 | expect(newString.characters[0].isWhitespace()).toBe false
320 | expect(
321 | newString.characters[newString.length() - 1].isWhitespace()
322 | ).toBe false
323 |
324 |
325 | describe 'HTMLString.String.trimLeft()', () ->
326 |
327 | it 'should return a copy of a string converted with whitespaces trimmed \
328 | from the left.`', () ->
329 |
330 | string = new HTMLString.String(quotes.Kernighan)
331 | newString = string.trimLeft()
332 |
333 | expect(newString.characters[0].isWhitespace()).toBe false
334 | expect(
335 | newString.characters[newString.length() - 1].isWhitespace()
336 | ).toBe true
337 |
338 |
339 | describe 'HTMLString.String.trimRight)', () ->
340 |
341 | it 'should return a copy of a string converted with whitespaces trimmed \
342 | from the left.`', () ->
343 |
344 | string = new HTMLString.String(quotes.Kernighan)
345 | newString = string.trimRight()
346 |
347 | expect(newString.characters[0].isWhitespace()).toBe true
348 | expect(
349 | newString.characters[newString.length() - 1].isWhitespace()
350 | ).toBe false
351 |
352 |
353 | describe 'HTMLString.String.unformat()', () ->
354 |
355 | it 'should return a sting unformatted (no tags)', () ->
356 | string = new HTMLString.String(quotes.Kernighan).trim()
357 |
358 | # Test clearing tags individually
359 | clearEachString = string.unformat(
360 | 0,
361 | -1,
362 | new HTMLString.Tag('q'),
363 | new HTMLString.Tag('span', {class: 'question'}),
364 | new HTMLString.Tag('b')
365 | )
366 | expect(clearEachString.html()).toBe string.text()
367 |
368 | # Test clearing tags by name
369 | clearEachString = string.unformat(0, -1, 'q', 'span', 'b')
370 | expect(clearEachString.html()).toBe string.text()
371 |
372 | # Test clearing all tags in one go
373 | clearAllString = string.unformat(0, -1)
374 | expect(clearAllString.html()).toBe string.text()
--------------------------------------------------------------------------------
/src/spec/spec-helper.coffee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetmeUK/HTMLString/1a41c2da17fa96331227883b45f68e696edcf8b9/src/spec/spec-helper.coffee
--------------------------------------------------------------------------------
/src/strings.coffee:
--------------------------------------------------------------------------------
1 | class HTMLString.String
2 |
3 | # A string of HTML
4 |
5 | @_parser = null
6 |
7 | constructor: (html, preserveWhitespace=false) ->
8 | @_preserveWhitespace = preserveWhitespace
9 |
10 | if html
11 | # For performance we only initialize the parser once and only the
12 | # first time a HTMLString instances is initialized with html.
13 | if HTMLString.String._parser is null
14 | HTMLString.String._parser = new _Parser()
15 |
16 | @characters = HTMLString.String._parser.parse(
17 | html,
18 | @_preserveWhitespace
19 | ).characters
20 | else
21 | @characters = []
22 |
23 | # Read-only properties
24 |
25 | isWhitespace: () ->
26 | # Return true if the string consists entirely of whitespace characters
27 | for c in @characters
28 | if not c.isWhitespace()
29 | return false
30 | return true
31 |
32 | length: () ->
33 | # Return the length of the string
34 | return @characters.length
35 |
36 | preserveWhitespace: () ->
37 | # Return true if the string is flagged to preserve whitespace
38 | return @_preserveWhitespace
39 |
40 | # Methods
41 |
42 | capitalize: () ->
43 | # Return a copy of the string with the first letter capitalized
44 | newString = @copy()
45 | if newString.length()
46 | c = newString.characters[0]._c.toUpperCase()
47 | newString.characters[0]._c = c
48 | return newString
49 |
50 | charAt: (index) ->
51 | # Return a single character from the string at the specified index
52 | return @characters[index].copy()
53 |
54 | concat: (strings..., inheritFormat) ->
55 | # Combine 2 or more strings and returns a new string. Optionally you
56 | # can specify whether the strings each inherit the previous strings
57 | # format (default true).
58 |
59 | # Check if the last argument was a string or the `inheritFormat` flag
60 | if not (typeof inheritFormat == 'undefined' or
61 | typeof inheritFormat == 'boolean')
62 |
63 | strings.push(inheritFormat)
64 | inheritFormat = true
65 |
66 | # Concat the strings
67 | newString = @copy()
68 | for string in strings
69 |
70 | # Skip empty strings
71 | if string.length == 0
72 | continue
73 |
74 | # If the string is supplied as text then convert it to an unformatted
75 | # string.
76 | tail = string
77 | if typeof string == 'string'
78 | tail = new HTMLString.String(string, @_preserveWhitespace)
79 |
80 | # Inherit the format of the existing string
81 | if inheritFormat and newString.length()
82 | indexChar = newString.charAt(newString.length() - 1)
83 | inheritedTags = indexChar.tags()
84 |
85 | # Don't inherit self-closing tags
86 | if indexChar.isTag()
87 | inheritedTags.shift()
88 |
89 | if typeof string != 'string'
90 | tail = tail.copy()
91 |
92 | for c in tail.characters
93 | c.addTags.apply(c, inheritedTags)
94 |
95 | # Build the new string
96 | for c in tail.characters
97 | newString.characters.push(c)
98 |
99 | return newString
100 |
101 | contains: (substring) ->
102 | # Return true if the string contains the specified sub-string
103 |
104 | # Compare to text
105 | if typeof substring == 'string'
106 | return @text().indexOf(substring) > -1
107 |
108 | # Compare to html string
109 | from = 0
110 | while from <= (@length() - substring.length())
111 | found = true
112 | for c, i in substring.characters
113 | if not c.eq(@characters[i + from])
114 | found = false
115 | break
116 | if found
117 | return true
118 | from++
119 |
120 | return false
121 |
122 | endsWith: (substring) ->
123 | # Return true if the string ends with the specified sub-string
124 |
125 | # Compare to text
126 | if typeof substring == 'string'
127 | return (substring == '' or
128 | @text().slice(-substring.length) == substring)
129 |
130 | # Compare to html string
131 | characters = @characters.slice().reverse()
132 | for c, i in substring.characters.slice().reverse()
133 | if not c.eq(characters[i])
134 | return false
135 |
136 | return true
137 |
138 | format: (from, to, tags...) ->
139 | # Apply the specified tags to a range (from, to) of characters in the
140 | # string.
141 |
142 | # Support for negative indexes on from/to
143 | if to < 0
144 | to = @length() + to + 1
145 |
146 | if from < 0
147 | from = @length() + from
148 |
149 | newString = @copy()
150 | for i in [from...to]
151 | c = newString.characters[i]
152 | c.addTags.apply(c, tags)
153 |
154 | return newString
155 |
156 | hasTags: (tags..., strict) ->
157 | # Return true if the specified tags are applied to some or all
158 | # (strict=true) characters within the string (default false).
159 |
160 | # Check if the last argument was a tag or the `strict` flag
161 | if not (typeof strict == 'undefined' or
162 | typeof strict == 'boolean')
163 | tags.push(strict)
164 | strict = false
165 |
166 | found = false
167 | for c in @characters
168 | if c.hasTags.apply(c, tags)
169 | found = true
170 | else
171 | if strict
172 | return false
173 |
174 | return found
175 |
176 | html: () ->
177 | # Return a HTML version of the string
178 |
179 | html = ''
180 | openTags = []
181 | openHeads = []
182 | closingTags = []
183 |
184 | for c in @characters
185 |
186 | # Close tags
187 | closingTags = []
188 | for openTag in openTags.slice().reverse()
189 | closingTags.push(openTag)
190 | if not c.hasTags(openTag)
191 | for closingTag in closingTags
192 | html += closingTag.tail()
193 | openTags.pop()
194 | openHeads.pop()
195 | closingTags = []
196 |
197 | # Open tags
198 | for tag in c._tags
199 | if openHeads.indexOf(tag.head()) == -1
200 | if not tag.selfClosing()
201 | head = tag.head()
202 | html += head
203 | openTags.push(tag)
204 | openHeads.push(head)
205 |
206 | # If the character is a self-closing tag add it to the HTML after
207 | # all other tags.
208 | if c._tags.length > 0 and c._tags[0].selfClosing()
209 | html += c._tags[0].head()
210 |
211 | html += c.c()
212 |
213 | for tag in openTags.reverse()
214 | html += tag.tail()
215 |
216 | return html
217 |
218 | indexOf: (substring, from=0) ->
219 | # Return the index of the first occurrence of the specified sub-string,
220 | # -1 is returned if no match is found.
221 |
222 | if from < 0
223 | from = 0
224 |
225 | # Find text
226 | if typeof substring == 'string'
227 | return @text().indexOf(substring, from)
228 |
229 | # Find html string
230 | while from <= (@length() - substring.length())
231 | found = true
232 | for c, i in substring.characters
233 | if not c.eq(@characters[i + from])
234 | found = false
235 | break
236 | if found
237 | return from
238 | from++
239 |
240 | return -1
241 |
242 | insert: (index, substring, inheritFormat=true) ->
243 | # Insert the specified sub-string at the specified index
244 |
245 | head = @slice(0, index)
246 | tail = @slice(index)
247 |
248 | if index < 0
249 | index = @length() + index
250 |
251 | # If the string is supplied as text then convert it to an unformatted
252 | # string.
253 | middle = substring
254 | if typeof substring == 'string'
255 | middle = new HTMLString.String(substring, @_preserveWhitespace)
256 |
257 | # Inherit the format of the existing string
258 | if inheritFormat and index > 0
259 | indexChar = @charAt(index - 1)
260 | inheritedTags = indexChar.tags()
261 |
262 | # Don't inherit self-closing tags
263 | if indexChar.isTag()
264 | inheritedTags.shift()
265 |
266 | if typeof substring != 'string'
267 | middle = middle.copy()
268 |
269 | for c in middle.characters
270 | c.addTags.apply(c, inheritedTags)
271 |
272 | # Build the new string
273 | newString = head
274 | for c in middle.characters
275 | newString.characters.push(c)
276 | for c in tail.characters
277 | newString.characters.push(c)
278 | return newString
279 |
280 | lastIndexOf: (substring, from=0) ->
281 | # Return the index of the last occurrence of the specified sub-string,
282 | # -1 is returned if no match is found.
283 |
284 | if from < 0
285 | from = 0
286 |
287 | characters = @characters.slice(from).reverse()
288 |
289 | # The from offset is applied by the slice so we reset it to 0
290 | from = 0
291 |
292 | # Find text
293 | if typeof substring == 'string'
294 |
295 | # Check the this string contains the specified string before
296 | # perform a full search.
297 | if not @contains(substring)
298 | return -1
299 |
300 | # Find text
301 | substring = substring.split('').reverse()
302 | while from <= (characters.length - substring.length)
303 | found = true
304 | skip = 0
305 | for c, i in substring
306 | if characters[i + from].isTag()
307 | skip += 1
308 | if c != characters[skip + i + from].c()
309 | found = false
310 | break
311 | if found
312 | return from
313 | from++
314 |
315 | return -1
316 |
317 | # Find html string
318 | substring = substring.characters.slice().reverse()
319 | while from <= (characters.length - substring.length)
320 | found = true
321 | for c, i in substring
322 | if not c.eq(characters[i + from])
323 | found = false
324 | break
325 | if found
326 | return from
327 | from++
328 |
329 | return -1
330 |
331 | optimize: () ->
332 | # Optimize the string so that tags are stacked in order of run length
333 | openTags = []
334 | openHeads = []
335 | lastC = null # Last character
336 |
337 | for c in @characters.slice().reverse()
338 | c._runLengthMap = {}
339 | c._runLengthMapSize = 0
340 |
341 | # Close tags
342 | closingTags = []
343 | for openTag in openTags.slice().reverse()
344 | closingTags.push(openTag)
345 | if not c.hasTags(openTag)
346 | for closingTag in closingTags
347 | openTags.pop()
348 | openHeads.pop()
349 | closingTags = []
350 |
351 | # Open tags
352 | for tag in c._tags
353 | if openHeads.indexOf(tag.head()) == -1
354 | unless tag.selfClosing()
355 | openTags.push(tag)
356 | openHeads.push(tag.head())
357 |
358 | # Calculate the run length of each tag
359 | for tag in openTags
360 | head = tag.head()
361 |
362 | # If this is the first character set the run length to 1 and
363 | # continue.
364 | if not lastC
365 | c._runLengthMap[head] = [tag, 1]
366 | continue
367 |
368 | # If there isn't one already add an entry for the tag against
369 | # the character.
370 | if not c._runLengthMap[head]
371 | c._runLengthMap[head] = [tag, 0]
372 |
373 | # Check to see if the last character also had this tag applied
374 | # and if so use the run length as a basis.
375 | run_length = 0
376 | if lastC._runLengthMap[head]
377 | run_length = lastC._runLengthMap[head][1]
378 |
379 | # Increment the run length for this character and tag
380 | c._runLengthMap[head][1] = run_length + 1
381 |
382 | lastC = c
383 |
384 | # Order the tags for each character based on their run length
385 | runLengthSort = (a, b) ->
386 | return b[1] - a[1]
387 |
388 | for c in @characters
389 | # Check for characters where there's only a single tag applied in
390 | # which case there's no need to apply a re-order.
391 | len = c._tags.length
392 | if (len > 0 and c._tags[0].selfClosing() and len < 3) or len < 2
393 | continue
394 |
395 | # Build a list of tags and sort them in order or run length
396 | runLengths = []
397 | for tag, runLength of c._runLengthMap
398 | runLengths.push(runLength)
399 | runLengths.sort(runLengthSort)
400 |
401 | # Re-add the characters tags in run length order
402 | for tag in c._tags.slice()
403 | unless tag.selfClosing()
404 | c.removeTags(tag)
405 | c.addTags.apply(c, (t[0] for t in runLengths))
406 |
407 | slice: (from, to) ->
408 | # Extract a section of the string and return a new string
409 | newString = new HTMLString.String('', @_preserveWhitespace)
410 | newString.characters = (c.copy() for c in @characters.slice(from, to))
411 | return newString
412 |
413 | split: (separator='', limit=0) ->
414 | # Split the string by the separator and return a list of sub-strings
415 |
416 | # Build a list of indexes for the separator in the string
417 | lastIndex = 0
418 | count = 0
419 | indexes = [0]
420 | loop
421 | if limit > 0 and count > limit
422 | break
423 | index = @indexOf(separator, lastIndex)
424 | if index == -1
425 | break
426 | indexes.push(index)
427 | lastIndex = index + 1
428 |
429 | indexes.push(@length())
430 |
431 | # Build a list of sub-strings based on the split indexes
432 | substrings = []
433 | for i in [0..(indexes.length - 2)]
434 | start = indexes[i]
435 | if i > 0
436 | start += 1
437 | end = indexes[i + 1]
438 | substrings.push(@slice(start, end))
439 |
440 | return substrings
441 |
442 | startsWith: (substring) ->
443 | # Return true if the string starts with the specified substring
444 |
445 | # Compare to text
446 | if typeof substring == 'string'
447 | return @text().slice(0, substring.length) == substring
448 |
449 | # Compare to html string
450 | for c, i in substring.characters
451 | if not c.eq(@characters[i])
452 | return false
453 |
454 | return true
455 |
456 | substr: (from, length) ->
457 | # Return a subset of a string between from and length, if length isn't
458 | # specified it will default to the end of the string.
459 |
460 | # Check for zero or negative length selections
461 | if length <= 0
462 | return new HTMLString.String('', @_preserveWhitespace)
463 |
464 | if from < 0
465 | from = @length() + from
466 |
467 | if length == undefined
468 | length = @length() - from
469 |
470 | return @slice(from, from + length)
471 |
472 | substring: (from, to) ->
473 | # Return a subset of a string between from and to, if to isn't
474 | # specified it will default to the end of the string.
475 | if to == undefined
476 | to = @length()
477 |
478 | return @slice(from, to)
479 |
480 | text: () ->
481 | # Return a text version of the string
482 | text = ''
483 | for c in @characters
484 |
485 | # Handle tag characters
486 | if c.isTag()
487 |
488 | # Handle line breaks
489 | if c.isTag('br')
490 | text += '\n'
491 | continue
492 |
493 | # Prevent multiple spaces (other than )
494 | if c.c() == ' '
495 | text += c.c()
496 | continue
497 |
498 | text += c.c()
499 |
500 | return @constructor.decode(text)
501 |
502 | toLowerCase: () ->
503 | # Return a copy of the string converted to lower case
504 | newString = @copy()
505 | for c in newString.characters
506 | if c._c.length == 1
507 | c._c = c._c.toLowerCase()
508 | return newString
509 |
510 | toUpperCase: () ->
511 | # Return a copy of the string converted to upper case
512 | newString = @copy()
513 | for c in newString.characters
514 | if c._c.length == 1
515 | c._c = c._c.toUpperCase()
516 | return newString
517 |
518 | trim: () ->
519 | # Return a copy of the string with whitespace trimmed from either end
520 |
521 | # Find the first non-whitespace character
522 | for c, from in @characters
523 | if not c.isWhitespace()
524 | break
525 |
526 | # Find the last non-whitespace character
527 | for c, to in @characters.slice().reverse()
528 | if not c.isWhitespace()
529 | break
530 |
531 | to = @length() - to - 1
532 |
533 | newString = new HTMLString.String('', @_preserveWhitespace)
534 | newString.characters = (c.copy() for c in @characters[from..to])
535 |
536 | return newString
537 |
538 | trimLeft: () ->
539 | # Return a copy of the string with whitespaces trimmed from the left
540 | to = @length() - 1
541 |
542 | # Find the first non-whitespace character
543 | for c, from in @characters
544 | if not c.isWhitespace()
545 | break
546 |
547 | newString = new HTMLString.String('', @_preserveWhitespace)
548 | newString.characters = (c.copy() for c in @characters[from..to])
549 |
550 | return newString
551 |
552 | trimRight: () ->
553 | # Return a copy of the string with whitespaces trimmed from the right
554 | from = 0
555 |
556 | # Find the last non-whitespace character
557 | for c, to in @characters.slice().reverse()
558 | if not c.isWhitespace()
559 | break
560 |
561 | to = @length() - to - 1
562 |
563 | newString = new HTMLString.String('', @_preserveWhitespace)
564 | newString.characters = (c.copy() for c in @characters[from..to])
565 |
566 | return newString
567 |
568 | unformat: (from, to, tags...) ->
569 | # Remove the specified tags from a range (from, to) of characters in
570 | # the string. Specifying no tags will clear all formatting form the
571 | # selection.
572 |
573 | # Support for negative indexes on from/to
574 | if to < 0
575 | to = @length() + to + 1
576 |
577 | if from < 0
578 | from = @length() + from
579 |
580 | newString = @copy()
581 | for i in [from...to]
582 | c = newString.characters[i]
583 | c.removeTags.apply(c, tags)
584 |
585 | return newString
586 |
587 | copy: () ->
588 | # Return a copy of the string
589 | stringCopy = new HTMLString.String('', @_preserveWhitespace)
590 | stringCopy.characters = (c.copy() for c in @characters)
591 | return stringCopy
592 |
593 | # Class methods
594 |
595 | @decode: (string) ->
596 | # Decode entities within the specified string
597 | textarea = document.createElement('textarea')
598 | textarea.innerHTML = string
599 | return textarea.textContent
600 |
601 | @encode: (string) ->
602 | # Encode entities within the specified string
603 | textarea = document.createElement('textarea')
604 | textarea.textContent = string
605 | return textarea.innerHTML
606 |
607 | @join: (separator, strings) ->
608 | # Join a list of strings together
609 | joined = strings.shift()
610 | for s in strings
611 | joined = joined.concat(separator, s)
612 | return joined
613 |
614 |
615 | # Constants
616 |
617 | # Define character sets
618 | ALPHA_CHARS = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz-_$'.split('')
619 | ALPHA_NUMERIC_CHARS = ALPHA_CHARS.concat('1234567890'.split(''))
620 | ATTR_NAME_CHARS = ALPHA_NUMERIC_CHARS.concat([':'])
621 | ENTITY_CHARS = ALPHA_NUMERIC_CHARS.concat(['#'])
622 | TAG_NAME_CHARS = ALPHA_NUMERIC_CHARS.concat([':'])
623 |
624 | # Define the parser states
625 | CHAR_OR_ENTITY_OR_TAG = 1
626 | ENTITY = 2
627 | OPENNING_OR_CLOSING_TAG = 3
628 | OPENING_TAG = 4
629 | CLOSING_TAG = 5
630 | TAG_NAME_OPENING = 6
631 | TAG_NAME_CLOSING = 7
632 | TAG_OPENING_SELF_CLOSING = 8
633 | TAG_NAME_MUST_CLOSE = 9
634 | ATTR_OR_TAG_END = 10
635 | ATTR_NAME = 11
636 | ATTR_NAME_FIND_VALUE = 12
637 | ATTR_DELIM = 13
638 | ATTR_VALUE_SINGLE_DELIM = 14
639 | ATTR_VALUE_DOUBLE_DELIM = 15
640 | ATTR_VALUE_NO_DELIM = 16
641 | ATTR_ENTITY_NO_DELIM = 17
642 | ATTR_ENTITY_SINGLE_DELIM = 18
643 | ATTR_ENTITY_DOUBLE_DELIM = 19
644 |
645 |
646 | class _Parser
647 |
648 | # A HTML parser for creating HTML strings
649 |
650 | constructor: () ->
651 |
652 | # Build the parser FSM
653 | @fsm = new FSM.Machine(@)
654 | @fsm.setInitialState(CHAR_OR_ENTITY_OR_TAG)
655 |
656 | # Character or tag
657 | @fsm.addTransitionAny CHAR_OR_ENTITY_OR_TAG, null, (c) ->
658 | @_pushChar(c)
659 |
660 | @fsm.addTransition '<', CHAR_OR_ENTITY_OR_TAG, OPENNING_OR_CLOSING_TAG
661 | @fsm.addTransition '&', CHAR_OR_ENTITY_OR_TAG, ENTITY
662 | @fsm.addTransition 'END', CHAR_OR_ENTITY_OR_TAG, null
663 |
664 | # Entity
665 | @fsm.addTransitions ENTITY_CHARS, ENTITY, null, (c) ->
666 | @entity += c
667 |
668 | @fsm.addTransition ';', ENTITY, CHAR_OR_ENTITY_OR_TAG, () ->
669 | @_pushChar("{ @entity };")
670 | @entity = ''
671 |
672 | @fsm.addTransitionAny ENTITY, CHAR_OR_ENTITY_OR_TAG, (c) ->
673 | @_pushChar('&')
674 | for c in @entity.split('')
675 | @_pushChar(c)
676 | @entity = ''
677 | @_back()
678 |
679 | @fsm.addTransition 'END', ENTITY, null, () ->
680 | @_pushChar('&')
681 | for c in @entity.split('')
682 | @_pushChar(c)
683 | @entity = ''
684 |
685 | # Opening or closing Tag
686 | @fsm.addTransitions [' ', '\n'], OPENNING_OR_CLOSING_TAG
687 | @fsm.addTransitions ALPHA_CHARS, OPENNING_OR_CLOSING_TAG, OPENING_TAG, () ->
688 | @_back()
689 |
690 | @fsm.addTransition '/', OPENNING_OR_CLOSING_TAG, CLOSING_TAG
691 |
692 | # Opening tag
693 | @fsm.addTransitions [' ', '\n'], OPENING_TAG
694 | @fsm.addTransitions ALPHA_CHARS, OPENING_TAG, TAG_NAME_OPENING, () ->
695 | @_back()
696 |
697 | # Closing tag
698 | @fsm.addTransitions [' ', '\n'], CLOSING_TAG
699 | @fsm.addTransitions ALPHA_CHARS, CLOSING_TAG, TAG_NAME_CLOSING, () ->
700 | @_back()
701 |
702 | # Tag name opening
703 | @fsm.addTransitions TAG_NAME_CHARS, TAG_NAME_OPENING, null, (c) ->
704 | @tagName += c
705 |
706 | @fsm.addTransitions [' ', '\n'], TAG_NAME_OPENING, ATTR_OR_TAG_END
707 | @fsm.addTransition '/', TAG_NAME_OPENING, TAG_OPENING_SELF_CLOSING, () ->
708 | @selfClosing = true
709 |
710 | @fsm.addTransition '>', TAG_NAME_OPENING, CHAR_OR_ENTITY_OR_TAG, () ->
711 | @_pushTag()
712 |
713 | @fsm.addTransitions [' ', '\n'], TAG_OPENING_SELF_CLOSING
714 | @fsm.addTransition '>', TAG_OPENING_SELF_CLOSING, CHAR_OR_ENTITY_OR_TAG, () ->
715 | @_pushTag()
716 |
717 | @fsm.addTransitions [' ', '\n'], ATTR_OR_TAG_END
718 | @fsm.addTransition '/', ATTR_OR_TAG_END, TAG_OPENING_SELF_CLOSING, () ->
719 | @selfClosing = true
720 |
721 | @fsm.addTransition '>', ATTR_OR_TAG_END, CHAR_OR_ENTITY_OR_TAG, () ->
722 | @_pushTag()
723 |
724 | @fsm.addTransitions ALPHA_CHARS, ATTR_OR_TAG_END, ATTR_NAME, () ->
725 | @_back()
726 |
727 | # Tag name closing
728 | @fsm.addTransitions TAG_NAME_CHARS, TAG_NAME_CLOSING, null, (c) ->
729 | @tagName += c
730 |
731 | @fsm.addTransitions [' ', '\n'], TAG_NAME_CLOSING, TAG_NAME_MUST_CLOSE
732 | @fsm.addTransition '>', TAG_NAME_CLOSING, CHAR_OR_ENTITY_OR_TAG, () ->
733 | @_popTag()
734 |
735 | @fsm.addTransitions [' ', '\n'], TAG_NAME_MUST_CLOSE
736 | @fsm.addTransition '>', TAG_NAME_MUST_CLOSE, CHAR_OR_ENTITY_OR_TAG, () ->
737 | @_popTag()
738 |
739 | # Attribute name
740 | @fsm.addTransitions ATTR_NAME_CHARS, ATTR_NAME, null, (c) ->
741 | @attributeName += c
742 |
743 | @fsm.addTransitions [' ', '\n'], ATTR_NAME, ATTR_NAME_FIND_VALUE
744 | @fsm.addTransition '=', ATTR_NAME, ATTR_DELIM
745 | @fsm.addTransitions [' ', '\n'], ATTR_NAME_FIND_VALUE
746 | @fsm.addTransition '=', ATTR_NAME_FIND_VALUE, ATTR_DELIM
747 |
748 | @fsm.addTransitions '>', ATTR_NAME, ATTR_OR_TAG_END, () ->
749 | @_pushAttribute()
750 | @_back()
751 |
752 | @fsm.addTransitionAny ATTR_NAME_FIND_VALUE, ATTR_OR_TAG_END, () ->
753 | @_pushAttribute()
754 | @_back()
755 |
756 | # Attribute delimiter
757 | @fsm.addTransitions [' ', '\n'], ATTR_DELIM
758 | @fsm.addTransition '\'', ATTR_DELIM, ATTR_VALUE_SINGLE_DELIM
759 | @fsm.addTransition '"', ATTR_DELIM, ATTR_VALUE_DOUBLE_DELIM
760 |
761 | # Fix for browsers (including IE) that output quoted attributes
762 | @fsm.addTransitions ALPHA_NUMERIC_CHARS.concat ['&'], ATTR_DELIM, ATTR_VALUE_NO_DELIM, () ->
763 | @_back()
764 |
765 | @fsm.addTransition ' ', ATTR_VALUE_NO_DELIM, ATTR_OR_TAG_END, () ->
766 | @_pushAttribute()
767 |
768 | @fsm.addTransitions ['/', '>'], ATTR_VALUE_NO_DELIM, ATTR_OR_TAG_END, () ->
769 | @_back()
770 | @_pushAttribute()
771 |
772 | @fsm.addTransition '&', ATTR_VALUE_NO_DELIM, ATTR_ENTITY_NO_DELIM
773 | @fsm.addTransitionAny ATTR_VALUE_NO_DELIM, null, (c) ->
774 | @attributeValue += c
775 |
776 | # Attribute value single delimiter
777 | @fsm.addTransition '\'', ATTR_VALUE_SINGLE_DELIM, ATTR_OR_TAG_END, () ->
778 | @_pushAttribute()
779 |
780 | @fsm.addTransition '&', ATTR_VALUE_SINGLE_DELIM, ATTR_ENTITY_SINGLE_DELIM
781 | @fsm.addTransitionAny ATTR_VALUE_SINGLE_DELIM, null, (c) ->
782 | @attributeValue += c
783 |
784 | # Attribte value double delimiter
785 | @fsm.addTransition '"', ATTR_VALUE_DOUBLE_DELIM, ATTR_OR_TAG_END, () ->
786 | @_pushAttribute()
787 |
788 | @fsm.addTransition '&', ATTR_VALUE_DOUBLE_DELIM, ATTR_ENTITY_DOUBLE_DELIM
789 | @fsm.addTransitionAny ATTR_VALUE_DOUBLE_DELIM, null, (c) ->
790 | @attributeValue += c
791 |
792 | # Entity in attribute value
793 | @fsm.addTransitions ENTITY_CHARS, ATTR_ENTITY_NO_DELIM, null, (c) ->
794 | @entity += c
795 |
796 | @fsm.addTransitions ENTITY_CHARS, ATTR_ENTITY_SINGLE_DELIM, null, (c) ->
797 | @entity += c
798 |
799 | @fsm.addTransitions ENTITY_CHARS, ATTR_ENTITY_DOUBLE_DELIM, null, (c) ->
800 | @entity += c
801 |
802 | @fsm.addTransition ';', ATTR_ENTITY_NO_DELIM, ATTR_VALUE_NO_DELIM, () ->
803 | @attributeValue += "{ @entity };"
804 | @entity = ''
805 |
806 | @fsm.addTransition ';', ATTR_ENTITY_SINGLE_DELIM, ATTR_VALUE_SINGLE_DELIM, () ->
807 | @attributeValue += "{ @entity };"
808 | @entity = ''
809 |
810 | @fsm.addTransition ';', ATTR_ENTITY_DOUBLE_DELIM, ATTR_VALUE_DOUBLE_DELIM, () ->
811 | @attributeValue += "{ @entity };"
812 | @entity = ''
813 |
814 | @fsm.addTransitionAny ATTR_ENTITY_NO_DELIM, ATTR_VALUE_NO_DELIM, (c) ->
815 | @attributeValue += '&' + @entity
816 | @entity = ''
817 | @_back()
818 |
819 | @fsm.addTransitionAny ATTR_ENTITY_SINGLE_DELIM, ATTR_VALUE_SINGLE_DELIM, (c) ->
820 | @attributeValue += '&' + @entity
821 | @entity = ''
822 | @_back()
823 |
824 | @fsm.addTransitionAny ATTR_ENTITY_DOUBLE_DELIM, ATTR_VALUE_DOUBLE_DELIM, (c) ->
825 | @attributeValue += '&' + @entity
826 | @entity = ''
827 | @_back()
828 |
829 | # Parsing methods
830 |
831 | _back: () ->
832 | # Move the parsing head back one characters
833 | @head--
834 |
835 | _pushAttribute: () ->
836 | # Remember an attribute for the current tag
837 |
838 | @attributes[@attributeName] = @attributeValue
839 |
840 | # Reset the attribute
841 | @attributeName = ''
842 | @attributeValue = ''
843 |
844 | _pushChar: (c) ->
845 | # Push a character on to the string
846 | character = new HTMLString.Character(c, @tags)
847 |
848 | # Do we need to preserve whitespace?
849 | if @_preserveWhitespace
850 | @string.characters.push(character)
851 | return
852 |
853 | # If the character is whitespace only add it if the last character
854 | # isn't (e.g don't build strings containing extra unused spaces).
855 | if @string.length() and not character.isTag() and
856 | not character.isEntity() and character.isWhitespace()
857 |
858 | lastCharacter = @string.characters[@string.length() - 1]
859 | if lastCharacter.isWhitespace() and not lastCharacter.isTag() and
860 | not lastCharacter.isEntity()
861 | return
862 |
863 | @string.characters.push(character)
864 |
865 | _pushTag: () ->
866 | # Push a tag on to the stack applied to characters
867 |
868 | # Push the Tag on to the stack
869 | tag = new HTMLString.Tag(@tagName, @attributes)
870 | @tags.push(tag)
871 |
872 | # Adding an empty character for self closing tags
873 | if tag.selfClosing()
874 | @._pushChar('')
875 | @tags.pop()
876 |
877 | # Check if the tag was self closed and if not update the FSM to
878 | # close it.
879 | if not @selfClosed and @tagName in HTMLString.Tag.SELF_CLOSING
880 | @fsm.reset()
881 |
882 | # Reset the tag buffers
883 | @tagName = ''
884 | @selfClosed = false
885 | @attributes = {}
886 |
887 | _popTag: () ->
888 | # Pop a tag from the stack applied to characters
889 |
890 | # Balanced the tags
891 | loop
892 | tag = @tags.pop()
893 |
894 | # Push whitespace at the end of a tag out
895 | if @string.length()
896 | character = @string.characters[@string.length() - 1]
897 | if not character.isTag() and
898 | not character.isEntity() and
899 | character.isWhitespace()
900 | character.removeTags(tag)
901 |
902 | break if tag.name() == @tagName.toLowerCase()
903 |
904 | # Reset the tag buffers
905 | @tagName = ''
906 |
907 | # Methods
908 |
909 | parse: (html, preserveWhitespace) ->
910 | # Parse a HTML string
911 | @_preserveWhitespace = preserveWhitespace
912 |
913 | # Prepare for parsing
914 | @reset()
915 | html = @preprocess(html)
916 | @fsm.parser = @
917 |
918 | # Parse the HTML
919 | while @head < html.length
920 | character = html[@head]
921 | try
922 | @fsm.process(character)
923 | catch error
924 | throw new Error("Error at char #{@head} >> #{error}")
925 |
926 | @head++
927 |
928 | @fsm.process('END')
929 |
930 | return @string
931 |
932 | preprocess: (html) ->
933 | # Preprocess a HTML string to prepare it for parsing
934 |
935 | # Normalize line endings
936 | html = html.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
937 |
938 | # Remove any comments
939 | html = html.replace(//g, '')
940 |
941 | # Replace multiple spaces with a single space
942 | if not @_preserveWhitespace
943 | html = html.replace(/\s+/g, ' ')
944 |
945 | return html
946 |
947 | reset: () ->
948 | # Reset the parser ready to parse a HTML string
949 | @fsm.reset()
950 |
951 | # The index of the character we're currently parsing
952 | @head = 0
953 |
954 | # Temporary properties to store the current parser state
955 | @string = new HTMLString.String()
956 | @entity = ''
957 | @tags = []
958 | @tagName = ''
959 | @selfClosing = false
960 | @attributes = {}
961 | @attributeName = ''
962 | @attributeValue = ''
--------------------------------------------------------------------------------
/src/tags.coffee:
--------------------------------------------------------------------------------
1 | class HTMLString.Tag
2 |
3 | # A HTML tag
4 |
5 | constructor: (name, attributes) ->
6 | @_name = name.toLowerCase()
7 | @_selfClosing = HTMLString.Tag.SELF_CLOSING[@_name] == true
8 | @_head = null
9 |
10 | # Copy the attributes
11 | @_attributes = {}
12 | for k, v of attributes
13 | @_attributes[k] = v
14 |
15 | # Constants
16 |
17 | # A list of tags that must self close
18 | @SELF_CLOSING = {
19 | 'area': true,
20 | 'base': true,
21 | 'br': true,
22 | 'hr': true,
23 | 'img': true,
24 | 'input': true,
25 | 'link meta': true,
26 | 'wbr': true
27 | }
28 |
29 | # Read only properties
30 |
31 | head: () ->
32 | # Return the head of the tag
33 |
34 | # For performance we cache the head part of the tag
35 | if not @_head
36 | components = []
37 |
38 | for k, v of @_attributes
39 | if v
40 | components.push("#{ k }=\"#{ v }\"")
41 | else
42 | components.push("#{ k }")
43 | components.sort()
44 |
45 | components.unshift(@_name)
46 |
47 | @_head = "<#{ components.join(' ') }>"
48 |
49 | return @_head
50 |
51 | name: () ->
52 | # Return the tag's name
53 | return @_name
54 |
55 | selfClosing: () ->
56 | # Return true if the tag is self closing
57 | return @_selfClosing
58 |
59 | tail: () ->
60 | # Return the tail of the tag
61 | if @_selfClosing
62 | return ''
63 | return "#{ @_name }>"
64 |
65 | # Methods
66 |
67 | attr: (name, value) ->
68 | # Get/Set the value of an attribute
69 |
70 | if value == undefined
71 | return @_attributes[name]
72 |
73 | # Set the attribute
74 | @_attributes[name] = value
75 |
76 | # Clear the head cache
77 | @_head = null
78 |
79 | removeAttr: (name) ->
80 | # Remove an attribute from the tag
81 |
82 | if @_attributes[name] == undefined
83 | return
84 |
85 | # Remove the attribute
86 | delete @_attributes[name]
87 |
88 | # Clear the head cache
89 | @_head = null
90 |
91 | copy: () ->
92 | # Return a copy of the tag
93 | return new HTMLString.Tag(@_name, @_attributes)
--------------------------------------------------------------------------------