Created
July 9, 2020 10:09
-
-
Save waterplea/6cde85e4655202dccf1ee31b5ab3c5fb to your computer and use it in GitHub Desktop.
A RegExp based CSS gradient parser by Dean Taylor turned to TypeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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