Initial Commit
This commit is contained in:
		
						commit
						3671fc067f
					
				|  | @ -0,0 +1,182 @@ | ||||||
|  | import { HTMLColorParser } from './HTMLColorParser'; | ||||||
|  | 
 | ||||||
|  | export class Colors | ||||||
|  | { | ||||||
|  |   static lighten( color:string, value:number ) | ||||||
|  |   { | ||||||
|  |     let htmlColor = HTMLColorParser.fromString( color ); | ||||||
|  | 
 | ||||||
|  |     return htmlColor.lighten( value ) + ""; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static saturate( color:string, value:number ) | ||||||
|  |   { | ||||||
|  |     let htmlColor = HTMLColorParser.fromString( color ); | ||||||
|  | 
 | ||||||
|  |     return htmlColor.saturate( value ) + ""; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static shiftHue( color:string, value:number ) | ||||||
|  |   { | ||||||
|  |     let htmlColor = HTMLColorParser.fromString( color ); | ||||||
|  | 
 | ||||||
|  |     return htmlColor.shiftHue( value ) + ""; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fade( color:string, value:number ) | ||||||
|  |   { | ||||||
|  |     let htmlColor = HTMLColorParser.fromString( color ); | ||||||
|  | 
 | ||||||
|  |     return htmlColor.fade( value ) + ""; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static readonly black = HTMLColorParser.fromStringHexColor( "#000000" ); | ||||||
|  |   static readonly silver = HTMLColorParser.fromStringHexColor( "#c0c0c0" ); | ||||||
|  |   static readonly gray = HTMLColorParser.fromStringHexColor( "#808080" ); | ||||||
|  |   static readonly white = HTMLColorParser.fromStringHexColor( "#ffffff" ); | ||||||
|  |   static readonly maroon = HTMLColorParser.fromStringHexColor( "#800000" ); | ||||||
|  |   static readonly red = HTMLColorParser.fromStringHexColor( "#ff0000" ); | ||||||
|  |   static readonly purple = HTMLColorParser.fromStringHexColor( "#800080" ); | ||||||
|  |   static readonly fuchsia = HTMLColorParser.fromStringHexColor( "#ff00ff" ); | ||||||
|  |   static readonly green = HTMLColorParser.fromStringHexColor( "#008000" ); | ||||||
|  |   static readonly lime = HTMLColorParser.fromStringHexColor( "#00ff00" ); | ||||||
|  |   static readonly olive = HTMLColorParser.fromStringHexColor( "#808000" ); | ||||||
|  |   static readonly yellow = HTMLColorParser.fromStringHexColor( "#ffff00" ); | ||||||
|  |   static readonly navy = HTMLColorParser.fromStringHexColor( "#000080" ); | ||||||
|  |   static readonly blue = HTMLColorParser.fromStringHexColor( "#0000ff" ); | ||||||
|  |   static readonly teal = HTMLColorParser.fromStringHexColor( "#008080" ); | ||||||
|  |   static readonly aqua = HTMLColorParser.fromStringHexColor( "#00ffff" ); | ||||||
|  |   static readonly orange = HTMLColorParser.fromStringHexColor( "#ffa500" ); | ||||||
|  |   static readonly aliceblue = HTMLColorParser.fromStringHexColor( "#f0f8ff" ); | ||||||
|  |   static readonly antiquewhite = HTMLColorParser.fromStringHexColor( "#faebd7" ); | ||||||
|  |   static readonly aquamarine = HTMLColorParser.fromStringHexColor( "#7fffd4" ); | ||||||
|  |   static readonly azure = HTMLColorParser.fromStringHexColor( "#f0ffff" ); | ||||||
|  |   static readonly beige = HTMLColorParser.fromStringHexColor( "#f5f5dc" ); | ||||||
|  |   static readonly bisque = HTMLColorParser.fromStringHexColor( "#ffe4c4" ); | ||||||
|  |   static readonly blanchedalmond = HTMLColorParser.fromStringHexColor( "#ffebcd" ); | ||||||
|  |   static readonly blueviolet = HTMLColorParser.fromStringHexColor( "#8a2be2" ); | ||||||
|  |   static readonly brown = HTMLColorParser.fromStringHexColor( "#a52a2a" ); | ||||||
|  |   static readonly burlywood = HTMLColorParser.fromStringHexColor( "#deb887" ); | ||||||
|  |   static readonly cadetblue = HTMLColorParser.fromStringHexColor( "#5f9ea0" ); | ||||||
|  |   static readonly chartreuse = HTMLColorParser.fromStringHexColor( "#7fff00" ); | ||||||
|  |   static readonly chocolate = HTMLColorParser.fromStringHexColor( "#d2691e" ); | ||||||
|  |   static readonly coral = HTMLColorParser.fromStringHexColor( "#ff7f50" ); | ||||||
|  |   static readonly cornflowerblue = HTMLColorParser.fromStringHexColor( "#6495ed" ); | ||||||
|  |   static readonly cornsilk = HTMLColorParser.fromStringHexColor( "#fff8dc" ); | ||||||
|  |   static readonly crimson = HTMLColorParser.fromStringHexColor( "#dc143c" ); | ||||||
|  |   static readonly cyan = HTMLColorParser.fromStringHexColor( "#00ffff" ); | ||||||
|  |   static readonly darkblue = HTMLColorParser.fromStringHexColor( "#00008b" ); | ||||||
|  |   static readonly darkcyan = HTMLColorParser.fromStringHexColor( "#008b8b" ); | ||||||
|  |   static readonly darkgoldenrod = HTMLColorParser.fromStringHexColor( "#b8860b" ); | ||||||
|  |   static readonly darkgray = HTMLColorParser.fromStringHexColor( "#a9a9a9" ); | ||||||
|  |   static readonly darkgreen = HTMLColorParser.fromStringHexColor( "#006400" ); | ||||||
|  |   static readonly darkgrey = HTMLColorParser.fromStringHexColor( "#a9a9a9" ); | ||||||
|  |   static readonly darkkhaki = HTMLColorParser.fromStringHexColor( "#bdb76b" ); | ||||||
|  |   static readonly darkmagenta = HTMLColorParser.fromStringHexColor( "#8b008b" ); | ||||||
|  |   static readonly darkolivegreen = HTMLColorParser.fromStringHexColor( "#556b2f" ); | ||||||
|  |   static readonly darkorange = HTMLColorParser.fromStringHexColor( "#ff8c00" ); | ||||||
|  |   static readonly darkorchid = HTMLColorParser.fromStringHexColor( "#9932cc" ); | ||||||
|  |   static readonly darkred = HTMLColorParser.fromStringHexColor( "#8b0000" ); | ||||||
|  |   static readonly darksalmon = HTMLColorParser.fromStringHexColor( "#e9967a" ); | ||||||
|  |   static readonly darkseagreen = HTMLColorParser.fromStringHexColor( "#8fbc8f" ); | ||||||
|  |   static readonly darkslateblue = HTMLColorParser.fromStringHexColor( "#483d8b" ); | ||||||
|  |   static readonly darkslategray = HTMLColorParser.fromStringHexColor( "#2f4f4f" ); | ||||||
|  |   static readonly darkslategrey = HTMLColorParser.fromStringHexColor( "#2f4f4f" ); | ||||||
|  |   static readonly darkturquoise = HTMLColorParser.fromStringHexColor( "#00ced1" ); | ||||||
|  |   static readonly darkviolet = HTMLColorParser.fromStringHexColor( "#9400d3" ); | ||||||
|  |   static readonly deeppink = HTMLColorParser.fromStringHexColor( "#ff1493" ); | ||||||
|  |   static readonly deepskyblue = HTMLColorParser.fromStringHexColor( "#00bfff" ); | ||||||
|  |   static readonly dimgray = HTMLColorParser.fromStringHexColor( "#696969" ); | ||||||
|  |   static readonly dimgrey = HTMLColorParser.fromStringHexColor( "#696969" ); | ||||||
|  |   static readonly dodgerblue = HTMLColorParser.fromStringHexColor( "#1e90ff" ); | ||||||
|  |   static readonly firebrick = HTMLColorParser.fromStringHexColor( "#b22222" ); | ||||||
|  |   static readonly floralwhite = HTMLColorParser.fromStringHexColor( "#fffaf0" ); | ||||||
|  |   static readonly forestgreen = HTMLColorParser.fromStringHexColor( "#228b22" ); | ||||||
|  |   static readonly gainsboro = HTMLColorParser.fromStringHexColor( "#dcdcdc" ); | ||||||
|  |   static readonly ghostwhite = HTMLColorParser.fromStringHexColor( "#f8f8ff" ); | ||||||
|  |   static readonly gold = HTMLColorParser.fromStringHexColor( "#ffd700" ); | ||||||
|  |   static readonly goldenrod = HTMLColorParser.fromStringHexColor( "#daa520" ); | ||||||
|  |   static readonly greenyellow = HTMLColorParser.fromStringHexColor( "#adff2f" ); | ||||||
|  |   static readonly grey = HTMLColorParser.fromStringHexColor( "#808080" ); | ||||||
|  |   static readonly honeydew = HTMLColorParser.fromStringHexColor( "#f0fff0" ); | ||||||
|  |   static readonly hotpink = HTMLColorParser.fromStringHexColor( "#ff69b4" ); | ||||||
|  |   static readonly indianred = HTMLColorParser.fromStringHexColor( "#cd5c5c" ); | ||||||
|  |   static readonly indigo = HTMLColorParser.fromStringHexColor( "#4b0082" ); | ||||||
|  |   static readonly ivory = HTMLColorParser.fromStringHexColor( "#fffff0" ); | ||||||
|  |   static readonly khaki = HTMLColorParser.fromStringHexColor( "#f0e68c" ); | ||||||
|  |   static readonly lavender = HTMLColorParser.fromStringHexColor( "#e6e6fa" ); | ||||||
|  |   static readonly lavenderblush = HTMLColorParser.fromStringHexColor( "#fff0f5" ); | ||||||
|  |   static readonly lawngreen = HTMLColorParser.fromStringHexColor( "#7cfc00" ); | ||||||
|  |   static readonly lemonchiffon = HTMLColorParser.fromStringHexColor( "#fffacd" ); | ||||||
|  |   static readonly lightblue = HTMLColorParser.fromStringHexColor( "#add8e6" ); | ||||||
|  |   static readonly lightcoral = HTMLColorParser.fromStringHexColor( "#f08080" ); | ||||||
|  |   static readonly lightcyan = HTMLColorParser.fromStringHexColor( "#e0ffff" ); | ||||||
|  |   static readonly lightgoldenrodyellow = HTMLColorParser.fromStringHexColor( "#fafad2" ); | ||||||
|  |   static readonly lightgray = HTMLColorParser.fromStringHexColor( "#d3d3d3" ); | ||||||
|  |   static readonly lightgreen = HTMLColorParser.fromStringHexColor( "#90ee90" ); | ||||||
|  |   static readonly lightgrey = HTMLColorParser.fromStringHexColor( "#d3d3d3" ); | ||||||
|  |   static readonly lightpink = HTMLColorParser.fromStringHexColor( "#ffb6c1" ); | ||||||
|  |   static readonly lightsalmon = HTMLColorParser.fromStringHexColor( "#ffa07a" ); | ||||||
|  |   static readonly lightseagreen = HTMLColorParser.fromStringHexColor( "#20b2aa" ); | ||||||
|  |   static readonly lightskyblue = HTMLColorParser.fromStringHexColor( "#87cefa" ); | ||||||
|  |   static readonly lightslategray = HTMLColorParser.fromStringHexColor( "#778899" ); | ||||||
|  |   static readonly lightslategrey = HTMLColorParser.fromStringHexColor( "#778899" ); | ||||||
|  |   static readonly lightsteelblue = HTMLColorParser.fromStringHexColor( "#b0c4de" ); | ||||||
|  |   static readonly lightyellow = HTMLColorParser.fromStringHexColor( "#ffffe0" ); | ||||||
|  |   static readonly limegreen = HTMLColorParser.fromStringHexColor( "#32cd32" ); | ||||||
|  |   static readonly linen = HTMLColorParser.fromStringHexColor( "#faf0e6" ); | ||||||
|  |   static readonly magenta = HTMLColorParser.fromStringHexColor( "#ff00ff" ); | ||||||
|  |   static readonly mediumaquamarine = HTMLColorParser.fromStringHexColor( "#66cdaa" ); | ||||||
|  |   static readonly mediumblue = HTMLColorParser.fromStringHexColor( "#0000cd" ); | ||||||
|  |   static readonly mediumorchid = HTMLColorParser.fromStringHexColor( "#ba55d3" ); | ||||||
|  |   static readonly mediumpurple = HTMLColorParser.fromStringHexColor( "#9370db" ); | ||||||
|  |   static readonly mediumseagreen = HTMLColorParser.fromStringHexColor( "#3cb371" ); | ||||||
|  |   static readonly mediumslateblue = HTMLColorParser.fromStringHexColor( "#7b68ee" ); | ||||||
|  |   static readonly mediumspringgreen = HTMLColorParser.fromStringHexColor( "#00fa9a" ); | ||||||
|  |   static readonly mediumturquoise = HTMLColorParser.fromStringHexColor( "#48d1cc" ); | ||||||
|  |   static readonly mediumvioletred = HTMLColorParser.fromStringHexColor( "#c71585" ); | ||||||
|  |   static readonly midnightblue = HTMLColorParser.fromStringHexColor( "#191970" ); | ||||||
|  |   static readonly mintcream = HTMLColorParser.fromStringHexColor( "#f5fffa" ); | ||||||
|  |   static readonly mistyrose = HTMLColorParser.fromStringHexColor( "#ffe4e1" ); | ||||||
|  |   static readonly moccasin = HTMLColorParser.fromStringHexColor( "#ffe4b5" ); | ||||||
|  |   static readonly navajowhite = HTMLColorParser.fromStringHexColor( "#ffdead" ); | ||||||
|  |   static readonly oldlace = HTMLColorParser.fromStringHexColor( "#fdf5e6" ); | ||||||
|  |   static readonly olivedrab = HTMLColorParser.fromStringHexColor( "#6b8e23" ); | ||||||
|  |   static readonly orangered = HTMLColorParser.fromStringHexColor( "#ff4500" ); | ||||||
|  |   static readonly orchid = HTMLColorParser.fromStringHexColor( "#da70d6" ); | ||||||
|  |   static readonly palegoldenrod = HTMLColorParser.fromStringHexColor( "#eee8aa" ); | ||||||
|  |   static readonly palegreen = HTMLColorParser.fromStringHexColor( "#98fb98" ); | ||||||
|  |   static readonly paleturquoise = HTMLColorParser.fromStringHexColor( "#afeeee" ); | ||||||
|  |   static readonly palevioletred = HTMLColorParser.fromStringHexColor( "#db7093" ); | ||||||
|  |   static readonly papayawhip = HTMLColorParser.fromStringHexColor( "#ffefd5" ); | ||||||
|  |   static readonly peachpuff = HTMLColorParser.fromStringHexColor( "#ffdab9" ); | ||||||
|  |   static readonly peru = HTMLColorParser.fromStringHexColor( "#cd853f" ); | ||||||
|  |   static readonly pink = HTMLColorParser.fromStringHexColor( "#ffc0cb" ); | ||||||
|  |   static readonly plum = HTMLColorParser.fromStringHexColor( "#dda0dd" ); | ||||||
|  |   static readonly powderblue = HTMLColorParser.fromStringHexColor( "#b0e0e6" ); | ||||||
|  |   static readonly rosybrown = HTMLColorParser.fromStringHexColor( "#bc8f8f" ); | ||||||
|  |   static readonly royalblue = HTMLColorParser.fromStringHexColor( "#4169e1" ); | ||||||
|  |   static readonly saddlebrown = HTMLColorParser.fromStringHexColor( "#8b4513" ); | ||||||
|  |   static readonly salmon = HTMLColorParser.fromStringHexColor( "#fa8072" ); | ||||||
|  |   static readonly sandybrown = HTMLColorParser.fromStringHexColor( "#f4a460" ); | ||||||
|  |   static readonly seagreen = HTMLColorParser.fromStringHexColor( "#2e8b57" ); | ||||||
|  |   static readonly seashell = HTMLColorParser.fromStringHexColor( "#fff5ee" ); | ||||||
|  |   static readonly sienna = HTMLColorParser.fromStringHexColor( "#a0522d" ); | ||||||
|  |   static readonly skyblue = HTMLColorParser.fromStringHexColor( "#87ceeb" ); | ||||||
|  |   static readonly slateblue = HTMLColorParser.fromStringHexColor( "#6a5acd" ); | ||||||
|  |   static readonly slategray = HTMLColorParser.fromStringHexColor( "#708090" ); | ||||||
|  |   static readonly slategrey = HTMLColorParser.fromStringHexColor( "#708090" ); | ||||||
|  |   static readonly snow = HTMLColorParser.fromStringHexColor( "#fffafa" ); | ||||||
|  |   static readonly springgreen = HTMLColorParser.fromStringHexColor( "#00ff7f" ); | ||||||
|  |   static readonly steelblue = HTMLColorParser.fromStringHexColor( "#4682b4" ); | ||||||
|  |   static readonly tan = HTMLColorParser.fromStringHexColor( "#d2b48c" ); | ||||||
|  |   static readonly thistle = HTMLColorParser.fromStringHexColor( "#d8bfd8" ); | ||||||
|  |   static readonly tomato = HTMLColorParser.fromStringHexColor( "#ff6347" ); | ||||||
|  |   static readonly turquoise = HTMLColorParser.fromStringHexColor( "#40e0d0" ); | ||||||
|  |   static readonly violet = HTMLColorParser.fromStringHexColor( "#ee82ee" ); | ||||||
|  |   static readonly wheat = HTMLColorParser.fromStringHexColor( "#f5deb3" ); | ||||||
|  |   static readonly whitesmoke = HTMLColorParser.fromStringHexColor( "#f5f5f5" ); | ||||||
|  |   static readonly yellowgreen = HTMLColorParser.fromStringHexColor( "#9acd32" ); | ||||||
|  |   static readonly rebeccapurple = HTMLColorParser.fromStringHexColor( "#663399" ); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,410 @@ | ||||||
|  | import { HTMLColor } from './HTMLColor'; | ||||||
|  | import { RGBColor } from './RGBColor'; | ||||||
|  | import { MathX } from '../math/MathX'; | ||||||
|  | import { YBColorSpace } from './YBColorSpace'; | ||||||
|  | 
 | ||||||
|  | export class HSLColor extends HTMLColor | ||||||
|  | { | ||||||
|  |   private _h = 0; | ||||||
|  |   get h(){ return this._h; }   | ||||||
|  |   set h( value:number ) | ||||||
|  |   { | ||||||
|  |     this._h = value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _s = 0; | ||||||
|  |   get s(){ return this._s; } | ||||||
|  |   set s( value:number ) | ||||||
|  |   { | ||||||
|  |     this._s = value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _l = 0; | ||||||
|  |   get l(){ return this._l; } | ||||||
|  |   set l( value:number ) | ||||||
|  |   { | ||||||
|  |     this._l = value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Ranges: From zero to [ 360, 100, 100, 1 ]
 | ||||||
|  |   constructor( h:number, s:number, l:number, a:number = 1 ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  | 
 | ||||||
|  |     this._h = h; | ||||||
|  |     this._s = s; | ||||||
|  |     this._l = l; | ||||||
|  |     this._a = a; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toHSL(){ return this;} | ||||||
|  |    | ||||||
|  |   clone() | ||||||
|  |   { | ||||||
|  |     return new HSLColor( this.h, this.s, this.l, this.a ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   copyFrom( other:HTMLColor ) | ||||||
|  |   { | ||||||
|  |     if ( this === other ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let otherInHSL = other.toHSL(); | ||||||
|  |      | ||||||
|  |     this._h = otherInHSL.h; | ||||||
|  |     this._s = otherInHSL.s; | ||||||
|  |     this._l = otherInHSL.l; | ||||||
|  |     this._a = otherInHSL.a; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shift( h:number, s:number, l:number ) | ||||||
|  |   { | ||||||
|  |     this._h = ( this._h + h ) % 360; | ||||||
|  |     this._s = MathX.clamp( this._s + s, 0, 100 ); | ||||||
|  |     this._l = MathX.clamp( this._l + l, 0, 100 ); | ||||||
|  | 
 | ||||||
|  |     return this;     | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shiftClamped( h:number, s:number, l:number ) | ||||||
|  |   { | ||||||
|  |     this._h = YBColorSpace.changeHue( this._h, h ); | ||||||
|  |     this._s = MathX.clamp( this._s + s, 0, 100 ); | ||||||
|  |     this._l = MathX.clamp( this._l + l, 0, 100 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shiftClampedHueBlendedFast( h:number, s:number, l:number, blendRadius:number = 20, blendStart:number = 1 ) | ||||||
|  |   { | ||||||
|  |     let distanceToYellow = Math.min( 1, Math.abs( this._h - 60 ) / blendRadius );  | ||||||
|  |     let distanceToBlue   = Math.min( 1, Math.abs( this._h - 240 ) / blendRadius ); | ||||||
|  | 
 | ||||||
|  |     this._h = YBColorSpace.changeHue( this._h, h * distanceToYellow * distanceToBlue ); | ||||||
|  |     this._s = MathX.clamp( this._s + s, 0, 100 ); | ||||||
|  |     this._l = MathX.clamp( this._l + l, 0, 100 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shiftClampedHueBlended( h:number, s:number, l:number, blendRadius:number = 20, blendStart:number = 1 ) | ||||||
|  |   { | ||||||
|  |     let distanceToYellow = Math.abs( MathX.angleDifference( this._h, 60 ) ); | ||||||
|  |     let distanceToBlue   = Math.abs( MathX.angleDifference( this._h, 240 ) ); | ||||||
|  | 
 | ||||||
|  |     let newHue = YBColorSpace.changeHue( this._h, h ); | ||||||
|  | 
 | ||||||
|  |     let blendPower = 1.5; | ||||||
|  | 
 | ||||||
|  |     let cached = { hue:this._h, change:h, newHue:newHue, distanceToYellow, distanceToBlue, clampedHue:0, lerpAmount:0 }; | ||||||
|  | 
 | ||||||
|  |     if ( distanceToYellow < blendRadius || distanceToBlue < blendRadius ) | ||||||
|  |     { | ||||||
|  |       if ( distanceToYellow < distanceToBlue ) | ||||||
|  |       { | ||||||
|  |         let lerpAmount = 1;  | ||||||
|  | 
 | ||||||
|  |         if ( distanceToYellow > blendStart ) | ||||||
|  |         { | ||||||
|  |           lerpAmount = MathX.map( distanceToYellow, blendStart, blendRadius, 1, 0 ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         newHue = MathX.lerpAngle( newHue, 60, Math.pow( lerpAmount, blendPower )  ); | ||||||
|  |         cached.lerpAmount = lerpAmount; | ||||||
|  |         cached.clampedHue = newHue;    | ||||||
|  |          | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         let lerpAmount = 1; | ||||||
|  | 
 | ||||||
|  |         if ( distanceToBlue > blendStart ) | ||||||
|  |         { | ||||||
|  |           lerpAmount = MathX.map( distanceToBlue, blendStart, blendRadius, 1, 0 ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         newHue = MathX.lerpAngle( newHue, 240, Math.pow( lerpAmount, blendPower ) );         | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //  console.log( cached )
 | ||||||
|  | 
 | ||||||
|  |     this._h = newHue; | ||||||
|  |     this._s = MathX.clamp( this._s + s, 0, 100 ); | ||||||
|  |     this._l = MathX.clamp( this._l + l, 0, 100 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shiftClampedHueSmoothed( h:number, s:number, l:number, kernelWidth:number = 30, kernel:number[]=[0.1,1,10,10,10,1,0.1] ) | ||||||
|  |   { | ||||||
|  |     let center = { x:0, y:0 }; | ||||||
|  | 
 | ||||||
|  |     let toRad = Math.PI * 2 / 360; | ||||||
|  |     let toDeg = 1 / toRad; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < kernel.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let state =  i / ( kernel.length  - 1  ) - 0.5; | ||||||
|  |       let hueOffset = state * kernelWidth; | ||||||
|  |       let kernelHue = ( this._h + hueOffset ) % 360; | ||||||
|  |       let hue = YBColorSpace.changeHue( kernelHue, h ); | ||||||
|  | 
 | ||||||
|  |       let x = Math.cos( hue * toRad ) * kernel[ i ]; | ||||||
|  |       let y = Math.sin( hue * toRad ) * kernel[ i ]; | ||||||
|  | 
 | ||||||
|  |       center.x += x; | ||||||
|  |       center.y += y; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this._h = Math.atan2( center.y, center.x ) * toDeg; | ||||||
|  | 
 | ||||||
|  |     this._s = MathX.clamp( this._s + s, 0, 100 ); | ||||||
|  |     this._l = MathX.clamp( this._l + l, 0, 100 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   warmen( amount:number ) | ||||||
|  |   { | ||||||
|  |     amount = MathX.clamp( amount, 0, 180 ); | ||||||
|  | 
 | ||||||
|  |     let h = this._h; | ||||||
|  | 
 | ||||||
|  |     if ( this.isOnGreenSide() ) | ||||||
|  |     { | ||||||
|  |       this._h = Math.max( 60, h - amount ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       if ( h >= 240 ) | ||||||
|  |       { | ||||||
|  |         h -= 360; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this._h = Math.min( 60, h + amount ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return this; | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   coolen( amount:number ) | ||||||
|  |   { | ||||||
|  |     amount = MathX.clamp( amount, 0, 180 ); | ||||||
|  | 
 | ||||||
|  |     let h = this._h; | ||||||
|  | 
 | ||||||
|  |     if ( this.isOnGreenSide() ) | ||||||
|  |     { | ||||||
|  |       this._h = Math.min( 240, h + amount ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       if ( h < 60 ) | ||||||
|  |       { | ||||||
|  |         h += 360; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this._h = Math.max( 240, h - amount ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   isOnGreenSide() | ||||||
|  |   { | ||||||
|  |     return this._h >= 60 && this._h < 240; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   isOnRedSide() | ||||||
|  |   { | ||||||
|  |     return ! this.isOnGreenSide; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shiftWarmerHue( amount:number ) | ||||||
|  |   { | ||||||
|  |     if ( this._h > 120 && this._h < 300 ) | ||||||
|  |     { | ||||||
|  |       amount = -amount; | ||||||
|  |     }     | ||||||
|  |      | ||||||
|  |     this._h = MathX.repeat( this._h + amount, 360 ); | ||||||
|  | 
 | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toString():string | ||||||
|  |   { | ||||||
|  |     return `hsla(${this._h},${this._s}%,${this._l}%,${this._a})`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   clamp():HSLColor | ||||||
|  |   { | ||||||
|  |     this._h = MathX.repeat( this._h, 360 ); | ||||||
|  |     this._s = MathX.clamp( this._s, 0, 100 ); | ||||||
|  |     this._l = MathX.clamp( this._l, 0, 100 ); | ||||||
|  |     return this;    | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   equals( other:HTMLColor ) | ||||||
|  |   { | ||||||
|  |     if ( ! other ) | ||||||
|  |     {  | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     let otherHSL = other.toHSL(); | ||||||
|  | 
 | ||||||
|  |     if ( otherHSL.h !== this.h ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( otherHSL.s !== this.s ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( otherHSL.l !== this.l ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( otherHSL.a !== this.a ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public toRGB( ):RGBColor | ||||||
|  | 	{ | ||||||
|  |     let s = this._s / 100; | ||||||
|  |     let l = this._l / 100; | ||||||
|  | 
 | ||||||
|  | 		let m2 = ( l <= 0.5 ) ?  | ||||||
|  |              ( l * ( 1 + s ) ) :  | ||||||
|  |              ( l + s - l * s ); | ||||||
|  | 
 | ||||||
|  | 		let m1 = 2 * l - m2; | ||||||
|  | 		 | ||||||
|  | 		if ( s == 0 )  | ||||||
|  | 		{ | ||||||
|  |       return new RGBColor( l, l, l, this.a ); | ||||||
|  | 		}  | ||||||
|  | 
 | ||||||
|  |     let r = HSLColor.computeChannel( m1, m2, this.h + 120 ); | ||||||
|  |     let g = HSLColor.computeChannel( m1, m2, this.h ); | ||||||
|  |     let b = HSLColor.computeChannel( m1, m2, this.h - 120 );		 | ||||||
|  | 		 | ||||||
|  | 		return new RGBColor( r, g, b, this.a ); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private static computeChannel( n1:number, n2:number, hue:number ):number | ||||||
|  | 	{ | ||||||
|  | 		hue = MathX.repeat( hue, 360 ); | ||||||
|  | 		 | ||||||
|  | 		if ( hue < 60 )  | ||||||
|  | 		{ | ||||||
|  | 			return n1 + ( n2 - n1 ) * hue / 60; | ||||||
|  | 		}  | ||||||
|  | 		else if ( hue < 180 )  | ||||||
|  | 		{ | ||||||
|  | 			return n2; | ||||||
|  | 		}  | ||||||
|  | 		else if ( hue < 240 )  | ||||||
|  | 		{ | ||||||
|  | 			return n1 + ( n2 - n1 ) * ( 240 - hue ) / 60; | ||||||
|  | 		}  | ||||||
|  | 		else  | ||||||
|  | 		{ | ||||||
|  | 			return n1; | ||||||
|  | 		} | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   public static fromRGBColor( c:RGBColor ):HSLColor  | ||||||
|  | 	{		 | ||||||
|  | 		 | ||||||
|  | 		let cmin = Math.min( Math.min( c.r, c.g ), c.b ); | ||||||
|  | 		let cmax = Math.max( Math.max( c.r, c.g ), c.b ); | ||||||
|  | 		 | ||||||
|  | 		let l = ( cmin + cmax ) / 2; | ||||||
|  | 		 | ||||||
|  | 		if ( cmin == cmax )  | ||||||
|  | 		{ | ||||||
|  |       return new HSLColor( 0, 0, l * 100, c.a ); | ||||||
|  | 		}  | ||||||
|  | 	 | ||||||
|  |     let delta = cmax - cmin; | ||||||
|  |      | ||||||
|  |     let s = ( l <= .5 ) ?  | ||||||
|  |             ( delta / ( cmax + cmin ) ) :  | ||||||
|  |             ( delta / ( 2 - ( cmax + cmin ) ) ); | ||||||
|  |      | ||||||
|  |     let h = 0; | ||||||
|  |      | ||||||
|  |     if ( c.r == cmax )  | ||||||
|  |     { | ||||||
|  |       h = ( c.g - c.b ) / delta; | ||||||
|  |     }  | ||||||
|  |     else if ( c.g == cmax )  | ||||||
|  |     { | ||||||
|  |       h = 2 + ( c.b - c.r ) / delta; | ||||||
|  |     } | ||||||
|  |     else if ( c.b == cmax )  | ||||||
|  |     { | ||||||
|  |       h = 4 + ( c.r - c.g ) / delta; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     h = MathX.repeat( h * 60, 360 ); | ||||||
|  | 		 | ||||||
|  | 		return new HSLColor( h, s * 100, l * 100, c.a ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static fromString( colorString:string ) | ||||||
|  |   { | ||||||
|  |     colorString = colorString.trim(); | ||||||
|  | 
 | ||||||
|  |     let hslStart = /hsla?\(/; | ||||||
|  | 
 | ||||||
|  |     if ( ! hslStart.test( colorString ) ) | ||||||
|  |     { | ||||||
|  |       console.error( "Does not start with", hslStart, " ==> '" + colorString + "'" ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     colorString = colorString.replace( hslStart, "" ); | ||||||
|  |     colorString = colorString.replace( /\)$/, "" ); | ||||||
|  |     colorString = colorString.replace( /%/g, "" ); | ||||||
|  |      | ||||||
|  |    | ||||||
|  |     let numbers = JSON.parse( "[" + colorString + "]" ) as number[]; | ||||||
|  |      | ||||||
|  |     if ( ! Array.isArray( numbers ) ) | ||||||
|  |     { | ||||||
|  |       console.error( "Is not a list of numbers", " ==> '" + colorString + "'" ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let notNumeric = numbers.findIndex( n => typeof n !== "number" ); | ||||||
|  | 
 | ||||||
|  |     if ( notNumeric !== -1 ) | ||||||
|  |     { | ||||||
|  |       console.error( "Found a not-numeric at index:", notNumeric, " ==> '" + colorString + "'" ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( ! ( numbers.length === 3 || numbers.length === 4 ) ) | ||||||
|  |     { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( numbers.length === 3 ) | ||||||
|  |     { | ||||||
|  |       numbers.push( 1 ); | ||||||
|  |     }     | ||||||
|  | 
 | ||||||
|  |     return new HSLColor( numbers[ 0 ], numbers[ 1 ], numbers[ 2 ] , numbers[ 3 ] ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,145 @@ | ||||||
|  | import { RGBColor } from './RGBColor'; | ||||||
|  | import { HSLColor } from './HSLColor'; | ||||||
|  | import { MathX } from '../math/MathX'; | ||||||
|  | 
 | ||||||
|  | export abstract class HTMLColor | ||||||
|  | { | ||||||
|  |   protected _a = 0; | ||||||
|  |   get a(){ return this._a; } | ||||||
|  |   set a( value:number ){ this._a = value; } | ||||||
|  |   | ||||||
|  |   abstract toRGB():RGBColor; | ||||||
|  |   abstract toHSL():HSLColor; | ||||||
|  |   abstract copyFrom( color:HTMLColor ):void; | ||||||
|  |   abstract clone():HTMLColor; | ||||||
|  |    | ||||||
|  |   get r() | ||||||
|  |   { | ||||||
|  |     return this.toRGB().r; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set r( value:number ) | ||||||
|  |   { | ||||||
|  |     let rgb = this.toRGB(); | ||||||
|  |     rgb.r = value; | ||||||
|  |     this.copyFrom( rgb ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get g() | ||||||
|  |   { | ||||||
|  |     return this.toRGB().g; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set g( value:number ) | ||||||
|  |   { | ||||||
|  |     let rgb = this.toRGB(); | ||||||
|  |     rgb.g = value; | ||||||
|  |     this.copyFrom( rgb ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get b() | ||||||
|  |   { | ||||||
|  |     return this.toRGB().b; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set b( value:number ) | ||||||
|  |   { | ||||||
|  |     let rgb = this.toRGB(); | ||||||
|  |     rgb.b = value; | ||||||
|  |     this.copyFrom( rgb ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get h() | ||||||
|  |   { | ||||||
|  |     return this.toHSL().h; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set h( value:number ) | ||||||
|  |   { | ||||||
|  |     let hsl = this.toHSL(); | ||||||
|  |     hsl.h = value; | ||||||
|  |     this.copyFrom( hsl ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get s() | ||||||
|  |   { | ||||||
|  |     return this.toHSL().s; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set s( value:number ) | ||||||
|  |   { | ||||||
|  |     let hsl = this.toHSL(); | ||||||
|  |     hsl.s = value; | ||||||
|  |     this.copyFrom( hsl ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get l() | ||||||
|  |   { | ||||||
|  |     return this.toHSL().l; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set l( value:number ) | ||||||
|  |   { | ||||||
|  |     let hsl = this.toHSL(); | ||||||
|  |     hsl.l = value; | ||||||
|  |     this.copyFrom( hsl ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   lighten( amount:number ):HTMLColor | ||||||
|  |   { | ||||||
|  |     this.l = this.l + amount; | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   multiplyLuminance( amount:number ):HTMLColor | ||||||
|  |   { | ||||||
|  |     this.l = this.l * amount; | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   saturate( amount:number ):HTMLColor | ||||||
|  |   { | ||||||
|  |     this.s = this.s + amount; | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shiftHue( amount:number ):HTMLColor | ||||||
|  |   { | ||||||
|  |     this.h = MathX.repeat( this.h + amount, 360 ); | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fade( amount:number ):HTMLColor | ||||||
|  |   { | ||||||
|  |     this.a = this.a + amount; | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get physicalLightness() | ||||||
|  |   { | ||||||
|  |     return this.r * 0.3 + this.g * 0.5 + this.b * 0.2; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setPhysicalLightness( target:number, amount:number ) | ||||||
|  |   { | ||||||
|  |     let rgb = this.toRGB(); | ||||||
|  |     let lightness = rgb.physicalLightness; | ||||||
|  |     let scale = target / lightness; | ||||||
|  |     scale = scale * amount + ( 1 - amount ); | ||||||
|  | 
 | ||||||
|  |     rgb.scaleRGB( scale ); | ||||||
|  | 
 | ||||||
|  |     this.copyFrom( rgb ); | ||||||
|  | 
 | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   equals( other:HTMLColor ) | ||||||
|  |   { | ||||||
|  |     return this.toHSL().equals( other ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,166 @@ | ||||||
|  | export class HTMLColorNameTable | ||||||
|  | { | ||||||
|  |   static getHexColorFromName( name:string ) | ||||||
|  |   { | ||||||
|  |     return HTMLColorNameTable.data[ name.toLocaleLowerCase() ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static contains( name:string ) | ||||||
|  |   { | ||||||
|  |     let value = HTMLColorNameTable.data[ name ]; | ||||||
|  |     return typeof value === "string"; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static readonly data:any =  | ||||||
|  |   { | ||||||
|  |     "black":"#000000", | ||||||
|  |     "silver":"#c0c0c0", | ||||||
|  |     "gray":"#808080", | ||||||
|  |     "white":"#ffffff", | ||||||
|  |     "maroon":"#800000", | ||||||
|  |     "red":"#ff0000", | ||||||
|  |     "purple":"#800080", | ||||||
|  |     "fuchsia":"#ff00ff", | ||||||
|  |     "green":"#008000", | ||||||
|  |     "lime":"#00ff00", | ||||||
|  |     "olive":"#808000", | ||||||
|  |     "yellow":"#ffff00", | ||||||
|  |     "navy":"#000080", | ||||||
|  |     "blue":"#0000ff", | ||||||
|  |     "teal":"#008080", | ||||||
|  |     "aqua":"#00ffff", | ||||||
|  |     "orange":"#ffa500", | ||||||
|  |     "aliceblue":"#f0f8ff", | ||||||
|  |     "antiquewhite":"#faebd7", | ||||||
|  |     "aquamarine":"#7fffd4", | ||||||
|  |     "azure":"#f0ffff", | ||||||
|  |     "beige":"#f5f5dc", | ||||||
|  |     "bisque":"#ffe4c4", | ||||||
|  |     "blanchedalmond":"#ffebcd", | ||||||
|  |     "blueviolet":"#8a2be2", | ||||||
|  |     "brown":"#a52a2a", | ||||||
|  |     "burlywood":"#deb887", | ||||||
|  |     "cadetblue":"#5f9ea0", | ||||||
|  |     "chartreuse":"#7fff00", | ||||||
|  |     "chocolate":"#d2691e", | ||||||
|  |     "coral":"#ff7f50", | ||||||
|  |     "cornflowerblue":"#6495ed", | ||||||
|  |     "cornsilk":"#fff8dc", | ||||||
|  |     "crimson":"#dc143c", | ||||||
|  |     "cyan":"#00ffff", | ||||||
|  |     "darkblue":"#00008b", | ||||||
|  |     "darkcyan":"#008b8b", | ||||||
|  |     "darkgoldenrod":"#b8860b", | ||||||
|  |     "darkgray":"#a9a9a9", | ||||||
|  |     "darkgreen":"#006400", | ||||||
|  |     "darkgrey":"#a9a9a9", | ||||||
|  |     "darkkhaki":"#bdb76b", | ||||||
|  |     "darkmagenta":"#8b008b", | ||||||
|  |     "darkolivegreen":"#556b2f", | ||||||
|  |     "darkorange":"#ff8c00", | ||||||
|  |     "darkorchid":"#9932cc", | ||||||
|  |     "darkred":"#8b0000", | ||||||
|  |     "darksalmon":"#e9967a", | ||||||
|  |     "darkseagreen":"#8fbc8f", | ||||||
|  |     "darkslateblue":"#483d8b", | ||||||
|  |     "darkslategray":"#2f4f4f", | ||||||
|  |     "darkslategrey":"#2f4f4f", | ||||||
|  |     "darkturquoise":"#00ced1", | ||||||
|  |     "darkviolet":"#9400d3", | ||||||
|  |     "deeppink":"#ff1493", | ||||||
|  |     "deepskyblue":"#00bfff", | ||||||
|  |     "dimgray":"#696969", | ||||||
|  |     "dimgrey":"#696969", | ||||||
|  |     "dodgerblue":"#1e90ff", | ||||||
|  |     "firebrick":"#b22222", | ||||||
|  |     "floralwhite":"#fffaf0", | ||||||
|  |     "forestgreen":"#228b22", | ||||||
|  |     "gainsboro":"#dcdcdc", | ||||||
|  |     "ghostwhite":"#f8f8ff", | ||||||
|  |     "gold":"#ffd700", | ||||||
|  |     "goldenrod":"#daa520", | ||||||
|  |     "greenyellow":"#adff2f", | ||||||
|  |     "grey":"#808080", | ||||||
|  |     "honeydew":"#f0fff0", | ||||||
|  |     "hotpink":"#ff69b4", | ||||||
|  |     "indianred":"#cd5c5c", | ||||||
|  |     "indigo":"#4b0082", | ||||||
|  |     "ivory":"#fffff0", | ||||||
|  |     "khaki":"#f0e68c", | ||||||
|  |     "lavender":"#e6e6fa", | ||||||
|  |     "lavenderblush":"#fff0f5", | ||||||
|  |     "lawngreen":"#7cfc00", | ||||||
|  |     "lemonchiffon":"#fffacd", | ||||||
|  |     "lightblue":"#add8e6", | ||||||
|  |     "lightcoral":"#f08080", | ||||||
|  |     "lightcyan":"#e0ffff", | ||||||
|  |     "lightgoldenrodyellow":"#fafad2", | ||||||
|  |     "lightgray":"#d3d3d3", | ||||||
|  |     "lightgreen":"#90ee90", | ||||||
|  |     "lightgrey":"#d3d3d3", | ||||||
|  |     "lightpink":"#ffb6c1", | ||||||
|  |     "lightsalmon":"#ffa07a", | ||||||
|  |     "lightseagreen":"#20b2aa", | ||||||
|  |     "lightskyblue":"#87cefa", | ||||||
|  |     "lightslategray":"#778899", | ||||||
|  |     "lightslategrey":"#778899", | ||||||
|  |     "lightsteelblue":"#b0c4de", | ||||||
|  |     "lightyellow":"#ffffe0", | ||||||
|  |     "limegreen":"#32cd32", | ||||||
|  |     "linen":"#faf0e6", | ||||||
|  |     "magenta":"#ff00ff", | ||||||
|  |     "mediumaquamarine":"#66cdaa", | ||||||
|  |     "mediumblue":"#0000cd", | ||||||
|  |     "mediumorchid":"#ba55d3", | ||||||
|  |     "mediumpurple":"#9370db", | ||||||
|  |     "mediumseagreen":"#3cb371", | ||||||
|  |     "mediumslateblue":"#7b68ee", | ||||||
|  |     "mediumspringgreen":"#00fa9a", | ||||||
|  |     "mediumturquoise":"#48d1cc", | ||||||
|  |     "mediumvioletred":"#c71585", | ||||||
|  |     "midnightblue":"#191970", | ||||||
|  |     "mintcream":"#f5fffa", | ||||||
|  |     "mistyrose":"#ffe4e1", | ||||||
|  |     "moccasin":"#ffe4b5", | ||||||
|  |     "navajowhite":"#ffdead", | ||||||
|  |     "oldlace":"#fdf5e6", | ||||||
|  |     "olivedrab":"#6b8e23", | ||||||
|  |     "orangered":"#ff4500", | ||||||
|  |     "orchid":"#da70d6", | ||||||
|  |     "palegoldenrod":"#eee8aa", | ||||||
|  |     "palegreen":"#98fb98", | ||||||
|  |     "paleturquoise":"#afeeee", | ||||||
|  |     "palevioletred":"#db7093", | ||||||
|  |     "papayawhip":"#ffefd5", | ||||||
|  |     "peachpuff":"#ffdab9", | ||||||
|  |     "peru":"#cd853f", | ||||||
|  |     "pink":"#ffc0cb", | ||||||
|  |     "plum":"#dda0dd", | ||||||
|  |     "powderblue":"#b0e0e6", | ||||||
|  |     "rosybrown":"#bc8f8f", | ||||||
|  |     "royalblue":"#4169e1", | ||||||
|  |     "saddlebrown":"#8b4513", | ||||||
|  |     "salmon":"#fa8072", | ||||||
|  |     "sandybrown":"#f4a460", | ||||||
|  |     "seagreen":"#2e8b57", | ||||||
|  |     "seashell":"#fff5ee", | ||||||
|  |     "sienna":"#a0522d", | ||||||
|  |     "skyblue":"#87ceeb", | ||||||
|  |     "slateblue":"#6a5acd", | ||||||
|  |     "slategray":"#708090", | ||||||
|  |     "slategrey":"#708090", | ||||||
|  |     "snow":"#fffafa", | ||||||
|  |     "springgreen":"#00ff7f", | ||||||
|  |     "steelblue":"#4682b4", | ||||||
|  |     "tan":"#d2b48c", | ||||||
|  |     "thistle":"#d8bfd8", | ||||||
|  |     "tomato":"#ff6347", | ||||||
|  |     "turquoise":"#40e0d0", | ||||||
|  |     "violet":"#ee82ee", | ||||||
|  |     "wheat":"#f5deb3", | ||||||
|  |     "whitesmoke":"#f5f5f5", | ||||||
|  |     "yellowgreen":"#9acd32", | ||||||
|  |     "rebeccapurple":"#663399" | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | import { HTMLColorNameTable } from './HTMLColorNameTable'; | ||||||
|  | import { HTMLColor } from './HTMLColor'; | ||||||
|  | import { RGBColor } from './RGBColor'; | ||||||
|  | import { HSLColor } from './HSLColor'; | ||||||
|  | 
 | ||||||
|  | export class HTMLColorParser | ||||||
|  | { | ||||||
|  |   static fromString( colorString:string ) | ||||||
|  |   { | ||||||
|  |     colorString = colorString.trim(); | ||||||
|  | 
 | ||||||
|  |     let hexMatcher = /^#[0-9a-f]{6}([0-9a-f][0-9a-f])?$/i; | ||||||
|  |     let rgbMatcher = /^rgba?\(/; | ||||||
|  |     let hslMatcher = /^hsla?\(/; | ||||||
|  | 
 | ||||||
|  |     | ||||||
|  |     if ( HTMLColorNameTable.contains( colorString ) ) | ||||||
|  |     { | ||||||
|  |       colorString = HTMLColorNameTable.getHexColorFromName( colorString );  | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if ( hexMatcher.test( colorString ) ) | ||||||
|  |     { | ||||||
|  |       return HTMLColorParser.fromStringHexColor( colorString ); | ||||||
|  |     }  | ||||||
|  |     else if ( rgbMatcher.test ( colorString ) ) | ||||||
|  |     { | ||||||
|  |       return RGBColor.fromString( colorString ); | ||||||
|  |     } | ||||||
|  |     else if ( hslMatcher.test( colorString ) ) | ||||||
|  |     { | ||||||
|  |       return HSLColor.fromString( colorString ); | ||||||
|  |     }     | ||||||
|  | 
 | ||||||
|  |     console.warn( `Couldn't parse: "${colorString}"`) | ||||||
|  |     return null; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromStringHexColor( colorString:string ) | ||||||
|  |   { | ||||||
|  |     colorString = colorString.replace( "#", "" ); | ||||||
|  |      | ||||||
|  |     let r = parseInt( colorString.substring( 0, 2 ), 16 ); | ||||||
|  |     let g = parseInt( colorString.substring( 2, 4 ), 16 ); | ||||||
|  |     let b = parseInt( colorString.substring( 4, 6 ), 16 ); | ||||||
|  | 
 | ||||||
|  |     let a = colorString.length === 6 ?  | ||||||
|  |       255 : parseInt( colorString.substring( 6, 8 ), 16 ); | ||||||
|  |      | ||||||
|  |     return new RGBColor( r / 255, g / 255, b / 255, a / 255 ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,221 @@ | ||||||
|  | import { HTMLColor } from './HTMLColor'; | ||||||
|  | import { HSLColor } from './HSLColor'; | ||||||
|  | 
 | ||||||
|  | export class RGBColor extends HTMLColor | ||||||
|  | { | ||||||
|  |   private _r = 0; | ||||||
|  |   get r(){ return this._r} | ||||||
|  |   set r( value:number ) | ||||||
|  |   { | ||||||
|  |     this._r = value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _g = 0; | ||||||
|  |   get g(){ return this._g} | ||||||
|  |   set g( value:number ) | ||||||
|  |   { | ||||||
|  |     this._g = value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _b = 0; | ||||||
|  |   get b(){ return this._b} | ||||||
|  |   set b( value:number ) | ||||||
|  |   { | ||||||
|  |     this._b = value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   constructor( r:number, g:number, b:number, a:number=1 ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  | 
 | ||||||
|  |     this._r = r; | ||||||
|  |     this._g = g; | ||||||
|  |     this._b = b; | ||||||
|  |     this._a = a; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   clone() | ||||||
|  |   { | ||||||
|  |     return new RGBColor( this.r, this.g, this.b, this.a ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   copyFrom( other:HTMLColor ) | ||||||
|  |   { | ||||||
|  |     if ( this === other ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let otherInRGB = other.toRGB(); | ||||||
|  |      | ||||||
|  |     this._r = otherInRGB.r; | ||||||
|  |     this._g = otherInRGB.g; | ||||||
|  |     this._b = otherInRGB.b; | ||||||
|  |     this._a = otherInRGB.a; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toRGB():RGBColor | ||||||
|  |   { | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toHSL():HSLColor | ||||||
|  |   { | ||||||
|  |     return HSLColor.fromRGBColor( this ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get r255() | ||||||
|  |   { | ||||||
|  |     return Math.round( this._r * 255 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get g255() | ||||||
|  |   { | ||||||
|  |     return Math.round( this._g * 255 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get b255() | ||||||
|  |   { | ||||||
|  |     return Math.round( this._b * 255 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toString():string | ||||||
|  |   {     | ||||||
|  |     return `rgba(${this.r255},${this.g255},${this.b255},${this._a})`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   scaleRGB( scalar:number ) | ||||||
|  |   { | ||||||
|  |     this.r *= scalar; | ||||||
|  |     this.g *= scalar; | ||||||
|  |     this.b *= scalar; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getEuclideanDistance( color:HTMLColor ) | ||||||
|  |   { | ||||||
|  |     let rgb = color.toRGB(); | ||||||
|  | 
 | ||||||
|  |     let rDiff = rgb.r - this.r; | ||||||
|  |     let gDiff = rgb.g - this.g; | ||||||
|  |     let bDiff = rgb.b - this.b; | ||||||
|  |     let aDiff = rgb.a - this.a; | ||||||
|  | 
 | ||||||
|  |     return Math.sqrt( rDiff * rDiff + gDiff * gDiff + bDiff * bDiff + aDiff * aDiff ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static lerp( colorA:HTMLColor, colorB:HTMLColor, lerp:number ):RGBColor | ||||||
|  |   { | ||||||
|  |     let A = colorA.toRGB(); | ||||||
|  |     let B = colorB.toRGB(); | ||||||
|  | 
 | ||||||
|  |     let inverseLerp = 1 - lerp; | ||||||
|  |      | ||||||
|  |     let r = A.r * inverseLerp + B.r * lerp; | ||||||
|  |     let g = A.g * inverseLerp + B.g * lerp; | ||||||
|  |     let b = A.b * inverseLerp + B.b * lerp; | ||||||
|  |     let a = A.a * inverseLerp + B.a * lerp; | ||||||
|  | 
 | ||||||
|  |     return new RGBColor( r, g, b, a ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static lerpFrom( colors:HTMLColor[], lerp:number ):RGBColor | ||||||
|  |   { | ||||||
|  |     if ( lerp <= 0 ) | ||||||
|  |     { | ||||||
|  |       return colors[ 0 ].toRGB(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( lerp >= 1 ) | ||||||
|  |     { | ||||||
|  |       return colors[ colors.length - 1 ].toRGB(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     let index   = lerp * ( colors.length - 1 ); | ||||||
|  |     let floored = Math.floor( index ); | ||||||
|  | 
 | ||||||
|  |     let amount  = index - floored; | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     let colorA = colors[ floored ]; | ||||||
|  |     let colorB = colors[ floored + 1 ]; | ||||||
|  |      | ||||||
|  |     return RGBColor.lerp( colorA, colorB, amount ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   equals( other:HTMLColor ) | ||||||
|  |   { | ||||||
|  |     if ( ! other ) | ||||||
|  |     {  | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     let otherRGB = other.toRGB(); | ||||||
|  | 
 | ||||||
|  |     if ( otherRGB.r !== this.r ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( otherRGB.g !== this.g ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( otherRGB.b !== this.b ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( otherRGB.a !== this.a ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromString( colorString:string ) | ||||||
|  |   { | ||||||
|  |     colorString = colorString.trim(); | ||||||
|  | 
 | ||||||
|  |     let rgbStart = /rgba?\(/; | ||||||
|  | 
 | ||||||
|  |     if ( ! rgbStart.test( colorString ) ) | ||||||
|  |     { | ||||||
|  |       console.error( "Does not start with", rgbStart, " ==> '" + colorString + "'" ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     colorString = colorString.replace( rgbStart, "" ); | ||||||
|  |     colorString = colorString.replace( /\)$/, "" ); | ||||||
|  |      | ||||||
|  |     let numbers = JSON.parse( "[" + colorString + "]" ) as number[]; | ||||||
|  |      | ||||||
|  |     if ( ! Array.isArray( numbers ) ) | ||||||
|  |     { | ||||||
|  |       console.error( "Is not a list of numbers", " ==> '" + colorString + "'" ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let notNumeric = numbers.findIndex( n => typeof n !== "number" ); | ||||||
|  | 
 | ||||||
|  |     if ( notNumeric !== -1 ) | ||||||
|  |     { | ||||||
|  |       console.error( "Found a not-numeric at index:", notNumeric, " ==> '" + colorString + "'" ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( ! ( numbers.length === 3 || numbers.length === 4 ) ) | ||||||
|  |     { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( numbers.length === 3 ) | ||||||
|  |     { | ||||||
|  |       numbers.push( 1 ); | ||||||
|  |     }     | ||||||
|  | 
 | ||||||
|  |     return new RGBColor( numbers[ 0 ] / 255, numbers[ 1 ] / 255, numbers[ 2 ] / 255, numbers[ 3 ] ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | import { MathX } from "../math/MathX"; | ||||||
|  | 
 | ||||||
|  | export class YBColorSpace | ||||||
|  | { | ||||||
|  |   static hueToYB( hue:number ) | ||||||
|  |   { | ||||||
|  |     return MathX.repeatPolar( hue - 240, 180 );     | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static ybToHue( yb:number )  | ||||||
|  |   { | ||||||
|  |     return MathX.repeat( yb + 240, 360 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static changeHue( hue:number, change:number ) | ||||||
|  |   { | ||||||
|  |     let yb = YBColorSpace.hueToYB( hue ); | ||||||
|  |      | ||||||
|  |     let absYB = Math.abs( yb ); | ||||||
|  | 
 | ||||||
|  |     absYB = MathX.clamp( absYB + change, 0, 180 ); | ||||||
|  | 
 | ||||||
|  |     let realYB = yb < 0 ? -absYB : absYB; | ||||||
|  | 
 | ||||||
|  |     return this.ybToHue( realYB ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,232 @@ | ||||||
|  | import { RegExpUtility } from "../text/RegExpUtitlity"; | ||||||
|  | import { DOMEditor } from "./DOMEditor"; | ||||||
|  | 
 | ||||||
|  | export class ClassFlag | ||||||
|  | { | ||||||
|  |   static readonly active = new ClassFlag( "active" ); | ||||||
|  |   static readonly inactive = new ClassFlag( "inactive" ); | ||||||
|  |    | ||||||
|  |   static readonly enabled = new ClassFlag( "enabled" ); | ||||||
|  |   static readonly disabled = new ClassFlag( "disabled" ); | ||||||
|  | 
 | ||||||
|  |   static readonly button = new ClassFlag( "button" ); | ||||||
|  | 
 | ||||||
|  |   static readonly visible = new ClassFlag( "visible" ); | ||||||
|  |   static readonly invisible = new ClassFlag( "invisible" ); | ||||||
|  | 
 | ||||||
|  |   static readonly hidden = new ClassFlag( "hidden" ); | ||||||
|  |   static readonly show = new ClassFlag( "show" ); | ||||||
|  |   static readonly hideable = new ClassFlag( "hideable" );  | ||||||
|  | 
 | ||||||
|  |   static readonly playing = new ClassFlag( "playing" ); | ||||||
|  |   static readonly paused = new ClassFlag( "paused" ); | ||||||
|  | 
 | ||||||
|  |   static readonly error   = new ClassFlag( "error" ); | ||||||
|  |   static readonly warning = new ClassFlag( "warning" ); | ||||||
|  |   static readonly ok      = new ClassFlag( "ok" ); | ||||||
|  | 
 | ||||||
|  |   static readonly debugging = new ClassFlag( "debugging" ); | ||||||
|  |    | ||||||
|  |   static readonly loggedIn = new ClassFlag( "logged-in" ); | ||||||
|  |   static readonly loggedOut = new ClassFlag( "logged-out" ); | ||||||
|  | 
 | ||||||
|  |   static readonly hovered  = new ClassFlag( "hovered" ); | ||||||
|  |   static readonly selected = new ClassFlag( "selected" ); | ||||||
|  |   static readonly dragging = new ClassFlag( "dragging" ); | ||||||
|  | 
 | ||||||
|  |   static readonly smooth = new ClassFlag( "smooth" ); | ||||||
|  | 
 | ||||||
|  |   static readonly debug  = new ClassFlag( "debug" ); | ||||||
|  |    | ||||||
|  |   static readonly open   = new ClassFlag( "open" ); | ||||||
|  | 
 | ||||||
|  |   static readonly default   = new ClassFlag( "default" ); | ||||||
|  | 
 | ||||||
|  |   static readonly clickable   = new ClassFlag( "clickable" ); | ||||||
|  | 
 | ||||||
|  |   private _value:string; | ||||||
|  | 
 | ||||||
|  |   constructor( value:string ) | ||||||
|  |   { | ||||||
|  |     this._value = value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromEnum( value:string, prefix = "", appendix = "" ) | ||||||
|  |   { | ||||||
|  |     let flag = value.toLowerCase().replace( "_", "-" ); | ||||||
|  | 
 | ||||||
|  |     return new ClassFlag( prefix + flag + appendix ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get selector() | ||||||
|  |   { | ||||||
|  |     return "." + this._value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   query( element:Element ) | ||||||
|  |   { | ||||||
|  |     return element.querySelector( this.selector ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryAll( element:Element ) | ||||||
|  |   { | ||||||
|  |     return DOMEditor.nodeListToArray( element.querySelectorAll( this.selector ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forEachInDoc( callback:( n:Element ) => void ) | ||||||
|  |   { | ||||||
|  |     let all = this.queryAll( document.body ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < all.length; i++) | ||||||
|  |     { | ||||||
|  |       callback( all[ i ] ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forEachIn( source:Element, callback:( n:Element ) => void ) | ||||||
|  |   { | ||||||
|  |     let all = this.queryAll( source); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < all.length; i++) | ||||||
|  |     { | ||||||
|  |       callback( all[ i ] ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryPrefixed( element:Element ) | ||||||
|  |   { | ||||||
|  |     return element.querySelector( `[class^="${this._value}"]` ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryPrefixedAll( element:Element ) | ||||||
|  |   { | ||||||
|  |     return element.querySelectorAll( `[class^="${this._value}"]` ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   queryDoc() | ||||||
|  |   { | ||||||
|  |     return document.querySelector( this.selector ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set( element:Element ) | ||||||
|  |   { | ||||||
|  |     ClassFlag.setClass( element, this._value, true ); | ||||||
|  |     return element; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setAll( elements:Element[], selectedElement:Element ) | ||||||
|  |   { | ||||||
|  |     elements.forEach( e => this.setAs( e, e === selectedElement ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setSolo( element:Element ) | ||||||
|  |   { | ||||||
|  |     element.setAttribute( "class", this._value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setAs( element:Element, value:boolean ) | ||||||
|  |   { | ||||||
|  |     ClassFlag.setClass( element, this._value, value ); | ||||||
|  |     return element; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   removeFrom( element:Element ) | ||||||
|  |   { | ||||||
|  |     ClassFlag.setClass( element, this._value, false ); | ||||||
|  |     return element; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toggle( element:Element ) | ||||||
|  |   { | ||||||
|  |     ClassFlag.toggleClass( element, this._value ); | ||||||
|  |     return element; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   in( element:Element ) | ||||||
|  |   { | ||||||
|  |     return ClassFlag.hasClass( element, this._value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static clear( element: Element ) | ||||||
|  |   { | ||||||
|  |     element.removeAttribute( "class" ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static assign( element:Element, value:string ) | ||||||
|  |   { | ||||||
|  |     new ClassFlag( value ).set( element ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static addClass( element:Element, classString:string ) | ||||||
|  |   { | ||||||
|  |     let classAttribute = element.getAttribute( "class" ) || ""; | ||||||
|  |     let matcher = RegExpUtility.createWordMatcher( classString ); | ||||||
|  |    | ||||||
|  |     if ( matcher.test( classAttribute ) ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |    | ||||||
|  |     classAttribute += " " + classString; | ||||||
|  |      | ||||||
|  |     ClassFlag.setNormalizedClassAttribute( element, classAttribute ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static removeClass( element:Element, classString:string ) | ||||||
|  |   { | ||||||
|  |     let matcher = RegExpUtility.createWordMatcher( classString ); | ||||||
|  |     let classAttribute = element.getAttribute( "class" ) || "";  | ||||||
|  |    | ||||||
|  |     classAttribute = classAttribute.replace( matcher, "" ); | ||||||
|  |     ClassFlag.setNormalizedClassAttribute( element, classAttribute ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static hasClass( element:Element, classString:string ) | ||||||
|  |   { | ||||||
|  |     let matcher = RegExpUtility.createWordMatcher( classString ); | ||||||
|  |     let classAttribute = element.getAttribute( "class" ) || "";  | ||||||
|  |      | ||||||
|  |     return matcher.test( classAttribute ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static toggleClass( element:Element, className:string ) | ||||||
|  |   { | ||||||
|  |     let flag = ! ClassFlag.hasClass( element, className ); | ||||||
|  |     ClassFlag.setClass( element, className, flag ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static setNormalizedClassAttribute( element:Element, classAttribute:string ) | ||||||
|  |   {  | ||||||
|  |     classAttribute = classAttribute.replace( /\s+/g, " " ); | ||||||
|  |     classAttribute = classAttribute.trim(); | ||||||
|  |      | ||||||
|  |     if ( "" === classAttribute ) | ||||||
|  |     { | ||||||
|  |       element.removeAttribute( "class" ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       element.setAttribute( "class", classAttribute ); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static setClass( element:Element, classString:string, enable:boolean ) | ||||||
|  |   { | ||||||
|  |     if ( enable ) | ||||||
|  |     { | ||||||
|  |       ClassFlag.addClass( element, classString );  | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       ClassFlag.removeClass( element, classString ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static toggleClassState( element:Element, classString:string ) | ||||||
|  |   { | ||||||
|  |     let hasClassAssigned = ClassFlag.hasClass( element, classString ); | ||||||
|  |    | ||||||
|  |     ClassFlag.setClass( element, classString, ! hasClassAssigned ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,162 @@ | ||||||
|  | export class DOMEditor | ||||||
|  | { | ||||||
|  |   static nodeListToArray( list:NodeList ) | ||||||
|  |   {  | ||||||
|  |     return Array.prototype.slice.call( list ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static prefixData( source:string )  | ||||||
|  |   { | ||||||
|  |     let singleReplaceRegex = /\[\s*-/g; | ||||||
|  |     source = source.replace(singleReplaceRegex, "[data-"); | ||||||
|  |     let autoDataExtensionRegex = /\[(?!data-|([a-zA-Z0-9]+s*(\=|\~|\||\^|\$|\*|\])))/g; | ||||||
|  |     return source.replace( autoDataExtensionRegex, "[data-" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static copyAttribute( destination:Element, source:Element, sourceAttribute:string)  | ||||||
|  |   { | ||||||
|  |     sourceAttribute = DOMEditor.prefixData( sourceAttribute ); | ||||||
|  |     let value = source.getAttribute (sourceAttribute ); | ||||||
|  |     destination.setAttribute( sourceAttribute, value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static copyAllAttributesThroughRawHTML( source:Element, destination:Element ) | ||||||
|  |   { | ||||||
|  |     let attributes:[string,string][] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < destination.attributes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let attribute = destination.attributes[ i ]; | ||||||
|  |       let attributeName = attribute.nodeName; | ||||||
|  |       let attributeValue = attribute.nodeValue; | ||||||
|  | 
 | ||||||
|  |       attributes.push( [ attributeName, attributeValue ] ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < source.attributes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let attribute = source.attributes[ i ]; | ||||||
|  |       let attributeName = attribute.nodeName; | ||||||
|  |       let attributeValue = attribute.nodeValue; | ||||||
|  | 
 | ||||||
|  |       let index = attributes.findIndex( a => a[ 0 ] === attributeName ); | ||||||
|  |        | ||||||
|  |       if ( index === -1 ) | ||||||
|  |       { | ||||||
|  |         attributes.push( [ attributeName, attributeValue ] ); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         attributes[ index ][ 1 ] = attributeValue; | ||||||
|  |       } | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     let innerHTML = destination.innerHTML; | ||||||
|  |     let attributesString = ""; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < attributes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( i !== 0 ){ attributesString += " "; } | ||||||
|  | 
 | ||||||
|  |       let attribute = attributes[ i ]; | ||||||
|  |       attributesString += `${attribute[ 0 ]}="${attribute[ 1 ]}"`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let tag = destination.nodeName.toLowerCase(); | ||||||
|  | 
 | ||||||
|  |     let html = `<${tag} ${attributesString}>${innerHTML}</${tag}>`; | ||||||
|  | 
 | ||||||
|  |     console.log( "Copied html:", html ); | ||||||
|  | 
 | ||||||
|  |     destination.outerHTML = html; | ||||||
|  | 
 | ||||||
|  |     return destination; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static copyAllAttributes( source:Element, destination:Element ) | ||||||
|  |   { | ||||||
|  |     for ( let i = 0; i < source.attributes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let attribute = source.attributes[ i ]; | ||||||
|  |       let attributeName = attribute.nodeName; | ||||||
|  |       let attributeValue = attribute.nodeValue;       | ||||||
|  |       destination.setAttribute( attributeName, attributeValue ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static cloneChildren( element:Element ):Node[] | ||||||
|  |   { | ||||||
|  |     let clones:Node[] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < element.childNodes.length; i++ ) | ||||||
|  |     { | ||||||
|  |        | ||||||
|  |       clones.push( element.childNodes[ i ].cloneNode( true ) ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     return clones; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static _cssCreationRegex:RegExp; | ||||||
|  | 
 | ||||||
|  |   static get cssCreationRegex()  | ||||||
|  |   { | ||||||
|  |     if ( DOMEditor._cssCreationRegex)  | ||||||
|  |     { | ||||||
|  |       return DOMEditor._cssCreationRegex; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let cssCreationRules = [ | ||||||
|  |       //"Attribute",
 | ||||||
|  |       /\[\s*(?:\w|-)+\s*(?:(?:\~|\||\^|\$|\*)?=)\s*(?:(?:"(?:\\"|.)*?")|(?:'(?:\\'|.)*?')|(?:(?:\w|-)+))\s*\]/, | ||||||
|  |       //"ID",
 | ||||||
|  |       /#(?:\w|-)+/, | ||||||
|  |       //"Class",
 | ||||||
|  |       /\.(?:\w|-)+/, | ||||||
|  |       //"Empty Attribute",
 | ||||||
|  |       /\[\s*(?:\w|-)+\s*]/, | ||||||
|  |       //"Element",
 | ||||||
|  |       /(?:\w|-)+/ | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     let regexString = ""; | ||||||
|  | 
 | ||||||
|  |     for (let i = 0; i < cssCreationRules.length; i++)  | ||||||
|  |     { | ||||||
|  |       if (i !== 0)  | ||||||
|  |       { | ||||||
|  |         regexString += "|"; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       regexString += "(" + cssCreationRules[i].source + ")"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     DOMEditor._cssCreationRegex = new RegExp(regexString, "g"); | ||||||
|  | 
 | ||||||
|  |     return DOMEditor._cssCreationRegex; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static stringToDocument( html:string ):Document | ||||||
|  |   {     | ||||||
|  |     let parser = new DOMParser(); | ||||||
|  |     let doc = parser.parseFromString( html, "text/html" ); | ||||||
|  |     return doc;     | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static remove( node:Node ) | ||||||
|  |   { | ||||||
|  |     if ( ! node ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( ! node.parentNode ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     node.parentNode.removeChild( node ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | export class DOMNameSpaces | ||||||
|  | {  | ||||||
|  |   static readonly SVG   = 'http://www.w3.org/2000/svg'; | ||||||
|  |   static readonly XLink = 'http://www.w3.org/1999/xlink'; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,412 @@ | ||||||
|  | import { HTMLNodeTreeWalker } from "../graphs/HTMLNodeTreeWalker"; | ||||||
|  | import { DOMEditor } from "./DOMEditor"; | ||||||
|  | import { DOMNameSpaces } from "./DOMNameSpaces"; | ||||||
|  | 
 | ||||||
|  | export class ElementAttribute | ||||||
|  | { | ||||||
|  |   static readonly nameAttribute = new ElementAttribute( "name", false ); | ||||||
|  |   static readonly style = new ElementAttribute( "style", false ); | ||||||
|  |   static readonly class = new ElementAttribute( "class", false ); | ||||||
|  |   static readonly id = new ElementAttribute( "id", false ); | ||||||
|  |   static readonly href = new ElementAttribute( "href", false ); | ||||||
|  |   static readonly download = new ElementAttribute( "download", false ); | ||||||
|  |   static readonly target = new ElementAttribute( "target", false ); | ||||||
|  |   static readonly type = new ElementAttribute( "type", false ); | ||||||
|  |   static readonly lang = new ElementAttribute( "lang", false ); | ||||||
|  |   static readonly src = new ElementAttribute( "src", false ); | ||||||
|  |   static readonly value = new ElementAttribute( "value", false ); | ||||||
|  |    | ||||||
|  |   static readonly svgClass = new ElementAttribute( "class", false, "" ); | ||||||
|  |   static readonly xlinkHref = new ElementAttribute( "xlink:href", false, DOMNameSpaces.XLink ); | ||||||
|  | 
 | ||||||
|  |   static readonly contentEditable = new ElementAttribute( "contenteditable", false ); | ||||||
|  |   static readonly allowfullscreen = new ElementAttribute( "allowfullscreen", false ); | ||||||
|  |   static readonly controls = new ElementAttribute( "controls", false ); | ||||||
|  |   static readonly preload = new ElementAttribute( "preload", false ); | ||||||
|  | 
 | ||||||
|  |   static readonly selected = new ElementAttribute( "selected", false ); | ||||||
|  | 
 | ||||||
|  |   static readonly data_lang = new ElementAttribute( "lang" ); | ||||||
|  | 
 | ||||||
|  |   static readonly title = new ElementAttribute( "title", false ); | ||||||
|  |   static readonly width = new ElementAttribute( "width", false ); | ||||||
|  |   static readonly height = new ElementAttribute( "height", false ); | ||||||
|  |   static readonly frameborder = new ElementAttribute( "frameborder", false ); | ||||||
|  |   static readonly allow = new ElementAttribute( "allow", false ); | ||||||
|  |   static readonly autoplay = new ElementAttribute( "autoplay", false ); | ||||||
|  |   static readonly muted = new ElementAttribute( "muted", false ); | ||||||
|  |   static readonly loop = new ElementAttribute( "loop", false ); | ||||||
|  |   static readonly readonly = new ElementAttribute( "readonly", false ); | ||||||
|  | 
 | ||||||
|  |   static readonly dataType = new ElementAttribute( "type" ); | ||||||
|  |   static readonly dataName = new ElementAttribute( "name" ); | ||||||
|  | 
 | ||||||
|  |   static readonly hidden = new ElementAttribute( "hidden", false ); | ||||||
|  | 
 | ||||||
|  |   static readonly rootHref = new ElementAttribute( "root-href" ); | ||||||
|  |    | ||||||
|  |   private _name:string; | ||||||
|  |   get name(){ return this._name; } | ||||||
|  | 
 | ||||||
|  |   get fullName() | ||||||
|  |   {  | ||||||
|  |     return this._useDataPrefix ? ( "data-" + this._name ) : this._name;  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _useDataPrefix = true; | ||||||
|  | 
 | ||||||
|  |   private _useNameSpace = false; | ||||||
|  |   get usesNameSpace(){ return this._useNameSpace; } | ||||||
|  | 
 | ||||||
|  |   private _nameSpace:string = null; | ||||||
|  |   get nameSpace(){ return this._nameSpace;} | ||||||
|  | 
 | ||||||
|  |   constructor( name:string, useDataPrefix:boolean = true, nameSpace:string = null ) | ||||||
|  |   { | ||||||
|  |     if ( this._name && this._name.startsWith( "data-" ) && useDataPrefix ) | ||||||
|  |     { | ||||||
|  |       console.warn(  | ||||||
|  |         `The name of the attribute starts with a 'data-' prefix AND the 'useDataPrefix' at the same time!` ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this._name = name; | ||||||
|  |     this._useDataPrefix = useDataPrefix; | ||||||
|  | 
 | ||||||
|  |     if ( nameSpace ) | ||||||
|  |     { | ||||||
|  |       this._useNameSpace = true; | ||||||
|  |       this._nameSpace = nameSpace === "" ? null : nameSpace; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forAll( target:Element|Document, callback:(element:Element)=>any) | ||||||
|  |   { | ||||||
|  |     let elements = target.querySelectorAll( this.selector ); | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < elements.length; i++ ) | ||||||
|  |     { | ||||||
|  |       callback( elements[ i ] ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   toString() | ||||||
|  |   { | ||||||
|  |     return this.fullName; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryDoc() | ||||||
|  |   { | ||||||
|  |     return document.querySelector( this.selector );  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   querySelfOrParent( element:Element ) | ||||||
|  |   { | ||||||
|  |     if ( this.in( element ) ) | ||||||
|  |     { | ||||||
|  |       return element; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return this.queryParent( element ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   queryParent( element:Element ) | ||||||
|  |   { | ||||||
|  |     let parent = element.parentElement; | ||||||
|  | 
 | ||||||
|  |     while ( parent ) | ||||||
|  |     { | ||||||
|  |       if ( this.in( parent ) ) | ||||||
|  |       { | ||||||
|  |         return parent; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       parent = parent.parentElement; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return null; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryParentValue( element:Element ) | ||||||
|  |   { | ||||||
|  |     let parent = this.queryParent( element ); | ||||||
|  |     return this.from( parent ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryWithValue( element:Element|Document, value:string ) | ||||||
|  |   { | ||||||
|  |     return element.querySelector( this.selectorEquals( value ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryDocWithValue( value:string ) | ||||||
|  |   { | ||||||
|  |     return this.queryWithValue( document, value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   hasParentWithValue( element:Element, value:string ) | ||||||
|  |   { | ||||||
|  |     return this.queryParentValue( element ) === value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   query<T=Element>( element:Element|Document ) | ||||||
|  |   { | ||||||
|  |     return element.querySelector( this.selector ) as any as T;  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   isQueriedChecked( element:Element ) | ||||||
|  |   { | ||||||
|  |     let queried = this.query<HTMLInputElement>( element ); | ||||||
|  | 
 | ||||||
|  |     if ( ! queried ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return queried.checked; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   matches( element:Element, regex:RegExp ) | ||||||
|  |   { | ||||||
|  |     if ( ! this.in( element ) ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return regex.exec( this.from( element ) ) !== null;  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryDocAll() | ||||||
|  |   { | ||||||
|  |     return DOMEditor.nodeListToArray( document.querySelectorAll( this.selector ) );  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forEachInDoc( callback:(element:Element)=>void ) | ||||||
|  |   { | ||||||
|  |     this.queryDocAll().forEach( callback ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryAll( t:Element|Document ) | ||||||
|  |   { | ||||||
|  |     if ( ! this._needsWalker( this.selector ) ) | ||||||
|  |     { | ||||||
|  |       return this._queryAllQuerySelector( t ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return this._queryAllWalker( t ); | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _needsWalker( selector:string ) | ||||||
|  |   { | ||||||
|  |     if ( "[xlink:href]" == selector ) | ||||||
|  |     { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   _queryAllQuerySelector( element:Element|Document ) | ||||||
|  |   { | ||||||
|  |     return DOMEditor.nodeListToArray( element.querySelectorAll( this.selector ) );  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _queryAllWalker( t:Element|Document ) | ||||||
|  |   { | ||||||
|  |     let list:Element[] = []; | ||||||
|  |     let walker = new HTMLNodeTreeWalker(); | ||||||
|  |     let end = walker.iterationEndOf( t ); | ||||||
|  |     let it:Node = t; | ||||||
|  | 
 | ||||||
|  |     while ( it !== end )  | ||||||
|  |     {  | ||||||
|  |       if ( Node.ELEMENT_NODE != it.nodeType ) | ||||||
|  |       { | ||||||
|  |         it = walker.nextNode( it ); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       let element = it as Element; | ||||||
|  | 
 | ||||||
|  |       if ( this.in( element ) ) | ||||||
|  |       { | ||||||
|  |         list.push( element ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       it = walker.nextNode( it ); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return list; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryValueInDoc( element:Element ) | ||||||
|  |   { | ||||||
|  |     return this.queryValueIn( element, document ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryValueIn( element:Element, source:Document|Element = undefined ) | ||||||
|  |   { | ||||||
|  |     source = source || element; | ||||||
|  |      | ||||||
|  |     let selector = this.from( element ); | ||||||
|  |     return source.querySelector( selector ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  |   get selector() | ||||||
|  |   { | ||||||
|  |     return `[${this.fullName}]`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   selectorContaining( fragment:string ) | ||||||
|  |   { | ||||||
|  |     return `[${this.fullName}*="${fragment}"]`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   selectorContainingWord( word:string ) | ||||||
|  |   { | ||||||
|  |     return `[${this.fullName}|="${word}"]`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   selectorStartsWith( word:string ) | ||||||
|  |   { | ||||||
|  |     return `[${this.fullName}^="${word}"]`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   selectorEquals( word:string ) | ||||||
|  |   {     | ||||||
|  |     let selector = `[${this.fullName}="${word}"]`; | ||||||
|  |     return selector; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   is( element:Element, value:string|RegExp ) | ||||||
|  |   { | ||||||
|  |     if ( ! this.in( element ) ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let stringValue = this.from( element ); | ||||||
|  | 
 | ||||||
|  |     if ( typeof value === "string" ) | ||||||
|  |     { | ||||||
|  |       return value === stringValue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return value.test( stringValue ); | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   in( element:Element ) | ||||||
|  |   { | ||||||
|  |     if ( ! element.attributes ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( this._useNameSpace ) | ||||||
|  |     { | ||||||
|  |       return element.hasAttributeNS( this._nameSpace, this.fullName ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return element.hasAttribute( this.fullName ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   inAll( elements:Element[] ) | ||||||
|  |   { | ||||||
|  |     if ( elements.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let e of elements ) | ||||||
|  |     { | ||||||
|  |       if ( ! this.in( e ) ) | ||||||
|  |       { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   from( element:Element, alternative:string = null ) | ||||||
|  |   { | ||||||
|  |     if ( ! element ) | ||||||
|  |     { | ||||||
|  |       console.log( "Element is null", this ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( this._useNameSpace ) | ||||||
|  |     { | ||||||
|  |       return element.getAttributeNS( this._nameSpace, this.fullName ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return element.getAttribute( this.fullName ) || alternative; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toggleRemove( element:Element, value:boolean ) | ||||||
|  |   { | ||||||
|  |     if ( value ) | ||||||
|  |     { | ||||||
|  |       this.to( element, "" ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       this.removeFrom( element ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   to( element:Element, value:string|number|boolean = "") | ||||||
|  |   { | ||||||
|  |     value = value + ""; | ||||||
|  |      | ||||||
|  |     if ( ! element ) | ||||||
|  |     { | ||||||
|  |       console.log( "Element is null", this ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( this._useNameSpace ) | ||||||
|  |     { | ||||||
|  |       element.setAttributeNS( this._nameSpace, this.fullName, value ); | ||||||
|  |        | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     element.setAttribute( this.fullName, value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   replaceIn( element:Element, matcher:RegExp, replacement:string ) | ||||||
|  |   { | ||||||
|  |     let value = this.from( element ); | ||||||
|  |     value = value.replace( matcher, replacement ); | ||||||
|  |     this.to( element, value ) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   removeFrom( element:Element ) | ||||||
|  |   { | ||||||
|  |     if ( this._useNameSpace ) | ||||||
|  |     { | ||||||
|  |       element.removeAttributeNS( this._nameSpace, this.fullName ); | ||||||
|  |        | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     element.removeAttribute( this.fullName ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   copy( source:Element, target:Element ) | ||||||
|  |   { | ||||||
|  |     this.to( target, this.from( source ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,172 @@ | ||||||
|  | import { DOMEditor } from "./DOMEditor"; | ||||||
|  | 
 | ||||||
|  | export class ElementType<E extends Element>  | ||||||
|  | { | ||||||
|  |   static readonly any = new ElementType<Element>( "*" ); | ||||||
|  |   static readonly input  = new ElementType<HTMLInputElement>( "input" ); | ||||||
|  | 
 | ||||||
|  |   static readonly meta = new ElementType<HTMLMetaElement>( "meta" ); | ||||||
|  |   static readonly title = new ElementType<HTMLMetaElement>( "title" ); | ||||||
|  | 
 | ||||||
|  |   static readonly anchor = new ElementType<HTMLAnchorElement>( "a" ); | ||||||
|  |   static readonly break  = new ElementType<HTMLBRElement>( "br" ); | ||||||
|  | 
 | ||||||
|  |   static readonly image  = new ElementType<HTMLImageElement>( "img" ); | ||||||
|  |   static readonly audio  = new ElementType<HTMLAudioElement>( "audio" ); | ||||||
|  |   static readonly video  = new ElementType<HTMLVideoElement>( "video" ); | ||||||
|  |   static readonly source = new ElementType<HTMLSourceElement>( "source" ); | ||||||
|  |   static readonly canvas = new ElementType<HTMLCanvasElement>( "canvas" ); | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   static readonly script = new ElementType<HTMLScriptElement>( "script" ); | ||||||
|  |   static readonly style  = new ElementType<HTMLStyleElement>( "style" ); | ||||||
|  |   static readonly form   = new ElementType<HTMLFormElement>( "form" ); | ||||||
|  |   static readonly button = new ElementType<HTMLButtonElement>( "button" ); | ||||||
|  |   static readonly select = new ElementType<HTMLSelectElement>( "select" ); | ||||||
|  |   static readonly option = new ElementType<HTMLOptionElement>( "option" ); | ||||||
|  |   static readonly textArea = new ElementType<HTMLTextAreaElement>( "textarea" ); | ||||||
|  | 
 | ||||||
|  |   static readonly table = new ElementType<HTMLTableElement>( "table" ); | ||||||
|  |   static readonly thead = new ElementType<HTMLTableElement>( "thead" ); | ||||||
|  |   static readonly tbody = new ElementType<HTMLTableElement>( "tbody" ); | ||||||
|  |    | ||||||
|  |   static readonly tr = new ElementType<HTMLTableRowElement>( "tr" ); | ||||||
|  |   static readonly th = new ElementType<HTMLTableCellElement>( "th" ); | ||||||
|  |   static readonly td = new ElementType<HTMLTableCellElement>( "td" ); | ||||||
|  |    | ||||||
|  |   static readonly hr = new ElementType<HTMLElement>( "hr" ); | ||||||
|  | 
 | ||||||
|  |   static readonly span = new ElementType<HTMLSpanElement>( "span" ); | ||||||
|  |   static readonly div = new ElementType<HTMLDivElement>( "div" ); | ||||||
|  |   static readonly code = new ElementType<HTMLElement>( "code" ); | ||||||
|  |   static readonly pre = new ElementType<HTMLPreElement>( "pre" ); | ||||||
|  | 
 | ||||||
|  |   static readonly p = new ElementType<HTMLElement>( "p" ); | ||||||
|  |   static readonly b = new ElementType<HTMLElement>( "b" ); | ||||||
|  |   static readonly i = new ElementType<HTMLElement>( "i" ); | ||||||
|  | 
 | ||||||
|  |   static readonly h1 = new ElementType<HTMLElement>( "h1" ); | ||||||
|  |   static readonly h2 = new ElementType<HTMLElement>( "h2" ); | ||||||
|  |   static readonly h3 = new ElementType<HTMLElement>( "h3" ); | ||||||
|  |   static readonly h4 = new ElementType<HTMLElement>( "h4" ); | ||||||
|  |   static readonly h5 = new ElementType<HTMLElement>( "h5" ); | ||||||
|  |   static readonly h6 = new ElementType<HTMLElement>( "h6" ); | ||||||
|  |   static readonly h7 = new ElementType<HTMLElement>( "h7" ); | ||||||
|  |   static readonly h8 = new ElementType<HTMLElement>( "h8" ); | ||||||
|  |   static readonly h9 = new ElementType<HTMLElement>( "h9" ); | ||||||
|  |   static readonly h10 = new ElementType<HTMLElement>( "h10" ); | ||||||
|  | 
 | ||||||
|  |   static readonly section = new ElementType<HTMLElement>( "section" ); | ||||||
|  | 
 | ||||||
|  |   static readonly iframe = new ElementType<HTMLIFrameElement>( "iframe" ); | ||||||
|  | 
 | ||||||
|  |   static readonly svg = new ElementType<SVGElement>( "svg" );  | ||||||
|  | 
 | ||||||
|  |   static readonly strong = new ElementType( "strong" );  | ||||||
|  | 
 | ||||||
|  |   _type:string; | ||||||
|  |   _rawType:string; | ||||||
|  |   _namespace:string; | ||||||
|  | 
 | ||||||
|  |   public constructor( type:string, namespace:string = undefined ) | ||||||
|  |   { | ||||||
|  |     this._rawType = type; | ||||||
|  |     this._type = this._rawType.toUpperCase(); | ||||||
|  | 
 | ||||||
|  |     if ( namespace !== undefined ) | ||||||
|  |     { | ||||||
|  |       this._namespace = namespace; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   get type() | ||||||
|  |   { | ||||||
|  |     return this._type; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get rawType() | ||||||
|  |   { | ||||||
|  |     return this._rawType; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get selector() | ||||||
|  |   { | ||||||
|  |     return this._type; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forAll( target:Element|Document, callback:(element:E)=>any) | ||||||
|  |   { | ||||||
|  |     let elements = this.queryAll( target ); | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < elements.length; i++ ) | ||||||
|  |     { | ||||||
|  |       callback( elements[ i ] ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forAllInDoc( callback:(element:E)=>any) | ||||||
|  |   { | ||||||
|  |     this.forAll( document, callback ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryDoc():E | ||||||
|  |   { | ||||||
|  |     return document.querySelector( this._rawType ) as E; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   query( element:Document|Element ):E | ||||||
|  |   { | ||||||
|  |     return element.querySelector( this._rawType ) as E; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   queryAll( element:Document|Element ):E[] | ||||||
|  |   { | ||||||
|  |     return DOMEditor.nodeListToArray( element.querySelectorAll( this._rawType ) ) as E[]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   create( children:(Node|string)|(Node|string)[] = undefined, doc:Document = undefined ):E | ||||||
|  |   { | ||||||
|  |     doc = doc || document; | ||||||
|  | 
 | ||||||
|  |     let anyElement = | ||||||
|  |     this._namespace === undefined ?  doc.createElement( this._rawType )  | ||||||
|  |                                   :  doc.createElementNS( this._namespace, this._rawType );  | ||||||
|  |      | ||||||
|  |     let element = anyElement as any as E; | ||||||
|  | 
 | ||||||
|  |     if ( children ) | ||||||
|  |     { | ||||||
|  |       if ( Array.isArray( children ) ) | ||||||
|  |       { | ||||||
|  |         for ( let i = 0; i < children.length; i++ ) | ||||||
|  |         { | ||||||
|  |           if ( typeof children[ i ] === "string" ) | ||||||
|  |           { | ||||||
|  |             element.appendChild( doc.createTextNode( children[ i ] as string ) ); | ||||||
|  |           } | ||||||
|  |           else | ||||||
|  |           { | ||||||
|  |             element.appendChild( children[ i ] as Node ); | ||||||
|  |           } | ||||||
|  |         }  | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         if ( typeof children === "string" ) | ||||||
|  |         { | ||||||
|  |           element.appendChild( doc.createTextNode( children as string ) ); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |           element.appendChild( children as Node ); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |             | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return element; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,101 @@ | ||||||
|  | export abstract class AsyncBooleanExpression<T> | ||||||
|  | { | ||||||
|  |   abstract evaluate( t:T ):Promise<boolean>; | ||||||
|  |    | ||||||
|  |   NOT():AsyncBooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     return new NotAsyncExpression<T>( this ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   AND( matcher:AsyncBooleanExpression<T>):AsyncBooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     return new AndAsyncExpression<T>( [ this, matcher ] ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   OR( matcher:AsyncBooleanExpression<T>):AsyncBooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     return new OrAsyncExpression<T>( [ this, matcher ] ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class NotAsyncExpression<T> extends AsyncBooleanExpression<T> | ||||||
|  | { | ||||||
|  |   private _matcher:AsyncBooleanExpression<T>; | ||||||
|  | 
 | ||||||
|  |   constructor( matcher:AsyncBooleanExpression<T> ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._matcher = matcher; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async evaluate( t:T ):Promise<boolean> | ||||||
|  |   { | ||||||
|  |     let result = await this._matcher.evaluate( t ); | ||||||
|  |     return Promise.resolve( ! result ); | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   NOT() | ||||||
|  |   { | ||||||
|  |     return this._matcher; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class ListInputAsyncExpression<T> extends AsyncBooleanExpression<T> | ||||||
|  | { | ||||||
|  |   protected _matchers:AsyncBooleanExpression<T>[]; | ||||||
|  |    | ||||||
|  |   constructor( matchers:AsyncBooleanExpression<T>[] ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._matchers = matchers; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AndAsyncExpression<T> extends ListInputAsyncExpression<T> | ||||||
|  | { | ||||||
|  |   async evaluate( t:T ):Promise<boolean> | ||||||
|  |   { | ||||||
|  |     for ( let matcher of this._matchers ) | ||||||
|  |     { | ||||||
|  |       let result = await matcher.evaluate( t ); | ||||||
|  |        | ||||||
|  |       if ( ! result ) | ||||||
|  |       { | ||||||
|  |         return Promise.resolve( false ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return Promise.resolve( true ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   AND( matcher:AsyncBooleanExpression<T> ) | ||||||
|  |   { | ||||||
|  |     this._matchers.push( matcher ); | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class OrAsyncExpression<T> extends ListInputAsyncExpression<T> | ||||||
|  | { | ||||||
|  |   async evaluate( t:T ):Promise<boolean> | ||||||
|  |   { | ||||||
|  |     for ( let matcher of this._matchers ) | ||||||
|  |     { | ||||||
|  |       let result = await matcher.evaluate( t ); | ||||||
|  |        | ||||||
|  |       if ( result ) | ||||||
|  |       { | ||||||
|  |         return Promise.resolve( true ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return Promise.resolve( false ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   OR( matcher:AsyncBooleanExpression<T> ) | ||||||
|  |   { | ||||||
|  |     this._matchers.push( matcher ); | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | import { AsyncBooleanExpression } from "./AsyncBooleanExpression"; | ||||||
|  | 
 | ||||||
|  | export abstract class StringValueAsyncMatcher<T> extends AsyncBooleanExpression<T> | ||||||
|  | { | ||||||
|  |   abstract getStringValue( t:T ):Promise<string>;  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class LitaralAsyncMatcher<T,L extends string> extends StringValueAsyncMatcher<T> | ||||||
|  | { | ||||||
|  |   _literal:L  | ||||||
|  |   constructor( literal:L ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._literal = literal; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async evaluate( t:T ):Promise<boolean> | ||||||
|  |   { | ||||||
|  | 
 | ||||||
|  |     let value = await this.getStringValue( t ); | ||||||
|  | 
 | ||||||
|  |     if ( value === null ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return Promise.resolve( this._literal === value ); | ||||||
|  |   }  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class ContainsLitaralAsyncMatcher<T,L extends string> extends StringValueAsyncMatcher<T> | ||||||
|  | { | ||||||
|  |   _literal:L  | ||||||
|  |   constructor( literal:L ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._literal = literal; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async evaluate( t:T ):Promise<boolean> | ||||||
|  |   { | ||||||
|  | 
 | ||||||
|  |     let value = await this.getStringValue( t ); | ||||||
|  | 
 | ||||||
|  |     if ( value === null ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return Promise.resolve( value.indexOf( this._literal ) !== -1 ); | ||||||
|  |   }  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class RegExpAsyncMatcher<T> extends StringValueAsyncMatcher<T> | ||||||
|  | { | ||||||
|  |   private _regexp:RegExp; | ||||||
|  |    | ||||||
|  |   constructor( regexp:RegExp ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._regexp = regexp; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async evaluate( t:T ):Promise<boolean> | ||||||
|  |   { | ||||||
|  |     let value = await this.getStringValue( t ); | ||||||
|  | 
 | ||||||
|  |     if ( value === null ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return Promise.resolve( this._regexp.test( value ) ); | ||||||
|  |   }  | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,114 @@ | ||||||
|  | export abstract class BooleanExpression<T> | ||||||
|  | { | ||||||
|  |   abstract evaluate( t:T ):boolean; | ||||||
|  |    | ||||||
|  |   NOT():BooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     return new NotExpression<T>( this ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   AND( matcher:BooleanExpression<T>):BooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     return new AndExpression<T>( [ this, matcher ] ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   OR( matcher:BooleanExpression<T>):BooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     return new OrExpression<T>( [ this, matcher ] ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   AND_ALL( matchers:BooleanExpression<T>[] ):BooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     if ( matchers.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let outputExpression = this.AND( matchers[ 0 ] ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 1; i < matchers.length; i++ ) | ||||||
|  |     { | ||||||
|  |       outputExpression = outputExpression.AND( matchers[ i ] ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return outputExpression;     | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   OR_ANY( matchers:BooleanExpression<T>[] ):BooleanExpression<T> | ||||||
|  |   { | ||||||
|  |     if ( matchers.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let outputExpression = this.OR( matchers[ 0 ] ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 1; i < matchers.length; i++ ) | ||||||
|  |     { | ||||||
|  |       outputExpression = outputExpression.OR( matchers[ i ] ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return outputExpression;    | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class NotExpression<T> extends BooleanExpression<T> | ||||||
|  | { | ||||||
|  |   private _matcher:BooleanExpression<T>; | ||||||
|  | 
 | ||||||
|  |   constructor( matcher:BooleanExpression<T> ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._matcher = matcher; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   evaluate( t:T ) | ||||||
|  |   { | ||||||
|  |     return ! this._matcher.evaluate( t ); | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   NOT() | ||||||
|  |   { | ||||||
|  |     return this._matcher; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class ListInputExpression<T> extends BooleanExpression<T> | ||||||
|  | { | ||||||
|  |   protected _matchers:BooleanExpression<T>[]; | ||||||
|  |    | ||||||
|  |   constructor( matchers:BooleanExpression<T>[] ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._matchers = matchers; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AndExpression<T> extends ListInputExpression<T> | ||||||
|  | { | ||||||
|  |   evaluate( t:T ) | ||||||
|  |   { | ||||||
|  |     return this._matchers.every( m => m.evaluate( t ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   AND( matcher:BooleanExpression<T> ) | ||||||
|  |   { | ||||||
|  |     this._matchers.push( matcher ); | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class OrExpression<T> extends ListInputExpression<T> | ||||||
|  | { | ||||||
|  |   evaluate( t:T ) | ||||||
|  |   { | ||||||
|  |     return this._matchers.some( m => m.evaluate( t ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   OR( matcher:BooleanExpression<T> ) | ||||||
|  |   { | ||||||
|  |     this._matchers.push( matcher ); | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | import { BooleanExpression } from "./BooleanExpression"; | ||||||
|  | 
 | ||||||
|  | export abstract class StringValueMatcher<T> extends BooleanExpression<T> | ||||||
|  | { | ||||||
|  |   abstract getStringValue( t:T ):string;  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class LitaralMatcher<T,L extends string> extends StringValueMatcher<T> | ||||||
|  | { | ||||||
|  |   _literal:L  | ||||||
|  |   constructor( literal:L ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._literal = literal; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   evaluate( t:T ) | ||||||
|  |   { | ||||||
|  |     let value = this.getStringValue( t ); | ||||||
|  |     return this._literal === value; | ||||||
|  |   }  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class RegExpMatcher<T> extends StringValueMatcher<T> | ||||||
|  | { | ||||||
|  |   private _regexp:RegExp; | ||||||
|  |    | ||||||
|  |   constructor( regexp:RegExp ) | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._regexp = regexp; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   evaluate( t:T ) | ||||||
|  |   { | ||||||
|  |     let value = this.getStringValue( t ); | ||||||
|  |     return this._regexp.test( value ); | ||||||
|  |   }  | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,115 @@ | ||||||
|  | import { MathX } from "../math/MathX"; | ||||||
|  | 
 | ||||||
|  | export class Range | ||||||
|  | { | ||||||
|  |   min:number; | ||||||
|  |   max:number; | ||||||
|  |    | ||||||
|  |   constructor( min:number, max:number ) | ||||||
|  |   { | ||||||
|  |     this.min = min; | ||||||
|  |     this.max = max || min; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static from( values:number[] ) | ||||||
|  |   { | ||||||
|  |     return new Range( values[ 0 ], values[ 1 ] );  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ensurecorrectness():void | ||||||
|  |   { | ||||||
|  |     if ( this.max < this.min ) | ||||||
|  |     { | ||||||
|  |       let b = this.max;  | ||||||
|  |       this.max = this.min;  | ||||||
|  |       this.min = b; | ||||||
|  |     } | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   contains( value:number ):boolean | ||||||
|  |   { | ||||||
|  |     return this.min <= value && value <= this.max; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   overlaps( other:Range ):boolean | ||||||
|  |   { | ||||||
|  |     if ( other.max < this.min ) { return false; } | ||||||
|  |     if ( other.min > this.max ) { return false; } | ||||||
|  |      | ||||||
|  |     if ( other.contains( this.min ) || other.contains( this.max ) )  | ||||||
|  |     { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( this.contains( this.min ) || this.contains( this.max ) )  | ||||||
|  |     { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getOverlap( other:Range ) | ||||||
|  |   { | ||||||
|  |     if ( ! this.overlaps( other ) ) | ||||||
|  |     { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return new Range( Math.max( this.min, other.min ), Math.min( this.max, other.max ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get center():number { return 0.5 * ( this.max + this.min ); }  | ||||||
|  |   get length():number {  return this.max - this.min; }  | ||||||
|  | 
 | ||||||
|  |   distanceTo( other:Range ):number | ||||||
|  |   { | ||||||
|  |     let center = this.center; | ||||||
|  |     let othercenter = other.center; | ||||||
|  | 
 | ||||||
|  |     let ownLength = this.length; | ||||||
|  |     let otherLength = other.length; | ||||||
|  | 
 | ||||||
|  |     let distance = Math.abs( center - othercenter ); | ||||||
|  | 
 | ||||||
|  |     return Math.max( 0, distance - 0.5 * ( ownLength + otherLength ) ); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   copy():Range | ||||||
|  |   { | ||||||
|  |     return new Range( this.min, this.max ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   sampleAt( normalized:number ):number | ||||||
|  |   { | ||||||
|  |     return this.min + ( this.max - this.min ) * normalized; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   normalize( value:number ):number | ||||||
|  |   { | ||||||
|  |     return ( value - this.min ) / ( this.max - this.min ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   clamp( value:number ) | ||||||
|  |   { | ||||||
|  |     return MathX.clamp( value, this.min, this.max ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   equals( range:Range ) | ||||||
|  |   { | ||||||
|  |     return range.min === this.min && range.max === this.max; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static get of_01():Range | ||||||
|  |   { | ||||||
|  |     return new Range( 0, 1 );  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static get Maximum():Range | ||||||
|  |   { | ||||||
|  |     return new Range( -Number.MAX_VALUE, Number.MAX_VALUE ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,48 @@ | ||||||
|  | import { TreeWalker } from './TreeWalker'; | ||||||
|  | 
 | ||||||
|  | export abstract class ArrayChildrenTreeWalker<T> extends TreeWalker<T> | ||||||
|  | { | ||||||
|  |   _parentMap = new Map<T,T>(); | ||||||
|  |    | ||||||
|  |   abstract getChildren( t:T ):T[]; | ||||||
|  | 
 | ||||||
|  |   clearParentMap() | ||||||
|  |   { | ||||||
|  |     this._parentMap.clear(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateParentMap( root:T, ...otherRoots:T[] ) | ||||||
|  |   { | ||||||
|  |     let stack = [ root ].concat( otherRoots ); | ||||||
|  | 
 | ||||||
|  |     while ( stack.length > 0 ) | ||||||
|  |     { | ||||||
|  |       let parent = stack.shift(); | ||||||
|  |       let children = this.getChildren( parent ); | ||||||
|  | 
 | ||||||
|  |       for ( let child of children ) | ||||||
|  |       { | ||||||
|  |         this._parentMap.set( child, parent ); | ||||||
|  |         stack.push( child ); | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   childAt( t:T, index:number ) | ||||||
|  |   { | ||||||
|  |     return this.getChildren( t )[ index ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   parent( t:T ):T | ||||||
|  |   { | ||||||
|  |     return this._parentMap.get( t ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   numChildren( t:T ) | ||||||
|  |   { | ||||||
|  |     return this.getChildren( t ).length; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | import { TreeWalker } from './TreeWalker'; | ||||||
|  | 
 | ||||||
|  | export class HTMLNodeTreeWalker extends TreeWalker<Node> | ||||||
|  | { | ||||||
|  |   private static _instance:HTMLNodeTreeWalker; | ||||||
|  | 
 | ||||||
|  |   static get $() | ||||||
|  |   { | ||||||
|  |     if ( HTMLNodeTreeWalker._instance ) | ||||||
|  |     { | ||||||
|  |       return HTMLNodeTreeWalker._instance; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     HTMLNodeTreeWalker._instance = new HTMLNodeTreeWalker(); | ||||||
|  |     return HTMLNodeTreeWalker._instance; | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   parent( node:Node ) | ||||||
|  |   { | ||||||
|  |     return node.parentNode; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   childAt( node:Node, index:number ) | ||||||
|  |   { | ||||||
|  |     if ( Node.ELEMENT_NODE !== node.nodeType ) | ||||||
|  |     { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( node as Element ).childNodes[ index ]; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   numChildren( node:Node ):number | ||||||
|  |   { | ||||||
|  |     if ( Node.ELEMENT_NODE !== node.nodeType ) | ||||||
|  |     { | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( node as Element ).childNodes.length; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,266 @@ | ||||||
|  | 
 | ||||||
|  | // (boolean|N|number)\s+(\w+)\(\s*(?:(\w+)\s*(\w+))\s*\)
 | ||||||
|  | // $2( $4:$3 ):$1
 | ||||||
|  | // (boolean|N|number)\s+(\w+)\(\s*(?:(\w+)\s*(\w+))\s*(?:\,\s*(\w+)\s*(\w+))\s*\)
 | ||||||
|  | // $2( $4:$3, $6:$5 ):$1
 | ||||||
|  | // (\w+)\(\s+(\w+)\s+\)
 | ||||||
|  | // this.$1( $2 )
 | ||||||
|  | // (\w+)\(\s+(\w+)\s+\)
 | ||||||
|  | // this.$1( $2 )
 | ||||||
|  | export abstract class TreeWalker<N>  | ||||||
|  | { | ||||||
|  |   abstract parent( node:N ):N;   | ||||||
|  |    | ||||||
|  |   abstract childAt( node:N, index:number ):N;   | ||||||
|  |    | ||||||
|  |   abstract numChildren( node:N ):number;   | ||||||
|  |    | ||||||
|  |   hasChildren( node:N ):boolean | ||||||
|  |   { | ||||||
|  | 		return this.numChildren( node )>0; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  |   hasParent( node:N ):boolean | ||||||
|  |   { | ||||||
|  | 		return this.parent( node ) != null; | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |   childIndexOf( node:N ):number | ||||||
|  |   { | ||||||
|  |     let p = this.parent( node ); | ||||||
|  | 
 | ||||||
|  |     if ( p == null ) | ||||||
|  |     { | ||||||
|  |       return -1; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     let numKids = this.numChildren( p ); | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i<numKids; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( this.childAt( p, i ) == node ) | ||||||
|  |       { | ||||||
|  |         return i; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return -1; | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 	 | ||||||
|  |   siblingAt( node:N, index:number ):N | ||||||
|  | 	{ | ||||||
|  | 		let p = this.parent( node ); | ||||||
|  |      | ||||||
|  | 		if ( p == null || index<0 || index >= this.numChildren( p ) ) | ||||||
|  | 		{ return null; } | ||||||
|  | 
 | ||||||
|  | 		return this.childAt( p, index ); | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   nextSibling( node:N ):N | ||||||
|  |   { | ||||||
|  |     let index = this.childIndexOf( node );       | ||||||
|  |     return this.siblingAt( node, index+1 ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   previousSibling( node:N ):N | ||||||
|  |   { | ||||||
|  |     let index = this.childIndexOf( node );       | ||||||
|  |     return this.siblingAt( node, index-1 ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   hasSiblingAt( node:N, index:number ):boolean | ||||||
|  | 	{ | ||||||
|  | 		let p = this.parent( node ); | ||||||
|  | 		if ( p == null || index<0 || index >= this.numChildren( p ) ) | ||||||
|  | 		{ return false; } | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   firstChild( node:N ):N | ||||||
|  |   { | ||||||
|  | 		return this.numChildren( node )<=0?null:this.childAt( node, 0 ); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
|  |   lastChild( node:N ):N | ||||||
|  |   { | ||||||
|  | 		let num = this.numChildren( node ); | ||||||
|  | 		return num <= 0?null:this.childAt( node, num-1 ); | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |   forAll( node:N, callback:( node:N ) => void ) | ||||||
|  |   { | ||||||
|  |     let end = this.iterationEndOf( node ); | ||||||
|  | 
 | ||||||
|  |     let it = node; | ||||||
|  |      | ||||||
|  |     while ( it && it !== end ) | ||||||
|  |     { | ||||||
|  |       callback( it ); | ||||||
|  |       it = this.nextNode( it ); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 	 | ||||||
|  |   nextNode( node:N ):N | ||||||
|  |   { | ||||||
|  | 		if ( this.hasChildren( node ) ) | ||||||
|  | 		{ | ||||||
|  | 			return this.firstChild( node ); | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		let next = this.nextSibling( node ); | ||||||
|  | 		 | ||||||
|  | 		if ( next != null ) | ||||||
|  | 		{ | ||||||
|  | 			return next; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		let parent = this.parent( node ); | ||||||
|  | 		 | ||||||
|  | 		while ( parent != null ) | ||||||
|  | 		{ | ||||||
|  | 			let n = this.nextSibling( parent ); | ||||||
|  | 
 | ||||||
|  | 			if ( n != null ) | ||||||
|  | 			{ | ||||||
|  | 				return n; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			parent = this.parent( parent ); | ||||||
|  | 		} | ||||||
|  |      | ||||||
|  | 		return null; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
|  |   previousNode( node:N ):N | ||||||
|  |   { | ||||||
|  | 		let prev = this.previousSibling( node ); | ||||||
|  | 		 | ||||||
|  | 		if ( prev != null ) | ||||||
|  | 		{ | ||||||
|  | 			while ( this.hasChildren( prev ) ) | ||||||
|  | 			{ | ||||||
|  | 				prev = this.lastChild( prev ); | ||||||
|  | 			} | ||||||
|  | 			return prev; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return this.parent( node ); | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   rootParent( node:N ):N | ||||||
|  |   { | ||||||
|  | 		node = this.parent( node ); | ||||||
|  | 
 | ||||||
|  | 		if ( node == null ) | ||||||
|  | 		{ | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		while ( this.hasParent( node ) ) | ||||||
|  | 		{ | ||||||
|  | 			node = this.parent( node ); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return node; | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   lastGrandChild( node:N ):N | ||||||
|  |   { | ||||||
|  | 		if ( this.hasChildren( node ) ) | ||||||
|  | 		{ | ||||||
|  | 			node = this.lastChild( node ); | ||||||
|  | 
 | ||||||
|  | 			while ( this.hasChildren( node ) ) | ||||||
|  | 			{ | ||||||
|  | 				node = this.lastChild( node ); | ||||||
|  | 			} | ||||||
|  |        | ||||||
|  | 			return node; | ||||||
|  | 		} | ||||||
|  |      | ||||||
|  | 		return null; | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |   isChildOfAny( child:N, set:Set<N> ):boolean  | ||||||
|  |   { | ||||||
|  |     let p = this.parent( child ); | ||||||
|  | 
 | ||||||
|  |     while ( p ) | ||||||
|  |     { | ||||||
|  |       if ( set.has( p ) ) | ||||||
|  |       { | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       p = this.parent( p );  | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   childOf( child:N, parent:N ):boolean | ||||||
|  | 	{ | ||||||
|  | 		let p = this.parent( child ); | ||||||
|  | 
 | ||||||
|  | 		while ( p != null ) | ||||||
|  | 		{ | ||||||
|  | 			if ( p == parent ) | ||||||
|  | 			{ | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  | 			p = this.parent( p ); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   numParents( node:N ):number | ||||||
|  |   { | ||||||
|  | 		let num = 0; | ||||||
|  | 		let p = this.parent( node ); | ||||||
|  | 
 | ||||||
|  | 		while ( p != null ) | ||||||
|  | 		{ | ||||||
|  | 			num++; | ||||||
|  | 			p = this.parent( p ); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return num; | ||||||
|  | 	} | ||||||
|  |    | ||||||
|  |    | ||||||
|  |   lastOuterNode( node:N ):N | ||||||
|  |   {  	 | ||||||
|  | 		while ( this.hasChildren( node ) ) | ||||||
|  | 		{ | ||||||
|  | 			node = this.lastChild( node ); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return node; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
|  |   nextNonChild( node:N ):N | ||||||
|  |   { | ||||||
|  | 		return this.nextNode( this.lastOuterNode( node ) ); | ||||||
|  | 	}     | ||||||
|  |    | ||||||
|  |   iterationEndOf( node:N ):N | ||||||
|  |   { | ||||||
|  |     return this.nextNonChild( node ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,283 @@ | ||||||
|  | export class MathX | ||||||
|  | { | ||||||
|  |   static stringify( value:number, fractionDigits:number = 3, intDigits:number = 0, cutZeroFraction:boolean = true ) | ||||||
|  |   { | ||||||
|  |     let fixed = value.toFixed( fractionDigits ); | ||||||
|  | 
 | ||||||
|  |     let regex  = /(\+|\-)?(\d*)\.(\d*)/; | ||||||
|  |     let parsed = regex.exec( fixed ); | ||||||
|  | 
 | ||||||
|  |     let sign     = parsed[ 1 ] || "";  | ||||||
|  |     let ints     = parsed[ 2 ]; | ||||||
|  |     let fraction = parsed[ 3 ]; | ||||||
|  | 
 | ||||||
|  |     while ( intDigits > ints.length ) | ||||||
|  |     { | ||||||
|  |       ints = "0" + ints; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( cutZeroFraction ) | ||||||
|  |     { | ||||||
|  |       fraction = fraction.replace( /0+$/, "" ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( fraction.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return sign + ints; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return sign + ints + "." + fraction; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static log( value:number, base:number = Math.E ) | ||||||
|  |   { | ||||||
|  |     return Math.log( value )/ Math.log( base ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static normalize( value:number, min:number, max:number ) | ||||||
|  |   { | ||||||
|  |     return ( value - min ) / ( max - min ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static map( value:number, inputMin:number, inputMax:number, outputMin:number, outputMax:number ) | ||||||
|  |   { | ||||||
|  |     let normalized = MathX.normalize( value, inputMin, inputMax ); | ||||||
|  |     return MathX.lerp( outputMin, outputMax, normalized ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static mapClamped( value:number, inputMin:number, inputMax:number, outputMin:number, outputMax:number ) | ||||||
|  |   { | ||||||
|  |     let normalized = MathX.normalize( value, inputMin, inputMax ); | ||||||
|  |     normalized = MathX.clamp01( normalized ); | ||||||
|  |     return MathX.lerp( outputMin, outputMax, normalized ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static maxAbs( ...values:number[] ) | ||||||
|  |   { | ||||||
|  |     let maxValue = 0; | ||||||
|  |     let index = 0; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < values.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let v = values[ i ]; | ||||||
|  |       let abs = Math.abs( v ); | ||||||
|  | 
 | ||||||
|  |       if ( Math.abs( v ) > maxValue ) | ||||||
|  |       { | ||||||
|  |         maxValue = abs; | ||||||
|  |         index = i; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return values[ index ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static median( ...values:number[] ) | ||||||
|  |   { | ||||||
|  |     let sorted = [].concat( values ).sort( ( a,b ) => a - b ); | ||||||
|  | 
 | ||||||
|  |     console.log( sorted ); | ||||||
|  |     return sorted[ Math.round( sorted.length/2 ) ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static average( ...values:number[] ) | ||||||
|  |   { | ||||||
|  |     let value = 0; | ||||||
|  | 
 | ||||||
|  |     values.forEach( v => value += v ) | ||||||
|  | 
 | ||||||
|  |     return value / values.length; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static base( exponent:number, power:number ) | ||||||
|  |   { | ||||||
|  |     return Math.pow( power, 1 / exponent ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static exponent( base:number, power:number ) | ||||||
|  |   { | ||||||
|  |     return Math.log( power ) / Math.log( base ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   static format( value:number, numZeros:number = 2 ):string | ||||||
|  |   { | ||||||
|  |     if ( numZeros <= 0 ) | ||||||
|  |     { | ||||||
|  |       return Math.round( value ) + ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     numZeros = Math.round( numZeros ); | ||||||
|  | 
 | ||||||
|  |     var sign = value < 0 ? "-" : ""; | ||||||
|  |     value = Math.abs( value ); | ||||||
|  | 
 | ||||||
|  |     var roundedBiggerValue = Math.round( value * Math.pow( 10, numZeros ) );   | ||||||
|  |     var stringValue = roundedBiggerValue + ""; | ||||||
|  |     var minimumLength = numZeros + 1; | ||||||
|  |      | ||||||
|  |     while ( stringValue.length < minimumLength ) | ||||||
|  |     { | ||||||
|  |       stringValue = "0" + stringValue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     var split = stringValue.length - numZeros; | ||||||
|  |      | ||||||
|  |     return sign + stringValue.substring( 0, split ) + "." + stringValue.substring( split ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static lerpAngle( a:number, b:number, amount:number ) | ||||||
|  |   { | ||||||
|  |     let difference = MathX.angleDifference( a, b ); | ||||||
|  |     | ||||||
|  | 
 | ||||||
|  |     let result = MathX.repeat( a + difference * amount, 360 ); | ||||||
|  |     console.log( "lerping angle", a, b, "diff:", difference, "amount:", amount, ">>", result ) | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static modulo( a:number, b:number ) | ||||||
|  |   { | ||||||
|  |     return a - Math.floor( a / b ) * b; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static angleDifference( a:number, b:number ) | ||||||
|  |   { | ||||||
|  |     a = MathX.repeat( a, 360 ); | ||||||
|  |     b = MathX.repeat( b, 360 ); | ||||||
|  | 
 | ||||||
|  |     let difference = b - a;    | ||||||
|  | 
 | ||||||
|  |     if ( difference < -180 ) | ||||||
|  |     { | ||||||
|  |       difference = 360 + difference; | ||||||
|  |       return difference; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( difference > 180 ) | ||||||
|  |     { | ||||||
|  |       difference = difference - 360; | ||||||
|  |       return difference; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return difference; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static align( baseWidth:number, elementWidth:number, alginmentNormalized:number ) | ||||||
|  |   { | ||||||
|  |     return ( baseWidth - elementWidth ) * alginmentNormalized; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static clamp( value:number, min:number, max:number ) | ||||||
|  |   { | ||||||
|  |     return value < min ? min : value > max ? max : value;  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static clamp01( value:number ) | ||||||
|  |   { | ||||||
|  |     return value < 0 ? 0 : value > 1 ? 1 : value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static repeat( value:number, range:number ) | ||||||
|  |   { | ||||||
|  |     while ( value < 0 ) | ||||||
|  |     { | ||||||
|  |       value += range; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while ( value >= range ) | ||||||
|  |     { | ||||||
|  |       value -= range; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static repeatPolar( value:number, max:number ) | ||||||
|  |   { | ||||||
|  |     while ( value > max ) | ||||||
|  |     { | ||||||
|  |       value -= max * 2; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while ( value < -max ) | ||||||
|  |     { | ||||||
|  |       value += max * 2; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return value; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static lerp( a:number, b:number, t:number ) | ||||||
|  |   { | ||||||
|  |     t = MathX.clamp01( t ); | ||||||
|  |     return a + t * ( b - a ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static lerpUnclamped( a:number, b:number, t:number ) | ||||||
|  |   { | ||||||
|  |     return a + t * ( b - a ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static triangle( normalized:number ) | ||||||
|  |   { | ||||||
|  |     normalized = normalized * 2; | ||||||
|  |     let invertedNormalized = 2 - normalized; | ||||||
|  | 
 | ||||||
|  |     return MathX.clamp01( Math.min( normalized, invertedNormalized ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static quantizeFloored( value:number, quantization:number ) | ||||||
|  |   { | ||||||
|  |     return Math.floor( value / quantization ) * quantization; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static quantizeCeiled( value:number, quantization:number ) | ||||||
|  |   { | ||||||
|  |     return Math.ceil( value / quantization ) * quantization; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static quantizeRounded( value:number, quantization:number ) | ||||||
|  |   { | ||||||
|  |     return Math.round( value / quantization ) * quantization; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static inRange( value:number, lower:number, higher:number ) | ||||||
|  |   { | ||||||
|  |     if ( value < lower ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( value > higher ) | ||||||
|  |     { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static advanceIndex<T>( array:T[], currentIndex:number, direction:number ) | ||||||
|  |   { | ||||||
|  |     let nextIndex = currentIndex + direction; | ||||||
|  |     nextIndex = MathX.repeat( nextIndex, array.length ); | ||||||
|  | 
 | ||||||
|  |     return nextIndex; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static nextIndex<T>(  array:T[], currentIndex:number ) | ||||||
|  |   { | ||||||
|  |     return MathX.advanceIndex( array, currentIndex, 1 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static previousIndex<T>(  array:T[], currentIndex:number ) | ||||||
|  |   { | ||||||
|  |     return MathX.advanceIndex( array, currentIndex, -1 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,68 @@ | ||||||
|  | import { RandomEngine } from "./RandomEngine"; | ||||||
|  | import { JSRandomEngine } from "./JSRandomEngine"; | ||||||
|  | 
 | ||||||
|  | export class IncrementalIDGenerator | ||||||
|  | {  | ||||||
|  |   static readonly LETTERS_CHARACTER_SET =  | ||||||
|  |   "ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwyz"; | ||||||
|  | 
 | ||||||
|  |   static readonly BASE_62_CHARACTER_SET =  | ||||||
|  |   "ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwyz0123456789"; | ||||||
|  | 
 | ||||||
|  |   static readonly BASE_64_URL_CHARACTER_SET =  | ||||||
|  |     "ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwyz0123456789-_"; | ||||||
|  | 
 | ||||||
|  |   static readonly BASE_64_CHARACTER_SET =  | ||||||
|  |     "ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwyz0123456789+/"; | ||||||
|  | 
 | ||||||
|  |   private _set = IncrementalIDGenerator.BASE_64_URL_CHARACTER_SET; | ||||||
|  |   private _state:number[] = []; | ||||||
|  | 
 | ||||||
|  |   constructor( set:string = null ) | ||||||
|  |   { | ||||||
|  |     this._set = set || IncrementalIDGenerator.BASE_64_URL_CHARACTER_SET; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setState( state:number[] ) | ||||||
|  |   { | ||||||
|  |     this._state = state; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _incrementAt( position:number ) | ||||||
|  |   { | ||||||
|  |     if ( position >= this._state.length ) | ||||||
|  |     { | ||||||
|  |       this._state.push( 0 ); | ||||||
|  |     } | ||||||
|  |     else if ( this._state[ position ] === ( this._set.length - 1 ) ) | ||||||
|  |     { | ||||||
|  |       this._state[ position ] = 0; | ||||||
|  |       this._incrementAt( position + 1 ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       this._state[ position ] ++; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _getID() | ||||||
|  |   { | ||||||
|  |     let id = ""; | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < this._state.length; i++ ) | ||||||
|  |     { | ||||||
|  |       id += this._set[ this._state[ i ] ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return id; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createID() | ||||||
|  |   { | ||||||
|  |     this._incrementAt( 0 ); | ||||||
|  |     return this._getID(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | import { RandomEngine } from './RandomEngine'; | ||||||
|  | export class JSRandomEngine extends RandomEngine | ||||||
|  | { | ||||||
|  |   public next() | ||||||
|  |   { | ||||||
|  |     return Math.random(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static _instance = new JSRandomEngine(); | ||||||
|  |   static get $() | ||||||
|  |   { | ||||||
|  |     return JSRandomEngine._instance; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | import { SeedableRandomEngine } from "./RandomEngine"; | ||||||
|  | 
 | ||||||
|  | export class LCGState | ||||||
|  | { | ||||||
|  |   public modulus:number; | ||||||
|  |   public multiplier:number; | ||||||
|  |   public increment:number; | ||||||
|  |   public state:number;   | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class LCG extends SeedableRandomEngine<LCGState> | ||||||
|  | {     | ||||||
|  |   private _seedState:LCGState  = new LCGState(); | ||||||
|  |   private _normalizer:number;  | ||||||
|  | 
 | ||||||
|  |   constructor() | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this.setParameters( Math.pow( 2, 32 ), 1664525, 1013904223 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setParameters( modulus:number, multiplier:number, increment:number ):void | ||||||
|  |   { | ||||||
|  |     this._seedState.modulus    = modulus; | ||||||
|  |     this._seedState.multiplier = multiplier; | ||||||
|  |     this._seedState.increment  = increment; | ||||||
|  |     this._seedState.state      = 0; | ||||||
|  |     this._normalizer = 1  / ( this._seedState.modulus );    | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getSeedState() | ||||||
|  |   { | ||||||
|  |     var seedState = new LCGState(); | ||||||
|  | 
 | ||||||
|  |     seedState.modulus = this._seedState.modulus; | ||||||
|  |     seedState.multiplier = this._seedState.multiplier; | ||||||
|  |     seedState.increment = this._seedState.increment; | ||||||
|  |     seedState.state = this._seedState.state;   | ||||||
|  | 
 | ||||||
|  |     return seedState; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setSeedState( seedState:LCGState ) | ||||||
|  |   { | ||||||
|  |     this._seedState.modulus    = seedState.modulus; | ||||||
|  |     this._seedState.multiplier = seedState.multiplier; | ||||||
|  |     this._seedState.increment  = seedState.increment; | ||||||
|  |     this._seedState.state      = seedState.state; | ||||||
|  |     this._normalizer = 1 / ( this._seedState.modulus );    | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   next() | ||||||
|  |   { | ||||||
|  |     this._seedState.state = ( this._seedState.state * this._seedState.multiplier + | ||||||
|  |        this._seedState.increment) % this._seedState.modulus; | ||||||
|  |     var value = this._seedState.state * this._normalizer; | ||||||
|  |     return Math.abs( value ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   setSeed( seed:number ) | ||||||
|  |   { | ||||||
|  |     this._seedState.state = seed % this._seedState.modulus;       | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   warmUp( runs:number = 1000 ) | ||||||
|  |   { | ||||||
|  |     for ( let i = 0; i < runs; i++) | ||||||
|  |     { | ||||||
|  |       this.next(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,212 @@ | ||||||
|  | import { HTMLColor } from '../colors/HTMLColor'; | ||||||
|  | import { HSLColor } from '../colors/HSLColor'; | ||||||
|  | import { RGBColor } from "../colors/RGBColor"; | ||||||
|  | import { Range } from '../geometry/Range'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export abstract class RandomEngine | ||||||
|  | { | ||||||
|  |   abstract next():number; | ||||||
|  |    | ||||||
|  |   value( scalar:number ):number  | ||||||
|  |   { | ||||||
|  |     return this.next() * scalar; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   range( a:number, b:number ):number  | ||||||
|  |   { | ||||||
|  |     return this.next()*( b - a ) + a; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   in( range:Range ):number | ||||||
|  |   { | ||||||
|  |     return this.range( range.min, range.max ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   polarOne():number  | ||||||
|  |   { | ||||||
|  |     return this.next() * 2 - 1; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   polar( value:number ):number  | ||||||
|  |   { | ||||||
|  |     return this.polarOne() * value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   bool():boolean  | ||||||
|  |   { | ||||||
|  |     return this.next() > 0.5; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   percentageVariation( variation:number ):Range  | ||||||
|  |   { | ||||||
|  |     var value = ( 100 - variation ) / 100; | ||||||
|  |     return new Range( value, 1 / value );   | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   withChanceOf( value:number ):boolean  | ||||||
|  |   { | ||||||
|  |     return ( this.next() * 100 ) <= value ; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   randomHue( saturation:number = 1, luminance:number = 0.5):HTMLColor | ||||||
|  |   { | ||||||
|  |     let hue = this.range( 0, 360 ); | ||||||
|  |     let color = new HSLColor( hue, saturation, luminance ); | ||||||
|  |     return color; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   lerpRGB( colors:HTMLColor[]):HTMLColor | ||||||
|  |   { | ||||||
|  |     if ( colors.length ==  1 ) | ||||||
|  |     { | ||||||
|  |       return colors[ 0 ]; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return RGBColor.lerpFrom( colors, this.next() ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /* | ||||||
|  |   inCubeOne():Vector3  | ||||||
|  |   { | ||||||
|  |     return new Vector3( this.polarOne(), this.polarOne(), this.polarOne() ); | ||||||
|  |   }     | ||||||
|  | 
 | ||||||
|  |   inCube( size:number ):Vector3  | ||||||
|  |   { | ||||||
|  |     return this.inCubeOne().multiplyScalar( size * 0.5 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   inSphereOne():Vector3  | ||||||
|  |   { | ||||||
|  |     var inCube = this.inCubeOne(); | ||||||
|  | 
 | ||||||
|  |     if ( inCube.lengthSq() > 1 ) | ||||||
|  |     { | ||||||
|  |       inCube.normalize().multiplyScalar( this.next() ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return inCube; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   inSphere( size:number ):Vector3  | ||||||
|  |   { | ||||||
|  |     return this.inSphereOne().multiplyScalar( size * 0.5 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   */ | ||||||
|  |   | ||||||
|  |   integerInclusive( min:number, max:number ):number   | ||||||
|  |   { | ||||||
|  |     return ( Math.floor( this.next() * ( max - min + 1 ) ) + min ) ; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   integerInclusiveFromZero( max:number ):number  | ||||||
|  |   { | ||||||
|  |     return this.integerInclusive( 0, max ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   integerExclusive( min:number, max:number ):number   | ||||||
|  |   { | ||||||
|  |     var nextValue = this.next(); | ||||||
|  |     var randomValue = nextValue * ( max - min ) + min; | ||||||
|  |      | ||||||
|  |     var value =  ( Math.floor( randomValue ) ); | ||||||
|  |     return Math.min( max - 1, value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   integerExclusiveFromZero( max:number ):number  | ||||||
|  |   { | ||||||
|  |     return this.integerExclusive( 0, max ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   fromString( text: string, alternative:string = null ):string | ||||||
|  |   { | ||||||
|  |     if ( ! text || text.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return alternative; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let index = this.integerExclusive( 0, text.length ); | ||||||
|  |     return text[ index ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   from<T>( array: T[], alternative:T = null ):T | ||||||
|  |   { | ||||||
|  |     if ( array.length == 0 ) | ||||||
|  |     { | ||||||
|  |       return alternative; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var index = this.integerExclusive( 0, array.length ); | ||||||
|  |     return array[ index ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   arrayFrom<T>( numElements:number,  array: T[], alternative:T = null ):T[] | ||||||
|  |   { | ||||||
|  |     let randomArray:T[] = []; | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < numElements; i++ ) | ||||||
|  |     { | ||||||
|  |       randomArray.push( this.from<T>( array, alternative ) ); | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     return randomArray; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fromValues<T>( first:T, ...values:T[] ):T | ||||||
|  |   { | ||||||
|  |     if ( values.length == 0 ) | ||||||
|  |     { | ||||||
|  |       return first; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var index = this.integerExclusive( 0, values.length + 1 ); | ||||||
|  | 
 | ||||||
|  |     return index == 0 ? first : values[ index - 1]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   arrayFromValues<T>( numElements:number, first:T, ...values:T[] ):T[] | ||||||
|  |   { | ||||||
|  |     let randomArray:T[] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < numElements; i++ ) | ||||||
|  |     { | ||||||
|  |       randomArray.push( this.from<T>( [first].concat( values ) ) ); | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     return randomArray; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fromSet<T>( set:Set<T> ) | ||||||
|  |   { | ||||||
|  |     let index = this.integerExclusiveFromZero( set.size ); | ||||||
|  |     let i = 0; | ||||||
|  | 
 | ||||||
|  |     for ( let t of set ) | ||||||
|  |     { | ||||||
|  |       if ( index === i ) | ||||||
|  |       { | ||||||
|  |         return t; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       i++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export abstract class SeedableRandomEngine<S> extends RandomEngine | ||||||
|  | { | ||||||
|  |   abstract setSeed( seed:number ):void; | ||||||
|  |   abstract getSeedState():S; | ||||||
|  |   abstract setSeedState( seedState:S ):void; | ||||||
|  |    | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,54 @@ | ||||||
|  | import { RandomEngine } from "./RandomEngine"; | ||||||
|  | import { JSRandomEngine } from "./JSRandomEngine"; | ||||||
|  | 
 | ||||||
|  | export class RandomUIDGenerator | ||||||
|  | {  | ||||||
|  |   static readonly UID_CHARACTER_SET:string =  | ||||||
|  |     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | ||||||
|  | 
 | ||||||
|  |   static readonly UID_NUM_CHARACTERS:number = 16; | ||||||
|  |    | ||||||
|  |   private _characterSet:string = RandomUIDGenerator.UID_CHARACTER_SET; | ||||||
|  |   get characterSet(){ return this._characterSet; } | ||||||
|  | 
 | ||||||
|  |   private _numCharacters:number = RandomUIDGenerator.UID_NUM_CHARACTERS; | ||||||
|  |   get numCharacters(){ return this._numCharacters; } | ||||||
|  |    | ||||||
|  |   set( characterSet:string, numCharacters:number ) | ||||||
|  |   { | ||||||
|  |     this._characterSet  = characterSet; | ||||||
|  |     this._numCharacters = numCharacters; | ||||||
|  |      | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static createUID( randomEngine:RandomEngine = null ) | ||||||
|  |   { | ||||||
|  |     let id = ""; | ||||||
|  |     randomEngine = randomEngine ||JSRandomEngine.$; | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < RandomUIDGenerator.UID_NUM_CHARACTERS; i++ ) | ||||||
|  |     { | ||||||
|  |       let randomCharacter = randomEngine.fromString( RandomUIDGenerator.UID_CHARACTER_SET );  | ||||||
|  |       id += randomCharacter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return id; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   generate( randomEngine:RandomEngine = null ) | ||||||
|  |   {  | ||||||
|  |     let id = ""; | ||||||
|  |     randomEngine = randomEngine || JSRandomEngine.$; | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < this._numCharacters; i++ ) | ||||||
|  |     { | ||||||
|  |       let randomCharacter = randomEngine.fromString( this._characterSet );  | ||||||
|  |       id += randomCharacter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return id; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | import { JSRandomEngine } from "./JSRandomEngine"; | ||||||
|  | import { LCG } from "./LCG"; | ||||||
|  | import { RandomEngine, SeedableRandomEngine } from "./RandomEngine"; | ||||||
|  | 
 | ||||||
|  | export class SeedGenerator | ||||||
|  | { | ||||||
|  |   static fromText( source:string, randomEngine:SeedableRandomEngine<any> = null ) | ||||||
|  |   { | ||||||
|  |     if ( ! randomEngine ) | ||||||
|  |     { | ||||||
|  |       randomEngine = new LCG(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     randomEngine.setSeed( 18593 ); | ||||||
|  | 
 | ||||||
|  |     let seed = 0; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < source.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let characterValue = source.charCodeAt( i ); | ||||||
|  | 
 | ||||||
|  |       seed += characterValue * 767417 + 13 + randomEngine.next() * characterValue; | ||||||
|  | 
 | ||||||
|  |       if ( seed > 32000 ) | ||||||
|  |       { | ||||||
|  |         seed = seed % 32000; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     console.log( "SEED FROM TEXT:", source, seed ) | ||||||
|  | 
 | ||||||
|  |     return seed; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | export abstract class ElementProcessor | ||||||
|  | { | ||||||
|  |   abstract getElementName():string; | ||||||
|  |   abstract processElement( element:Element ):Element;   | ||||||
|  |   hasPostProcessor(){ return false;} | ||||||
|  |   postProcessElement( inputElement:Element, processedElement:Element ){} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,406 @@ | ||||||
|  | 
 | ||||||
|  | import { HTMLNodeTreeWalker } from "../graphs/HTMLNodeTreeWalker"; | ||||||
|  | import { DOMEditor as HTMLEditor } from "../dom/DOMEditor"; | ||||||
|  | import { TemplatesSourceLexer } from "./TemplatesSourceLexer"; | ||||||
|  | import { TemplateSourceMatchers } from "./TemplateSourceMatchers"; | ||||||
|  | import { ElementAttribute } from "../dom/ElementAttribute"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class TemplateReplacer | ||||||
|  | { | ||||||
|  |   static readonly dataAttributeAssignmentPrefix = "data-@"; | ||||||
|  |   static readonly unwrapTemplate = new ElementAttribute( "-unwrap--template" ); | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   replace( target:Element, classElement:Element ) | ||||||
|  |   { | ||||||
|  |     let replacingElement = target.ownerDocument.importNode( classElement, true ) as Element;     | ||||||
|  | 
 | ||||||
|  |     this._processElement( target, replacingElement );   | ||||||
|  |      | ||||||
|  |     if ( target.parentElement ) | ||||||
|  |     { | ||||||
|  |       target.parentElement.replaceChild( replacingElement, target ); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |     let walker = new HTMLNodeTreeWalker(); | ||||||
|  |     walker.forAll( replacingElement, e => this._processAttributes( target, e as Element ) );     | ||||||
|  | 
 | ||||||
|  |     try | ||||||
|  |     { | ||||||
|  |       HTMLEditor.copyAllAttributes( target, replacingElement ); | ||||||
|  |     } | ||||||
|  |     catch( e ) | ||||||
|  |     { | ||||||
|  |       replacingElement = HTMLEditor.copyAllAttributesThroughRawHTML( target, replacingElement ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return replacingElement; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   replaceChildren( target:Element, replacement:Element ) | ||||||
|  |   { | ||||||
|  |     //console.log( "REPLACING CHILDREN", target.nodeName, ">>", replacement.nodeName );
 | ||||||
|  |     this._processElement( target, replacement );   | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _processAttributes( target:Element, element:Element ) | ||||||
|  |   { | ||||||
|  |     if ( ! element.attributes ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let attributes = [];     | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < element.attributes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       attributes.push(  element.attributes[ i ] ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < attributes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let attributeName = attributes[ i ].nodeName; | ||||||
|  |        | ||||||
|  | 
 | ||||||
|  |       if ( ! attributeName.startsWith( TemplateReplacer.dataAttributeAssignmentPrefix ) ) | ||||||
|  |       { | ||||||
|  |         //console.log( "NOT ASSIGNMENT:", attributeName );
 | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         //console.log( "IS ASSIGNMENT:", attributeName );
 | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       let name = attributeName.substring( TemplateReplacer.dataAttributeAssignmentPrefix.length ); | ||||||
|  |       element.removeAttribute( attributeName );   | ||||||
|  | 
 | ||||||
|  |       TemplateSourceMatchers.regex.lastIndex = 0; | ||||||
|  | 
 | ||||||
|  |       let match = TemplateSourceMatchers.regex.exec( attributes[ i ].nodeValue ); | ||||||
|  |        | ||||||
|  |       if ( ! match ) | ||||||
|  |       { | ||||||
|  |          | ||||||
|  |         let nodeValueSnippet = "(...too much data...)"; | ||||||
|  |          | ||||||
|  |         if ( attributes[ i ].nodeValue.length < 50 ) | ||||||
|  |         { | ||||||
|  |           nodeValueSnippet = attributes[ i ].nodeValue; | ||||||
|  |         } | ||||||
|  |         else if ( attributes[ i ].nodeValue.length < 250 ) | ||||||
|  |         { | ||||||
|  |           nodeValueSnippet = attributes[ i ].nodeValue.substring( 0, 50 ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |        | ||||||
|  |   | ||||||
|  |        // console.log( "NOTHING FOR ASSIGNMENT:", match, name, attributeName, nodeValueSnippet );
 | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |        | ||||||
|  |       let lexer = new TemplatesSourceLexer(); | ||||||
|  |       let outputValue:string[] = []; | ||||||
|  |       let nodeValueBefore = attributes[ i ].nodeValue; | ||||||
|  | 
 | ||||||
|  |      // outputValue = this._processTemplateSourceText( target, nodeValueBefore );
 | ||||||
|  |        | ||||||
|  |       let lexerEvents = lexer.lexToList( nodeValueBefore ); | ||||||
|  |        | ||||||
|  |       let defaultValue = null; | ||||||
|  |        | ||||||
|  |       for ( let i = 0; i < lexerEvents.length; i++ ) | ||||||
|  |       { | ||||||
|  |         let event = lexerEvents[ i ]; | ||||||
|  |         let subMatchValue = event.getMatch( nodeValueBefore ); | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  |         if ( event.isError ) | ||||||
|  |         {  | ||||||
|  |           console.log( `Error lexing attribute ${nodeValueBefore} at position ${event.offset}` ); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ( event.isDone ) | ||||||
|  |         {           | ||||||
|  |           continue; | ||||||
|  |         }  | ||||||
|  | 
 | ||||||
|  |         if ( TemplatesSourceLexer.DEFAULT === event.type ) | ||||||
|  |         { | ||||||
|  |           defaultValue = TemplatesSourceLexer.defaultMatcher.getMatch( subMatchValue, 1 ); | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         if ( TemplatesSourceLexer.CONTENT === event.type ) | ||||||
|  |         { | ||||||
|  |           outputValue.push( subMatchValue ); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |           TemplateSourceMatchers.regex.lastIndex = 0; | ||||||
|  |           let subMatch = TemplateSourceMatchers.regex.exec( subMatchValue ); | ||||||
|  |           let templateSource = TemplateSourceMatchers.createTemplateSource( subMatch ); | ||||||
|  | 
 | ||||||
|  |           if ( defaultValue ) | ||||||
|  |           { | ||||||
|  |             templateSource.defaultValue = defaultValue; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           let sourceValue = templateSource.getInnerHTML( target ); | ||||||
|  |           sourceValue = sourceValue.replace( "&&", "&" ); | ||||||
|  |           outputValue.push( sourceValue ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         defaultValue = null; | ||||||
|  | 
 | ||||||
|  |       } | ||||||
|  |        | ||||||
|  | 
 | ||||||
|  |        | ||||||
|  |       let newNodeValue = outputValue.join( "" ); | ||||||
|  | 
 | ||||||
|  |       if ( newNodeValue !== "" ) | ||||||
|  |       { | ||||||
|  |         element.setAttribute( name, newNodeValue ); | ||||||
|  |         //console.log( "ASSIGNING ", name, newNodeValue );
 | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         //console.log( "NOT ASSIGNING (VALUE INVALID): ", name, "(" + attributeName + ")", lexerEvents );
 | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /* | ||||||
|  |       let elementSource = this._createElementSource( match ); | ||||||
|  |       let sourceValue = elementSource.getInnerHTML( target ); | ||||||
|  | 
 | ||||||
|  |       let setAttributeValue = true; | ||||||
|  | 
 | ||||||
|  |       if ( sourceValue === "" ) | ||||||
|  |       { | ||||||
|  |         setAttributeValue = false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ( setAttributeValue ) | ||||||
|  |       { | ||||||
|  |         element.setAttribute( name, sourceValue ); | ||||||
|  |         //console.log( "setAttribute", name, attributeName, elementSource );
 | ||||||
|  |       }*/ | ||||||
|  |        | ||||||
|  | 
 | ||||||
|  |        | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   private _processElement( target:Element, childElement:Element ) | ||||||
|  |   { | ||||||
|  |     var children = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < childElement.childNodes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let child = childElement.childNodes[ i ]; | ||||||
|  |       children.push( child ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < children.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let child = children[ i ]; | ||||||
|  | 
 | ||||||
|  |       if ( Node.TEXT_NODE == child.nodeType ) | ||||||
|  |       { | ||||||
|  |         this._processTextNode( target, child ); | ||||||
|  |       } | ||||||
|  |       else if ( Node.ELEMENT_NODE == child.nodeType ) | ||||||
|  |       { | ||||||
|  |         this._processElement( target, child as Element); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   private _processTextNodeOld( target:Element, textNode:Node ) | ||||||
|  |   { | ||||||
|  |     let text = textNode.nodeValue; | ||||||
|  |      | ||||||
|  |   | ||||||
|  |     TemplateSourceMatchers.regex.lastIndex = 0; | ||||||
|  |      | ||||||
|  |     let match = TemplateSourceMatchers.regex.exec( text ); | ||||||
|  | 
 | ||||||
|  |     //console.log( "Matching", `"${text}"`, match );
 | ||||||
|  | 
 | ||||||
|  |     if ( ! match ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let lastIndex = 0; | ||||||
|  |     let nodes:Node[] = [];  | ||||||
|  | 
 | ||||||
|  |     while ( match ) | ||||||
|  |     { | ||||||
|  |       let matchIndex = match.index; | ||||||
|  | 
 | ||||||
|  |       let range = matchIndex - lastIndex; | ||||||
|  |        | ||||||
|  |       if ( range > 0 ) | ||||||
|  |       { | ||||||
|  |         let textRange = text.substring( lastIndex, matchIndex ); | ||||||
|  |         let rangeNode = document.createTextNode( textRange ) | ||||||
|  |         nodes.push( rangeNode ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let templateSource = TemplateSourceMatchers.createTemplateSource( match ); | ||||||
|  |       let targetNodes = templateSource.getNodes( target ); | ||||||
|  |       targetNodes.forEach( n => nodes.push( n ) ); | ||||||
|  | 
 | ||||||
|  |       lastIndex = matchIndex + match[ 0 ].length; | ||||||
|  | 
 | ||||||
|  |       match = TemplateSourceMatchers.regex.exec( text );       | ||||||
|  | 
 | ||||||
|  |       if ( match ) | ||||||
|  |       { | ||||||
|  |         continue;  | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       range = text.length - lastIndex; | ||||||
|  |        | ||||||
|  |       if ( range > 0 ) | ||||||
|  |       { | ||||||
|  |         let textRange = text.substring( lastIndex, text.length ); | ||||||
|  |         let rangeNode = document.createTextNode( textRange ) | ||||||
|  |         nodes.push( rangeNode ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < nodes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       textNode.parentElement.insertBefore( nodes[ i ], textNode ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     textNode.parentElement.removeChild( textNode ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _processTextNode( target:Element, textNode:Node ) | ||||||
|  |   { | ||||||
|  |     let text = textNode.nodeValue; | ||||||
|  | 
 | ||||||
|  |     let nodes = this._processTemplateSourceText( target, text ); | ||||||
|  | 
 | ||||||
|  |     //let nodes = outputValue.map( o => document.createTextNode( o ) );
 | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < nodes.length; i++ ) | ||||||
|  |     { | ||||||
|  |       textNode.parentElement.insertBefore( nodes[ i ], textNode ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     textNode.parentElement.removeChild( textNode ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _processTemplateSourceText( target:Element, text:string ):Node[] | ||||||
|  |   { | ||||||
|  |     let lexer = new TemplatesSourceLexer(); | ||||||
|  |     let lexerEvents = lexer.lexToList( text ); | ||||||
|  | 
 | ||||||
|  |     let outputs:Node[] = [];  | ||||||
|  | 
 | ||||||
|  |     let defaultValue = null; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < lexerEvents.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let event = lexerEvents[ i ]; | ||||||
|  |       let subMatchValue = event.getMatch( text );       | ||||||
|  | 
 | ||||||
|  |       if ( event.isError ) | ||||||
|  |       {  | ||||||
|  |         console.log( `Error lexing '${text}' at position ${event.offset}` ); | ||||||
|  |         return [ document.createTextNode( text ) ]; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ( event.isDone ) | ||||||
|  |       {           | ||||||
|  |         continue; | ||||||
|  |       }  | ||||||
|  | 
 | ||||||
|  |       if ( TemplatesSourceLexer.DEFAULT === event.type ) | ||||||
|  |       { | ||||||
|  |         defaultValue = TemplatesSourceLexer.defaultMatcher.getMatch( subMatchValue, 1 ); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |       if ( TemplatesSourceLexer.CONTENT === event.type ) | ||||||
|  |       { | ||||||
|  |         outputs.push( document.createTextNode( subMatchValue ) ); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         TemplateSourceMatchers.regex.lastIndex = 0; | ||||||
|  |         let subMatch = TemplateSourceMatchers.regex.exec( subMatchValue ); | ||||||
|  |         let templateSource = TemplateSourceMatchers.createTemplateSource( subMatch ); | ||||||
|  | 
 | ||||||
|  |         if ( defaultValue ) | ||||||
|  |         { | ||||||
|  |           templateSource.defaultValue = defaultValue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let targetNodes = templateSource.getNodes( target ); | ||||||
|  |         outputs = outputs.concat( targetNodes ); | ||||||
|  |         //targetNodes.forEach( n => outputs.push( n ) );
 | ||||||
|  | 
 | ||||||
|  |         //let sourceValue = templateSource.getInnerHTML( target );
 | ||||||
|  |         //outputs.push( sourceValue );
 | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       defaultValue = null; | ||||||
|  | 
 | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     return outputs; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   /* | ||||||
|  |   static createElementSource( match:RegExpExecArray ) | ||||||
|  |   { | ||||||
|  |     let elementSource = new TemplateSource(); | ||||||
|  | 
 | ||||||
|  |     //console.log( "Create Element SOurce", match );
 | ||||||
|  | 
 | ||||||
|  |     if ( match[ 1 ] ) | ||||||
|  |     { | ||||||
|  |       elementSource.selector = match[ 1 ]; | ||||||
|  |       elementSource.attribute = match[ 2 ];  | ||||||
|  |     } | ||||||
|  |     else if ( match[ 3 ] ) | ||||||
|  |     { | ||||||
|  |       elementSource.selector = match[ 3 ]; | ||||||
|  |     } | ||||||
|  |     else if ( match[ 4 ] ) | ||||||
|  |     { | ||||||
|  |       elementSource.attribute = match[ 4 ]; | ||||||
|  |     } | ||||||
|  |     else if ( match[ 5 ] ) | ||||||
|  |     { | ||||||
|  |       elementSource.innerHTML = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return elementSource; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   */ | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,132 @@ | ||||||
|  | import { DOMEditor as HTMLEditor } from "../dom/DOMEditor"; | ||||||
|  | 
 | ||||||
|  | export type SpecialTemplateSourceElementCallback =  | ||||||
|  |   (special:string,selector:string,attribute:string) => Node[]; | ||||||
|  | 
 | ||||||
|  | export class SpecialTemplateSourceElement | ||||||
|  | { | ||||||
|  |   _specialCallback:SpecialTemplateSourceElementCallback; | ||||||
|  |    | ||||||
|  |   static assignSpecialTemplateSource( element:Element,  callback:SpecialTemplateSourceElementCallback ) | ||||||
|  |   { | ||||||
|  |     let sp = element as any as SpecialTemplateSourceElement; | ||||||
|  |     sp._specialCallback = callback; | ||||||
|  | 
 | ||||||
|  |    // console.log( "Adding speical cb to:", element.nodeName );
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getNodes( element:Element, templateSource:TemplateSource):Node[] | ||||||
|  |   { | ||||||
|  |     let sp = element as any as  SpecialTemplateSourceElement; | ||||||
|  |      | ||||||
|  |     if ( ! sp._specialCallback ) | ||||||
|  |     { | ||||||
|  |       console.log( "NOT ASSIGNED SPECIAL:", element.nodeName, templateSource.special, templateSource.selector, templateSource.attribute ); | ||||||
|  |       return []; | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     let special   = templateSource.special; | ||||||
|  |     let selector  = templateSource.selector; | ||||||
|  |     let attribute = templateSource.attribute; | ||||||
|  | 
 | ||||||
|  |     let result = sp._specialCallback( special, selector, attribute ); | ||||||
|  | 
 | ||||||
|  |     //console.log( "LOADED SPECIAL:", templateSource.special, templateSource.selector, templateSource.attribute, "\n>>", result );
 | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class TemplateSource | ||||||
|  | { | ||||||
|  |   special:string; | ||||||
|  |   selector:string; | ||||||
|  |   attribute:string; | ||||||
|  |   innerHTML:boolean; | ||||||
|  |   defaultValue:string; | ||||||
|  |   copyParent:boolean; | ||||||
|  | 
 | ||||||
|  |   getInnerHTML( target:Element ) | ||||||
|  |   { | ||||||
|  |     if ( this.innerHTML ) | ||||||
|  |     { | ||||||
|  |       return target.innerHTML; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let nodes = this.getNodes( target ); | ||||||
|  |     let container = target.ownerDocument.createElement( "div" ) as HTMLElement; | ||||||
|  |     nodes.forEach( n => container.appendChild( n ) ); | ||||||
|  | 
 | ||||||
|  |     return container.innerHTML; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   get emptyNodes():Node[] | ||||||
|  |   { | ||||||
|  |     if ( this.defaultValue ) | ||||||
|  |     { | ||||||
|  |       return [ document.createTextNode(  this.defaultValue ) ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getNodes( target:Element ):Node[] | ||||||
|  |   {  | ||||||
|  |     if ( this.innerHTML ) | ||||||
|  |     {  | ||||||
|  |       return HTMLEditor.cloneChildren( target ); | ||||||
|  |     } | ||||||
|  |     else if ( this.special ) | ||||||
|  |     { | ||||||
|  |       return SpecialTemplateSourceElement.getNodes( target, this ); | ||||||
|  |     } | ||||||
|  |     else if ( this.attribute ) | ||||||
|  |     { | ||||||
|  |       let attributTarget = this.selector ? target.querySelector( this.selector ) : target; | ||||||
|  | 
 | ||||||
|  |       if ( ! attributTarget ) | ||||||
|  |       { | ||||||
|  |         return this.emptyNodes; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ( ! attributTarget.hasAttribute( this.attribute ) ) | ||||||
|  |       { | ||||||
|  |         return this.emptyNodes; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let attributeValue = attributTarget.getAttribute( this.attribute ); | ||||||
|  |       | ||||||
|  | 
 | ||||||
|  |       let textNode = target.ownerDocument.createTextNode( attributeValue ); | ||||||
|  | 
 | ||||||
|  |       return [ textNode ]; | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |      | ||||||
|  |       let node = target.querySelector( this.selector );       | ||||||
|  | 
 | ||||||
|  |       if ( ! node ) | ||||||
|  |       { | ||||||
|  |         return this.emptyNodes; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ( this.copyParent ) | ||||||
|  |       { | ||||||
|  |         return [ target.ownerDocument.importNode( node, true ) ]; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let nodes = []; | ||||||
|  | 
 | ||||||
|  |       for ( let i = 0; i < node.childNodes.length; i++ ) | ||||||
|  |       { | ||||||
|  |         let clone = target.ownerDocument.importNode( node.childNodes[ i ], true ); | ||||||
|  |         nodes.push( clone ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return nodes;       | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,118 @@ | ||||||
|  | import { ExtendedRegex } from "../text/ExtendedRegex"; | ||||||
|  | import { TemplateSource } from "./TemplateSource"; | ||||||
|  | import { TemplatesSourceLexer } from "./TemplatesSourceLexer"; | ||||||
|  | 
 | ||||||
|  | export enum TemplateMatchType | ||||||
|  | { | ||||||
|  |   SPECIAL, | ||||||
|  |   SELECTOR, | ||||||
|  |   ATTRIBUTE, | ||||||
|  |   PASS_THROUGH | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class TemplateSourceMatchers | ||||||
|  | { | ||||||
|  |   static readonly regex = ExtendedRegex.create(  | ||||||
|  |     /@\{(\P):(\V)\}\[(\P)\]|@\{(\V)\}\[(\P)\]|@\{(\P):(\V)\}|@\{(\V)\}|@\[(\P)\]|(@\{\})/g  | ||||||
|  |   ); | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |   static readonly matchers =  | ||||||
|  |   [ | ||||||
|  |     { | ||||||
|  |       type:  | ||||||
|  |         TemplatesSourceLexer.SPECIAL_SELECTOR_AND_ATTRIBUTE, | ||||||
|  |       values:  | ||||||
|  |         [ TemplateMatchType.SPECIAL, TemplateMatchType.SELECTOR, TemplateMatchType.ATTRIBUTE ] | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       type:  | ||||||
|  |         TemplatesSourceLexer.SELECTOR_AND_ATTRIBUTE, | ||||||
|  |       values:  | ||||||
|  |         [ TemplateMatchType.SELECTOR, TemplateMatchType.ATTRIBUTE ] | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       type:  | ||||||
|  |         TemplatesSourceLexer.SPECIAL_SELECTOR, | ||||||
|  |       values:  | ||||||
|  |         [ TemplateMatchType.SPECIAL, TemplateMatchType.SELECTOR ] | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       type:  | ||||||
|  |         TemplatesSourceLexer.SELECTOR, | ||||||
|  |       values:  | ||||||
|  |         [ TemplateMatchType.SELECTOR ] | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       type:  | ||||||
|  |         TemplatesSourceLexer.ATTRIBUTE, | ||||||
|  |       values:  | ||||||
|  |         [ TemplateMatchType.ATTRIBUTE ] | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       type:  | ||||||
|  |         TemplatesSourceLexer.PASS_THROUGH, | ||||||
|  |       values:  | ||||||
|  |         [ TemplateMatchType.PASS_THROUGH ] | ||||||
|  |     } | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   static createTemplateSource( match:RegExpExecArray ) | ||||||
|  |   { | ||||||
|  |     let elementSource = new TemplateSource(); | ||||||
|  | 
 | ||||||
|  |     var offset = 1; | ||||||
|  | 
 | ||||||
|  |     if ( match == null || offset > match.length ) | ||||||
|  |     { | ||||||
|  |       elementSource.defaultValue = ""; | ||||||
|  |       console.log( "No matching value >> ", match ); | ||||||
|  |       return elementSource; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < this.matchers.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let matcher = this.matchers[ i ]; | ||||||
|  | 
 | ||||||
|  |       if ( ! match[ offset ] ) | ||||||
|  |       {  | ||||||
|  |         offset += matcher.values.length; | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       let matchValuesOffset = offset; | ||||||
|  | 
 | ||||||
|  |       for ( let j = 0; j < matcher.values.length; j++ ) | ||||||
|  |       { | ||||||
|  |         let value = matcher.values[ j ]; | ||||||
|  |         let valueOffset = matchValuesOffset + j; | ||||||
|  | 
 | ||||||
|  |         if ( TemplateMatchType.SPECIAL === value ) | ||||||
|  |         { | ||||||
|  |           elementSource.special = match[ valueOffset ]; | ||||||
|  |         } | ||||||
|  |         else if ( TemplateMatchType.SELECTOR === value ) | ||||||
|  |         { | ||||||
|  |           elementSource.selector = match[ valueOffset ]; | ||||||
|  |         } | ||||||
|  |         else if ( TemplateMatchType.ATTRIBUTE === value ) | ||||||
|  |         { | ||||||
|  |           elementSource.attribute = match[ valueOffset ]; | ||||||
|  |         } | ||||||
|  |         else if ( TemplateMatchType.PASS_THROUGH === value ) | ||||||
|  |         { | ||||||
|  |           elementSource.innerHTML = true; | ||||||
|  |         }  | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return elementSource; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return elementSource; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,241 @@ | ||||||
|  | import { IncrementalIDGenerator } from "../random/IncrementalIDGenerator"; | ||||||
|  | import { ElementAttribute } from "../dom/ElementAttribute"; | ||||||
|  | import { TemplateReplacer } from "./TemplateReplacer"; | ||||||
|  | import { ElementProcessor } from "./ElementProcessor"; | ||||||
|  | import { DOMEditor as HTMLEditor } from "../dom/DOMEditor"; | ||||||
|  | import { StylesProcessor } from "./styles-processor/StylesProcessor"; | ||||||
|  | import { ElementType } from "../dom/ElementType"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export enum TemplatesManagerMode | ||||||
|  | { | ||||||
|  |   ADD_STYLES_TO_HEAD, | ||||||
|  |   IGNORE_STYLES | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class TemplatesManager | ||||||
|  | { | ||||||
|  |   static readonly defaultOverloadAttributeName = "templates-overload-type"; | ||||||
|  |   static readonly defaultIDAttributeName = "templates-id"; | ||||||
|  |   static readonly id = new ElementAttribute( TemplatesManager.defaultIDAttributeName ); | ||||||
|  | 
 | ||||||
|  |   private _overloadAttribute = new ElementAttribute( TemplatesManager.defaultOverloadAttributeName ); | ||||||
|  |   private _idAttribute = new ElementAttribute( TemplatesManager.defaultIDAttributeName ); | ||||||
|  | 
 | ||||||
|  |   private _templatesMap:Map<string,Element> = new Map<string,Element>(); | ||||||
|  |   private _idGenerator:IncrementalIDGenerator = new IncrementalIDGenerator(); | ||||||
|  |   private _replacer:TemplateReplacer = new TemplateReplacer(); | ||||||
|  |   get replacer(){ return this._replacer; } | ||||||
|  |   private _mode = TemplatesManagerMode.ADD_STYLES_TO_HEAD; | ||||||
|  |   private _templateStyles:string[] = [];  | ||||||
|  |   private _additionalProcessor = new Map<string,ElementProcessor>(); | ||||||
|  | 
 | ||||||
|  |   setMode( mode:TemplatesManagerMode ) | ||||||
|  |   { | ||||||
|  |     this._mode = mode; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addProcessor( processor:ElementProcessor ) | ||||||
|  |   { | ||||||
|  |     this._additionalProcessor.set( processor.getElementName(), processor ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addTemplate( element:Element ) | ||||||
|  |   { | ||||||
|  | 
 | ||||||
|  |     if ( Node.ELEMENT_NODE !== element.nodeType ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( "STYLE" === element.nodeName.toUpperCase() ) | ||||||
|  |     { | ||||||
|  |       this._addStyle( element ); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let hash = element.nodeName; | ||||||
|  |      | ||||||
|  |     if ( this._overloadAttribute.in( element ) ) | ||||||
|  |     { | ||||||
|  |       let overloadType = this._overloadAttribute.from( element ); | ||||||
|  |       hash += " " + overloadType; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this._templatesMap.set( hash, element ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static templateIDCounter = 0; | ||||||
|  |   addTemplateHTML( html:string ) | ||||||
|  |   { | ||||||
|  |     TemplatesManager.templateIDCounter ++; | ||||||
|  | 
 | ||||||
|  |     let generatedDocument = HTMLEditor.stringToDocument( `<html><body>${html}</body>` ); | ||||||
|  | 
 | ||||||
|  |     let children = HTMLEditor.nodeListToArray( generatedDocument.body.childNodes ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < children.length; i++ ) | ||||||
|  |     { | ||||||
|  |       this.addTemplate( children[ i ] ); | ||||||
|  |     } | ||||||
|  |   }  | ||||||
|  |   | ||||||
|  | 
 | ||||||
|  |   processChildren( root:Element ) | ||||||
|  |   { | ||||||
|  |     let children = HTMLEditor.nodeListToArray( root.childNodes );     | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < children.length; i++ ) | ||||||
|  |     { | ||||||
|  |       this._processElement( children[ i ] ); | ||||||
|  |     }  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   processElement( element:Element ) | ||||||
|  |   { | ||||||
|  |     return this._processElement( element ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getTemplate( nodeName:string ) | ||||||
|  |   { | ||||||
|  |     return this._templatesMap.get( nodeName.toUpperCase() ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createTemplate( nodeName:string, doc:Document = undefined ) | ||||||
|  |   { | ||||||
|  |     doc = doc || document; | ||||||
|  | 
 | ||||||
|  |     let templateNode = doc.importNode( this.getTemplate( nodeName ), true ); | ||||||
|  |     templateNode = this._processElement( templateNode ); | ||||||
|  | 
 | ||||||
|  |     return templateNode; | ||||||
|  |   }    | ||||||
|  | 
 | ||||||
|  |   private _addStyle( styleElement:Element )  | ||||||
|  |   {  | ||||||
|  |     if ( TemplatesManagerMode.IGNORE_STYLES === this._mode ) | ||||||
|  |     { | ||||||
|  |       //this._templateStyles.push( styleElement.innerHTML );
 | ||||||
|  |       //console.log( "Adding style to templates.css", styleElement );
 | ||||||
|  |       return; | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     let styleContent = styleElement.textContent; | ||||||
|  |     let processedStyleContent = StylesProcessor.convert( styleContent ); | ||||||
|  |     let clonedStyle = ElementType.style.create(); | ||||||
|  |     clonedStyle.innerHTML = processedStyleContent; | ||||||
|  |     //let clonedStyle = document.importNode( styleElement, true );
 | ||||||
|  |     document.head.appendChild( clonedStyle ); | ||||||
|  | 
 | ||||||
|  |     let id = this._idGenerator.createID(); | ||||||
|  |     this._idAttribute.to( styleElement, id );   | ||||||
|  |     this._idAttribute.to( clonedStyle, id );   | ||||||
|  | 
 | ||||||
|  |     //console.log( "Adding style to head", styleElement, clonedStyle );
 | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   private _processElement( element:Element ):Element | ||||||
|  |   {  | ||||||
|  |     //console.log( "Processing:", HierarchyName.of( element ) );
 | ||||||
|  | 
 | ||||||
|  |     if ( this._idAttribute.in( element ) ) | ||||||
|  |     { | ||||||
|  |       this.processChildren( element ); | ||||||
|  |       return element; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( this._additionalProcessor.has( element.nodeName ) ) | ||||||
|  |     { | ||||||
|  |       let processor = this._additionalProcessor.get( element.nodeName ); | ||||||
|  |       let processedElement:Element = null; | ||||||
|  |        | ||||||
|  |       //console.log( "ADDITIONAL PROCESSOR", processor );
 | ||||||
|  |       try | ||||||
|  |       {         | ||||||
|  |         processedElement = processor.processElement( element );        | ||||||
|  |       } | ||||||
|  |       catch ( e ) | ||||||
|  |       { | ||||||
|  |         console.log( "Processor:", processor, `ERROR: in <${element.nodeName.toLowerCase()}>` ); | ||||||
|  |         throw e; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this._idAttribute.to( processedElement, this._idGenerator.createID() );   | ||||||
|  |       //console.log( "Replacing Kids:", HierarchyName.of( processedElement ) );
 | ||||||
|  | 
 | ||||||
|  |       this._replacer.replaceChildren( element, processedElement ); | ||||||
|  | 
 | ||||||
|  |       //console.log( "PROCESSING Kids:", HierarchyName.of( processedElement ) );
 | ||||||
|  |       this.processChildren( processedElement ); | ||||||
|  | 
 | ||||||
|  |       if ( processor.hasPostProcessor ) | ||||||
|  |       { | ||||||
|  |         processor.postProcessElement( element, processedElement ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |       return processedElement; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( ! this._templatesMap.has( element.nodeName ) ) | ||||||
|  |     { | ||||||
|  |       this.processChildren( element ); | ||||||
|  |       return element; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let classElement = this._templatesMap.get( element.nodeName.toUpperCase() ); | ||||||
|  |     | ||||||
|  |     let replacedElement:Element = null; | ||||||
|  |      | ||||||
|  |     try | ||||||
|  |     { | ||||||
|  |       replacedElement = this._replacer.replace( element, classElement ); | ||||||
|  |     } | ||||||
|  |     catch ( e ) | ||||||
|  |     { | ||||||
|  |       console.log( `Replacer-Error: in <${element.nodeName.toLowerCase()}>`, e.stack ); | ||||||
|  |       throw e; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |     //console.log( "CLASS ELEMENT", classElement, replacedElement );
 | ||||||
|  |     this._idAttribute.to( replacedElement, this._idGenerator.createID() );   | ||||||
|  | 
 | ||||||
|  |     this.processChildren( replacedElement ); | ||||||
|  | 
 | ||||||
|  |     return replacedElement; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static addCustomElements() | ||||||
|  |   { | ||||||
|  |     let customElementsManager = new TemplatesManager(); | ||||||
|  | 
 | ||||||
|  |     let customElements = document.querySelector( "custom-elements" ); | ||||||
|  | 
 | ||||||
|  |     if ( ! customElements ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     let customElementsNodes = customElements.childNodes; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < customElementsNodes.length; i++ ) | ||||||
|  |     {     | ||||||
|  |       if ( Node.ELEMENT_NODE != customElementsNodes[ i ].nodeType ) | ||||||
|  |       { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       //console.log( customElementsNodes[ i ].nodeName );
 | ||||||
|  | 
 | ||||||
|  |       customElementsManager.addTemplate( customElementsNodes[ i ] as Element); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     HTMLEditor.remove( customElements ); | ||||||
|  |      | ||||||
|  |     customElementsManager.processChildren( document.body ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,78 @@ | ||||||
|  | import { Lexer } from "../text/lexer/Lexer"; | ||||||
|  | import { LexerMatcher } from "../text/lexer/LexerMatcher"; | ||||||
|  | import { ExtendedRegex } from "../text/ExtendedRegex"; | ||||||
|  | 
 | ||||||
|  | export type SPECIAL_SELECTOR_AND_ATTRIBUTE = "SPECIAL_SELECTOR_AND_ATTRIBUTE"; | ||||||
|  | export type SELECTOR_AND_ATTRIBUTE = "SELECTOR_AND_ATTRIBUTE"; | ||||||
|  | export type SPECIAL_SELECTOR = "SPECIAL_SELECTOR"; | ||||||
|  | export type SELECTOR = "SELECTOR"; | ||||||
|  | export type ATTRIBUTE = "ATTRIBUTE"; | ||||||
|  | export type PASS_THROUGH = "PASS_THROUGH"; | ||||||
|  | export type CONTENT = "CONTENT"; | ||||||
|  | export type DEFAULT = "DEFAULT"; | ||||||
|  | 
 | ||||||
|  | export type TemplatesSourceType = SELECTOR_AND_ATTRIBUTE | SELECTOR | ATTRIBUTE | PASS_THROUGH; | ||||||
|  | 
 | ||||||
|  | export class TemplatesSourceLexer extends Lexer | ||||||
|  | { | ||||||
|  |   static readonly SPECIAL_SELECTOR_AND_ATTRIBUTE:SPECIAL_SELECTOR_AND_ATTRIBUTE = "SPECIAL_SELECTOR_AND_ATTRIBUTE"; | ||||||
|  |   static readonly SELECTOR_AND_ATTRIBUTE:SELECTOR_AND_ATTRIBUTE = "SELECTOR_AND_ATTRIBUTE";    | ||||||
|  |   static readonly SPECIAL_SELECTOR:SPECIAL_SELECTOR = "SPECIAL_SELECTOR"; | ||||||
|  |   static readonly SELECTOR:SELECTOR = "SELECTOR"; | ||||||
|  |    | ||||||
|  |   static readonly ATTRIBUTE:ATTRIBUTE = "ATTRIBUTE"; | ||||||
|  |   static readonly PASS_THROUGH:PASS_THROUGH = "PASS_THROUGH"; | ||||||
|  |   static readonly CONTENT:CONTENT = "CONTENT"; | ||||||
|  | 
 | ||||||
|  |   static readonly DEFAULT:DEFAULT = "DEFAULT"; | ||||||
|  | 
 | ||||||
|  |   static readonly defaultMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.DEFAULT, ExtendedRegex.create( /@\(\(([^\)\)]+)\)\)/ ) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   static readonly specialSelectorAndAttributeMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.SPECIAL_SELECTOR_AND_ATTRIBUTE, ExtendedRegex.create( /@\{(\P):(\V)\}\[(\P)\]/ ) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   static readonly selectorAndAttributeMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.SELECTOR_AND_ATTRIBUTE, ExtendedRegex.create( /@\{(\V)\}\[(\P)\]/ ) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   static readonly specialSelectorMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.SELECTOR, ExtendedRegex.create( /@\{(\P):(\V)\}/ ) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   static readonly selectorMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.SELECTOR, ExtendedRegex.create( /@\{(\V)\}/ ) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   static readonly attributeMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.ATTRIBUTE, ExtendedRegex.create( /@\[(\P)\]/ ) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   static readonly passThroughMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.ATTRIBUTE, ExtendedRegex.create( /@\{\}/ )  | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   static readonly contentMatcher = new LexerMatcher(  | ||||||
|  |     TemplatesSourceLexer.CONTENT, ExtendedRegex.create( /.|\n|\r/ ) | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   constructor() | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  |     this._create();     | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _create() | ||||||
|  |   { | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.defaultMatcher ); | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.specialSelectorAndAttributeMatcher ); | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.selectorAndAttributeMatcher ); | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.specialSelectorMatcher ); | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.selectorMatcher ); | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.attributeMatcher ); | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.passThroughMatcher ); | ||||||
|  |     this.addMatcher( TemplatesSourceLexer.contentMatcher ) ; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | import { StylesProcessorLexer } from "./StylesProcessorLexer";  | ||||||
|  | 
 | ||||||
|  | export class StylesProcessor | ||||||
|  | { | ||||||
|  |   static convert( styles:string ) | ||||||
|  |   { | ||||||
|  |     let lexer = new StylesProcessorLexer(); | ||||||
|  |     return lexer.convert( styles ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,168 @@ | ||||||
|  | import { Lexer } from "../../text/lexer/Lexer"; | ||||||
|  | import { LexerEvent } from "../../text/lexer/LexerEvent"; | ||||||
|  | import { LexerMatcher } from "../../text/lexer/LexerMatcher"; | ||||||
|  | import { LexerMatcherLibrary } from "../../text/lexer/LexerMatcherLibrary"; | ||||||
|  | import { LexerType, LexerTypes } from "../../text/lexer/LexerType"; | ||||||
|  | import { LexerQuery } from "../../text/lexer/LexerQuery"; | ||||||
|  | import { LiteralLexerEventTypeMatcher } from "../../text/lexer/LexerEventMatcher"; | ||||||
|  | import { StylesProcessor } from "./StylesProcessor"; | ||||||
|  | 
 | ||||||
|  | export type PORTRAIT_MARKER = "PORTRAIT_MARKER"; | ||||||
|  | export type LANDSCAPE_MARKER = "LANDSCAPE_MARKER"; | ||||||
|  | export type ROOT_ELEMENT_REPLACER = "ROOT_ELEMENT_REPLACER"; | ||||||
|  | 
 | ||||||
|  | export type StylesProcessorLexerType = LexerType | ROOT_ELEMENT_REPLACER | PORTRAIT_MARKER | LANDSCAPE_MARKER; | ||||||
|  | 
 | ||||||
|  | export class StylesProcessorLexer extends Lexer | ||||||
|  | { | ||||||
|  |   static readonly PORTRAIT_MARKER:PORTRAIT_MARKER = "PORTRAIT_MARKER"; | ||||||
|  |   static readonly PORTRAIT_MARKER_MATCHER =  | ||||||
|  |     new LexerMatcher( StylesProcessorLexer.PORTRAIT_MARKER,  /#portrait/i ); | ||||||
|  | 
 | ||||||
|  |   static readonly LANDSCAPE_MARKER:LANDSCAPE_MARKER = "LANDSCAPE_MARKER"; | ||||||
|  |   static readonly LANDSCAPE_MARKER_MATCHER =  | ||||||
|  |       new LexerMatcher( StylesProcessorLexer.LANDSCAPE_MARKER,  /#landscape/i ); | ||||||
|  | 
 | ||||||
|  |   static readonly ROOT_ELEMENT_REPLACER:ROOT_ELEMENT_REPLACER = "ROOT_ELEMENT_REPLACER"; | ||||||
|  |   static readonly ROOT_ELEMENT_REPLACER_MATCHER =  | ||||||
|  |       new LexerMatcher( StylesProcessorLexer.ROOT_ELEMENT_REPLACER,  /\[_?\]/ );  | ||||||
|  |   constructor() | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  | 
 | ||||||
|  |     this.addAllMatchers(  | ||||||
|  |        | ||||||
|  |       LexerMatcherLibrary.SINGLE_LINE_COMMENT_MATCHER, | ||||||
|  |       LexerMatcherLibrary.MULTI_LINE_COMMENT_MATCHER, | ||||||
|  |       LexerMatcherLibrary.DOUBLE_QUOTED_STRING_MATCHER, | ||||||
|  |       LexerMatcherLibrary.SINGLE_QUOTED_STRING_MATCHER,  | ||||||
|  |       LexerMatcherLibrary.WHITESPACE_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BLOCKSTART_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BLOCKEND_MATCHER, | ||||||
|  |       StylesProcessorLexer.PORTRAIT_MARKER_MATCHER, | ||||||
|  |       StylesProcessorLexer.LANDSCAPE_MARKER_MATCHER, | ||||||
|  |       StylesProcessorLexer.ROOT_ELEMENT_REPLACER_MATCHER, | ||||||
|  |       LexerMatcherLibrary.CSS_CLASS_SELECTOR_MATCHER, | ||||||
|  |       LexerMatcherLibrary.CSS_ID_SELECTOR_MATCHER, | ||||||
|  |       LexerMatcherLibrary.CSS_WORD_MATCHER, | ||||||
|  |       LexerMatcherLibrary.ANY_SYMBOL_MATCHER | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getCompressedTokens( source:string ) | ||||||
|  |   { | ||||||
|  |     let events = this.lexToList( source ); | ||||||
|  |     let types = new Set<string>(); | ||||||
|  |     types.add( LexerMatcherLibrary.WHITESPACE_MATCHER.type ); | ||||||
|  |     types.add( LexerMatcherLibrary.ANY_SYMBOL_MATCHER.type ); | ||||||
|  | 
 | ||||||
|  |     events = this.compress( events, types ); | ||||||
|  | 
 | ||||||
|  |     return events; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   convert( source:string ) | ||||||
|  |   { | ||||||
|  |     let tokens = this.getCompressedTokens( source ); | ||||||
|  |     this.resolveMatches( source, tokens ); | ||||||
|  |     let query = new LexerQuery(); | ||||||
|  |     query.source = source; | ||||||
|  |     query.tokens = tokens; | ||||||
|  | 
 | ||||||
|  |     let rootElement = ""; | ||||||
|  | 
 | ||||||
|  |     query.forAllWithType( LexerTypes.CSS_WORD,  | ||||||
|  |       ( t ) => | ||||||
|  |       { | ||||||
|  |         if ( rootElement !==  "" ) | ||||||
|  |         { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let isCustomElement =  LexerMatcherLibrary.HTML_CUSTOM_ELEMENT_MATCHER.isMatching( t.match );        | ||||||
|  |        | ||||||
|  |         console.log( "IsCustom:", t.match, isCustomElement ); | ||||||
|  |         if ( ! isCustomElement ) | ||||||
|  |         { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         rootElement = t.match; | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     let emptyRootElementMessage = false; | ||||||
|  | 
 | ||||||
|  |     query.forAllWithType( StylesProcessorLexer.ROOT_ELEMENT_REPLACER,  | ||||||
|  |       ( t )=> | ||||||
|  |       { | ||||||
|  |         if ( rootElement == "" && ! emptyRootElementMessage ) | ||||||
|  |         { | ||||||
|  |           emptyRootElementMessage = true; | ||||||
|  |           console.log( "No root element detected for [] replacements!" ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         t.setMatch( rootElement ); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     let output:string[] = []; | ||||||
|  |      | ||||||
|  |     let markerReplacements =  | ||||||
|  |     { | ||||||
|  |       [ StylesProcessorLexer.PORTRAIT_MARKER ]: "@media ( orientation:portrait )", | ||||||
|  |       [ StylesProcessorLexer.LANDSCAPE_MARKER ]: "@media ( orientation:landscape )", | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |     let start = new LiteralLexerEventTypeMatcher( LexerTypes.BLOCKSTART ); | ||||||
|  |     let end = new LiteralLexerEventTypeMatcher( LexerTypes.BLOCKEND ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < tokens.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let token = tokens[ i ]; | ||||||
|  | 
 | ||||||
|  |       if (  | ||||||
|  |         token.isType( StylesProcessorLexer.PORTRAIT_MARKER ) || | ||||||
|  |         token.isType( StylesProcessorLexer.LANDSCAPE_MARKER )  | ||||||
|  |       ) | ||||||
|  |       { | ||||||
|  |         let blockIndices = query.searchBlockIndices( i, start, end );  | ||||||
|  | 
 | ||||||
|  |         if ( blockIndices == null ) | ||||||
|  |         { | ||||||
|  |           output.push( token.match ); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |           let markerType = token.type as ( PORTRAIT_MARKER | LANDSCAPE_MARKER ); | ||||||
|  |           let replacement = markerReplacements[ markerType ]; | ||||||
|  |           output.push( replacement ); | ||||||
|  |           output.push( "{" ); | ||||||
|  | 
 | ||||||
|  |            | ||||||
|  |           query.forAllMatches(  | ||||||
|  |             i + 1 , blockIndices.endIndex,  | ||||||
|  |             ( match ) =>  | ||||||
|  |             {  | ||||||
|  |               output.push( match ); | ||||||
|  |             } | ||||||
|  |           ); | ||||||
|  |            | ||||||
|  |           output.push( "}" ); | ||||||
|  | 
 | ||||||
|  |           i = blockIndices.endIndex; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         output.push( token.match ); | ||||||
|  |       }   | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     return output.join( "" ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,96 @@ | ||||||
|  | import { RegExpUtility } from "./RegExpUtitlity"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class ExtendedRegex | ||||||
|  | {    | ||||||
|  |   private static _extensions:Map<RegExp,string> = null; | ||||||
|  |    | ||||||
|  |   static create( regex:string|RegExp, flags:string = undefined ) | ||||||
|  |   { | ||||||
|  |     if ( typeof regex === "string" ) | ||||||
|  |     { | ||||||
|  |       let rgx = new RegExp( ExtendedRegex.parseSource( regex ), flags ); | ||||||
|  |       return rgx; | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       let source = regex.source; | ||||||
|  |       let parsed = ExtendedRegex.parseSource( source ); | ||||||
|  |       flags = flags || regex.flags; | ||||||
|  |       let rgx =  new RegExp( parsed, flags ); | ||||||
|  |       return rgx; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static get extensions() | ||||||
|  |   { | ||||||
|  |     if ( ExtendedRegex._extensions ) | ||||||
|  |     { | ||||||
|  |       return ExtendedRegex._extensions; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let extensions:any = {};    | ||||||
|  | 
 | ||||||
|  |     // \P = Property, word with hyphen  
 | ||||||
|  |     extensions[ "\\\\P" ] = "(?:\\w|\\-)+"; | ||||||
|  |     // \V = CSS Attribute Value
 | ||||||
|  |     extensions[ "\\\\V" ] = "(?:\\w|\-|\\||^|$|~|=|#|\\*|\\.|\\\"|\\\'|\\[|\\]|\\s)+"; | ||||||
|  | 
 | ||||||
|  |     // \a = Vowels en
 | ||||||
|  |     extensions[ "\\\\a" ] = "[aeiouAEIOU]";     | ||||||
|  |     // \A = Not Vowels en
 | ||||||
|  |     extensions[ "\\\\A" ] = "[^aeiouAEIOU]"; | ||||||
|  |      | ||||||
|  |     // \y = Vowels en with y
 | ||||||
|  |     extensions[ "\\\\y" ] = "[aeiouyAEIOUY]"; | ||||||
|  |     // \Y = Not Vowels en with y
 | ||||||
|  |     extensions[ "\\\\Y" ] = "[^aeiouyAEIOUY]"; | ||||||
|  | 
 | ||||||
|  |     // \ä = Vowels de extended 
 | ||||||
|  |     extensions[ "\\\\ä" ] = "[aeiouyAEIOUYäöüÄÖÜ]"; | ||||||
|  |     // \Ä = Not Vowels de extended 
 | ||||||
|  |     extensions[ "\\\\Ä" ] = "[^aeiouyAEIOUYäöüÄÖÜ]"; | ||||||
|  | 
 | ||||||
|  |     // \k = Consonants 
 | ||||||
|  |     extensions[ "\\\\k" ] = "[b-df-hj-np-tv-zB-DF-HJ-NP-TV-Z]"; | ||||||
|  | 
 | ||||||
|  |     // \K = Not Consonants 
 | ||||||
|  |     extensions[ "\\\\K" ] = "[^b-df-hj-np-tv-zB-DF-HJ-NP-TV-Z]"; | ||||||
|  | 
 | ||||||
|  |     // \z = Any Break
 | ||||||
|  |     extensions[ "\\\\z" ] = "(?:.|\n|\r)*?"; | ||||||
|  |        | ||||||
|  |     // \h = Hex Only lower case
 | ||||||
|  |     extensions[ "\\\\h" ] = "[a-z0-9]"; | ||||||
|  | 
 | ||||||
|  |     // \H = Hex
 | ||||||
|  |     extensions[ "\\\\H" ] = "[a-zA-Z0-9]"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     ExtendedRegex._extensions = new Map<RegExp,string>(); | ||||||
|  | 
 | ||||||
|  |     for ( let key in extensions ) | ||||||
|  |     { | ||||||
|  |       let regexSource = RegExpUtility.toRegexSource( key ); | ||||||
|  |       let regex = new RegExp( regexSource, "g" ); | ||||||
|  | 
 | ||||||
|  |       ExtendedRegex._extensions.set( regex, extensions[ key ] ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ExtendedRegex._extensions; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   private static parseSource( source:string ) | ||||||
|  |   { | ||||||
|  |     for ( let entry of ExtendedRegex.extensions ) | ||||||
|  |     { | ||||||
|  |       let regex = entry[ 0 ]; | ||||||
|  |       let replacement = entry[ 1 ]; | ||||||
|  | 
 | ||||||
|  |       source = source.replace( regex, replacement ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return source; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,108 @@ | ||||||
|  | 
 | ||||||
|  | export class LevenshteinFuzzyMatch | ||||||
|  | { | ||||||
|  |   lowestDistance:number; | ||||||
|  |   index:number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class LevenshteinDistance | ||||||
|  | {    | ||||||
|  |   protected static getMatrix( aLength:number, bLength:number ) | ||||||
|  |   { | ||||||
|  |     let matrix:number[][] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < aLength + 1; i++ ) | ||||||
|  |     { | ||||||
|  |       let bArray = []; | ||||||
|  | 
 | ||||||
|  |       for ( let j = 0; j < bLength + 1; j++ ) | ||||||
|  |       { | ||||||
|  |         bArray.push( 0 ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       matrix.push( bArray ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i <= aLength; i++ ) | ||||||
|  |     { | ||||||
|  |       matrix[ i ][ 0 ] = i; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let j = 0; j <= bLength; j++ ) | ||||||
|  |     { | ||||||
|  |       matrix[ 0 ][ j ] = j; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return matrix; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static computeFuzzyMatch( value:string, matcher:string ) | ||||||
|  |   { | ||||||
|  |     let result = new LevenshteinFuzzyMatch(); | ||||||
|  | 
 | ||||||
|  |     if ( matcher.length >= value.length ) | ||||||
|  |     { | ||||||
|  |       result.index = 0; | ||||||
|  |       result.lowestDistance = LevenshteinDistance.compute( value, matcher ); | ||||||
|  |        | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       let offsets = value.length - matcher.length; | ||||||
|  | 
 | ||||||
|  |       for ( let i = 0; i < offsets; i++ ) | ||||||
|  |       { | ||||||
|  |         let subValue = value.substring( i, i + matcher.length ); | ||||||
|  |         let distance = LevenshteinDistance.compute( subValue, matcher ); | ||||||
|  | 
 | ||||||
|  |         if ( i === 0 ) | ||||||
|  |         { | ||||||
|  |           result.index = 0; | ||||||
|  |           result.lowestDistance = distance; | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ( distance < result.lowestDistance ) | ||||||
|  |         { | ||||||
|  |           result.index = i; | ||||||
|  |           result.lowestDistance = distance; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static compute( a:string, b:string ):number | ||||||
|  |   { | ||||||
|  |     let aLength = a.length; | ||||||
|  |     let bLength = b.length; | ||||||
|  | 
 | ||||||
|  |     if ( aLength == 0 ) { return bLength; } | ||||||
|  |     if ( bLength == 0 ) { return aLength; } | ||||||
|  | 
 | ||||||
|  |     let evaluationMatrix = LevenshteinDistance.getMatrix( aLength, bLength );         | ||||||
|  |        | ||||||
|  |     for ( let i = 1; i <= aLength; i++ ) | ||||||
|  |     {           | ||||||
|  |       for ( let j = 1; j <= bLength; j++ ) | ||||||
|  |       {             | ||||||
|  |         let isSame = b[ j - 1 ] === a[ i - 1 ]; | ||||||
|  | 
 | ||||||
|  |         let isDifferentCost = isSame ? 0 : 1; | ||||||
|  | 
 | ||||||
|  |         let insertionCost    = evaluationMatrix[ i - 1][ j ] + 1; | ||||||
|  |         let deletionCost     = evaluationMatrix[ i ][ j - 1 ] + 1; | ||||||
|  |         let substitutionCost = evaluationMatrix[ i - 1][ j - 1 ] + isDifferentCost; | ||||||
|  |            | ||||||
|  |         evaluationMatrix[ i ][ j ] = Math.min( insertionCost, deletionCost, substitutionCost ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |        | ||||||
|  |     return evaluationMatrix[ aLength][ bLength ]; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,952 @@ | ||||||
|  | import { MathX } from "../math/MathX"; | ||||||
|  | import { Arrays } from "../tools/Arrays"; | ||||||
|  | import { LevenshteinDistance } from "./Levehshtein"; | ||||||
|  | import { LexerMatcher } from "./lexer/LexerMatcher"; | ||||||
|  | 
 | ||||||
|  | export class RegExpUtility | ||||||
|  | { | ||||||
|  |   static repeat( text:string, times:number ) | ||||||
|  |   { | ||||||
|  |     let output = []; | ||||||
|  | 
 | ||||||
|  |     while ( times > 0 ) | ||||||
|  |     { | ||||||
|  |       output.push( text ); | ||||||
|  |       times--; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return output.join( "" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static collapse( text:string, collapsingPattern:string ) | ||||||
|  |   { | ||||||
|  |     let collapsingRegex = /((?:XXX)+)/g; | ||||||
|  |     let escapedRegexContent = collapsingRegex.source; | ||||||
|  |     let escapedCollapsedPattern = RegExpUtility.toRegexSource( collapsingPattern ); | ||||||
|  |     escapedRegexContent = escapedRegexContent.replace( "XXX", escapedCollapsedPattern ); | ||||||
|  | 
 | ||||||
|  |     let regex = new RegExp( escapedRegexContent, "g" ); | ||||||
|  | 
 | ||||||
|  |     return text.replace( regex, collapsingPattern ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static collapseWhiteSpace( text:string ) | ||||||
|  |   { | ||||||
|  |     text = text.replace( /((?:\s|\n|\r|\t)+)/g, " " ); | ||||||
|  | 
 | ||||||
|  |     return text.trim(); | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   static toFileName( text:string ) | ||||||
|  |   { | ||||||
|  |     let fileNameOutput:string[] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < text.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let character = text[ i ]; | ||||||
|  | 
 | ||||||
|  |       if ( /[a-zA-Z0-9\-]/.test( character ) ) | ||||||
|  |       { | ||||||
|  |         fileNameOutput.push( character ); | ||||||
|  |         //console.log( "Not escaped:", character );
 | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         fileNameOutput.push( "_" ); | ||||||
|  |         //console.log( "Escaped:", character, ">>", "_" );
 | ||||||
|  |       } | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     return fileNameOutput.join( "" ); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static trimCharacters( text:string, fromStart:number, fromEnd:number ) | ||||||
|  |   { | ||||||
|  |     let start = fromStart; | ||||||
|  |     let end   = text.length - fromEnd; | ||||||
|  | 
 | ||||||
|  |     return text.substring( start, end ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static splitLines( text:string ) | ||||||
|  |   { | ||||||
|  |     return text.split( /(?:\r\n)|\n|\r/g ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static splitLinesCaptureBreaks( text:string ) | ||||||
|  |   { | ||||||
|  |     return text.split( /((?:\r\n)|\n|\r)/g ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static createMatcherFromCombiningWords( words:string[], noSubmatches:boolean ):RegExp | ||||||
|  |   { | ||||||
|  |     let sources = words.map( w => RegExpUtility.toRegexSource( w ) );  | ||||||
|  | 
 | ||||||
|  |     let regexSource = sources.join( "|" ); | ||||||
|  | 
 | ||||||
|  |     if ( ! noSubmatches ) | ||||||
|  |     { | ||||||
|  |       regexSource = `^(${regexSource})$`;  | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     return new RegExp( regexSource ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static matches( value:string, matcher:string|RegExp ) | ||||||
|  |   { | ||||||
|  |     if ( ! matcher ) | ||||||
|  |     { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( typeof matcher === "string" ) | ||||||
|  |     { | ||||||
|  |       return value === matcher; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return matcher.test( value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static cutout( value:string, regex:RegExp ):string[] | ||||||
|  |   { | ||||||
|  |     let result = regex.exec( value ); | ||||||
|  | 
 | ||||||
|  |     if ( ! result ) | ||||||
|  |     { | ||||||
|  |       return [ value, null ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let match = result[ 0 ]; | ||||||
|  | 
 | ||||||
|  |     let cutout = RegExpUtility.cutoutRange( value, result.index, match.length ); | ||||||
|  | 
 | ||||||
|  |     return [ cutout, match ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static chopRange( value:string, start:number, end:number ) | ||||||
|  |   { | ||||||
|  |     let chopped:string[] = []; | ||||||
|  | 
 | ||||||
|  |     if ( start > 0 ) | ||||||
|  |     { | ||||||
|  |       chopped.push( value.substring( 0, start ) ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       chopped.push( null ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     chopped.push( value.substring( start, end ) ); | ||||||
|  | 
 | ||||||
|  |     if ( end < value.length ) | ||||||
|  |     { | ||||||
|  |       chopped.push( value.substring( end, value.length ) ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       chopped.push( null ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return chopped; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static cutoutRange( value:string, start:number, length:number ):string | ||||||
|  |   { | ||||||
|  |     if ( start == 0 ) | ||||||
|  |     { | ||||||
|  |       return value.substring( length ); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     let before = value.substring( 0, start ); | ||||||
|  |      | ||||||
|  |     let after = value.substring( start + length ); | ||||||
|  | 
 | ||||||
|  |     return before + after; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static parseDuration( value:string, alternative:number = 0 ) | ||||||
|  |   { | ||||||
|  |     if ( ! value ) | ||||||
|  |     { | ||||||
|  |       return alternative; | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     if ( value.indexOf( ":" ) === -1 ) | ||||||
|  |     { | ||||||
|  |       return parseFloat( value ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let values = this.parseNumbers( value, [ 0, 0 ], ":" ); | ||||||
|  | 
 | ||||||
|  |     return values[ 0 ] * 60 + values[ 1 ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static asDurationString( duration:number, delimiter:string = ":", zerofillMinutes:boolean = false ) | ||||||
|  |   { | ||||||
|  |     let seconds = Math.floor( MathX.repeat( duration, 60 ) ) + ""; | ||||||
|  |     let minutes = Math.floor( duration / 60 ) + ""; | ||||||
|  | 
 | ||||||
|  |     if ( seconds.length < 2 ){ seconds = "0" + seconds; } | ||||||
|  |     if ( zerofillMinutes && minutes.length < 2 ){ minutes = "0" + minutes; } | ||||||
|  | 
 | ||||||
|  |     return minutes + delimiter + seconds; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static parseNumbers( value:string, alternative:number[] = [], delimiter = "," ) | ||||||
|  |   { | ||||||
|  |     if ( ! value ) | ||||||
|  |     { | ||||||
|  |       return alternative; | ||||||
|  |     }   | ||||||
|  | 
 | ||||||
|  |     var splitted = value.split( delimiter ); | ||||||
|  | 
 | ||||||
|  |     var numberValues = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < splitted.length; i++ ) | ||||||
|  |     { | ||||||
|  |       var splitValue = splitted[ i ].trim(); | ||||||
|  |       var numericValue = parseFloat( splitValue ); | ||||||
|  | 
 | ||||||
|  |       numberValues.push( numericValue ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return numberValues; | ||||||
|  |   }   | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  |   static splitPath( path:string ) | ||||||
|  |   { | ||||||
|  |     return path.split( /\\|\// ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static startsWithSome( text:string, starts:string[] ) | ||||||
|  |   { | ||||||
|  |     return starts.some( s => text.startsWith( s ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static escapePathFragmentForWindows( fragmentWithoutSlashes:string, replacement:string = "_" ):string | ||||||
|  |   { | ||||||
|  |     return fragmentWithoutSlashes.replace( /(\\|\/|\?|\:|\||\*|\<|\>)/g, replacement ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static resolvePath( path:string ) | ||||||
|  |   { | ||||||
|  |     let pathFragments = path.split( "/" ); | ||||||
|  |     let resolvedFragments = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < pathFragments.length; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( pathFragments[ i ] === ".." ) | ||||||
|  |       { | ||||||
|  |         if ( resolvedFragments.length === 0 || resolvedFragments[ i - 1 ] === ".." ) | ||||||
|  |         { | ||||||
|  |           resolvedFragments.push( ".." ); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |           resolvedFragments.pop(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         resolvedFragments.push( pathFragments[ i ] ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let resolvedPath = resolvedFragments.join( "/" ); | ||||||
|  |     return resolvedPath; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static createRelativeDirectoryPath( sourceDirectory:string, targetDirectory:string) | ||||||
|  |   { | ||||||
|  |     if ( sourceDirectory === targetDirectory ) | ||||||
|  |     { | ||||||
|  |       return ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     sourceDirectory = this.normalizePath( sourceDirectory ); | ||||||
|  |     targetDirectory = this.normalizePath( targetDirectory ); | ||||||
|  | 
 | ||||||
|  |     if ( sourceDirectory === targetDirectory ) | ||||||
|  |     { | ||||||
|  |       return ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     let matching = RegExpUtility.getMatchingDirectories( sourceDirectory, targetDirectory ); | ||||||
|  |              | ||||||
|  |      | ||||||
|  |     let shortSource = sourceDirectory.substring( matching.length ); | ||||||
|  |     let shortTarget = targetDirectory.substring( matching.length ); | ||||||
|  | 
 | ||||||
|  |     shortSource = this.normalizePath( shortSource ); | ||||||
|  |     shortTarget = this.normalizePath( shortTarget ); | ||||||
|  | 
 | ||||||
|  |     if ( this.isEmptyPath( shortSource ) ) | ||||||
|  |     { | ||||||
|  |       return shortTarget; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let sourceDirectories = RegExpUtility.splitPath( shortSource ); | ||||||
|  | 
 | ||||||
|  |     let path = ""; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < sourceDirectories.length; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( path !== "" ) | ||||||
|  |       { | ||||||
|  |         path += "/"; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       path += ".."; | ||||||
|  |        | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( path !== "" ) | ||||||
|  |     { | ||||||
|  |       path += "/" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     path += shortTarget; | ||||||
|  |     return path; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static isEmptyPath( path:string ) | ||||||
|  |   { | ||||||
|  |     return /^\s*(\\|\/)?\s*$/.test( path ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getAllMatchResultsOf( source:string, regex:RegExp ) | ||||||
|  |   { | ||||||
|  |     let matcher = new LexerMatcher( "match", RegExpUtility.makeSticky( regex ) ); | ||||||
|  |     let offset = 0; | ||||||
|  |      | ||||||
|  |     let results:RegExpExecArray[] = []; | ||||||
|  | 
 | ||||||
|  |     while ( offset < source.length ) | ||||||
|  |     { | ||||||
|  |       let result = matcher.getMatchResult( source, offset ); | ||||||
|  | 
 | ||||||
|  |       if ( result ) | ||||||
|  |       { | ||||||
|  |         results.push( result ); | ||||||
|  |         offset += result[ 0 ].length; | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         offset ++; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return results; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static removeLeadingSlashes( text:string ) | ||||||
|  |   { | ||||||
|  |     return text.replace( /^\s*(\\|\/)/, "" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static removeTrailingSlashes( text:string ) | ||||||
|  |   { | ||||||
|  |     return text.replace( /(\\|\/)\s*$/, "" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static trimSlashes( text:string ) | ||||||
|  |   { | ||||||
|  |     return this.removeTrailingSlashes( this.removeLeadingSlashes( text ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static removeLeadingSpace( textContent:string, removeFirstEmpty:boolean = true ) | ||||||
|  |   { | ||||||
|  |     var lines = textContent.split( /(?:\r\n|\r|\n)/ ); | ||||||
|  | 
 | ||||||
|  |     if ( removeFirstEmpty && /^\s*$/.test( lines[ 0 ] )) | ||||||
|  |     { | ||||||
|  |       lines.shift(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var offset:number|null = null; | ||||||
|  | 
 | ||||||
|  |     lines.forEach( | ||||||
|  |       ( line ) => | ||||||
|  |       { | ||||||
|  |         if ( /^\s*$/.test( line ) ) | ||||||
|  |         { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         var result = /^\s+/.exec( line ); | ||||||
|  | 
 | ||||||
|  |         //console.log( "LINE RESULT:", result, "line:", line );
 | ||||||
|  | 
 | ||||||
|  |         if ( result == null ) | ||||||
|  |         { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         var numSpaces = result[ 0 ].length; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         offset = offset === null ? numSpaces : Math.min( offset, numSpaces ); | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     //console.log( "offset", offset );
 | ||||||
|  | 
 | ||||||
|  |     if ( offset == null ) | ||||||
|  |     { | ||||||
|  |       return lines.join( "\n" ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     lines = lines.map( line =>  | ||||||
|  |       { | ||||||
|  |         if ( /^\s*$/.test( line ) ) | ||||||
|  |         { | ||||||
|  |           return line; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         var result = /^\s+/.exec( line ); | ||||||
|  |         if ( result === null ) | ||||||
|  |         { | ||||||
|  |           return line; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return line.substring( offset ); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return lines.join( "\n" ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static getMatchingStartOfAll( s:string[] ):string | ||||||
|  |   { | ||||||
|  |     if ( s === null || s === undefined || s.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let empty = s.some( e => e === null || e === undefined || e === "" ); | ||||||
|  | 
 | ||||||
|  |     if ( empty ) | ||||||
|  |     { | ||||||
|  |       return ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( s.length === 1 ) | ||||||
|  |     { | ||||||
|  |       return s[ 0 ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let length = s.map( e => e.length ).reduce( ( a, b ) => Math.min( a,b ) ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < length; i++ ) | ||||||
|  |     { | ||||||
|  |       let value = s[ 0 ][ i ]; | ||||||
|  | 
 | ||||||
|  |       for ( let j = 1; j < s.length; j ++ ) | ||||||
|  |       { | ||||||
|  |         if ( s[ j ][ i ] !== value ) | ||||||
|  |         { | ||||||
|  |           return s[ 0 ].substring( 0, i ); | ||||||
|  |         }  | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return s[ 0 ].substring( 0, length );  | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getMatchingDirectories( a:string, b:string ) | ||||||
|  |   { | ||||||
|  |     let matching = []; | ||||||
|  | 
 | ||||||
|  |     let directoriesA = this.splitPath( a ); | ||||||
|  |     let directoriesB = this.splitPath( b ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < directoriesA.length && i < directoriesB.length; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( directoriesA[ i ] == directoriesB[ i ] ) | ||||||
|  |       { | ||||||
|  |         matching.push( directoriesA[ i ] ); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         return matching.join( "/" ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return matching.join( "/" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getMatchingStart( a:string, b:string ) | ||||||
|  |   { | ||||||
|  |     if ( ! a || ! b ) | ||||||
|  |     { | ||||||
|  |       return ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let length = Math.min( a.length, b.length ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < length; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( a[ i ] !== b[ i ] ) | ||||||
|  |       { | ||||||
|  |         return a.substring( 0, i ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( length === a.length ) | ||||||
|  |     { | ||||||
|  |       return a; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return b; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getMatchingEnd( a:string, b:string ) | ||||||
|  |   { | ||||||
|  |     if ( ! a || ! b ) | ||||||
|  |     { | ||||||
|  |       return ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let length = Math.min( a.length, b.length ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0 ; i < length; i++ ) | ||||||
|  |     { | ||||||
|  |       let aIndex = a.length - ( 1 + i ); | ||||||
|  |       let bIndex = b.length - ( 1 + i ); | ||||||
|  | 
 | ||||||
|  |       if ( a[ aIndex  ] !== b[ bIndex ] ) | ||||||
|  |       { | ||||||
|  |         return a.substring( aIndex + 1, a.length ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( length === a.length ) | ||||||
|  |     { | ||||||
|  |       return a; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return b; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static readonly tagMatchingRegex =  | ||||||
|  |     /(<tag(?: (?:\w|\s|\-|\=|"(?:\.|(?:\\\")|[^\""\n])*"|'(?:\.|(?:\\\')|[^\''\n])*')*)?>)((?:.|\n)*)(<\/tag>)/; | ||||||
|  | 
 | ||||||
|  |   static getTagRegex( tag:string ) | ||||||
|  |   { | ||||||
|  |     return new RegExp( RegExpUtility.tagMatchingRegex.source.replace( /tag/g, tag ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getTagContent( source:string, tag:string ) | ||||||
|  |   { | ||||||
|  |     let regex = RegExpUtility.getTagRegex( tag ); | ||||||
|  | 
 | ||||||
|  |     let result = regex.exec( source ); | ||||||
|  | 
 | ||||||
|  |     if ( ! result ) | ||||||
|  |     { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result[ 2 ]; | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   static setTagContent( source:string, tag:string, value:string ) | ||||||
|  |   { | ||||||
|  |     let regex = RegExpUtility.getTagRegex( tag ); | ||||||
|  | 
 | ||||||
|  |     return source.replace( regex, `$1${value}$3`); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static ensureSlash( path:string ) | ||||||
|  |   { | ||||||
|  |     if ( path.endsWith( "/" ) ) | ||||||
|  |     { | ||||||
|  |       return path; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return path + "/"; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static minimumDigitsInt( value:number, digits:number = 2 ) | ||||||
|  |   { | ||||||
|  |     let stringValue = Math.round( value ) + ""; | ||||||
|  | 
 | ||||||
|  |     while ( stringValue.length < digits ) | ||||||
|  |     { | ||||||
|  |       stringValue = "0" + stringValue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return stringValue; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static round( value:number, numZeros:number ) | ||||||
|  |   { | ||||||
|  |     if ( numZeros <= 0 ) | ||||||
|  |     { | ||||||
|  |       return Math.round( value ) + ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     numZeros = Math.round( numZeros ); | ||||||
|  | 
 | ||||||
|  |     var sign = value < 0 ? "-" : ""; | ||||||
|  |     value = Math.abs( value ); | ||||||
|  | 
 | ||||||
|  |     var roundedBiggerValue = Math.round( value * Math.pow( 10, numZeros ) );   | ||||||
|  |     var stringValue = roundedBiggerValue + ""; | ||||||
|  |     var minimumLength = numZeros + 1; | ||||||
|  |      | ||||||
|  |     while ( stringValue.length < minimumLength ) | ||||||
|  |     { | ||||||
|  |       stringValue = "0" + stringValue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     var split = stringValue.length - numZeros; | ||||||
|  |      | ||||||
|  |     return sign + stringValue.substring( 0, split ) + "." + stringValue.substring( split ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static createFromList( list:string[] ) | ||||||
|  |   { | ||||||
|  |     list = list.map( l => RegExpUtility.toRegexSource( l ) ); | ||||||
|  | 
 | ||||||
|  |     return new RegExp( list.join( "|" ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static toRegexSource( source:string ) | ||||||
|  |   { | ||||||
|  |     source = source.replace( /\./g, "\\." ); | ||||||
|  |     source = source.replace( /\(/g, "\\(" ); | ||||||
|  |     source = source.replace( /\)/g, "\\)" ); | ||||||
|  |     source = source.replace( /\[/g, "\\[" ); | ||||||
|  |     source = source.replace( /\]/g, "\\]" ); | ||||||
|  |     source = source.replace( /\^/g, "\\^" ); | ||||||
|  |     source = source.replace( /\$/g, "\\$" ); | ||||||
|  |     source = source.replace( /\*/g, "\\*" ); | ||||||
|  |     source = source.replace( /\+/g, "\\+" ); | ||||||
|  |     source = source.replace( /\-/g, "\\-" ); | ||||||
|  |     source = source.replace( /\?/g, "\\?" ); | ||||||
|  |     source = source.replace( /\//g, "\\/" ); | ||||||
|  |     source = source.replace( /\|/g, "\\|" ); | ||||||
|  | 
 | ||||||
|  |     return source;   | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static upperCaseFirst( source:string ) | ||||||
|  |   { | ||||||
|  |     if ( source === null || source === undefined || source.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return source[ 0 ].toUpperCase() + source.substring( 1 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static lowerCaseFirst( source:string ) | ||||||
|  |   { | ||||||
|  |     if ( source === null || source === undefined || source.length === 0 ) | ||||||
|  |     { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return source[ 0 ].toLowerCase() + source.substring( 1 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static fileTypeExtensionRegex = /\.(\w+)$/; | ||||||
|  | 
 | ||||||
|  |   static trimProtocols( link:string, protocols:string[]=["https","http","ftp","sftp"] ) | ||||||
|  |   { | ||||||
|  |     let genericRegex = /^XXX?\:\/\//; | ||||||
|  |     let escapedProtocols = protocols.map( p => RegExpUtility.toRegexSource( p ) ); | ||||||
|  |     let combinedProtocols = `(${escapedProtocols.join("|")})`; | ||||||
|  |     let protocolRegexSource = genericRegex.source.replace( "XXX", combinedProtocols ); | ||||||
|  | 
 | ||||||
|  |     let regex = new RegExp( protocolRegexSource ); | ||||||
|  | 
 | ||||||
|  |     // console.log( "REGEX:", regex, ">>", link );
 | ||||||
|  |     return link.replace( regex, "" ); | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static trimFileTypeExtension( source:string ) | ||||||
|  |   { | ||||||
|  |     return source.replace( RegExpUtility.fileTypeExtensionRegex, "" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static replaceAll( text:string, replacements:Map<string,string> ) | ||||||
|  |   { | ||||||
|  |     for ( let [ variable, replacement ] of replacements ) | ||||||
|  |     { | ||||||
|  |       let replacementRegexSource = RegExpUtility.toRegexSource( variable ); | ||||||
|  |       let regex = new RegExp( replacementRegexSource, "g" ); | ||||||
|  |       text = text.replace( regex, replacement ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return text; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getFileTypeExtension( source:string ) | ||||||
|  |   { | ||||||
|  |     let result = source.lastIndexOf( "." ); | ||||||
|  | 
 | ||||||
|  |     if ( result === - 1 ) | ||||||
|  |     { | ||||||
|  |       return ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return source.substring( result + 1 ); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static removeStarting( text:string, start:string ) | ||||||
|  |   { | ||||||
|  |     if ( text.startsWith( start ) ) | ||||||
|  |     { | ||||||
|  |       return text.substring( start.length ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return text; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static removeInner( text:string, start:number, length:number ) | ||||||
|  |   { | ||||||
|  |     return text.substring( 0, start ) + text.substring( start + length ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static makeSticky( regexp:RegExp ) | ||||||
|  |   { | ||||||
|  |     if ( regexp.sticky ) | ||||||
|  |     { | ||||||
|  |       return regexp; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var source = regexp.source; | ||||||
|  |     var flags  = regexp.flags; | ||||||
|  | 
 | ||||||
|  |     if ( flags.indexOf( "y" ) === -1 ) | ||||||
|  |     { | ||||||
|  |       flags += "y"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return new RegExp( source, flags ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getClosestMatching<T>( list:T[], matching:string, getText:(t:T)=>string = null ):T | ||||||
|  |   { | ||||||
|  |     let index = this.getClosestIndex( list, matching, getText ); | ||||||
|  | 
 | ||||||
|  |     return list[ index ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getClosestIndex<T>( list:T[], matching:string, getText:(t:T)=>string = null ):number | ||||||
|  |   { | ||||||
|  |     if ( ! getText ) | ||||||
|  |     { | ||||||
|  |       getText = ( t )=>{ return t + "" }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let index = list.findIndex( l => getText( l ) === matching ); | ||||||
|  |      | ||||||
|  |     if ( index !== -1 ) | ||||||
|  |     { | ||||||
|  |       return index; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let closestIndex = -1; | ||||||
|  |     let closestValue = 100000; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < list.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let listValue = getText( list[ i ] ); | ||||||
|  |       let distance = LevenshteinDistance.compute( listValue, matching ); | ||||||
|  |        | ||||||
|  |       if ( distance >= closestValue ) | ||||||
|  |       {  | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       closestValue = distance; | ||||||
|  |       closestIndex = i; | ||||||
|  |        | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return closestIndex; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static makeGlobal( regexp:RegExp ) | ||||||
|  |   { | ||||||
|  |     if ( regexp.global ) | ||||||
|  |     { | ||||||
|  |       return regexp; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var source = regexp.source; | ||||||
|  |     var flags = regexp.flags + "g";  | ||||||
|  | 
 | ||||||
|  |     return new RegExp( source, flags ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static makeIgnoreCase( regexp:RegExp ) | ||||||
|  |   { | ||||||
|  |     if ( regexp.ignoreCase ) | ||||||
|  |     { | ||||||
|  |       return regexp; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var source = regexp.source; | ||||||
|  |     var flags = regexp.flags + "i";  | ||||||
|  | 
 | ||||||
|  |     return new RegExp( source, flags ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static prependZeros( source:string, minimumLength:number = 2 ) | ||||||
|  |   { | ||||||
|  |     while ( source.length < minimumLength ) | ||||||
|  |     { | ||||||
|  |       source = "0" + source; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return source; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static createWordMatcher( source:string ) | ||||||
|  |   { | ||||||
|  |     source = "\\b" + RegExpUtility.toRegexSource( source ) + "\\b"; | ||||||
|  |     return new RegExp( source ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static createMatcher( source:string ) | ||||||
|  |   { | ||||||
|  |     return new RegExp( RegExpUtility.toRegexSource( source ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static createRegExp( regexp:RegExp, matching:string, replacement:string ) | ||||||
|  |   {  | ||||||
|  |      | ||||||
|  |     let source = regexp.source; | ||||||
|  |     let flags = regexp.flags; | ||||||
|  |     source = source.replace( matching, replacement ); | ||||||
|  | 
 | ||||||
|  |     return new RegExp( source, flags ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   static readonly ES_NUMBER_REGEX = /((\d+)?\.)?\d+(e(\+|\-)\d+)?/; | ||||||
|  | 
 | ||||||
|  |   static isESNumber( value:string ) | ||||||
|  |   { | ||||||
|  |     return RegExpUtility.ES_NUMBER_REGEX.test( value ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static parentPath( path:string, alternative:string ="") | ||||||
|  |   { | ||||||
|  |     let lastSlash = path.lastIndexOf( "/" ); | ||||||
|  |     let lastBackSlash = path.lastIndexOf( "\\" ) | ||||||
|  | 
 | ||||||
|  |     if ( lastSlash === -1 && lastBackSlash === -1 ) | ||||||
|  |     { | ||||||
|  |       return alternative; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let highest = Math.max( lastSlash, lastBackSlash ); | ||||||
|  | 
 | ||||||
|  |     return path.substring( 0, highest ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  |   static fileNameWithoutExtension( path:string ) | ||||||
|  |   { | ||||||
|  |     return RegExpUtility.trimFileTypeExtension( RegExpUtility.fileNameOrLastPath( path ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fileNameOrLastPath( path:string ) | ||||||
|  |   { | ||||||
|  |     path = this.normalizePath( path ); | ||||||
|  |     let lastSlash = path.lastIndexOf( "/" ); | ||||||
|  | 
 | ||||||
|  |     if ( lastSlash === -1 ) | ||||||
|  |     { | ||||||
|  |       return path; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return path.substring( lastSlash + 1 ); | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     path = path.replace( /(\\|\/)$/, "" ); | ||||||
|  |     let parentPath = RegExpUtility.parentPath( path ); | ||||||
|  |      | ||||||
|  |     if ( parentPath === "" ) | ||||||
|  |     { | ||||||
|  |       path = path.replace( /^(\/|\\)/, "" ); | ||||||
|  |       return path; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let last = path.substring( parentPath.length + 1 ); | ||||||
|  | 
 | ||||||
|  |     last = last.replace( /^(\/|\\)/, "" ); | ||||||
|  |     return last; | ||||||
|  |     */ | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static join( pathA:string, pathB:string, ...paths:string[] ) | ||||||
|  |   { | ||||||
|  |     let normalizedPaths = [pathA,pathB].concat( paths ); | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < normalizedPaths.length; i++ ) | ||||||
|  |     { | ||||||
|  |       normalizedPaths[ i ] = this.normalizePath( normalizedPaths[ i ] ); | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     return normalizedPaths.join( "/" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static joinPaths( paths:string[] ) | ||||||
|  |   { | ||||||
|  |     let normalizedPaths = paths; | ||||||
|  |      | ||||||
|  |     for ( let i = normalizedPaths.length - 1;  i >= 0; i-- ) | ||||||
|  |     { | ||||||
|  |       if ( this.isEmptyPath( normalizedPaths[ i ] ) ) | ||||||
|  |       { | ||||||
|  |         Arrays.removeAt( normalizedPaths, i ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < normalizedPaths.length; i++ ) | ||||||
|  |     { | ||||||
|  |       normalizedPaths[ i ] = this.normalizePath( normalizedPaths[ i ] ); | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|  |     return normalizedPaths.join( "/" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static normalizePath( path:string ) | ||||||
|  |   { | ||||||
|  |     let slashMatcher = /\\/g;     | ||||||
|  |     let multiples = /\/\/+/g; | ||||||
|  |     let startSlashes = /^\/+/; | ||||||
|  |     let endSlashes = /\/+$/; | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |     path = path.replace( slashMatcher, "/" ); | ||||||
|  |     path = path.replace( multiples, "/" ); | ||||||
|  |     path = path.replace( startSlashes, "" ); | ||||||
|  |     path = path.replace( endSlashes, "" ); | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |     return path; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,34 @@ | ||||||
|  | import { Lexer } from "./Lexer"; | ||||||
|  | import { LexerMatcher } from "./LexerMatcher"; | ||||||
|  | import { LexerMatcherLibrary } from "./LexerMatcherLibrary"; | ||||||
|  | import { LexerType } from "./LexerType"; | ||||||
|  | 
 | ||||||
|  | export class CLikeLexer extends Lexer | ||||||
|  | { | ||||||
|  |   constructor() | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  | 
 | ||||||
|  |     this.addAllMatchers(  | ||||||
|  |       LexerMatcherLibrary.SINGLE_LINE_COMMENT_MATCHER, | ||||||
|  |       LexerMatcherLibrary.MULTI_LINE_COMMENT_MATCHER, | ||||||
|  |       LexerMatcherLibrary.DOUBLE_QUOTED_STRING_MATCHER, | ||||||
|  |       LexerMatcherLibrary.SINGLE_QUOTED_STRING_MATCHER, | ||||||
|  |       LexerMatcherLibrary.C_INSTRUCTION_MATCHER, | ||||||
|  |       LexerMatcherLibrary.NUMBER_MATCHER, | ||||||
|  |       LexerMatcherLibrary.NULL_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BOOL_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BREAK_MATCHER, | ||||||
|  |       LexerMatcherLibrary.WHITESPACE_MATCHER, | ||||||
|  |       LexerMatcherLibrary.LOGIC_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BRACKET_MATCHER, | ||||||
|  |       LexerMatcherLibrary.ACCESS_MODIFIER_MATCHER, | ||||||
|  |       LexerMatcherLibrary.CLASS_MATCHER, | ||||||
|  |       LexerMatcherLibrary.OPERATOR_MATCHER, | ||||||
|  |       LexerMatcherLibrary.CFUNCTION_MATCHER, | ||||||
|  |       LexerMatcherLibrary.CWORD_MATCHER, | ||||||
|  |       LexerMatcherLibrary.ANY_SYMBOL_MATCHER | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | 
 | ||||||
|  | import { Lexer } from "./Lexer"; | ||||||
|  | import { LexerMatcher } from "./LexerMatcher"; | ||||||
|  | import { LexerMatcherLibrary } from "./LexerMatcherLibrary"; | ||||||
|  | import { LexerType } from "./LexerType"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class CSVLexer extends Lexer | ||||||
|  | { | ||||||
|  |   static readonly COMMA = "COMMA"; | ||||||
|  |   static readonly COMMA_MATCHER = new LexerMatcher( CSVLexer.COMMA, /\,/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly DATA = "DATA"; | ||||||
|  |   static readonly DATA_MATCHER = new LexerMatcher( CSVLexer.DATA, /(\w|\d|\s)+/ ); | ||||||
|  | 
 | ||||||
|  |   constructor() | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  | 
 | ||||||
|  |     this.addAllMatchers(  | ||||||
|  |       LexerMatcherLibrary.DOUBLE_QUOTED_STRING_MATCHER, | ||||||
|  |       CSVLexer.COMMA_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BREAK_MATCHER, | ||||||
|  |       LexerMatcherLibrary.ANY_SYMBOL_MATCHER | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,99 @@ | ||||||
|  | import { LexerEvent } from "./LexerEvent"; | ||||||
|  | import { Lexer } from "./Lexer"; | ||||||
|  | import { LexerQuery } from "./LexerQuery"; | ||||||
|  | import { ElementType } from "../../dom/ElementType"; | ||||||
|  | import { ElementAttribute } from "../../dom/ElementAttribute"; | ||||||
|  | 
 | ||||||
|  | export class HighlightedHTML | ||||||
|  | { | ||||||
|  |   static readonly codeToken = new ElementType( "code-token" ); | ||||||
|  |   static readonly type = new ElementAttribute( "type" ); | ||||||
|  | 
 | ||||||
|  |   static createLexerStyles( lexer:Lexer, prefixSelector:string = "", styles:string="" ) | ||||||
|  |   { | ||||||
|  |     let matchers = lexer.matchers;     | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < matchers.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let matcher = matchers[ i ]; | ||||||
|  |       let hue = 360 - i / matchers.length * 360; | ||||||
|  | 
 | ||||||
|  |       let style = `\n${prefixSelector}code-token-${matcher.type}{ color: hsl(${hue}, 60%, 60%)}`; | ||||||
|  | 
 | ||||||
|  |       styles += style; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return styles; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static tokenize( query:LexerQuery ) | ||||||
|  |   { | ||||||
|  |     let htmlTags = query.tokens.map( | ||||||
|  |       t=>  | ||||||
|  |       { | ||||||
|  |         if ( t.isDone || t.isError ) | ||||||
|  |         { | ||||||
|  |           return ""; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         let rawValue = t.getMatch( query.source ); | ||||||
|  |         let textNode = document.createTextNode( rawValue ); | ||||||
|  |         let wrapper = document.createElement( "div" ); | ||||||
|  |         wrapper.appendChild( textNode ); | ||||||
|  |         let escapedText = wrapper.innerHTML; | ||||||
|  |         return `<code-token-${t.type}>${escapedText}</code-token-${t.type}>` | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return htmlTags.join( "" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static auto( lexer:Lexer, query:LexerQuery ) | ||||||
|  |   {  | ||||||
|  |     let tokens = query.tokens; | ||||||
|  |     let source = query.source;    | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |     let htmlTags = tokens.map( | ||||||
|  |       t=>  | ||||||
|  |       { | ||||||
|  |         let rawValue = t.getMatch( source ); | ||||||
|  |         let textNode = document.createTextNode( rawValue ); | ||||||
|  |         let wrapper = document.createElement( "div" ); | ||||||
|  |         wrapper.appendChild( textNode ); | ||||||
|  |         let escapedText = wrapper.innerHTML; | ||||||
|  |         return `<code-token data-type="${t.type}">${escapedText}</code-token>` | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     let styles =  | ||||||
|  |     ` | ||||||
|  |       body | ||||||
|  |       { | ||||||
|  |         background-color: hsl(0,0%,10%); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       pre | ||||||
|  |       { | ||||||
|  |         font-family: Ubuntu Mono; | ||||||
|  |       } | ||||||
|  |     `;
 | ||||||
|  | 
 | ||||||
|  |     let matchers = lexer.matchers;     | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < matchers.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let matcher = matchers[ i ]; | ||||||
|  |       let hue = i / matchers.length * 360; | ||||||
|  | 
 | ||||||
|  |       let style = `\ncode-token[data-type="${matcher.type}"]{ color: hsl(${hue}, 60%, 60%)}`; | ||||||
|  | 
 | ||||||
|  |       styles += style; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let html = `<html><meta charset="utf-8"></meta><head><style>${styles}</style></head><body><pre>${htmlTags.join( "" )}</pre></body></html>` | ||||||
|  |      | ||||||
|  |     return html; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,170 @@ | ||||||
|  | import { LexerMatcher } from "./LexerMatcher"; | ||||||
|  | import { LexerEvent } from "./LexerEvent"; | ||||||
|  | 
 | ||||||
|  | export class Lexer | ||||||
|  | { | ||||||
|  |   static readonly defaultMode = "default"; | ||||||
|  |   private _modes = new Map<string,LexerMatcher[]>(); | ||||||
|  | 
 | ||||||
|  |   get modes() | ||||||
|  |   { | ||||||
|  |     return this._modes; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get matchers():LexerMatcher[] | ||||||
|  |   { | ||||||
|  |     let output:LexerMatcher[] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let mode of this._modes ) | ||||||
|  |     { | ||||||
|  |       let matchers = mode[ 1 ]; | ||||||
|  |        | ||||||
|  |       for ( let matcher of matchers ) | ||||||
|  |       { | ||||||
|  |         output.push( matcher ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return output; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addMatcher( matcher:LexerMatcher ) | ||||||
|  |   { | ||||||
|  |     let list:LexerMatcher[] = this._modes.get( matcher.mode ); | ||||||
|  |      | ||||||
|  |     if ( ! list ) | ||||||
|  |     { | ||||||
|  |       list = []; | ||||||
|  |       this._modes.set( matcher.mode, list ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     list.push( matcher ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   resolveMatches( source:string, events:LexerEvent[] ) | ||||||
|  |   { | ||||||
|  |     events.forEach( e => e.getMatch( source ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addAllMatchers( ...matchers:LexerMatcher[] ) | ||||||
|  |   { | ||||||
|  |     for ( let m of matchers ) | ||||||
|  |     { | ||||||
|  |       this.addMatcher( m ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public lex( source:string, callback:( e:LexerEvent) => void, offset:number = 0, mode:string = "" ) | ||||||
|  |   { | ||||||
|  |      | ||||||
|  |     let lexerEvent = new LexerEvent( "", 0, -2 ); | ||||||
|  |     let lastMatcher = null; | ||||||
|  | 
 | ||||||
|  |     while ( offset < source.length ) | ||||||
|  |     { | ||||||
|  |       if ( ! this._modes.has( mode ) ) | ||||||
|  |       { | ||||||
|  |         let errorMessage = "@Lexer-Error. Mode not found: '" + mode + "'"; | ||||||
|  |         console.log( errorMessage, "@", offset ); | ||||||
|  |         lexerEvent.set( errorMessage, offset, LexerEvent.LENGTH_ERROR_ID ); | ||||||
|  | 
 | ||||||
|  |         callback( lexerEvent ); | ||||||
|  | 
 | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let matchers = this._modes.get( mode ); | ||||||
|  |       let foundSomething = false; | ||||||
|  | 
 | ||||||
|  |       for ( let i = 0; i < matchers.length; i++ ) | ||||||
|  |       { | ||||||
|  |         let matcher = matchers[ i ]; | ||||||
|  |         let matchLength = matcher.matchLength( source, offset ); | ||||||
|  | 
 | ||||||
|  |         if ( matchLength > 0 ) | ||||||
|  |         { | ||||||
|  |           lexerEvent.set( matcher.type, offset, matchLength ); | ||||||
|  |           callback( lexerEvent ); | ||||||
|  | 
 | ||||||
|  |           foundSomething = true; | ||||||
|  | 
 | ||||||
|  |           i = matchers.length; | ||||||
|  |            | ||||||
|  |           if ( matcher.nextMode ) | ||||||
|  |           { | ||||||
|  |             mode = matcher.nextMode; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           offset += matchLength; | ||||||
|  |            | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ( ! foundSomething ) | ||||||
|  |       { | ||||||
|  |         let errorMessage = "@Lexer-Error. No match: '" + mode + "'"; | ||||||
|  |         console.log( errorMessage, "@", offset ); | ||||||
|  |         lexerEvent.set( errorMessage, offset, LexerEvent.LENGTH_ERROR_ID ); | ||||||
|  | 
 | ||||||
|  |         callback( lexerEvent ); | ||||||
|  | 
 | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     lexerEvent.set( mode, offset, LexerEvent.LENGTH_DONE_ID ); | ||||||
|  |     callback( lexerEvent ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   lexTokens( source:string, offset:number = 0, mode:string = Lexer.defaultMode ) | ||||||
|  |   { | ||||||
|  |     let events = this.lexToList( source, offset, mode ); | ||||||
|  | 
 | ||||||
|  |     this.resolveMatches( source, events ); | ||||||
|  | 
 | ||||||
|  |     return events; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   lexToList( source:string, offset:number = 0, mode:string = Lexer.defaultMode ) | ||||||
|  |   { | ||||||
|  |     var list:LexerEvent[] = []; | ||||||
|  | 
 | ||||||
|  |     this.lex( source,  | ||||||
|  |       ( token:LexerEvent )=> | ||||||
|  |       { | ||||||
|  |         list.push( token.clone() );           | ||||||
|  |       }, | ||||||
|  |       offset, mode  | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return list; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   compress( tokens:LexerEvent[], compressTypes:Set<string> ) | ||||||
|  |   { | ||||||
|  |     let lastToken:LexerEvent = null; | ||||||
|  |     let compressedTokens:LexerEvent[] = []; | ||||||
|  |      | ||||||
|  |     tokens.forEach( | ||||||
|  |       t =>  | ||||||
|  |       { | ||||||
|  |         if ( lastToken && t.type === lastToken.type && compressTypes.has( t.type ) ) | ||||||
|  |         { | ||||||
|  |           lastToken.extendLength( t.length ); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |           lastToken = t.clone(); | ||||||
|  |           compressedTokens.push( lastToken ); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return compressedTokens; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,80 @@ | ||||||
|  | export class LexerEvent | ||||||
|  | { | ||||||
|  |   static readonly LENGTH_ERROR_ID = -1; | ||||||
|  |   static readonly LENGTH_DONE_ID = -2; | ||||||
|  | 
 | ||||||
|  |   private _type:string; | ||||||
|  |   get type(){ return this._type; } | ||||||
|  |   private _offset:number; | ||||||
|  |   get offset(){ return this._offset; } | ||||||
|  |   private _length:number; | ||||||
|  |   get length(){ return this._length; } | ||||||
|  |   private _match:string = null; | ||||||
|  |   get match(){ return this._match;} | ||||||
|  | 
 | ||||||
|  |   get exclusiveEnd() | ||||||
|  |   { | ||||||
|  |     return this.offset + this.length; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get lastSourceIndex() | ||||||
|  |   { | ||||||
|  |     return this.exclusiveEnd - 1; | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   extendLength( num:number ) | ||||||
|  |   { | ||||||
|  |     this._length += num; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   constructor( type:string, offset:number, length:number ) | ||||||
|  |   { | ||||||
|  |     this.set( type, offset, length );   | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set( type:string, offset:number, length:number ) | ||||||
|  |   { | ||||||
|  |     this._type = type; | ||||||
|  |     this._offset = offset; | ||||||
|  |     this._length = length;       | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   isType( type:string ) | ||||||
|  |   { | ||||||
|  |     return this._type == type; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get isError(){ return this._length === LexerEvent.LENGTH_ERROR_ID; } | ||||||
|  |   get isDone(){ return this._length === LexerEvent.LENGTH_DONE_ID; } | ||||||
|  |   get isDoneOrError(){ return this.isDone || this.isError } | ||||||
|  | 
 | ||||||
|  |   get end() { return this._offset + this._length; } | ||||||
|  | 
 | ||||||
|  |   toString() | ||||||
|  |   { | ||||||
|  |     return "Token{ '" + this._type + "' (" + this._offset + "-" + this.end + ") }";  | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   clone() | ||||||
|  |   { | ||||||
|  |     return new LexerEvent( this._type, this._offset, this._length ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  |   getMatch( source:string ) | ||||||
|  |   { | ||||||
|  |     if ( this._match ) | ||||||
|  |     { | ||||||
|  |       return this._match; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     this.setMatch( source.substring( this._offset, this.end ) ); | ||||||
|  |     return this._match; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setMatch( source:string ) | ||||||
|  |   { | ||||||
|  |     this._match = source; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,94 @@ | ||||||
|  | import { BooleanExpression } from "../../expressions/BooleanExpression"; | ||||||
|  | import { StringValueMatcher, LitaralMatcher, RegExpMatcher } from "../../expressions/StringMatcher"; | ||||||
|  | import { Arrays } from "../../tools/Arrays"; | ||||||
|  | import { LexerEvent } from "./LexerEvent"; | ||||||
|  | import { LexerSequence } from "./LexerSequence"; | ||||||
|  | import { LexerType } from "./LexerType"; | ||||||
|  | 
 | ||||||
|  | export class LiteralLexerEventTypeMatcher extends LitaralMatcher<LexerEvent,string> | ||||||
|  | { | ||||||
|  |   getStringValue( l:LexerEvent){return l.type;} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class RegExpLexerEventTypeMatcher extends RegExpMatcher<LexerEvent> | ||||||
|  | { | ||||||
|  |   getStringValue( l:LexerEvent){return l.type;} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class LiteralLexerEventValueMatcher extends LitaralMatcher<LexerEvent,string> | ||||||
|  | { | ||||||
|  |   getStringValue( l:LexerEvent){return l.match;} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class RegExpLexerEventValueMatcher extends RegExpMatcher<LexerEvent> | ||||||
|  | { | ||||||
|  |   getStringValue( l:LexerEvent){return l.match;} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class LexerEventMatcherTools | ||||||
|  | { | ||||||
|  |   static createTypeAndValueMatcher<Type extends LexerType>( type:Type, value:string ) | ||||||
|  |   { | ||||||
|  |     let typeMatcher  = new LiteralLexerEventTypeMatcher( type ); | ||||||
|  |     let valueMatcher = new LiteralLexerEventValueMatcher( value );     | ||||||
|  | 
 | ||||||
|  |     return typeMatcher.AND( valueMatcher ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static convertToExpression<Type extends LexerType>( value:BooleanExpression<LexerEvent>|Type[]|string ):BooleanExpression<LexerEvent> | ||||||
|  |   { | ||||||
|  |     if ( typeof value === "string" ) | ||||||
|  |     { | ||||||
|  |       return new LiteralLexerEventValueMatcher( value ) | ||||||
|  |     } | ||||||
|  |     else if ( Array.isArray( value ) ) | ||||||
|  |     { | ||||||
|  |       let types = value as LexerType[]; | ||||||
|  |       let outputMatcher:BooleanExpression<LexerEvent> = null; | ||||||
|  | 
 | ||||||
|  |       for ( let i = 0; i < types.length; i++ ) | ||||||
|  |       { | ||||||
|  |         let matcher = new LiteralLexerEventTypeMatcher( types[ i ] ); | ||||||
|  |          | ||||||
|  |         if ( outputMatcher ) | ||||||
|  |         { | ||||||
|  |           outputMatcher = outputMatcher.OR( matcher ); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |           outputMatcher = matcher; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return outputMatcher; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return value as BooleanExpression<LexerEvent>; | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   static createMatchSequence<Type extends LexerType>( matcherDefinition:(BooleanExpression<LexerEvent>|Type[]|string)[],  | ||||||
|  |     ignoreDefinition:(BooleanExpression<LexerEvent>|Type[]|string)[] ) | ||||||
|  |   { | ||||||
|  |     let matchers = matcherDefinition.map( md => LexerEventMatcherTools.convertToExpression<Type>( md ) ); | ||||||
|  |     let ignores  = ignoreDefinition.map( id => LexerEventMatcherTools.convertToExpression<Type>( id ) ); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     let ignore = null; | ||||||
|  |      | ||||||
|  |     if ( ignores.length === 1 ) | ||||||
|  |     { | ||||||
|  |       ignore = ignores[ 0 ]; | ||||||
|  |     } | ||||||
|  |     else if ( ignores.length > 1 ) | ||||||
|  |     { | ||||||
|  |       ignore = ignores[ 0 ].OR_ANY( Arrays.copyRangeFromStart( ignores, 1 ) ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let sequence = new LexerSequence(); | ||||||
|  |     sequence.sequence = matchers; | ||||||
|  |     sequence.ignore = ignore; | ||||||
|  | 
 | ||||||
|  |     return sequence;     | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | 
 | ||||||
|  | import { RegExpUtility } from "../RegExpUtitlity"; | ||||||
|  | import { Lexer } from "./Lexer"; | ||||||
|  | 
 | ||||||
|  | export class LexerMatcher | ||||||
|  | { | ||||||
|  |   _mode:string; | ||||||
|  |   _type:string; | ||||||
|  |   _nextMode:string; | ||||||
|  |   _matcher:RegExp; | ||||||
|  |   compressTokens:boolean = false; | ||||||
|  | 
 | ||||||
|  |   get mode(){ return this._mode; } | ||||||
|  |   get type(){ return this._type; } | ||||||
|  |   get nextMode(){ return this._nextMode ? this._nextMode : this._mode; } | ||||||
|  |   get matcher() {  return this._matcher; } | ||||||
|  | 
 | ||||||
|  |   constructor( type:string, matcher:RegExp, mode:string = Lexer.defaultMode, nextMode:string=null ) | ||||||
|  |   { | ||||||
|  |     this._type = type; | ||||||
|  |     this._matcher = RegExpUtility.makeSticky( matcher ); | ||||||
|  |     this._mode = mode; | ||||||
|  |     this._nextMode = nextMode; | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   getMatch( value:string, index:number = 0, alternative:string = null) | ||||||
|  |   { | ||||||
|  |     let result = this._matcher.exec( value ); | ||||||
|  |      | ||||||
|  |     if ( ! result || result.length <= index ) | ||||||
|  |     { | ||||||
|  |       return alternative; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return result[ index ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   matchLength( source:string, offset:number ) | ||||||
|  |   { | ||||||
|  |     this._matcher.lastIndex = offset; | ||||||
|  |     var result = this._matcher.exec( source ); | ||||||
|  | 
 | ||||||
|  |     if ( result && result.index != offset ) | ||||||
|  |     { | ||||||
|  |       var message = "Illegal match index! Not a sticky matcher: " + this._matcher + "."; | ||||||
|  |       message += "Offset: " + offset + " Matched Index: " + result.index ; | ||||||
|  |       throw message; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( result ) | ||||||
|  |     { | ||||||
|  |       return result[ 0 ].length; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   getMatchResult( source:string, offset:number ) | ||||||
|  |   { | ||||||
|  |     this._matcher.lastIndex = offset; | ||||||
|  |     var result = this._matcher.exec( source ); | ||||||
|  | 
 | ||||||
|  |     if ( result && result.index != offset ) | ||||||
|  |     { | ||||||
|  |       var message = "Illegal match index! Not a sticky matcher: " + this._matcher + "."; | ||||||
|  |       message += "Offset: " + offset + " Matched Index: " + result.index ; | ||||||
|  |       throw message; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   isMatching( source:string ) | ||||||
|  |   {     | ||||||
|  |     let matchLength = this.matchLength( source, 0 ); | ||||||
|  | 
 | ||||||
|  |     if ( matchLength === source.length ) | ||||||
|  |     { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,95 @@ | ||||||
|  | import { LexerTypes } from "./LexerType"; | ||||||
|  | import { LexerMatcher } from "./LexerMatcher"; | ||||||
|  | 
 | ||||||
|  | export class LexerMatcherLibrary | ||||||
|  | { | ||||||
|  |   static readonly CWORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CWORD, /[a-zA-Z_]\w*/ ); | ||||||
|  |    | ||||||
|  |   static readonly CFUNCTION_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CFUNCTION, /[a-zA-Z_]\w*(?=\s*\()/ ); | ||||||
|  | 
 | ||||||
|  | //  static readonly CLASSNAME_MATCHER = 
 | ||||||
|  | //    new LexerMatcher( LexerTypes.CLASSNAME, /(?<=(?:(?:class|interface|struct)\s+))[a-zA-Z_]\w*/ );
 | ||||||
|  | 
 | ||||||
|  |   static readonly JSWORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.JSWORD, /[a-zA-Z_\$]\w*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly PHPWORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.PHPWORD, /\$?[a-zA-Z_]\w*/i ); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static readonly CSS_CLASS_SELECTOR_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CSS_CLASS_SELECTOR, /\.[a-zA-Z_](\w|\-)*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly CSS_ID_SELECTOR_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CSS_ID_SELECTOR, /\#[a-zA-Z_](\w|\-)*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly CSS_WORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CSS_WORD, /[a-zA-Z_](\w|\-)*/ ); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static readonly HTML_CUSTOM_ELEMENT_MATCHER = | ||||||
|  |     new LexerMatcher( LexerTypes.HTML_CUSTOM_ELEMENT, /[a-zA-Z]\w*\-(\w|\-)+/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly DOUBLE_QUOTED_STRING_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.DOUBLE_QUOTED_STRING, /"(?:[^"\\]|\\.)*"/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly SINGLE_QUOTED_STRING_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.SINGLE_QUOTED_STRING, /'(?:[^'\\]|\\.)*'/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly NUMBER_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.NUMBER, /(?=\.\d|\d)(?:\d+)?(?:\.?\d*)(?:[eE][+-]?\d+)?/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly WHITESPACE_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.WHITESPACE, /\s+/ ); | ||||||
|  |    | ||||||
|  |   static readonly BREAK_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.BREAK, /(\r\n|\r|\n)/ ); | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |   static readonly NULL_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.NULL, /null/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly BOOL_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.BOOL, /true|false/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly LOGIC_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.LOGIC, /if|else|switch|do|while|for|break|continue|return/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly OPERATOR_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.OPERATOR, /(?:\=\=)|(?:\+\+)|(?:\-\-)|\+|\-|\*|\/|\^|\||\~|\&|\%|\<|\>|\=|\!|\.|\:|\,|\;/ );   | ||||||
|  | 
 | ||||||
|  |   static readonly BRACKET_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.BRACKET, /\(|\)|\[|\]|\{|\}/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly BLOCKSTART_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.BLOCKSTART, /\{/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly BLOCKEND_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.BLOCKEND, /\}/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly CLASS_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.CLASS, /\bclass\b/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly ACCESS_MODIFIER_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.ACCESS_MODIFIER, /\b(?:public|protected|private)\b/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly SINGLE_LINE_COMMENT_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.SINGLE_LINE_COMMENT, /\/\/.*/ ); | ||||||
|  | 
 | ||||||
|  |    static readonly C_INSTRUCTION_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.C_INSTRUCTION, /\#.*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly MULTI_LINE_COMMENT_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.MULTI_LINE_COMMENT, /\/\*(.|(\r\n|\r|\n))*?\*\// ); | ||||||
|  | 
 | ||||||
|  |   static readonly ANY_SYMBOL_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.ANY_SYMBOL, /./ ); | ||||||
|  | 
 | ||||||
|  |   static readonly HASH_TAG =  | ||||||
|  |    new LexerMatcher( LexerTypes.HASH_TAG, /\#(\w|-|\d)+/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly URL =  | ||||||
|  |    new LexerMatcher( LexerTypes.URL, /https?\:\/\/(\w|\.|\-|\?|\=|\+|\/)+/ ); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,91 @@ | ||||||
|  | import { LexerTypes } from "./LexerType"; | ||||||
|  | import { LexerMatcher } from "./LexerMatcher"; | ||||||
|  | 
 | ||||||
|  | export class LexerMatcherLibraryTest | ||||||
|  | { | ||||||
|  |   static readonly CWORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CWORD, /[a-zA-Z_]\w*/ ); | ||||||
|  |    | ||||||
|  |   static readonly CFUNCTION_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CFUNCTION, /[a-zA-Z_]\w*(?=\s*\()/ ); | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  |   // static readonly CLASSNAME_MATCHER = 
 | ||||||
|  |   //   new LexerMatcher( LexerTypes.CLASSNAME, /(?<=(?:(?:class|interface|struct)\s+))[a-zA-Z_]\w*/ );
 | ||||||
|  | 
 | ||||||
|  |   static readonly JSWORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.JSWORD, /[a-zA-Z_\$]\w*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly PHPWORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.PHPWORD, /\$?[a-zA-Z_]\w*/ ); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static readonly CSS_CLASS_SELECTOR_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CSS_CLASS_SELECTOR, /\.[a-zA-Z_](\w|\-)*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly CSS_ID_SELECTOR_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CSS_ID_SELECTOR, /\#[a-zA-Z_](\w|\-)*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly CSS_WORD_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.CSS_WORD, /[a-zA-Z_](\w|\-)*/ ); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static readonly HTML_CUSTOM_ELEMENT_MATCHER = | ||||||
|  |     new LexerMatcher( LexerTypes.HTML_CUSTOM_ELEMENT, /[a-zA-Z]\w*\-(\w|\-)+/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly DOUBLE_QUOTED_STRING_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.DOUBLE_QUOTED_STRING, /"(?:[^"\\]|\\.)*"/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly SINGLE_QUOTED_STRING_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.SINGLE_QUOTED_STRING, /'(?:[^'\\]|\\.)*'/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly NUMBER_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.NUMBER, /(?=\.\d|\d)(?:\d+)?(?:\.?\d*)(?:[eE][+-]?\d+)?/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly WHITESPACE_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.WHITESPACE, /\s+/ ); | ||||||
|  |    | ||||||
|  |   static readonly BREAK_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.BREAK, /\n/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly NULL_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.NULL, /null/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly BOOL_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.BOOL, /true|false/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly LOGIC_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.LOGIC, /if|else|switch|do|while|for|break|continue|return/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly OPERATOR_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.OPERATOR, /\+|\-|\*|\/|\^|\||\~|\&|\%|\<|\>|\=|\!|\.|\:|\,|\;/ ); | ||||||
|  |    | ||||||
|  |   static readonly BRACKET_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.BRACKET, /\(|\)|\[|\]|\{|\}/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly BLOCKSTART_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.BLOCKSTART, /\{/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly BLOCKEND_MATCHER =  | ||||||
|  |     new LexerMatcher( LexerTypes.BLOCKEND, /\}/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly CLASS_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.CLASS, /class/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly ACCESS_MODIFIER_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.ACCESS_MODIFIER, /public|protected|private/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly SINGLE_LINE_COMMENT_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.SINGLE_LINE_COMMENT, /\/\/.*/ ); | ||||||
|  | 
 | ||||||
|  |    static readonly C_INSTRUCTION_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.C_INSTRUCTION, /\#.*/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly MULTI_LINE_COMMENT_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.MULTI_LINE_COMMENT, /\/\*(.|(\r\n|\r|\n))*?\*\// ); | ||||||
|  | 
 | ||||||
|  |   static readonly ANY_SYMBOL_MATCHER =  | ||||||
|  |    new LexerMatcher( LexerTypes.ANY_SYMBOL, /./ ); | ||||||
|  | 
 | ||||||
|  |     | ||||||
|  | } | ||||||
|  | @ -0,0 +1,261 @@ | ||||||
|  | import { BooleanExpression } from "../../expressions/BooleanExpression"; | ||||||
|  | import { LexerEvent } from "./LexerEvent"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class LexerQuery | ||||||
|  | { | ||||||
|  |   source:string; | ||||||
|  |   tokens:LexerEvent[]; | ||||||
|  | 
 | ||||||
|  |   _index:Map<LexerEvent,number> = new Map<LexerEvent,number>(); | ||||||
|  | 
 | ||||||
|  |   createTokenIndex() | ||||||
|  |   { | ||||||
|  |     for ( let i = 0; i < this.tokens.length; i++ ) | ||||||
|  |     { | ||||||
|  |       this.tokens[ i ].getMatch( this.source ); | ||||||
|  |       this._index.set( this.tokens[ i ], i ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createReplacedOutput( replacements:Map<LexerEvent,string> ) | ||||||
|  |   { | ||||||
|  |     let output = []; | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < this.tokens.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let token = this.tokens[ i ]; | ||||||
|  |        | ||||||
|  |       if ( token.isDone || token.isError ) | ||||||
|  |       { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ( replacements.has( token ) ) | ||||||
|  |       { | ||||||
|  |         output.push( replacements.get( token ) );  | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         output.push( token.match ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return output.join( "" ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   linePosition( offset:number ) | ||||||
|  |   { | ||||||
|  |     let lineIndex = 1; | ||||||
|  |     let lastLineBreak = 0; | ||||||
|  |      | ||||||
|  |     for ( let i = 0; i < offset; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( this.source[ i ] === "\n" || this.source === "\r" ) | ||||||
|  |       { | ||||||
|  |         if ( this.source[ i ] === "\n" && this.source[ i + 1 ] === "\r" ) | ||||||
|  |         { | ||||||
|  |           i++ | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         lineIndex++; | ||||||
|  |         lastLineBreak = i; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let characterIndex = offset - lastLineBreak; | ||||||
|  | 
 | ||||||
|  |     return { line: lineIndex, characterIndex: characterIndex };  | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   lineInfo( l:LexerEvent, breaksBefore:number=2, breaksAfter:number=2 ) | ||||||
|  |   { | ||||||
|  |     let startIndex = l.offset - 1; | ||||||
|  |     let endIndex   = l.offset + 1; | ||||||
|  | 
 | ||||||
|  |     let numLineBreakBeforeFound = 0; | ||||||
|  | 
 | ||||||
|  |     for ( let i = startIndex; i >= 0 && numLineBreakBeforeFound < breaksBefore; i-- ) | ||||||
|  |     { | ||||||
|  |       if ( this.source[ i ] === "\n" || this.source[ i ] === "\r" ) | ||||||
|  |       { | ||||||
|  |         if ( this.source[ i ] === "\n" && i > 0 && this.source[ i - 1 ] === "\r" )  | ||||||
|  |         { | ||||||
|  |           i--; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         numLineBreakBeforeFound ++; | ||||||
|  |          | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         startIndex = i; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     let numLineBreakAfterFound = 0; | ||||||
|  | 
 | ||||||
|  |     for ( let i = startIndex; i >= 0 && numLineBreakAfterFound < breaksAfter; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( this.source[ i ] === "\n" || this.source[ i ] === "\r" ) | ||||||
|  |       { | ||||||
|  |         if ( this.source[ i ] === "\r" && i < ( this.source.length - 1 ) && this.source[ i + 1 ] === "\n" )  | ||||||
|  |         { | ||||||
|  |           i++; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         numLineBreakAfterFound ++; | ||||||
|  |          | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |         endIndex = i; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     let info = this.source.substring( startIndex, endIndex ); | ||||||
|  | 
 | ||||||
|  |     return info; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   index( l:LexerEvent ) | ||||||
|  |   { | ||||||
|  |     return this._index.get( l ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   all( matcher:BooleanExpression<LexerEvent> ):LexerEvent[] | ||||||
|  |   { | ||||||
|  |     let output:LexerEvent[] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < this.tokens.length; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( matcher.evaluate( this.tokens[ i ] ) ) | ||||||
|  |       { | ||||||
|  |         output.push( this.tokens[ i ] ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return output; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forAllMatches( index:number, end:number, callback:(s:string)=>void) | ||||||
|  |   { | ||||||
|  |     for ( let i = index; i <= end; i++ ) | ||||||
|  |     { | ||||||
|  |       callback( this.tokens[ i ].match ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   forAllWithType( type:string, callback:( t:LexerEvent )=>void ) | ||||||
|  |   { | ||||||
|  |     for ( let i =0; i < this.tokens.length; i++ ) | ||||||
|  |     { | ||||||
|  |       if ( ! this.tokens[ i ].isType( type ) ) | ||||||
|  |       { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       callback( this.tokens[ i ] ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   searchBlockIndices( index:number, start:BooleanExpression<LexerEvent>, end:BooleanExpression<LexerEvent>) | ||||||
|  |   { | ||||||
|  |     let startIndex = -1; | ||||||
|  | 
 | ||||||
|  |     let numOpen = 0; | ||||||
|  |     let blocksFound = false; | ||||||
|  | 
 | ||||||
|  |     for ( let i = index; i < this.tokens.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let token = this.tokens[ i ]; | ||||||
|  |        | ||||||
|  |       if ( start.evaluate( token ) ) | ||||||
|  |       { | ||||||
|  |         if ( ! blocksFound ) | ||||||
|  |         { | ||||||
|  |           startIndex = i; | ||||||
|  |           blocksFound = true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         numOpen ++; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ( end.evaluate( token ) ) | ||||||
|  |       { | ||||||
|  |         numOpen --; | ||||||
|  |          | ||||||
|  |         if ( numOpen < 0 ) | ||||||
|  |         { | ||||||
|  |           return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ( numOpen === 0 ) | ||||||
|  |         { | ||||||
|  |           return { startIndex, endIndex: i, length: ( 1 + i - startIndex ) }; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   searchIndex( index:number, forward:boolean, matcher:BooleanExpression<LexerEvent>, ignore?:BooleanExpression<LexerEvent>, maxItems:number=100000  ) | ||||||
|  |   {  | ||||||
|  |     let itemCount = 0; | ||||||
|  | 
 | ||||||
|  |     if ( forward ) | ||||||
|  |     { | ||||||
|  |       let next = index + 1; | ||||||
|  | 
 | ||||||
|  |       for ( let i = next; i < this.tokens.length && itemCount < maxItems; i++, itemCount++ ) | ||||||
|  |       { | ||||||
|  |         let token = this.tokens[ i ]; | ||||||
|  |          | ||||||
|  |         if ( ignore && ignore.evaluate( token ) ) | ||||||
|  |         { | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ( matcher.evaluate( token ) ) | ||||||
|  |         { | ||||||
|  |           return i; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         itemCount++; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       let previous = index - 1; | ||||||
|  |        | ||||||
|  |       for ( let i = previous; i >=0 && itemCount < maxItems; i--, itemCount++ ) | ||||||
|  |       { | ||||||
|  |         let token = this.tokens[ i ]; | ||||||
|  |          | ||||||
|  |         if ( ignore && ignore.evaluate( token ) ) | ||||||
|  |         { | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ( matcher.evaluate( token ) ) | ||||||
|  |         { | ||||||
|  |           return i; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         itemCount++; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   searchItem( index:number, forward:boolean, matcher:BooleanExpression<LexerEvent>, ignore?:BooleanExpression<LexerEvent>, maxItems:number = 100000  ) | ||||||
|  |   { | ||||||
|  |     let nextIndex = this.searchIndex( index, forward, matcher, ignore ); | ||||||
|  | 
 | ||||||
|  |     return nextIndex === -1 ? null : this.tokens[ nextIndex ]; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,88 @@ | ||||||
|  | import { LexerEvent } from "./LexerEvent"; | ||||||
|  | import { BooleanExpression } from "../../expressions/BooleanExpression"; | ||||||
|  | import { LexerQuery } from "./LexerQuery"; | ||||||
|  | 
 | ||||||
|  | export class LexerSequence | ||||||
|  | { | ||||||
|  |   sequence:BooleanExpression<LexerEvent>[] = []; | ||||||
|  |   ignore:BooleanExpression<LexerEvent> = null; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   get( query:LexerQuery, tokenOffset:number, sequenceOffset:number ):number[] | ||||||
|  |   { | ||||||
|  |     let sequenceIndices:number[] = []; | ||||||
|  |      | ||||||
|  |     for ( let s of this.sequence ){ sequenceIndices.push( -1 ); } | ||||||
|  |      | ||||||
|  |     if ( ! this.sequence[ sequenceOffset ].evaluate( query.tokens[ tokenOffset ] ) ) | ||||||
|  |     { | ||||||
|  |       //console.log( "Offset is not matching" );
 | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     sequenceIndices[ sequenceOffset ] = tokenOffset; | ||||||
|  | 
 | ||||||
|  |     let previousSearchIndex = tokenOffset; | ||||||
|  | 
 | ||||||
|  |     for ( let i = sequenceOffset - 1; i >= 0; i-- ) | ||||||
|  |     { | ||||||
|  |       let matcher = this.sequence[ i ]; | ||||||
|  |       let index = query.searchIndex( previousSearchIndex, false, matcher, this.ignore, 2 );       | ||||||
|  | 
 | ||||||
|  |       if ( index === -1 ) | ||||||
|  |       { | ||||||
|  |         let startTokenIndex = Math.max( 0, previousSearchIndex - 5 ); | ||||||
|  |         let previousTokens = query.tokens.slice( startTokenIndex, previousSearchIndex ); | ||||||
|  |         let infos = previousTokens.map( | ||||||
|  |           ( le )=> | ||||||
|  |           { | ||||||
|  |             let ignoreResult = this.ignore.evaluate( le ); | ||||||
|  |             let matchResult  = matcher.evaluate( le ); | ||||||
|  |             return `'${le.match}' ${query.index(le)} ${le.type} ignore:${ignoreResult} match:${matchResult}`; | ||||||
|  |           } | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         //console.log( `Sequence not matching at #${i}(${previousSearchIndex})`, matcher, "\n", `[${infos}]` );
 | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       sequenceIndices[ i ] = index; | ||||||
|  |       previousSearchIndex = index; | ||||||
|  |       let token = query.tokens[ index ]; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let nextSearchIndex = tokenOffset; | ||||||
|  | 
 | ||||||
|  |     for ( let i = sequenceOffset + 1; i < this.sequence.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let matcher = this.sequence[ i ]; | ||||||
|  |       let index = query.searchIndex( nextSearchIndex, true, matcher, this.ignore, 2 );       | ||||||
|  | 
 | ||||||
|  |       if ( index === -1 ) | ||||||
|  |       { | ||||||
|  |         let endTokenIndex = Math.min( query.tokens.length, nextSearchIndex + 5 ); | ||||||
|  |         let nextTokens = query.tokens.slice( nextSearchIndex, endTokenIndex ); | ||||||
|  | 
 | ||||||
|  |         let infos = nextTokens.map( | ||||||
|  |           ( le )=> | ||||||
|  |           { | ||||||
|  |             let ignoreResult = this.ignore.evaluate( le ); | ||||||
|  |             let matchResult  = matcher.evaluate( le ); | ||||||
|  |             return `'${le.match}' ${query.index(le)} ${le.type} ignore:${ignoreResult} match:${matchResult}`; | ||||||
|  |           } | ||||||
|  |         ); | ||||||
|  |    | ||||||
|  |         //console.log( `Sequence not at #${i}(${nextSearchIndex})`, matcher, "\n", `[${infos}]` );
 | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       sequenceIndices[ i ] = index; | ||||||
|  |       nextSearchIndex = index; | ||||||
|  |       let token = query.tokens[ index ]; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return sequenceIndices; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,143 @@ | ||||||
|  | import { Lexer } from "./Lexer"; | ||||||
|  | 
 | ||||||
|  | export type CWORD = "CWORD"; | ||||||
|  | export type JSWORD = "JSWORD"; | ||||||
|  | export type PHPWORD = "PHPWORD"; | ||||||
|  | export type CFUNCTION = "CFUNCTION"; | ||||||
|  | export type CLASSNAME = "CLASSNAME"; | ||||||
|  | 
 | ||||||
|  | export type CSS_ID_SELECTOR = "CSS_ID_SELECTOR"; | ||||||
|  | export type CSS_CLASS_SELECTOR = "CSS_CLASS_SELECTOR"; | ||||||
|  | export type CSS_WORD = "CSS_WORD"; | ||||||
|  | 
 | ||||||
|  | export type HTML_CUSTOM_ELEMENT = "HTML_CUSTOM_ELEMENT"; | ||||||
|  | 
 | ||||||
|  | export type DOUBLE_QUOTED_STRING = "DOUBLE_QUOTED_STRING"; | ||||||
|  | export type SINGLE_QUOTED_STRING = "SINGLE_QUOTED_STRING"; | ||||||
|  | 
 | ||||||
|  | export type NUMBER = "NUMBER"; | ||||||
|  | export type BOOL = "BOOL"; | ||||||
|  | 
 | ||||||
|  | export type WHITESPACE = "WHITESPACE"; | ||||||
|  | 
 | ||||||
|  | export type BREAK = "BREAK"; | ||||||
|  | export type NULL = "NULL"; | ||||||
|  | 
 | ||||||
|  | export type LOGIC = "LOGIC"; | ||||||
|  | export type OPERATOR = "OPERATOR"; | ||||||
|  | export type BRACKET = "BRACKET"; | ||||||
|  | 
 | ||||||
|  | export type BLOCKSTART = "BLOCKSTART"; | ||||||
|  | export type BLOCKEND = "BLOCKEND"; | ||||||
|  | 
 | ||||||
|  | export type ANY_SYMBOL = "ANY_SYMBOL"; | ||||||
|  | 
 | ||||||
|  | export type HASH_TAG = "HASH_TAG"; | ||||||
|  | export type URL = "URL"; | ||||||
|  | 
 | ||||||
|  | export type CLASS = "CLASS"; | ||||||
|  | 
 | ||||||
|  | export type ACCESS_MODIFIER = "ACCESS_MODIFIER"; | ||||||
|  | 
 | ||||||
|  | export type SINGLE_LINE_COMMENT = "SINGLE_LINE_COMMENT"; | ||||||
|  | export type MULTI_LINE_COMMENT = "MULTI_LINE_COMMENT"; | ||||||
|  | 
 | ||||||
|  | export type C_INSTRUCTION = "C_INSTRUCTION"; | ||||||
|  | 
 | ||||||
|  | export type LexerType =  | ||||||
|  |   CWORD |  | ||||||
|  |   JSWORD | | ||||||
|  |   PHPWORD | | ||||||
|  | 
 | ||||||
|  |   CFUNCTION | | ||||||
|  |   CLASSNAME | | ||||||
|  | 
 | ||||||
|  |   CSS_ID_SELECTOR | | ||||||
|  |   CSS_CLASS_SELECTOR | | ||||||
|  |   CSS_WORD | | ||||||
|  | 
 | ||||||
|  |   HTML_CUSTOM_ELEMENT | | ||||||
|  |    | ||||||
|  |   DOUBLE_QUOTED_STRING |  | ||||||
|  |   SINGLE_QUOTED_STRING |  | ||||||
|  |   NUMBER | | ||||||
|  |   BOOL | | ||||||
|  |   WHITESPACE |  | ||||||
|  |   BREAK |  | ||||||
|  |   NULL | | ||||||
|  | 
 | ||||||
|  |   LOGIC | | ||||||
|  |   OPERATOR |  | ||||||
|  |   BRACKET | | ||||||
|  | 
 | ||||||
|  |   BLOCKSTART |  | ||||||
|  |   BLOCKEND | | ||||||
|  |    | ||||||
|  |   ANY_SYMBOL |  | ||||||
|  |    | ||||||
|  |   CLASS | | ||||||
|  | 
 | ||||||
|  |   ACCESS_MODIFIER |  | ||||||
|  |   SINGLE_LINE_COMMENT | | ||||||
|  |   MULTI_LINE_COMMENT | | ||||||
|  | 
 | ||||||
|  |   C_INSTRUCTION | ||||||
|  | ; | ||||||
|  |   | ||||||
|  | export class LexerTypes | ||||||
|  | { | ||||||
|  |   static readonly CWORD:CWORD = "CWORD";  | ||||||
|  |   static readonly JSWORD:JSWORD = "JSWORD"; | ||||||
|  |   static readonly PHPWORD:PHPWORD = "PHPWORD"; | ||||||
|  | 
 | ||||||
|  |   static readonly CFUNCTION:CFUNCTION = "CFUNCTION"; | ||||||
|  |   static readonly CLASSNAME:CLASSNAME = "CLASSNAME"; | ||||||
|  | 
 | ||||||
|  |   static readonly CSS_ID_SELECTOR:CSS_ID_SELECTOR = "CSS_ID_SELECTOR"; | ||||||
|  |   static readonly CSS_CLASS_SELECTOR:CSS_CLASS_SELECTOR = "CSS_CLASS_SELECTOR"; | ||||||
|  |   static readonly CSS_WORD:CSS_WORD = "CSS_WORD"; | ||||||
|  | 
 | ||||||
|  |   static readonly HTML_CUSTOM_ELEMENT:HTML_CUSTOM_ELEMENT = "HTML_CUSTOM_ELEMENT"; | ||||||
|  | 
 | ||||||
|  |   static readonly DOUBLE_QUOTED_STRING:DOUBLE_QUOTED_STRING = "DOUBLE_QUOTED_STRING"; | ||||||
|  |   static readonly SINGLE_QUOTED_STRING:SINGLE_QUOTED_STRING = "SINGLE_QUOTED_STRING"; | ||||||
|  |   static readonly NUMBER:NUMBER = "NUMBER"; | ||||||
|  |   static readonly BOOL:BOOL = "BOOL"; | ||||||
|  |   static readonly WHITESPACE:WHITESPACE = "WHITESPACE"; | ||||||
|  |   static readonly BREAK:BREAK = "BREAK"; | ||||||
|  |   static readonly NULL:NULL = "NULL"; | ||||||
|  | 
 | ||||||
|  |   static readonly LOGIC:LOGIC = "LOGIC"; | ||||||
|  |   static readonly OPERATOR:OPERATOR = "OPERATOR"; | ||||||
|  |   static readonly BRACKET:BRACKET = "BRACKET"; | ||||||
|  | 
 | ||||||
|  |   static readonly BLOCKSTART:BLOCKSTART = "BLOCKSTART"; | ||||||
|  |   static readonly BLOCKEND:BLOCKEND = "BLOCKEND"; | ||||||
|  | 
 | ||||||
|  |   static readonly CLASS:CLASS = "CLASS"; | ||||||
|  |   static readonly C_INSTRUCTION = "C_INSTRUCTION"; | ||||||
|  | 
 | ||||||
|  |   static readonly ANY_SYMBOL:ANY_SYMBOL = "ANY_SYMBOL"; | ||||||
|  | 
 | ||||||
|  |   static readonly HASH_TAG:HASH_TAG = "HASH_TAG"; | ||||||
|  | 
 | ||||||
|  |   static readonly URL:URL = "URL"; | ||||||
|  | 
 | ||||||
|  |   static readonly ACCESS_MODIFIER = "ACCESS_MODIFIER"; | ||||||
|  | 
 | ||||||
|  |   static readonly SINGLE_LINE_COMMENT = "SINGLE_LINE_COMMENT"; | ||||||
|  |   static readonly MULTI_LINE_COMMENT = "MULTI_LINE_COMMENT"; | ||||||
|  | 
 | ||||||
|  |   static get IGNORE_TYPES():LexerType[] | ||||||
|  |   { | ||||||
|  |     let ignoreTypes:LexerType[] = [ | ||||||
|  |       LexerTypes.SINGLE_LINE_COMMENT, | ||||||
|  |       LexerTypes.DOUBLE_QUOTED_STRING, | ||||||
|  |       LexerTypes.WHITESPACE, | ||||||
|  |       LexerTypes.BREAK | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     return ignoreTypes; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | export class PHPClasses | ||||||
|  | { | ||||||
|  |   static list:string[] =  | ||||||
|  |   [ | ||||||
|  |     "ReflectionClass", | ||||||
|  |     "ReflectionNamedType" | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | @ -0,0 +1,77 @@ | ||||||
|  | 
 | ||||||
|  | import { RegExpUtility } from "../RegExpUtitlity"; | ||||||
|  | import { Lexer } from "./Lexer"; | ||||||
|  | import { LexerMatcher } from "./LexerMatcher"; | ||||||
|  | import { LexerMatcherLibrary } from "./LexerMatcherLibrary"; | ||||||
|  | import { LexerType } from "./LexerType"; | ||||||
|  | 
 | ||||||
|  | export type PHP_KEYWORD = "PHP_KEYWORD"; | ||||||
|  | export type PHP_START = "PHP_START"; | ||||||
|  | export type PHP_END = "PHP_END"; | ||||||
|  | export type PHP_CONSTANT = "PHP_CONSTANT"; | ||||||
|  | 
 | ||||||
|  | export type PHPLexerType = LexerType | PHP_KEYWORD | PHP_START | PHP_END | PHP_CONSTANT; | ||||||
|  | 
 | ||||||
|  | export class PHPLexer extends Lexer | ||||||
|  | { | ||||||
|  |   static readonly predefined =  | ||||||
|  |   [ | ||||||
|  |     "NULL","E_CORE_ERROR","Exception","PATHINFO_FILENAME","JSON_PRETTY_PRINT","__DIR__", | ||||||
|  |     "ENT_QUOTES","PREG_OFFSET_CAPTURE","PREG_SPLIT_DELIM_CAPTURE","PREG_SPLIT_NO_EMPTY", | ||||||
|  |     "PHP_INT_MAX","SEEK_CUR","SEEK_SET", | ||||||
|  | 
 | ||||||
|  |     "CURLOPT_URL","CURLOPT_RETURNTRANSFER","CURLOPT_USERAGENT","E_USER_ERROR","E_USER_NOTICE","E_USER_WARNING", | ||||||
|  | 
 | ||||||
|  |     "JSON_ERROR_NONE", "JSON_ERROR_DEPTH", "JSON_ERROR_STATE_MISMATCH", "JSON_ERROR_CTRL_CHAR",  | ||||||
|  |     "JSON_ERROR_SYNTAX", "JSON_ERROR_UTF8", "JSON_ERROR_RECURSION", "JSON_ERROR_INF_OR_NAN",  | ||||||
|  |     "JSON_ERROR_UNSUPPORTED_TYPE", "JSON_ERROR_INVALID_PROPERTY_NAME", "JSON_ERROR_UTF16" | ||||||
|  |   ] | ||||||
|  |    | ||||||
|  |   static readonly PHP_CONSTANT:PHP_CONSTANT = "PHP_CONSTANT"; | ||||||
|  |   static readonly PHP_CONSTANT_MATCHER =  | ||||||
|  |     new LexerMatcher( PHPLexer.PHP_CONSTANT, RegExpUtility.createFromList( PHPLexer.predefined ) ); | ||||||
|  | 
 | ||||||
|  |   static readonly PHP_KEYWORD:PHP_KEYWORD = "PHP_KEYWORD"; | ||||||
|  | 
 | ||||||
|  |   static readonly PHP_KEYWORD_MATCHER =  | ||||||
|  |     new LexerMatcher( PHPLexer.PHP_KEYWORD, /include_once|include|require_once|require|extends|function|const/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly PHP_START:PHP_START = "PHP_START"; | ||||||
|  |    | ||||||
|  |   static readonly PHP_START_MATCHER =  | ||||||
|  |       new LexerMatcher( PHPLexer.PHP_START, /\<\?php/ ); | ||||||
|  | 
 | ||||||
|  |   static readonly PHP_END:PHP_END = "PHP_END"; | ||||||
|  |    | ||||||
|  |   static readonly PHP_END_MATCHER =  | ||||||
|  |         new LexerMatcher( PHPLexer.PHP_END, /\?\>/ ); | ||||||
|  | 
 | ||||||
|  |   constructor() | ||||||
|  |   { | ||||||
|  |     super(); | ||||||
|  | 
 | ||||||
|  |     this.addAllMatchers(  | ||||||
|  |       LexerMatcherLibrary.SINGLE_LINE_COMMENT_MATCHER, | ||||||
|  |       LexerMatcherLibrary.MULTI_LINE_COMMENT_MATCHER, | ||||||
|  |       LexerMatcherLibrary.DOUBLE_QUOTED_STRING_MATCHER, | ||||||
|  |       LexerMatcherLibrary.SINGLE_QUOTED_STRING_MATCHER, | ||||||
|  |       PHPLexer.PHP_CONSTANT_MATCHER, | ||||||
|  |       PHPLexer.PHP_START_MATCHER, | ||||||
|  |       PHPLexer.PHP_END_MATCHER, | ||||||
|  |       LexerMatcherLibrary.NUMBER_MATCHER, | ||||||
|  |       LexerMatcherLibrary.NULL_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BOOL_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BREAK_MATCHER, | ||||||
|  |       LexerMatcherLibrary.WHITESPACE_MATCHER, | ||||||
|  |       LexerMatcherLibrary.LOGIC_MATCHER, | ||||||
|  |       LexerMatcherLibrary.BRACKET_MATCHER, | ||||||
|  |       LexerMatcherLibrary.ACCESS_MODIFIER_MATCHER, | ||||||
|  |       LexerMatcherLibrary.CLASS_MATCHER, | ||||||
|  |       PHPLexer.PHP_KEYWORD_MATCHER, | ||||||
|  |       LexerMatcherLibrary.OPERATOR_MATCHER, | ||||||
|  |       LexerMatcherLibrary.PHPWORD_MATCHER, | ||||||
|  |       LexerMatcherLibrary.ANY_SYMBOL_MATCHER | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | import { SourceInfo } from "../parsing/SourceInfo"; | ||||||
|  | import { ExpressionNode } from "./ExpressionNode"; | ||||||
|  | 
 | ||||||
|  | export class BinaryExpression<T> extends ExpressionNode<T> | ||||||
|  | { | ||||||
|  |   constructor( left:ExpressionNode<T>, right:ExpressionNode<T>, data:T, sourceInfo:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     super( [ left, right ], data, sourceInfo ); | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   get left(){ return this._children[ 0 ]; } | ||||||
|  |   get right(){ return this._children[ 1 ]; } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | import { SourceInfo } from "../parsing/SourceInfo"; | ||||||
|  | 
 | ||||||
|  | export class ExpressionNode<T> | ||||||
|  | { | ||||||
|  |   _sourceInfo:SourceInfo; | ||||||
|  |   _children:ExpressionNode<T>[]; | ||||||
|  |   _data:T; | ||||||
|  | 
 | ||||||
|  |   constructor( children:ExpressionNode<T>[], data:T, sourceInfo:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     this._children = children; | ||||||
|  |     this._data = data; | ||||||
|  |     this._sourceInfo = sourceInfo; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | import { SourceInfo } from "../parsing/SourceInfo"; | ||||||
|  | import { ExpressionNode } from "./ExpressionNode"; | ||||||
|  | 
 | ||||||
|  | export class LiteralNode<T> extends ExpressionNode<T> | ||||||
|  | { | ||||||
|  |   constructor( literal:ExpressionNode<T>, data:T, sourceInfo:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     super( [ literal ], data, sourceInfo ); | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  |   get value(){ return this._children[ 0 ] } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | export class CSharpLanguage | ||||||
|  | { | ||||||
|  |   public static readonly token ="c#"; | ||||||
|  | 
 | ||||||
|  |   public static readonly keywords:string[] =  | ||||||
|  |   [ | ||||||
|  |     "var","new","void","short","long","uint", | ||||||
|  |     "float","int","byte","bool","double","string", | ||||||
|  |     "using" | ||||||
|  |   ] | ||||||
|  |    | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | export class GodotShader | ||||||
|  | { | ||||||
|  |   public static readonly token ="gdshader"; | ||||||
|  | 
 | ||||||
|  |   public static readonly keywords:string[] =  | ||||||
|  |   [ | ||||||
|  |     "shader_type","uniform","void", | ||||||
|  |     "sampler2D","vec4","vec3","vec2","float","void" | ||||||
|  |   ] | ||||||
|  | 
 | ||||||
|  |   public static readonly keywords2:string[] =  | ||||||
|  |   [ | ||||||
|  |     "canvas_item","spatial", | ||||||
|  |     "hint_screen_texture",  | ||||||
|  |     "repeat_disable",  | ||||||
|  |     "filter_linear_mipmap", | ||||||
|  |     "COLOR","UV","ALBEDO" | ||||||
|  |   ] | ||||||
|  |    | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | import { UnityLibrary } from "./UnityLibrary"; | ||||||
|  | 
 | ||||||
|  | export class SteamworksNetLibrary | ||||||
|  | { | ||||||
|  |   public static readonly token = "steamworks-net"; | ||||||
|  |   public static keywords:string[] =  | ||||||
|  |   [ | ||||||
|  |     "SteamAPI","SteamFriends","SteamUser","SteamUserStats", | ||||||
|  |     "CallResult", | ||||||
|  | 
 | ||||||
|  |     "SteamLeaderboard_t","SteamLeaderboardEntries_t","LeaderboardScoresDownloaded_t","ELeaderboardDataRequest", | ||||||
|  |     "ELeaderboardUploadScoreMethod" | ||||||
|  |   ] | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | import { CSharpLanguage as CSharpLanguage } from "./CSharpLanguage"; | ||||||
|  | import { SteamworksNetLibrary } from "./SteamworksNetLibrary"; | ||||||
|  | 
 | ||||||
|  | export class UnityLibrary | ||||||
|  | { | ||||||
|  |   public static readonly token = "unity"; | ||||||
|  |   public static keywords:string[] =  | ||||||
|  |   CSharpLanguage.keywords.concat( | ||||||
|  |     [ | ||||||
|  |       "Vector2","Vector3","Vector4", | ||||||
|  |       "Transform","MonoBehaviour","GameObject", | ||||||
|  |       "Undo", | ||||||
|  |       "PrefabUtility", | ||||||
|  |     ].concat( SteamworksNetLibrary.keywords ) | ||||||
|  |   ) | ||||||
|  |    | ||||||
|  | } | ||||||
|  | @ -0,0 +1,4 @@ | ||||||
|  | export class OperatorResolver | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | import { MultiString } from "../../../i18n/MultiString"; | ||||||
|  | import { MessageType, MessageTypes } from "../../../messages/MessageType"; | ||||||
|  | import { ParserMessage } from "./ParserMessage"; | ||||||
|  | import { ParserPhase } from "./ParserPhase"; | ||||||
|  | import { SourceInfo } from "./SourceInfo"; | ||||||
|  | 
 | ||||||
|  | export abstract class Parser   | ||||||
|  | { | ||||||
|  |   _phases:ParserPhase<any>[] = []; | ||||||
|  |   _source:string; | ||||||
|  |   get source(){ return this._source; } | ||||||
|  |   _messages:ParserMessage[] = []; | ||||||
|  |   get messages():ParserMessage[]{ return this._messages } | ||||||
|  |    | ||||||
|  |   _add( phase:ParserPhase<any> ) | ||||||
|  |   { | ||||||
|  |     this._phases.push( phase ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get hasErrors() | ||||||
|  |   { | ||||||
|  |     return this._messages.find( m => MessageTypes.Error == m.type ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _message( type:MessageType, content:string|MultiString, sourceInfo:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     this._messages.push( ParserMessage.create( type, MultiString.resolve( content ), sourceInfo ) ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   verbose( content:string|MultiString, sourceInfo?:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     this._message( MessageTypes.Verbose, content, sourceInfo ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   info( content:string|MultiString, sourceInfo?:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     this._message( MessageTypes.Info, content, sourceInfo ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   warn( content:string|MultiString, sourceInfo?:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     this._message( MessageTypes.Warning, content, sourceInfo ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   error( content:string|MultiString, sourceInfo?:SourceInfo ) | ||||||
|  |   { | ||||||
|  |     this._message( MessageTypes.Error, content, sourceInfo ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   parse( source:string ) | ||||||
|  |   { | ||||||
|  |     this._source = source; | ||||||
|  |      | ||||||
|  |     for ( let phase of this._phases ) | ||||||
|  |     { | ||||||
|  |       phase.reset(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for ( let phase of this._phases ) | ||||||
|  |     { | ||||||
|  |       phase.process(); | ||||||
|  | 
 | ||||||
|  |       if ( this.hasErrors ) | ||||||
|  |       { | ||||||
|  |         this.error( "Stopped at: " + phase.phaseInfo ); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.info( "Parsed successfully" ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | import { Message } from "../../../messages/Message"; | ||||||
|  | import { MessageType } from "../../../messages/MessageType"; | ||||||
|  | import { SourceInfo } from "./SourceInfo"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class ParserMessage extends Message | ||||||
|  | { | ||||||
|  |   sourceInfo:SourceInfo; | ||||||
|  | 
 | ||||||
|  |   static create( type:MessageType, content:string, sourceInfo:SourceInfo ) | ||||||
|  |   {  | ||||||
|  |     let m = new ParserMessage(); | ||||||
|  |      | ||||||
|  |     m.type = type; | ||||||
|  |     m.content = content; | ||||||
|  |     m.sourceInfo = sourceInfo; | ||||||
|  | 
 | ||||||
|  |     return m; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | import { Parser } from "./Parser"; | ||||||
|  | 
 | ||||||
|  | export abstract class ParserPhase<P extends Parser> | ||||||
|  | { | ||||||
|  |   #parser:P; | ||||||
|  |    | ||||||
|  |   get parser(){ return this.#parser; } | ||||||
|  |    | ||||||
|  |   abstract get phaseInfo():string; | ||||||
|  | 
 | ||||||
|  |   constructor( parser:P ) | ||||||
|  |   { | ||||||
|  |     this.#parser = parser; | ||||||
|  |     this.#parser._add( this ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   reset(){} | ||||||
|  | 
 | ||||||
|  |   abstract process():void; | ||||||
|  | 
 | ||||||
|  | }  | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | import { OperatorResolver } from "./OperatorResolver"; | ||||||
|  | 
 | ||||||
|  | export class PrecedenceLevel | ||||||
|  | { | ||||||
|  |   fromLeft:boolean = true; | ||||||
|  |   operatorResolver:OperatorResolver[]; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,87 @@ | ||||||
|  | import { RJExpressionNode } from "../../../rj/expressions/RJExpressionNode"; | ||||||
|  | import { LexerEvent } from "../LexerEvent"; | ||||||
|  | 
 | ||||||
|  | export class SourceRange | ||||||
|  | { | ||||||
|  |   offset:number; | ||||||
|  |   length:number; | ||||||
|  | 
 | ||||||
|  |   get exclusiveEnd() | ||||||
|  |   { | ||||||
|  |     return this.offset + this.length; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get lastSourceIndex() | ||||||
|  |   { | ||||||
|  |     return this.exclusiveEnd - 1; | ||||||
|  |   }  | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static create( offset:number, length:number ) | ||||||
|  |   { | ||||||
|  |     let range = new SourceRange(); | ||||||
|  |     range.offset = offset; | ||||||
|  |     range.length = length; | ||||||
|  | 
 | ||||||
|  |     return range; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static combine( a:SourceRange, b:SourceRange ) | ||||||
|  |   { | ||||||
|  |     let start = Math.min( a.offset, b.offset ); | ||||||
|  |     let end   = Math.max( a.exclusiveEnd, b.exclusiveEnd ); | ||||||
|  | 
 | ||||||
|  |     let range = new SourceRange(); | ||||||
|  |     range.offset = start; | ||||||
|  |     range.length = end - start; | ||||||
|  | 
 | ||||||
|  |     return range; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static combineNodes( a:RJExpressionNode, b:RJExpressionNode ) | ||||||
|  |   { | ||||||
|  |     return SourceRange.combine( a.sourceRange, b.sourceRange ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromNodes( nodes:RJExpressionNode[], start:number, end:number ) | ||||||
|  |   { | ||||||
|  |     return SourceRange.combine( nodes[ start ].sourceRange, nodes[ end ].sourceRange ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromAllNodes( nodes:RJExpressionNode[] ) | ||||||
|  |   { | ||||||
|  |     return SourceRange.fromNodes( nodes, 0, nodes.length - 1  ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class SourceInfo | ||||||
|  | { | ||||||
|  |   range:SourceRange; | ||||||
|  | 
 | ||||||
|  |   static asRange( start:number, length:number ) | ||||||
|  |   { | ||||||
|  |     let sourceInfo = new SourceInfo(); | ||||||
|  | 
 | ||||||
|  |     sourceInfo.range = SourceRange.create( start, length ); | ||||||
|  | 
 | ||||||
|  |     return sourceInfo; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromRange( range:SourceRange ) | ||||||
|  |   { | ||||||
|  |     let sourceInfo = new SourceInfo(); | ||||||
|  | 
 | ||||||
|  |     sourceInfo.range = range; | ||||||
|  | 
 | ||||||
|  |     return sourceInfo; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromEvent( le:LexerEvent  ) | ||||||
|  |   { | ||||||
|  |     return SourceInfo.asRange( le.offset, le.length ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | export abstract class TextReplacement | ||||||
|  | { | ||||||
|  |   abstract replace( source:string ):string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class RegexReplacement extends TextReplacement | ||||||
|  | { | ||||||
|  |   regex:RegExp; | ||||||
|  |   replacement:string; | ||||||
|  | 
 | ||||||
|  |   toString() | ||||||
|  |   { | ||||||
|  |     let info = `RegexReplacement{${this.regex} >> '${this.replacement}'`; | ||||||
|  |     return info; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   replace( source:string ) | ||||||
|  |   { | ||||||
|  |     return source.replace( this.regex, this.replacement ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | import { Files } from "../../node/Files"; | ||||||
|  | import { TextReplacer } from "./TextReplacer"; | ||||||
|  | 
 | ||||||
|  | export class TextReplacementProcessor | ||||||
|  | { | ||||||
|  |   replacers:TextReplacer[] = []; | ||||||
|  | 
 | ||||||
|  |   process( source:string ):string | ||||||
|  |   { | ||||||
|  |     let text = source; | ||||||
|  | 
 | ||||||
|  |     this.replacers.forEach( r => text = r.process( text ) ); | ||||||
|  | 
 | ||||||
|  |     return text; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   replaceFile( filePath:string, suffix:string = ".replaced.html") | ||||||
|  |   { | ||||||
|  |     let newFilePath = filePath + suffix; | ||||||
|  |     let text = Files.loadUTF8( filePath ); | ||||||
|  |     let replacedText = this.process( text ); | ||||||
|  |     Files.saveUTF8( newFilePath, replacedText ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,134 @@ | ||||||
|  | import { ExtendedRegex } from "../ExtendedRegex"; | ||||||
|  | import { RegExpUtility } from "../RegExpUtility"; | ||||||
|  | import { RegexReplacement, TextReplacement } from "./TextReplacement"; | ||||||
|  | import { TextSelectionRange } from "./TextSelectionRange"; | ||||||
|  | import { RegexStartEndSelector, TextSelector } from "./TextSelector"; | ||||||
|  | 
 | ||||||
|  | export class TextReplacer | ||||||
|  | { | ||||||
|  |   selector:TextSelector; | ||||||
|  |   replacements:TextReplacement[] = []; | ||||||
|  | 
 | ||||||
|  |   process( source:string ):string | ||||||
|  |   { | ||||||
|  |     let ranges = this.selector ? | ||||||
|  |                   this.selector.select( source ) :  | ||||||
|  |                   [ TextSelectionRange.fromString( source ) ]; | ||||||
|  | 
 | ||||||
|  |     let replacedRanges:string[] = []; | ||||||
|  | 
 | ||||||
|  |     if ( this.selector ) | ||||||
|  |     { | ||||||
|  |       console.log(  | ||||||
|  |         "RANGES", this.selector + "",  | ||||||
|  |         ranges.map(  | ||||||
|  |           r =>  | ||||||
|  |           r.start === 0 && r.length === source.length ? ( "complete source (" + source.length+ ")" ) | ||||||
|  |           : ( r.start + ":" + r.end )  | ||||||
|  |         ).join()  | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     | ||||||
|  | 
 | ||||||
|  |     ranges.forEach( | ||||||
|  |       range => | ||||||
|  |       { | ||||||
|  |         let replacingSelection = range.get( source ); | ||||||
|  |         this.replacements.forEach( r => { replacingSelection = r.replace( replacingSelection ); } ); | ||||||
|  |         replacedRanges.push( replacingSelection ); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     let replacedSource = TextSelectionRange.replaceRanges( source, ranges, replacedRanges ); | ||||||
|  | 
 | ||||||
|  |     return replacedSource; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setRegexStartEndSelection( start:RegExp, end:RegExp, inner:boolean = false ) | ||||||
|  |   { | ||||||
|  |     let regexSelector = new RegexStartEndSelector(); | ||||||
|  |     regexSelector.startRegex = start; | ||||||
|  |     regexSelector.endRegex = end; | ||||||
|  |     regexSelector.inner = inner; | ||||||
|  |     regexSelector.multiple = true; | ||||||
|  | 
 | ||||||
|  |     this.selector = regexSelector; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setOuterTagSelection( tag:string ) | ||||||
|  |   { | ||||||
|  |     this.setRegexStartEndSelection( new RegExp( `<${tag}` ), new RegExp( `<\\/${tag}>` ) ) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setInnerTagSelection( tag:string ) | ||||||
|  |   { | ||||||
|  |     this.setRegexStartEndSelection( new RegExp( `<${tag}` ), new RegExp( `<\\/${tag}>` ), true ) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addRegexReplacement( match:RegExp, replacement:string ) | ||||||
|  |   { | ||||||
|  |     let regexReplacement = new RegexReplacement(); | ||||||
|  |     regexReplacement.regex = match; | ||||||
|  |     regexReplacement.replacement = replacement; | ||||||
|  | 
 | ||||||
|  |     this.replacements.push( regexReplacement ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static readonly attributeInnerRegExp = /(<TAG\zATTRIBUTE\s*=\s*"(.*)"\z>)(\z)(<\/TAG>)/g; | ||||||
|  |    | ||||||
|  |   addTagAttributeToInnerTagReplacement( tag:string, attribute:string, innerTag:string ) | ||||||
|  |   { | ||||||
|  |     let regexSource = TextReplacer.attributeInnerRegExp.source.replace( /TAG/g, tag ); | ||||||
|  |     regexSource = regexSource.replace( /ATTRIBUTE/g, attribute ); | ||||||
|  | 
 | ||||||
|  |     let matcher = ExtendedRegex.create( regexSource ); | ||||||
|  |     console.log( "ATTRIBUTE TO INNER", matcher ); | ||||||
|  |     let replacement = `$1$3<${innerTag}>$2</${innerTag}>$4`; | ||||||
|  | 
 | ||||||
|  |     this.addRegexReplacement( matcher, replacement ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addTagRenaming( oldTag:string, newTag:string ) | ||||||
|  |   { | ||||||
|  |     let matcher = RegExpUtility.createRegExp( /(<\/?)TAG/g, "TAG", oldTag ); | ||||||
|  |     let replacement = `$1${newTag}`; | ||||||
|  | 
 | ||||||
|  |     this.addRegexReplacement( matcher, replacement ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static readonly removeAttributeREgex = /(<\P\z)ATTRIBUTE\s*=\s*".*"(\z>\z<\/\P>)/g; | ||||||
|  | 
 | ||||||
|  |   addAttributeRemoving( attribute:string) | ||||||
|  |   { | ||||||
|  |     let regexSource = TextReplacer.removeAttributeREgex.source.replace( /ATTRIBUTE/g, attribute ); | ||||||
|  | 
 | ||||||
|  |     let matcher = ExtendedRegex.create( regexSource ); | ||||||
|  | 
 | ||||||
|  |     let replacement = `$1 $2`; | ||||||
|  | 
 | ||||||
|  |     this.addRegexReplacement( matcher, replacement ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static create( match:RegExp, replacement:string ) | ||||||
|  |   { | ||||||
|  |     let replacer = new TextReplacer(); | ||||||
|  |     replacer.addRegexReplacement( match, replacement ); | ||||||
|  |     return replacer; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static createInRegexSelection( match:RegExp, replacement:string, selectionStart:RegExp, selectionEnd:RegExp ) | ||||||
|  |   { | ||||||
|  |     let replacer = new TextReplacer(); | ||||||
|  |     replacer.setRegexStartEndSelection( selectionStart, selectionEnd );  | ||||||
|  |     replacer.addRegexReplacement( match, replacement ); | ||||||
|  |     return replacer; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static createInTag( match:RegExp, replacement:string, tag:string ) | ||||||
|  |   { | ||||||
|  |     let replacer = new TextReplacer(); | ||||||
|  |     replacer.setRegexStartEndSelection( new RegExp( `<${tag}` ), new RegExp( `<\\/${tag}>` ) );  | ||||||
|  |     replacer.addRegexReplacement( match, replacement ); | ||||||
|  |     return replacer; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,99 @@ | ||||||
|  | export class TextSelectionRange | ||||||
|  | { | ||||||
|  |   start:number; | ||||||
|  |   length:number; | ||||||
|  | 
 | ||||||
|  |   get end():number | ||||||
|  |   { | ||||||
|  |     return this.start + this.length; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set end( value:number )  | ||||||
|  |   { | ||||||
|  |     this.length = value - this.start; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get( source:string ) | ||||||
|  |   { | ||||||
|  |     return source.substring( this.start, this.end ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   replace( source:string, replacement:string ) | ||||||
|  |   { | ||||||
|  |     let before = source.substring( 0, this.start ); | ||||||
|  |     let after  = source.substring( this.end );   | ||||||
|  |      | ||||||
|  |     console.log( "start:", this.start, "end:", this.end) | ||||||
|  | 
 | ||||||
|  |     return [ before, replacement, after ].join( "" ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromWithLength( start:number, length:number ) | ||||||
|  |   { | ||||||
|  |     let range = new TextSelectionRange(); | ||||||
|  |     range.start = start; | ||||||
|  |     range.length = length; | ||||||
|  | 
 | ||||||
|  |     return range; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromToExclusive( start:number, end:number ) | ||||||
|  |   { | ||||||
|  |     let range = new TextSelectionRange(); | ||||||
|  | 
 | ||||||
|  |     range.start = start; | ||||||
|  |     range.end = end; | ||||||
|  | 
 | ||||||
|  |     return range; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromString( source:string ) | ||||||
|  |   { | ||||||
|  |     let range = new TextSelectionRange(); | ||||||
|  |     range.start = 0; | ||||||
|  |     range.length = source.length; | ||||||
|  | 
 | ||||||
|  |     return range; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static replaceRanges( source:string, ranges:TextSelectionRange[], replacements:string[] ) | ||||||
|  |   { | ||||||
|  |     let fragments:string[] = []; | ||||||
|  |     let lastEnd = 0; | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < ranges.length; i++ ) | ||||||
|  |     { | ||||||
|  |       let range       = ranges[ i ]; | ||||||
|  |       let replacement = replacements[ i ]; | ||||||
|  |        | ||||||
|  |       let beforeRange = source.substring( lastEnd, range.start ); | ||||||
|  |        | ||||||
|  |       fragments.push( beforeRange ); | ||||||
|  |       fragments.push( replacement ); | ||||||
|  | 
 | ||||||
|  |       lastEnd = range.end; | ||||||
|  | 
 | ||||||
|  |       if ( i !== ( ranges.length - 1 ) ) | ||||||
|  |       { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let afterRange = source.substring( lastEnd, source.length ); | ||||||
|  | 
 | ||||||
|  |       fragments.push( afterRange ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let replaced = fragments.join( "" ); | ||||||
|  | 
 | ||||||
|  |     if ( replaced === undefined ) | ||||||
|  |     { | ||||||
|  |       console.log( "Merging ranges failed:", ranges.length, fragments.length ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return replaced; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,128 @@ | ||||||
|  | import { RegExpUtility } from "../RegExpUtility"; | ||||||
|  | import { TextSelectionRange } from "./TextSelectionRange"; | ||||||
|  | import { ColorConsole } from "../../node/ColorConsole"; | ||||||
|  | 
 | ||||||
|  | export abstract class TextSelector | ||||||
|  | { | ||||||
|  |   abstract select( source:string ):TextSelectionRange[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class RegexStartEndSelector extends TextSelector | ||||||
|  | { | ||||||
|  |   startRegex:RegExp; | ||||||
|  |   endRegex:RegExp; | ||||||
|  |   inner:boolean = false; | ||||||
|  |   multiple:boolean = true; | ||||||
|  |   maxRanges:number = 100; | ||||||
|  | 
 | ||||||
|  |   toString() | ||||||
|  |   { | ||||||
|  |     let start = ColorConsole.fg( this.startRegex.source, ColorConsole.red ); | ||||||
|  |     let end   = ColorConsole.fg( this.endRegex.source, ColorConsole.red ); | ||||||
|  |     let info = `RegexSelector{${start} ${end} ${this.multiple?"multiple":""} ${this.inner?"inner":""}}`; | ||||||
|  |     return info; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   select( source:string ) | ||||||
|  |   { | ||||||
|  |     this.startRegex = RegExpUtility.makeGlobal( this.startRegex ); | ||||||
|  |     this.endRegex   = RegExpUtility.makeGlobal( this.endRegex ); | ||||||
|  | 
 | ||||||
|  |     if ( this.multiple ) | ||||||
|  |     { | ||||||
|  |       return this.selectMultiple( source ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let range = TextSelectionRange.fromString( source ); | ||||||
|  | 
 | ||||||
|  |     this.startRegex.lastIndex = 0; | ||||||
|  |     this.endRegex.lastIndex   = 0; | ||||||
|  | 
 | ||||||
|  |     let startResult = this.startRegex.exec( source ); | ||||||
|  |      | ||||||
|  |     if ( startResult ) | ||||||
|  |     { | ||||||
|  |       range.start = this.inner ? ( startResult.index + startResult[ 0 ].length ) : startResult.index; | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       console.log( "Start not found: ", this.startRegex,  | ||||||
|  |                     source.substring( 0, Math.min( source.length,100 ) ) ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let endResult = this.endRegex.exec( source ); | ||||||
|  | 
 | ||||||
|  |     if ( endResult ) | ||||||
|  |     { | ||||||
|  |       range.end = this.inner ? endResult.index : ( endResult.index + endResult[ 0 ].length ); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       console.log( "End not found: ", this.endRegex,  | ||||||
|  |                     source.substring( 0, Math.min( source.length,100 ) ) ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return [ range ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   selectMultiple( source:string ):TextSelectionRange[] | ||||||
|  |   { | ||||||
|  |     let ranges:TextSelectionRange[] = []; | ||||||
|  | 
 | ||||||
|  |     let currentIndex = -1; | ||||||
|  |     let searching = true; | ||||||
|  | 
 | ||||||
|  |     let currentElements = 0; | ||||||
|  | 
 | ||||||
|  |     while ( searching && currentElements < this.maxRanges ) | ||||||
|  |     { | ||||||
|  |       let starterIndex = Math.max( 0, currentIndex ); | ||||||
|  |       this.startRegex.lastIndex = starterIndex; | ||||||
|  |       let startResult = this.startRegex.exec( source ); | ||||||
|  |        | ||||||
|  |       if ( ! startResult || startResult.index <= currentIndex ) | ||||||
|  |       { | ||||||
|  |         if ( startResult ) | ||||||
|  |         { | ||||||
|  |           console.log( "Start Index invalid:", currentIndex, startResult.index ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         searching = false; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.endRegex.lastIndex = startResult.index + startResult[ 0 ].length; | ||||||
|  | 
 | ||||||
|  |       let endResult = this.endRegex.exec( source ); | ||||||
|  | 
 | ||||||
|  |       if ( ! endResult || endResult.index <= currentIndex ) | ||||||
|  |       { | ||||||
|  |         if ( endResult ) | ||||||
|  |         { | ||||||
|  |           console.log( "End Index invalid:", currentIndex, endResult.index ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         searching = false; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let range = new TextSelectionRange(); | ||||||
|  |       range.start = this.inner ? ( startResult.index + startResult[ 0 ].length ) : startResult.index; | ||||||
|  |       range.end   = this.inner ? endResult.index : ( endResult.index + endResult[ 0 ].length ); | ||||||
|  |        | ||||||
|  |       ranges.push( range ); | ||||||
|  | 
 | ||||||
|  |       currentIndex = endResult.index + endResult[ 0 ].length; | ||||||
|  |       searching = currentIndex < source.length; | ||||||
|  |       currentElements ++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( ranges.length === 0 ) | ||||||
|  |     { | ||||||
|  |       console.log( "No ranges found!" ); | ||||||
|  |       ranges.push( TextSelectionRange.fromString( source ) );  | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ranges; | ||||||
|  |   }  | ||||||
|  | } | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | import { RegExpUtility } from "../RegExpUtility"; | ||||||
|  | 
 | ||||||
|  | export type Variables = {[index:string]:string } | ||||||
|  | export class VariableReplacer | ||||||
|  | { | ||||||
|  |   static replace( source:string, variables:Variables ) | ||||||
|  |   { | ||||||
|  |     for ( let it in variables ) | ||||||
|  |     { | ||||||
|  |       let regexSource = RegExpUtility.toRegexSource( "${" + it + "}" );  | ||||||
|  |       source = source.replace( new RegExp( regexSource, "g" ), variables[ it ] );       | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return source; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,179 @@ | ||||||
|  | export class Arrays | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |   static combine<T>( t:T, array:T[] ):T[] | ||||||
|  |   { | ||||||
|  |     let combined = [ t ]; | ||||||
|  | 
 | ||||||
|  |     if ( array ) | ||||||
|  |     { | ||||||
|  |       combined = combined.concat( array ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return combined; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static combine2<T>( t:T, t2:T, array:T[] ):T[] | ||||||
|  |   { | ||||||
|  |     let combined = [ t, t2 ]; | ||||||
|  | 
 | ||||||
|  |     if ( array ) | ||||||
|  |     { | ||||||
|  |       combined = combined.concat( array ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return combined; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static combine3<T>( t:T, t2:T, t3:T, array:T[] ):T[] | ||||||
|  |   { | ||||||
|  |     let combined = [ t, t2, t3 ]; | ||||||
|  | 
 | ||||||
|  |     if ( array ) | ||||||
|  |     { | ||||||
|  |       combined = combined.concat( array ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return combined; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static pushIfNotInside<T>( array:T[], element:T ) | ||||||
|  |   { | ||||||
|  |     let index = array.indexOf( element ); | ||||||
|  | 
 | ||||||
|  |     if ( index !== - 1) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     array.push( element ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   static create<T>( size:number, creator:( i:number ) => T ):T[] | ||||||
|  |   { | ||||||
|  |     let created:T[] = []; | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < size; i++ ) | ||||||
|  |     { | ||||||
|  |       let t = creator( i ); | ||||||
|  |       created.push( t ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return created; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static isEmpty<T>( source:T[] ) | ||||||
|  |   { | ||||||
|  |     return source.length === 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static isLast<T>( source:T[], index:number ) | ||||||
|  |   { | ||||||
|  |     return index === ( source.length -1 ) ; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static getLast<T>( source:T[] ):T | ||||||
|  |   { | ||||||
|  |     return source[ source.length - 1 ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static forEachIndex<T>( source:T[], callback:( t:T, index:number, normalized?:number )=>void ) | ||||||
|  |   { | ||||||
|  |     let normalizer = 1 / ( source.length - 1 ); | ||||||
|  | 
 | ||||||
|  |     for ( let i = 0; i < source.length; i++ ) | ||||||
|  |     {  | ||||||
|  |       let normalized = i * normalizer; | ||||||
|  |       callback( source[ i ], i, normalized ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static transfer<T>( source:T[], target:T[] ) | ||||||
|  |   { | ||||||
|  |     this.transferRange( source, 0, target, 0, source.length ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static transferRange<T>( source:T[], sourceOffset:number, target:T[], targetOffset:number, numItems:number ) | ||||||
|  |   { | ||||||
|  |     for ( let i = 0; i < numItems; i++ ) | ||||||
|  |     { | ||||||
|  |       target[ i + targetOffset ] = source[ i + sourceOffset ]; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static copyRange<T>( array:T[], start:number, length:number ) | ||||||
|  |   {  | ||||||
|  |     return array.slice( start, start + length ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static copyRangeFromStart<T>( array:T[], start:number ) | ||||||
|  |   {  | ||||||
|  |     return array.slice( start, array.length ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static insert<T>( array:T[], element:T, index:number ) | ||||||
|  |   { | ||||||
|  |     array.splice( index, 0, element ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static moveToFront<T>( array:T[], element:T ) | ||||||
|  |   { | ||||||
|  |     if ( array[ 0 ] === element ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Arrays.remove( array, element ); | ||||||
|  |     Arrays.insert( array, element, 0 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static remove<T>( array:T[], element:T ) | ||||||
|  |   { | ||||||
|  |     if ( ! array || ! element ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let index = array.indexOf( element ); | ||||||
|  |      | ||||||
|  |     if ( index === -1 ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     array.splice( index, 1 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static addIfNotPresent<T>( array:T[], t:T) | ||||||
|  |   { | ||||||
|  |     let index = array.indexOf( t ); | ||||||
|  |      | ||||||
|  |     if ( index !== -1 ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     array.push( t ); | ||||||
|  |   }  | ||||||
|  |   | ||||||
|  |   static removeAt<T>( array:T[], index:number ) | ||||||
|  |   { | ||||||
|  |     if ( ! array ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ( index < 0 || index > array.length ) | ||||||
|  |     { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     array.splice( index, 1 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static removeAllAfter( array:any[], index:number ) | ||||||
|  |   { | ||||||
|  |     array.splice( index, array.length - index ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | { | ||||||
|  |   "compilerOptions":  | ||||||
|  |   { | ||||||
|  |      | ||||||
|  |     "outDir": "../../builds/rokojori-com/", | ||||||
|  |     "sourceMap": true,     | ||||||
|  |     "noImplicitAny": true, | ||||||
|  |     "module": "commonjs",  | ||||||
|  |     "target": "es2020", | ||||||
|  |     "baseUrl": ".", | ||||||
|  |     "paths":  | ||||||
|  |     { | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     "typeRoots": [ "node_modules/@types","node_modules/@types/three/"], | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "lib": ["ES2020"], | ||||||
|  |     "types": ["node"],  // Includes Node.js types | ||||||
|  |   }, | ||||||
|  |   "include": ["./*"] | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	 Josef
						Josef