Skip to content

Instantly share code, notes, and snippets.

@waterplea
Created July 9, 2020 10:09
Show Gist options
  • Save waterplea/6cde85e4655202dccf1ee31b5ab3c5fb to your computer and use it in GitHub Desktop.
Save waterplea/6cde85e4655202dccf1ee31b5ab3c5fb to your computer and use it in GitHub Desktop.
A RegExp based CSS gradient parser by Dean Taylor turned to TypeScript
//
// TypeScript parser based on Dean Taylor's answer:
// https://stackoverflow.com/a/20238168/2706426
//
interface GradientStop {
color: string;
position?: string;
}
interface ParsedGraient {
stops: GradientStop[];
line?: string;
side?: string;
angle?: string;
}
// SETUP CODE
// Note any variables with "Capture" in name include capturing bracket set(s).
const searchFlags = 'gi'; // ignore case for angles, "rgb" etc
const rAngle = /(?:[+-]?\d*\.?\d+)(?:deg|grad|rad|turn)/; // Angle +ive, -ive and angle types
const rSideCornerCapture = /to\s+((?:(?:left|right)(?:\s+(?:top|bottom))?))/; // optional 2nd part
const rComma = /\s*,\s*/; // Allow space around comma.
const rColorHex = /#(?:[a-f0-9]{6}|[a-f0-9]{3})/; // 3 or 6 character form
const rDigits3 = /\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}\s*\)/; // "(1, 2, 3)"
const rDigits4 = /\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}\s*,\s*\d*\.?\d+\)/; // "(1, 2, 3, 4)"
const rValue = /(?:[+-]?\d*\.?\d+)(?:%|[a-z]+)?/; // ".9", "-5px", "100%".
const rKeyword = /[_a-z-][_a-z0-9-]*/; // "red", "transparent", "border-collapse".
const rColor = combineRegExp(
[
'(?:',
rColorHex,
'|',
'(?:rgb|hsl)',
rDigits3,
'|',
'(?:rgba|hsla)',
rDigits4,
'|',
rKeyword,
')',
],
'',
);
const rColorStop = combineRegExp(
[rColor, '(?:\\s+', rValue, '(?:\\s+', rValue, ')?)?'],
'',
); // Single Color Stop, optional %, optional length.
const rColorStopList = combineRegExp(['(?:', rColorStop, rComma, ')*', rColorStop], ''); // List of color stops min 1.
const rLineCapture = combineRegExp(['(?:(', rAngle, ')|', rSideCornerCapture, ')'], ''); // Angle or SideCorner
const gradientSearchList = [
'(?:(',
rLineCapture,
')',
rComma,
')?(',
rColorStopList,
')',
];
const colorStopSearchList = [
'\\s*(',
rColor,
')',
'(?:\\s+',
'(',
rValue,
'))?',
'(?:',
rComma,
'\\s*)?',
];
function combineRegExp(
regexpList: ReadonlyArray<string | RegExp>,
flags: string,
): RegExp {
return new RegExp(
regexpList.reduce<string>(
(result, item) => result + (typeof item === 'string' ? item : item.source),
'',
),
flags,
);
}
export function parseGradient(input: string): ParsedGraient | null {
// Capture 1:"line", 2:"angle" (optional), 3:"side corner" (optional) and 4:"stop list".
const gradientSearch = combineRegExp(gradientSearchList, searchFlags);
// Capture 1:"color" and 2:"position" (optional).
const colorStopSearch = combineRegExp(colorStopSearchList, searchFlags);
const matchGradient = gradientSearch.exec(input);
if (!matchGradient) {
// Match not found.
return null;
}
const result: ParsedGraient = {
stops: [],
line: matchGradient[1],
angle: matchGradient[2],
side: matchGradient[3],
};
// Loop though all the color-stops.
let matchColorStop = colorStopSearch.exec(matchGradient[4]);
while (matchColorStop !== null) {
result.stops.push({
color: matchColorStop[1],
position: matchColorStop[2],
});
// Continue searching from previous position.
matchColorStop = colorStopSearch.exec(matchGradient[4]);
}
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment