117 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google Inc. All Rights Reserved.
 | 
						|
 *
 | 
						|
 * Use of this source code is governed by an MIT-style license that can be
 | 
						|
 * found in the LICENSE file at https://angular.io/license
 | 
						|
 */
 | 
						|
 | 
						|
const enum Char {
 | 
						|
  OpenParen = 40,
 | 
						|
  CloseParen = 41,
 | 
						|
  Colon = 58,
 | 
						|
  Semicolon = 59,
 | 
						|
  BackSlash = 92,
 | 
						|
  QuoteNone = 0,  // indicating we are not inside a quote
 | 
						|
  QuoteDouble = 34,
 | 
						|
  QuoteSingle = 39,
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Parses string representation of a style and converts it into object literal.
 | 
						|
 *
 | 
						|
 * @param value string representation of style as used in the `style` attribute in HTML.
 | 
						|
 *   Example: `color: red; height: auto`.
 | 
						|
 * @returns An array of style property name and value pairs, e.g. `['color', 'red', 'height',
 | 
						|
 * 'auto']`
 | 
						|
 */
 | 
						|
export function parse(value: string): string[] {
 | 
						|
  // we use a string array here instead of a string map
 | 
						|
  // because a string-map is not guaranteed to retain the
 | 
						|
  // order of the entries whereas a string array can be
 | 
						|
  // construted in a [key, value, key, value] format.
 | 
						|
  const styles: string[] = [];
 | 
						|
 | 
						|
  let i = 0;
 | 
						|
  let parenDepth = 0;
 | 
						|
  let quote: Char = Char.QuoteNone;
 | 
						|
  let valueStart = 0;
 | 
						|
  let propStart = 0;
 | 
						|
  let currentProp: string|null = null;
 | 
						|
  let valueHasQuotes = false;
 | 
						|
  while (i < value.length) {
 | 
						|
    const token = value.charCodeAt(i++) as Char;
 | 
						|
    switch (token) {
 | 
						|
      case Char.OpenParen:
 | 
						|
        parenDepth++;
 | 
						|
        break;
 | 
						|
      case Char.CloseParen:
 | 
						|
        parenDepth--;
 | 
						|
        break;
 | 
						|
      case Char.QuoteSingle:
 | 
						|
        // valueStart needs to be there since prop values don't
 | 
						|
        // have quotes in CSS
 | 
						|
        valueHasQuotes = valueHasQuotes || valueStart > 0;
 | 
						|
        if (quote === Char.QuoteNone) {
 | 
						|
          quote = Char.QuoteSingle;
 | 
						|
        } else if (quote === Char.QuoteSingle && value.charCodeAt(i - 1) !== Char.BackSlash) {
 | 
						|
          quote = Char.QuoteNone;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case Char.QuoteDouble:
 | 
						|
        // same logic as above
 | 
						|
        valueHasQuotes = valueHasQuotes || valueStart > 0;
 | 
						|
        if (quote === Char.QuoteNone) {
 | 
						|
          quote = Char.QuoteDouble;
 | 
						|
        } else if (quote === Char.QuoteDouble && value.charCodeAt(i - 1) !== Char.BackSlash) {
 | 
						|
          quote = Char.QuoteNone;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case Char.Colon:
 | 
						|
        if (!currentProp && parenDepth === 0 && quote === Char.QuoteNone) {
 | 
						|
          currentProp = hyphenate(value.substring(propStart, i - 1).trim());
 | 
						|
          valueStart = i;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case Char.Semicolon:
 | 
						|
        if (currentProp && valueStart > 0 && parenDepth === 0 && quote === Char.QuoteNone) {
 | 
						|
          const styleVal = value.substring(valueStart, i - 1).trim();
 | 
						|
          styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
 | 
						|
          propStart = i;
 | 
						|
          valueStart = 0;
 | 
						|
          currentProp = null;
 | 
						|
          valueHasQuotes = false;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (currentProp && valueStart) {
 | 
						|
    const styleVal = value.substr(valueStart).trim();
 | 
						|
    styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
 | 
						|
  }
 | 
						|
 | 
						|
  return styles;
 | 
						|
}
 | 
						|
 | 
						|
export function stripUnnecessaryQuotes(value: string): string {
 | 
						|
  const qS = value.charCodeAt(0);
 | 
						|
  const qE = value.charCodeAt(value.length - 1);
 | 
						|
  if (qS == qE && (qS == Char.QuoteSingle || qS == Char.QuoteDouble)) {
 | 
						|
    const tempValue = value.substring(1, value.length - 1);
 | 
						|
    // special case to avoid using a multi-quoted string that was just chomped
 | 
						|
    // (e.g. `font-family: "Verdana", "sans-serif"`)
 | 
						|
    if (tempValue.indexOf('\'') == -1 && tempValue.indexOf('"') == -1) {
 | 
						|
      value = tempValue;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return value;
 | 
						|
}
 | 
						|
 | 
						|
export function hyphenate(value: string): string {
 | 
						|
  return value.replace(/[a-z][A-Z]/g, v => {
 | 
						|
                return v.charAt(0) + '-' + v.charAt(1);
 | 
						|
              }).toLowerCase();
 | 
						|
}
 |