1 | var stringifier = require('./stringifier'),
|
2 | Node,
|
3 | set,
|
4 | isSet,
|
5 | isBlock,
|
6 | filterNode,
|
7 | getNodeClass,
|
8 | getStringifier,
|
9 | getTypeClass,
|
10 | setTypes = {};
|
11 |
|
12 | Node = function (isBlock) {
|
13 | this.extend(isBlock);
|
14 | };
|
15 | Node.prototype.extend = function (isBlock) {
|
16 | var protected = {};
|
17 |
|
18 |
|
19 | protected.parent = null;
|
20 | Object.defineProperty(this, 'parent', {
|
21 | configurable: true,
|
22 | enumerable: true,
|
23 | get: function () {
|
24 | return protected.parent;
|
25 | }
|
26 | });
|
27 |
|
28 |
|
29 | protected.children = {all: [], types: {}};
|
30 | Object.defineProperty(this, 'children', {
|
31 | enumerable: true,
|
32 | get: function () {
|
33 |
|
34 | return protected.children.all.slice();
|
35 | }
|
36 | });
|
37 |
|
38 |
|
39 | Object.defineProperty(this, 'length', {
|
40 | configurable: true,
|
41 | enumerable: true,
|
42 | get: function () {
|
43 | return protected.children.all.length;
|
44 | }
|
45 | });
|
46 |
|
47 | Object.defineProperty(this, 'depth', { get: function () {
|
48 | var node, depth = 0;
|
49 | node = this;
|
50 | while (node = node.parent) {
|
51 | depth++;
|
52 | }
|
53 | return depth;
|
54 | }});
|
55 |
|
56 |
|
57 | this.getDefinition = function () {
|
58 | return {};
|
59 | };
|
60 |
|
61 | this.copy = function (overlay) {
|
62 | var definition = this.getDefinition(),
|
63 | copy,
|
64 | i;
|
65 |
|
66 |
|
67 | for (i in overlay) {
|
68 | if (overlay.hasOwnProperty(i)) {
|
69 | definition[i] = overlay[i];
|
70 | }
|
71 | }
|
72 |
|
73 |
|
74 | copy = new setTypes[this.type](definition);
|
75 |
|
76 |
|
77 | for (i = 0; i < protected.children.all.length; i++) {
|
78 | copy.append(protected.children.all[i].copy());
|
79 | }
|
80 | return copy;
|
81 | };
|
82 |
|
83 |
|
84 | if (!isBlock) {
|
85 |
|
86 | this.each =
|
87 | this.children =
|
88 | this.insertAt =
|
89 | this.append =
|
90 | this.prepend =
|
91 | this.remove = function () {
|
92 | return this;
|
93 | };
|
94 | this.locate = function () {
|
95 | return false;
|
96 | };
|
97 | }
|
98 |
|
99 | else {
|
100 | protected.getChildrenOfType = function (type) {
|
101 | if (!protected.children.types[type]) {
|
102 | protected.children.types[type] = [];
|
103 | }
|
104 | return protected.children.types[type];
|
105 | };
|
106 |
|
107 | this.remove = function (node) {
|
108 | var locations;
|
109 |
|
110 | filterNode(node);
|
111 |
|
112 |
|
113 | locations = this.locate(node);
|
114 | protected.children.all.splice(locations[0], 1);
|
115 | protected.getChildrenOfType(node.type).splice(locations[1], 1);
|
116 |
|
117 |
|
118 | node.parent = undefined;
|
119 |
|
120 | return this;
|
121 | };
|
122 |
|
123 |
|
124 | this.append = function (node) {
|
125 | filterNode(node);
|
126 |
|
127 | Object.defineProperty(node.detach(), 'parent', {configurable: true, value: this});
|
128 | protected.children.all.push(node);
|
129 | protected.getChildrenOfType(node.type).push(node);
|
130 | return this;
|
131 | };
|
132 |
|
133 |
|
134 | this.prepend = function (node) {
|
135 | filterNode(node);
|
136 |
|
137 | Object.defineProperty(node.detach(), 'parent', {configurable: true, value: this});
|
138 | protected.children.all.unshift(node);
|
139 | protected.getChildrenOfType(node.type).unshift(node);
|
140 | return this;
|
141 | };
|
142 |
|
143 | this.insertAt = function (node, index) {
|
144 | var location = [index, 0];
|
145 |
|
146 | filterNode(node);
|
147 |
|
148 | if (index < 0) {
|
149 | index = 0;
|
150 | } else if (index > protected.children.all.length) {
|
151 | index = protected.children.all.length;
|
152 | }
|
153 |
|
154 |
|
155 | if (!index) {
|
156 | return this.prepend(node);
|
157 |
|
158 | } else if (index === protected.children.all.length) {
|
159 | return this.append(node);
|
160 | }
|
161 |
|
162 |
|
163 |
|
164 | if (!protected.children.types[node.type]) {
|
165 | protected.children.types[node.type] = [];
|
166 | location[1] = 0;
|
167 | } else {
|
168 |
|
169 | for (i = location[0] - 1; i > 0; i--) {
|
170 | if (protected.children.all[i].type === node.type) {
|
171 | break;
|
172 | }
|
173 | }
|
174 | location[1] = this.locate(protected.children.all[i])[1] + 1;
|
175 | }
|
176 |
|
177 | protected.children.all.splice(location[0], 0, node);
|
178 | protected.getChildrenOfType(node.type).splice(location[1], 0, node);
|
179 |
|
180 | Object.defineProperty(node.detach(), 'parent', {configurable: true, value: this});
|
181 |
|
182 | return this;
|
183 | };
|
184 |
|
185 | this.children = function (type) {
|
186 | var selection = [], i;
|
187 | if (typeof type === 'string') {
|
188 | selection = protected.children[type] || [];
|
189 | } else if(type instanceof Array) {
|
190 | for (i = 0; i < type.length; i++) {
|
191 | selection.concat(protected.children[type[i]] || []);
|
192 | }
|
193 | } else {
|
194 | for (i in protected.children) {
|
195 | if (protected.children.hasOwnProperty(i)) {
|
196 | selection.concat(protected.children[i]);
|
197 | }
|
198 | }
|
199 | }
|
200 | return protected.children;
|
201 | };
|
202 |
|
203 | this.each = function (type, limit, callback, _loop) {
|
204 | var nodes = protected.children.all.slice(), i, loop;
|
205 |
|
206 | if (typeof type === 'function') {
|
207 | callback = type;
|
208 | limit = 0;
|
209 | type = false;
|
210 | }
|
211 |
|
212 |
|
213 | if (typeof type === 'number') {
|
214 | callback = limit;
|
215 | limit = type;
|
216 | type = false;
|
217 | }
|
218 |
|
219 | if (typeof type === 'string') {
|
220 | if (typeof limit !== 'number') {
|
221 | limit = 0;
|
222 | }
|
223 | }
|
224 |
|
225 |
|
226 | if (typeof callback === 'function') {
|
227 |
|
228 | if (!_loop) {
|
229 | _loop = {
|
230 | cursor: 0,
|
231 | depth: 0
|
232 | };
|
233 | }
|
234 | for (i = 0; i < nodes.length; i++) {
|
235 |
|
236 | loop = {
|
237 | cursor: _loop.cursor,
|
238 | depth: _loop.depth,
|
239 | index: i,
|
240 |
|
241 | descent: true
|
242 | };
|
243 |
|
244 | if (!type || type === nodes[i].type) {
|
245 |
|
246 |
|
247 | if (false === callback.call(this, nodes[i], loop)) {
|
248 | return false;
|
249 | }
|
250 | _loop.cursor++;
|
251 | }
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | if (nodes[i] && limit !== 0 && nodes[i].isBlock && loop.descent) {
|
257 | _loop.depth++;
|
258 |
|
259 | if (false === nodes[i].each(type, limit - 1, callback, _loop)) {
|
260 | return false;
|
261 | }
|
262 | _loop.depth--;
|
263 | }
|
264 | }
|
265 | }
|
266 |
|
267 | return this;
|
268 | };
|
269 |
|
270 | this.locate = function (node) {
|
271 | var all, byType, i, max, found, locations = false;
|
272 |
|
273 | filterNode(node);
|
274 |
|
275 | locations = [2];
|
276 |
|
277 | all = protected.children.all;
|
278 | byType = protected.getChildrenOfType(node.type);
|
279 | max = all.length > byType.length ? all.length : byType.length;
|
280 | found = 0;
|
281 | for (i = 0; i < max; i++) {
|
282 |
|
283 | if (all[i] === node) {
|
284 | locations[0] = i;
|
285 | if (++found === 2) {
|
286 | break;
|
287 | }
|
288 | }
|
289 |
|
290 | if (byType[i] === node) {
|
291 | locations[1] = i;
|
292 | if (++found === 2) {
|
293 | break;
|
294 | }
|
295 | }
|
296 | }
|
297 | return locations;
|
298 | };
|
299 | }
|
300 |
|
301 | this.detach = function () {
|
302 | if (this.parent) {
|
303 | this.parent.remove(this);
|
304 | }
|
305 | return this;
|
306 | };
|
307 |
|
308 | this.before = function (node) {
|
309 | filterNode(node);
|
310 |
|
311 | if (!this.parent) {
|
312 | throw 'Can\t insert a node before a node without parent';
|
313 | }
|
314 |
|
315 | return this.parent.insertAt(node, this.parent.locate(this)[0]);
|
316 | };
|
317 |
|
318 | this.after = function (node) {
|
319 | filterNode(node);
|
320 |
|
321 | if (!this.parent) {
|
322 | throw 'Can\t insert a node after a node without parent';
|
323 | }
|
324 |
|
325 | return this.parent.insertAt(node, this.parent.locate(this)[0] + 1);
|
326 | };
|
327 |
|
328 | return protected;
|
329 | };
|
330 |
|
331 |
|
332 | set = function (constructor, type, isBlock) {
|
333 | var TypedNode;
|
334 |
|
335 |
|
336 | if (isSet(type)) {
|
337 | throw 'Type `' + type + '` was already defined';
|
338 | }
|
339 |
|
340 |
|
341 | TypedNode = function (definition) {
|
342 | var protected;
|
343 |
|
344 |
|
345 | Object.defineProperty(this, '_super', { value: new Node(isBlock) });
|
346 |
|
347 |
|
348 | protected = this._super.extend.call(this, isBlock);
|
349 | constructor.call(this, protected, definition instanceof Object ? definition : {});
|
350 | };
|
351 |
|
352 |
|
353 | Object.defineProperty(TypedNode.prototype, 'type', { enumerable: true, value: type });
|
354 | Object.defineProperty(TypedNode.prototype, 'isBlock', { enumerable: true, value: !!isBlock });
|
355 |
|
356 |
|
357 | setTypes[type] = TypedNode;
|
358 |
|
359 | return TypedNode;
|
360 | };
|
361 |
|
362 | get = function (type, definition) {
|
363 | if (!isSet(type)) {
|
364 | throw 'Unknown type `' + type + '`';
|
365 | }
|
366 | return new setTypes[type](definition);
|
367 | };
|
368 |
|
369 |
|
370 |
|
371 | filterNode = function (node, type) {
|
372 | if (typeof node !== 'object') {
|
373 | throw 'Invalid node';
|
374 | }
|
375 | if (type && node.type !== type) {
|
376 | throw 'Node is not of type `' + type + '`';
|
377 | }
|
378 | if (!isSet(node.type)) {
|
379 | throw 'Unknown type `' + node.type + '`';
|
380 | }
|
381 | if (!(node instanceof setTypes[node.type])) {
|
382 | throw 'Node\'s type and class mismatch';
|
383 | }
|
384 | };
|
385 |
|
386 |
|
387 | isSet = function (type) {
|
388 | return !!setTypes[type];
|
389 | };
|
390 |
|
391 | setStringifier = function (type, func, main) {
|
392 | stringifier.set(type, func, main);
|
393 | };
|
394 | getStringifier = function (type, style) {
|
395 | stringifier.get(type, style);
|
396 | };
|
397 |
|
398 | isBlock = function (type) {
|
399 | return !!setTypes[type] && setTypes[type].prototype.isBlock;
|
400 | };
|
401 | getNodeClass = function (node) {
|
402 | if (isSet(node.type)) {
|
403 | return setTypes[node.type];
|
404 | }
|
405 | };
|
406 | getTypeClass = function (type) {
|
407 | if (isSet(type)) {
|
408 | return setTypes[type];
|
409 | }
|
410 | };
|
411 |
|
412 | exports.filter = filterNode;
|
413 | exports.set = set;
|
414 | exports.get = get;
|
415 | exports.setStringifier = setStringifier;
|
416 | exports.getStringifier = getStringifier;
|
417 |
|
418 | exports.isSet = isSet;
|
419 | exports.isBlock = isBlock;
|
420 | exports.getNodeClass = getNodeClass;
|
421 | exports.getTypeClass = getTypeClass; |
\ | No newline at end of file |