UNPKG

10.6 kBJavaScriptView Raw
1var stringifier = require('./stringifier'),
2 Node,
3 set,
4 isSet,
5 isBlock,
6 filterNode,
7 getNodeClass,
8 getStringifier,
9 getTypeClass,
10 setTypes = {};
11
12Node = function (isBlock) {
13 this.extend(isBlock);
14};
15Node.prototype.extend = function (isBlock) {
16 var protected = {};
17
18 // parent node
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 // children nodes
29 protected.children = {all: [], types: {}};
30 Object.defineProperty(this, 'children', {
31 enumerable: true,
32 get: function () {
33 // return a copy
34 return protected.children.all.slice();
35 }
36 });
37
38 // return number of children
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 // return a node definition for copy
57 this.getDefinition = function () {
58 return {};
59 };
60
61 this.copy = function (overlay) {
62 var definition = this.getDefinition(),
63 copy,
64 i;
65
66 // merge definition with overlay
67 for (i in overlay) {
68 if (overlay.hasOwnProperty(i)) {
69 definition[i] = overlay[i];
70 }
71 }
72
73 // instanciate a copy
74 copy = new setTypes[this.type](definition);
75
76 // copy children
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 // non block node
84 if (!isBlock) {
85 // block "placeholders"
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 // Block node
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 // remove from parent protected.children lists
113 locations = this.locate(node);
114 protected.children.all.splice(locations[0], 1);
115 protected.getChildrenOfType(node.type).splice(locations[1], 1);
116
117 // remove parent reference
118 node.parent = undefined;
119
120 return this;
121 };
122
123 // override protected.children related methods
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 // override protected.children related methods
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 // index is 0
155 if (!index) {
156 return this.prepend(node);
157 // index is just after the last node
158 } else if (index === protected.children.all.length) {
159 return this.append(node);
160 }
161
162 // find position in node's type list
163 // first node of this type
164 if (!protected.children.types[node.type]) {
165 protected.children.types[node.type] = [];
166 location[1] = 0;
167 } else {
168 // search for same type node before this
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 // default limit
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 // loop through nodes if a callback provided
226 if (typeof callback === 'function') {
227 // loop informations
228 if (!_loop) {
229 _loop = {
230 cursor: 0,
231 depth: 0
232 };
233 }
234 for (i = 0; i < nodes.length; i++) {
235 // preserve and expose loop
236 loop = {
237 cursor: _loop.cursor,
238 depth: _loop.depth,
239 index: i,
240 // allow descent for current node
241 descent: true
242 };
243
244 if (!type || type === nodes[i].type) {
245 // return false if callback returns false
246 //throw '';// bug hear
247 if (false === callback.call(this, nodes[i], loop)) {
248 return false;
249 }
250 _loop.cursor++;
251 }
252
253
254 // loop through nested blocks
255 // nodes[i] could have been detached
256 if (nodes[i] && limit !== 0 && nodes[i].isBlock && loop.descent) {
257 _loop.depth++;
258 // return false if eachNode returns false
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 // remove from parent protected.children lists
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 // check if node found in the global list
283 if (all[i] === node) {
284 locations[0] = i;
285 if (++found === 2) {
286 break;
287 }
288 }
289 // check if node found in the type list
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// return a new node class for the given type
332set = function (constructor, type, isBlock) {
333 var TypedNode;
334
335 // prevent type overwrite
336 if (isSet(type)) {
337 throw 'Type `' + type + '` was already defined';
338 }
339
340 // New typed node class
341 TypedNode = function (definition) {
342 var protected;
343
344 // set reference to Node class
345 Object.defineProperty(this, '_super', { value: new Node(isBlock) });
346
347 // Call constructors
348 protected = this._super.extend.call(this, isBlock);
349 constructor.call(this, protected, definition instanceof Object ? definition : {});
350 };
351
352 // set type attribute once for all
353 Object.defineProperty(TypedNode.prototype, 'type', { enumerable: true, value: type });
354 Object.defineProperty(TypedNode.prototype, 'isBlock', { enumerable: true, value: !!isBlock });
355
356 // register node
357 setTypes[type] = TypedNode;
358
359 return TypedNode;
360};
361
362get = function (type, definition) {
363 if (!isSet(type)) {
364 throw 'Unknown type `' + type + '`';
365 }
366 return new setTypes[type](definition);
367};
368
369// throw exception if node is not a node class
370// and is not of given type if provided
371filterNode = 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// return if a node type is prepared or not
387isSet = function (type) {
388 return !!setTypes[type];
389};
390
391setStringifier = function (type, func, main) {
392 stringifier.set(type, func, main);
393};
394getStringifier = function (type, style) {
395 stringifier.get(type, style);
396};
397
398isBlock = function (type) {
399 return !!setTypes[type] && setTypes[type].prototype.isBlock;
400};
401getNodeClass = function (node) {
402 if (isSet(node.type)) {
403 return setTypes[node.type];
404 }
405};
406getTypeClass = function (type) {
407 if (isSet(type)) {
408 return setTypes[type];
409 }
410};
411
412exports.filter = filterNode;
413exports.set = set;
414exports.get = get;
415exports.setStringifier = setStringifier;
416exports.getStringifier = getStringifier;
417
418exports.isSet = isSet;
419exports.isBlock = isBlock;
420exports.getNodeClass = getNodeClass;
421exports.getTypeClass = getTypeClass;
\No newline at end of file