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