-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsassijs.js
1030 lines (911 loc) · 31.5 KB
/
sassijs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* sassijs 0.4.71 - Syntactically Awesome StyleSheets in JavaScript
*
* Copyright (c) 2009 Casey Rosenthal (github.net/clr)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* $Date: 2009-04-25 Sat Apr 25 15:11:12 -0400 2009 $
* $Rev: 1 more than last time $
*/
// This file contains various helper methods.
// Sugar functions follow, some of which were ispired by
// [ http://www.crockford.com/javascript/inheritance.html ]
Function.prototype.method = function( name, lambda ){
this.prototype[name] = lambda;
return this;
};
// To be used as in ChildClass.inherits( ParentClass )
Function.method( 'inherits', function( parent ) {
var d = {};
var p = ( this.prototype = new parent() );
this.method( '_super', function _super( name ){
if( !( name in d ) ){
d[name] = 0;
}
var f, r, t = d[name];
var v = parent.prototype;
if( t ){
while( t ){
v = v.constructor.prototype;
t -= 1;
}
f = v[name];
} else {
f = p[name];
if( f == this[name] ){
f = v[name];
}
}
d[name] += 1;
r = f.apply( this, Array.prototype.slice.apply( arguments, [1] ) );
d[name] -= 1;
return r;
});
return this;
});
Function.method( 'swiss', function( parent ){
for( var i = 1; i < arguments.length; i++ ){
var name = arguments[i];
this.prototype[name] = parent.prototype[name];
}
return this;
});
Function.prototype.bind = function( object ){
var method = this;
var temp = function() {
return method.apply( object, arguments );
};
return temp;
}
// Camel case is useful for generating dynamic functions.
String.method( 'toCamelCase', function(){
if( this.length < 1 ){
return this;
}
var newString = '';
var parts = this.split( /[^a-zA-Z0-9]/ );
for( var i = 0; i < parts.length; i++ ){
var part = parts[i];
if( part.length > 0 ){
newString += ( part[0].toUpperCase() + part.slice( 1 ) );
}
}
return newString;
});
// This is just to test to make sure that my Psuedo-class structure is sound.
DummyPepperClass = function(){
this.dummyAttr = null;
};
DummyPepperClass.method( 'getDummyAttr', function(){
return this.dummyAttr;
});
DummyPepperClass.method( 'setDummyAttr', function( newValue ){
this.dummyAttr = newValue;
return this;
});
// Surprised that javascript doesn't have a function like .includes?()
Array.method( 'hasElement', function ( element ){
for( var i = 0; i < this.length; i++ ){
if( element == this[i] ){
return true;
}
}
return false;
});
Sassijs = function( template ){
this.template = template;
};
Sassijs.method( 'getTree', function(){
// Lazy-load.
if( this.tree == null ){
this.determineTree();
}
return this.tree;
});
Sassijs.method( 'getTemplate', function(){
return this.template;
});
Sassijs.method( 'determineTree', function(){
this.tree = new SassijsTree( this.getTemplate() );
});
Sassijs.method( 'getCss', function(){
return this.getTree().getRoot().getCss().join( '\n' );
});
Sassijs.method( 'getStyleElement', function(){
return '<style type="text/css"><!--\n' + this.getCss() + '\n--></style>';
});
Sassijs.method( 'writeToDocument', function(){
var style = document.createElement( 'style' );
var definition = this.getTree().getRoot().getCss().join( '\n' );
style.setAttribute( "type", "text/css" );
if( style.styleSheet ){
// IE
style.styleSheet.cssText = definition;
} else {
// Good browsers.
var text = document.createTextNode( definition );
style.appendChild( text );
}
document.getElementsByTagName('head')[0].appendChild( style );
});
Helper = function(){};
// def self.handle_interpolation(str)
// scan = StringScanner.new(str)
// yield scan while scan.scan(/(.*?)(\\*)\#\{/)
// scan.rest
// end
Helper.method( 'handle_interpolation', function( string ){
});
// CAN'T FIND THIS METHOD BEING CALLED ANYWHERE
// def self.balance(scanner, start, finish, count = 0)
// str = ''
// scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
// regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
// while scanner.scan(regexp)
// str << scanner.matched
// count += 1 if scanner.matched[-1] == start
// count -= 1 if scanner.matched[-1] == finish
// return [str.strip, scanner.rest] if count == 0
// end
// end
// This is used to generate an error that reads something like "Inconsistent indentation: space
// used for indentation, but the rest of the document was indented using tab."
Helper.method( 'human_indentation', function( indentation, was ){
was = ( was == true ) ? true : false;
if( indentation.indexOf( '\t' ) == -1 ){
noun = ' space';
} else if( indentation.indexOf( ' ' ) == -1 ){
noun = ' tab';
} else {
return "'" + indentation + ( was ? "' was" : "'" );
}
// Check and see how many spaces or tabs there were for verb agreement.
singular = ( ( indentation.length == 1 ) ? true : false );
if( was ){
was = ( singular ? ' was' : ' were' );
} else {
was = '';
}
return ( indentation.length.toString() + noun + ( singular ? '' : 's' ) + was );
});
Sassijs.prototype.helper = new Helper();
// This class is inspired by the Ruby class of the same name. Essentially, this
// is a string which keeps track of an index and only scans the remainder of the
// string from that index forward. Think of a StringIO.
StringScanner = function( string ){
this.original = string;
this.index = 0;
};
StringScanner.method( 'advanceIndex', function( advance ){
this.index = this.index + advance;
return this;
});
StringScanner.method( 'getCurrent', function(){
return this.original.slice( this.index );
});
StringScanner.method( 'scan', function( pattern ){
var newIndex = this.getCurrent().search( pattern );
if( newIndex >= 0 ){
var result = this.getCurrent().match( pattern );
this.advanceIndex( newIndex + result.toString().length );
return result.toString();
}
return false;
});
StringScanner.method( 'scanIndex', function( pattern ){
var newIndex = this.getCurrent().search( pattern );
if( newIndex >= 0 ){
var result = this.getCurrent().match( pattern );
this.advanceIndex( newIndex + result.toString().length );
return this.index;
}
return false;
});
SassijError = function( newComment, newLineNumber, supressRaise ){
this.setComment( newComment == undefined ? "Unspecified Error" : newComment );
this.setLineNumber( newLineNumber == undefined ? "None" : newLineNumber );
if( supressRaise != true ){
throw( this.toString() );
}
}
SassijError.method( 'getComment', function(){
return this.comment;
});
SassijError.method( 'getLineNumber', function(){
return this.lineNumber;
});
SassijError.method( 'setComment', function( newValue ){
this.comment = newValue;
return this;
});
SassijError.method( 'setLineNumber', function( newValue ){
this.lineNumber = newValue;
return this;
});
SassijError.method( 'shout', function(){
alert( this.toString() );
});
SassijError.method( 'toString', function(){
return ( this.getComment() + ": " + this.getLineNumber() );
});
SassijEnvironment = function(){
this.parent = null;
this.vars = null;
this.mixins = null;
}
SassijsTreeNode = function( line, newOptions ){
this.children = [];
if( newOptions ){
this.options = newOptions;
} else {
this.options = { style: 'none' };
}
this.style = this.options.style;
this.line = line;
};
// It's an odd kind of tree, but we can only traverse out,
// and these poor children will never know their parents.
SassijsTreeNode.method( 'getChildren', function(){
return this.children;
});
// It's an odd kind of tree, but we can only traverse out,
// and these poor children will never know their parents.
SassijsTreeNode.method( 'hasChildren', function(){
return( this.children.length > 0 );
});
SassijsTreeNode.method( 'getLine', function(){
return this.line;
});
SassijsTreeNode.method( 'setChildren', function( newValue ){
this.children = newValue;
return this;
});
SassijsTreeNode.method( 'appendChild', function( child ){
if( error = this.isInvalidChild( child ) ) {
throw( new SassijError( error, child.line ) );
}
this.children.push( child );
return this;
});
SassijsTreeNode.method( 'getLastChild', function() {
return this.getChildren()[ this.getChildren().length - 1 ];
});
SassijsTreeNode.method( 'getSpecies', function(){
return this.species;
});
// This method should be overridden by subclasses to return an error message
// if the given child node is invalid,
// and false or nil otherwise.
SassijsTreeNode.method( 'isInvalidChild', function( child ){
return false;
});
// This is a funny function, because it has to take the tree and flip
// it around into an array such that each leaf is a new array entry,
// with its lineage prepended. The catch is that nodes don't know
// their parents, so this has to be done in one large recursive crawl.
SassijsTreeNode.method( 'getCss', function(){
var css = '';
var ruleChildren = [];
var attributeChildren = [];
for( var i = 0; i < this.getChildren().length; i++ ){
switch( this.getChildren()[ i ].getSpecies() ){
case 'rule':
ruleChildren = ruleChildren.concat( this.getChildren()[ i ].getCss() );
break;
case 'attribute':
attributeChildren.push( this.getChildren()[ i ].getCss() );
break;
default:
break;
}
}
if( attributeChildren.length > 0 ){
ruleChildren.unshift( "{ " + attributeChildren.join( ' ' ) + " }" );
}
if( ruleChildren.length > 0 ){
// Rules should always have ruleChildren, either as subchildren or as
// a collection of attributes.
for( var i = 0; i < ruleChildren.length; i++ ){
prefixRule = this.getLine().getSyntax();
if( prefixRule.length > 0 ){
// Sometimes CSS rules are compressed like 'a span, a div' and we
// need to expand those children here and prepend the parent to
// both sub-rules, span and div in the above example.
if( ruleChildren[ i ].indexOf( ',' ) ){
ruleChildrenParts = ruleChildren[ i ].split( ', ' );
for( var j = 0; j < ruleChildrenParts.length; j++ ){
ruleChildrenParts[ j ] = prefixRule + " " + ruleChildrenParts[ j ];
}
ruleChildren[ i ] = ruleChildrenParts.join( ', ' );
} else {
ruleChildren[ i ] = prefixRule + " " + ruleChildren[ i ];
}
}
}
return ruleChildren;
} else {
// This is returned by an attribute.
// return this.getLine().getSyntax();
}
});
// There are several cases where the children of a node are compressed
// in a short-hand. When this node is a Rule, it needs to have its
// children duplicated and appended to the split Rule in order to be
// uncompressed.
SassijsTreeNode.method( 'expandChildren', function(){
});
SassijsTreeNodeAttribute = function( line ){
this.children = [];
this.species = 'attribute';
this.line = line;
// The regex that matches and extracts data from attributes
// of the form <tt>:name attr</tt>.
this.regex = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/;
// The regex that matches and extracts data from attributes
// of the form <tt>name: attr</tt>.
this.regexAlternate = /^([^\s=:]+)(\s*=|:)(?:\s+|$)(.*)/;
};
SassijsTreeNodeAttribute.inherits( SassijsTreeNode );
SassijsTreeNodeAttribute.method( 'getParts', function(){
if( this.parts != null ){
return this.parts;
}
this.parts = this.getLine().getSyntax().match( this.regex );
// If we don't get a match, try the alternate syntax, which is much closer
// to original CSS. This is one of the only places where we may differ
// from the Ruby SASS implementation, because they don't appear to allow
// mixing of the normal and alternate sytax.
if( this.parts == null ){
this.parts = this.getLine().getSyntax().match( this.regexAlternate );
}
return this.parts;
});
SassijsTreeNodeAttribute.method( 'getKey', function(){
// Lazy load the key.
if( this.key == null ){
this.key = this.getParts()[1];
}
return this.key;
});
SassijsTreeNodeAttribute.method( 'getValue', function(){
// The character that designates that an attribute should be assigned
// to a SassScript expression.
var expressionChar = '!';
// Lazy load the value.
if( this.value != null ){
return this.value;
}
if( this.getParts()[2] == expressionChar ){
// Expressions are for evaluation.
} else {
this.value = this.getParts()[3];
}
return this.value;
});
SassijsTreeNodeAttribute.method( 'getCss', function(){
// If an Attribute node has children, then those children have
// to be Attribute nodes as well, because that means that this
// node is a family definition of attributes, like font-size,
// font-weight, font-style, etc.
if( this.hasChildren() ){
var attributeChildren = [];
for( var i = 0; i < this.getChildren().length; i++ ){
var child = this.getChildren()[ i ];
if( child.getSpecies() != 'attribute' ){
// Raise error.
}
attributeChildren.push( this.getKey() + "-" + child.getKey() + ": " + child.getValue() + ";" );
}
return attributeChildren.join( ' ' );
} else {
return this.getKey() + ": " + this.getValue() + ";";
}
});
SassijsTreeNodeComment = function( line ){
this.children = [];
this.species = 'comment';
this.line = line;
};
SassijsTreeNodeComment.inherits( SassijsTreeNode );
SassijsTreeNodeComment.method( 'getCss', function(){
var css = this.getLine().getSyntax() + " */";
return css;
});
SassijsTreeNodeDirective = function( line ){
this.children = [];
this.species = 'directive';
this.line = line;
};
SassijsTreeNodeDirective.inherits( SassijsTreeNode );
SassijsTreeNodeDirective.method( 'getCss', function(){
// directives of the form <tt>@import file</tt>.
var directive = /^@([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/;
parts = this.getLine().getSyntax().match( directive );
// If we don't get a match, we should raise an error?
if( parts == null ){
//raise error, I guess;
}
var css = '@' + parts[1] + ' url(' + parts[3] + '.css);';
return css;
});
SassijsTreeNodeMixinDefinition = function( line ){
this.species = 'mixinDefinition';
this.line = line;
};
SassijsTreeNodeMixinDefinition.inherits( SassijsTreeNode );
// In this special case, we want the node to return its children
// rendered as CSS. This node will be removed from the tree
// root in pre-processing.
SassijsTreeNodeMixinDefinition.method( 'getCss', function(){
var css = '';
var children = this.getChildren();
for( i = 0; i < children.length; i++ ){
css += children[ i ].getCss();
}
return css;
});
SassijsTreeNodeMixinInclude = function( ){
this.species = 'mixinInclude';
};
SassijsTreeNodeMixinInclude.inherits( SassijsTreeNode );
SassijsTreeNodeRule = function( line ){
this.children = [];
this.species = 'rule';
this.line = line;
};
SassijsTreeNodeRule.inherits( SassijsTreeNode );
SassijsTreeNodeRule.method( 'determineMitosis', function(){
var css = '';
var attributes = [];
for( var i = 0; i < this.getChildren().length; i++ ){
switch( this.getChildren()[ i ].getSpecies() ){
case 'rule':
css += this.getLine().getSyntax() + ' ' + this.getChildren()[ i ].getCss();
case 'attribute':
attributes.push( this.getChildren()[ i ].getCss() );
}
}
if( attributes.length > 0 ){
return this.getLine().getSyntax() + " { " + attributes.join( '; ' ) + " }";
} else {
return css;
}
});
/*
SassijsTreeNodeRule.method( 'getCss', function(){
var css = '';
var attributes = [];
for( var i = 0; i < this.getChildren().length; i++ ){
switch( this.getChildren()[ i ].getSpecies() ){
case 'rule':
css += this.getLine().getSyntax() + ' ' + this.getChildren()[ i ].getCss();
case 'attribute':
attributes.push( this.getChildren()[ i ].getCss() );
}
}
if( attributes.length > 0 ){
return this.getLine().getSyntax() + " { " + attributes.join( '; ' ) + " }";
} else {
return css;
}
});
*/
SassijsTreeNodeVariable = function( line ){
this.children = [];
this.species = 'variable';
this.line = line;
// The regex that matches and extracts data from attributes
// of the form <tt>!name = value</tt>.
this.regex = /^!([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/;
};
SassijsTreeNodeVariable.inherits( SassijsTreeNode );
SassijsTreeNodeVariable.method( 'getParts', function(){
if( this.parts != null ){
return this.parts;
}
this.parts = this.getLine().getSyntax().match( this.regex );
// If we don't get a match, try the alternate syntax, which is much closer
// to original CSS. This is one of the only places where we may differ
// from the Ruby SASS implementation, because they don't appear to allow
// mixing of the normal and alternate sytax.
if( this.parts == null ){
this.parts = this.getLine().getSyntax().match( this.regexAlternate );
}
return this.parts;
});
SassijsTreeNodeVariable.method( 'getKey', function(){
// Lazy load the key.
if( this.key == null ){
this.key = this.getParts()[1];
}
return this.key;
});
// Should this method discriminate between text and expressions, or
// just pass it to an expression and let that evaluate it as text?
SassijsTreeNodeVariable.method( 'getValue', function(){
// The character that designates that an attribute should be assigned
// to a SassScript expression.
var expressionChar = '!';
// Lazy load the value.
if( this.value != null ){
return this.value;
}
if( this.getParts()[2] == expressionChar ){
// Expressions are for evaluation.
} else {
this.value = this.getParts()[3];
}
return this.value;
});
// BIG TODO: Figure out wtf to do with em and ens.
SassijsExpressionUnit = function( string ){
this.unit = string;
this.acceptableChars = [ '\\*', '\\/', '\\%', '\\+', '\\-', '\\(', '\\)', '\\=\\=', '\\!\\=', '\\>\\=', '\\<\\=', '\\>', '\\<' ];
this.sizeSpecies = [ 'in', 'cm', 'pc', 'mm', 'pt', 'px' ];
this.sizeConversion_table = [
[ 1, 2.54, 6, 25.4, 72, 72 ], // in
[ null, 1, 2.36220473, 10, 28.3464567, 28.3464567 ], // cm
[ null, null, 1, 4.23333333, 12, 12 ], // pc
[ null, null, null, 1, 2.83464567, 2.83464567 ], // mm
[ null, null, null, null, 1, 1 ], // pt
[ null, null, null, null, null, 1 ] // px
];
this.sizeConversionToPoints = { in: 72, cm: 28.3464567, pc: 12, mm: 2.83464567, px: 1, pt: 1 };
this.termRegex = /([\d\.]+)([\w]*)/;
this.termsAndOperatorsRegex = /[\d\.\w]+|\+|\*|\/|%|-|\(|\)|==|!=|>=|<=|>|</g;
};
SassijsExpressionUnit.method( 'getSpecies', function( sample ){
// Break the sample into terms, and look at the first one to
// determine what unit type. The default is text.
terms = sample.match( /(\w|\d|\.)+/g );
firstTerm = terms[0];
sizeRegex = new RegExp( '(\d|\.)+' + this.sizeSpecies.join( '|' ) );
if( sizeRegex.exec( firstTerm ) ){
return 'size';
}
});
// This function assumes that we have already identified the unit as a size.
SassijsExpressionUnit.method( 'getSizeInPoints', function( term ){
var unitAndSpecies = term.match( this.termRegex );
if( !unitAndSpecies ){
return false;
}
var unit = unitAndSpecies[1];
var species = unitAndSpecies[2];
return( unit * this.sizeConversionToPoints[ species ] );
});
// You kids mess around too much. Just convert everything into points.
// This function assumes that we have already identified the expression in size.
SassijsExpressionUnit.method( 'getNormalizedSize', function( sample ){
// Break the sample into terms, and look at the first one to
// determine what unit type. The default is text.
// var termsAndOperatorsRegex = new RegExp( '(\\d|\\.|\\w)+|' + this.acceptableChars.join( '|' ) );
//var termsAndOperatorsRegex = /(\d|\.|\w)+|\*|\/|%|\+|-|\(|\)|==|!=|>=|<=|>|</g;
var termsAndOperators = sample.match( this.termsAndOperatorsRegex );
var normalized = [];
for( var i = 0; i < termsAndOperators.length; i++ ){
var unitInPoints = this.getSizeInPoints( termsAndOperators[ i ] );
if( unitInPoints ){
normalized.push( unitInPoints );
} else {
normalized.push( termsAndOperators[ i ] );
}
}
return normalized.join( ' ' ); // Just for readability in development.
});
// This class evaluates variables and expressions, but not
// mixins, which are appended to trees at an earlier stage
// in pre-processing.
SassijsExpression = function( string, variables ){
this.string = string;
// variables are the databank prepolated with SassijsVariables.
this.variables = variables;
this.parts = [];
this.identifiedParts = [];
this.acceptableChars = [ '*', '/', '%', '+', '-', '(', ')', '==', '!=', '>=', '<=', '>', '<' ];
this.operators = [ '*', '/', '%', '+', '-' ];
this.first_operators = [ '*', '/', '%' ];
this.second_operators = [ '+', '-' ];
};
SassijsExpression.method( 'getExpression', function(){
return this.string.slice( 1, ( this.string.length ) );
});
SassijsExpression.method( 'getValue', function(){
var sassijsExpressionUnit = new SassijsExpressionUnit();
var species = sassijsExpressionUnit.getSpecies( this.getExpression() );
switch( species ){
case 'size':
var expressionString = sassijsExpressionUnit.getNormalizedSize( this.getExpression() );
var points = eval( expressionString );
return points + 'pt';
default:
return this.getExpression();
}
});
// This class wraps Sassijs and simply loads a file into the template variable. We
// assume the presence of a loading DOM element 'document'.
SassijsFile = function( url, async ){
this.loaded = false;
this.template = null;
this.fetch( url, async );
}
SassijsFile.inherits( Sassijs );
SassijsFile.method( 'isLoaded', function(){
return this.loaded;
});
SassijsFile.method( 'fetch', function( url, async ){
// Asynchronous transfer is the default.
if( async != false ){
async = true;
}
if( window.XMLHttpRequest ){
req = new XMLHttpRequest();
} else if( window.ActiveXObject ){
req = new ActiveXObject("Microsoft.XMLHTTP");
}
// This is an asynch call that won't freeze up the browser.
if( ( async ) && ( req != undefined ) ){
var that = this;
req.onreadystatechange = function(){
if( req.readyState == 4 ) { // only if req is "loaded"
if( ( req.status == 200 ) || ( req.status == 0 ) ) { // only if "OK" (0 is status for locally served files)
this.loaded = true;
this.template = req.responseText;
} else {
this.loaded = false;
}
}
};
req.open( "GET", url );
req.send( "" );
// This is a synchronous call that holds up the browser, which is
// necessary for qUnit to work properly, for example.
} else {
var that = this;
req.open( "GET", url, false );
req.send( "" );
if( req.readyState == 4 ) { // only if req is "loaded"
if( ( req.status == 200 ) || ( req.status == 0 ) ) { // only if "OK" (0 is status for locally served files)
this.loaded = true;
this.template = req.responseText;
} else {
this.loaded = false;
}
}
}
});
// This class populates an object that establishes the properties
// of one syntax element, and hints at some of its context in the tree.
SassijsLine = function( string, tab, lineNumber ){
this.lineNumber = lineNumber;
this.syntax = '';
this.tabCount = 0;
// A reference to our context.
// this.sassijs = sassijs;
// Count the tabs.
findTab = new RegExp( "^" + tab );
var potentialSyntax = string;
while( result = findTab.exec( potentialSyntax ) ){
potentialSyntax = RegExp.rightContext;
this.tabCount++;
}
// Strip extra whitespace from the rule.
this.syntax = string.replace( /[\s|\t]+/g, ' ' ).replace( /^\s+|\s+$/g, '' );
}
SassijsLine.method( 'getSyntax', function(){
return this.syntax;
});
//SassijsLine.method( 'getSassijs', function(){
// return this.sassijs;
//});
SassijsLine.method( 'getTabCount', function(){
return this.tabCount;
});
SassijsLine.method( 'getLineNumber', function(){
return this.lineNumber;
});
// This method implements the Factory Pattern that determines
// what kind of Node we are dealing with.
SassijsLine.method( 'determineNode', function(){
// The character that begins a CSS attribute.
var attributeChar = ':';
// The character that designates that an node SassijsExpression.
var variableChar = '!';
// The character that designates the beginning of a comment, either Sass or CSS.
var commentChar = '/';
// The character that follows the general COMMENT_CHAR and designates a Sass comment,
// which is not output as a CSS comment.
var sassCommentChar = '/';
// The character that follows the general COMMENT_CHAR and designates a CSS comment,
// which is embedded in the CSS document.
var cssCommentChar = '*';
// The character used to denote a compiler directive.
var directiveChar = '@';
// Designates a non-parsed rule.
var escapeChar = '\\';
// Designates block as mixin definition rather than CSS rules to output
var mixinDefinitionChar = '=';
// Includes named mixin declared using MIXIN_DEFINITION_CHAR
var mixinIncludeChar = '+';
// The regex that matches attributes of the form <tt>name: attr</tt>.
var attributeAlternateMatcher = /^[^\s:]+\s*[=:](\s|$)/;
switch( this.getSyntax()[0] ){
case attributeChar:
// CSS3 pseudo-elements begin with '::'
if( this.getSyntax()[1] != ':' ){
return new SassijsTreeNodeAttribute( this );
} else {
return new SassijsTreeNodeRule( this );
}
// case expressionChar:
// return new SassijsScript( this );
case variableChar:
return new SassijsTreeNodeVariable( this );
case commentChar:
// Two kinds of comments: SASS comments and CSS comments.
if( this.getSyntax()[ 1 ] == sassCommentChar ){
return null;
} else if( this.getSyntax()[ 1 ] == cssCommentChar ){
return new SassijsTreeNodeComment( this );
} else {
return new SassijsTreeNodeRule( this );
}
case directiveChar:
return new SassijsTreeNodeDirective( this );
case escapeChar:
//??? return new SassijsTreeNodeEscape();
// when ESCAPE_CHAR
// Tree::RuleNode.new(line.text[1..-1], @options)
case mixinDefinitionChar:
return new SassijsTreeNodeMixinDefinition( this );
case mixinIncludeChar:
//????
// if line.text[1].nil?
// Tree::RuleNode.new(line.text, @options)
// else
// parse_mixin_include(line, root)
// end
return new SassijsTreeNodeMixinInclude( this );
default:
if( this.getSyntax().match( attributeAlternateMatcher ) ){
return new SassijsTreeNodeAttribute( this );
//??? parse_attribute(line, ATTRIBUTE_ALTERNATE)
} else {
return new SassijsTreeNodeRule( this );
}
}
});
// This class takes a sass template and breaks it down into children nodes.
SassijsTree = function( template ){
this.template = template.replace( /\r|\n|\r\n/g, "\n");
this.determineTab();
var rootLine = new SassijsLine( '', this.getTab(), 0 );
this.root = rootLine.determineNode();
this.determineLines();
this.determineNodes();
}
SassijsTree.method( 'getTab', function(){
return this.tab;
});
SassijsTree.method( 'setTab', function( newValue ){
this.tab = newValue;
return this;
});
SassijsTree.method( 'getRoot', function(){
return this.root;
});
SassijsTree.method( 'getLines', function(){
return this.lines;
});
SassijsTree.method( 'getTemplate', function(){
return this.template;
});
SassijsTree.method( 'getTemplateLines', function(){
return this.getTemplate().split( '\n' );
});
// The tab is the form of indentation, which can be tabs or spaces,
// but not both.
SassijsTree.method( 'determineTab', function(){
var lines = this.getTemplateLines();
for( var i = 0; i < lines.length; i++ ){
var firstTab = lines[ i ].match( /^(\s|\t)+/ );
if( firstTab ){
this.setTab( firstTab[0] );
break;
}
}
});
// These are just the individual lines of the syntax template. The
// lines themselves determine their own Node Species before being
// converted to Nodes.
SassijsTree.method( 'determineLines', function(){
this.lines = new Array();
var lines = this.getTemplateLines();
for( var i = 0; i < lines.length; i++ ){
this.lines.push( new SassijsLine( lines[ i ], this.getTab(), ( i + 1 ) ) );
}
});
// Here we take the previously determined lines and parse them into a
// tree of nodes based on the tab length and context relative to previous
// nodes. This function is currently not optimized. We would have to
// take a few cases and compare parsing speed in a for-loop, string-
// scanner style parsing, and while-loop, etc.
SassijsTree.method( 'determineNodes', function(){
var previousTabCount = 0;
var previousNode = this.getRoot();
for( var i = 0; i < this.getLines().length; i++ ){
var line = this.getLines()[ i ];
// Dump empty rules.
if( line.getSyntax().length > 0 ){
var newNode = line.determineNode();
// Comments don't return anything that we want in our tree.
if( newNode != null ){
// This node is a sibling to the previous node.
if( line.getTabCount() == previousTabCount ){
// Get the Parent by procedural induction. The last node at the
// previous depth is the parent.
parent = this.getRoot();
for( var j = 0; j < previousTabCount; j++ ){
var parent = parent.getLastChild();
}
parent.appendChild( newNode );
}
// This node is a child to the previous node.
if( line.getTabCount() > previousTabCount ){
// Insert error check here that it can't be more than 1 tab count greater.
previousNode.appendChild( newNode );
}
// This node is a child to something above the previous node.
if( line.getTabCount() < previousTabCount ){
// Get the Parent by procedural induction.
parent = this.getRoot();
for( var j = 0; j < line.getTabCount(); j++ ){
var parent = parent.getLastChild();
}
parent.appendChild( newNode );
}
previousTabCount = newNode.getLine().getTabCount();
previousNode = newNode;
}
}
}
});
/*
SassijsTreeNodeRule.method( 'getCss', function(){
var cssLines = [];
for( var i = 0; i < this.root.getChildren().length; i++ ){
switch( this.getChildren()[ i ].getSpecies() ){
case 'rule':
css += this.getLine().getSyntax() + ' ' + this.getChildren()[ i ].getCss();
case 'attribute':
attributes.push( this.getChildren()[ i ].getCss() );
}
}
if( attributes.length > 0 ){
return this.getLine().getSyntax() + " { " + attributes.join( '; ' ) + " }";