internal static SpriteBlock ParseSpriteDeclaration(ParserStream stream) { var start = stream.Position; stream.AdvancePast("("); var ignored = new StringBuilder(); var adTo = stream.ScanUntil(ignored, '"', '\''); if (ignored.ToString().Any(a => !char.IsWhiteSpace(a))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } if (adTo == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var nameStart = stream.Position; var name = new StringBuilder(); name.Append(adTo.Value); stream.ScanUntil(name, adTo.Value); name.Append(adTo.Value); var nameStop = stream.Position; stream.AdvancePast(")"); var rules = ParseSpriteRules(stream); return(new SpriteBlock((QuotedStringValue)Value.Parse(name.ToString(), nameStart, nameStop, Current.CurrentFilePath), rules, start, stream.Position, Current.CurrentFilePath)); }
internal static Import ParseImportDirective(ParserStream stream) { var start = stream.Position; var buffer = new StringBuilder(); stream.ScanUntilWithNesting(buffer, ';'); var toParse = buffer.ToString().Trim(); Value val; MediaQuery media; string mediaStr; if (Regex.IsMatch(toParse, @"url\s*?\(", RegexOptions.IgnoreCase)) { var i = toParse.IndexOf(')'); if (i == -1) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected ')'");; throw new StoppedParsingException(); } val = Value.Parse(toParse.Substring(0, i + 1), start, start + i + 1, Current.CurrentFilePath); mediaStr = toParse.Substring(i + 1); } else { if (!(toParse.StartsWith("\"") || toParse.StartsWith("'"))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quote"); throw new StoppedParsingException(); } var i = toParse.LastIndexOf(toParse[0]); if (i == -1) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '" + toParse[0] + "'"); throw new StoppedParsingException(); } val = Value.Parse(toParse.Substring(0, i + 1), start, start + i + 1, Current.CurrentFilePath); mediaStr = toParse.Substring(i + 1); } mediaStr = mediaStr.Trim(); if (mediaStr.Length > 0) { media = MediaQueryParser.Parse(mediaStr, Position.Create(start, stream.Position, Current.CurrentFilePath)); } else { media = new MediaType(Media.all, Position.Create(start, stream.Position, Current.CurrentFilePath)); } return(new Import(val, media, start, stream.Position, Current.CurrentFilePath)); }
private static MathValue ParseMathValue(char op, Value lhs, ParserStream stream, IPosition forPosition) { stream.Advance(); // skip operator if (lhs == null) { Current.RecordError(ErrorType.Parser, forPosition, "Expected value, found '" + op + "'"); throw new StoppedParsingException(); } Operator @operator; switch (op) { case '+': @operator = Operator.Plus; break; case '-': @operator = Operator.Minus; break; case '*': @operator = Operator.Mult; break; case '/': @operator = Operator.Div; break; case '%': @operator = Operator.Mod; break; default: throw new InvalidOperationException("Unexpected operator [" + op + "]"); } var rhs = ParseImpl(stream, forPosition, allowSelectorIncludes: false); return(new MathValue(lhs, @operator, rhs)); }
internal static CssCharset ParseCharsetDirective(ParserStream stream) { var start = stream.Position; var ignored = new StringBuilder(); var quote = stream.ScanUntil(ignored, '"', '\''); if (quote == null) { Current.RecordError(ErrorType.Parser, Model.Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var isoName = new StringBuilder(); stream.ScanUntil(isoName, quote.Value); stream.AdvancePast(";"); var isoNameStr = isoName.ToString(); if (!CssCharset.KnownCharset(isoNameStr)) { Current.RecordWarning(ErrorType.Parser, Model.Position.Create(start, stream.Position, Current.CurrentFilePath), "Unrecognized charset"); } return(new CssCharset(new QuotedStringValue(isoNameStr), start, stream.Position)); }
internal static MixinBlock ParseMixinDeclaration(string name, ParserStream stream) { var start = stream.Position; var @params = new StringBuilder(); stream.ScanUntil(@params, ')'); name = name.Trim(); if (name.Length == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected mixin name"); throw new StoppedParsingException(); } if (name.ToLower().In(ReservedWords)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "'" + name + "' cannot be the name of a mixin."); } stream.AdvancePast("{"); return (new MixinBlock( name, ParseMixinDeclarationParameter(@params.ToString(), start), ParseCssRules(InvalidSelector.Singleton, stream), start, stream.Position, Current.CurrentFilePath )); }
// Basically, it's realy common for background-position to be something like "0px -20px", which shouldn't be interpretted as a math value // so detect the case where two number with unit values are separated by whitespace and treat them as a compound value instead // otherwise, fallback to normal parsing internal static Value ParseBackgroundPositionValue(ParserStream stream) { var start = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = valueStr.ToString(); var parts = value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var pos = Position.Create(start, stream.Position, Current.CurrentFilePath); // something's up if (parts.Length != 2) { return(MoreValueParser.Parse(value, pos)); } var p1 = parts[0]; var p2 = parts[1]; var v1 = MoreValueParser.Parse(p1, pos); var v2 = MoreValueParser.Parse(p2, pos); if (v1 is NumberWithUnitValue && v2 is NumberWithUnitValue) { return(new CompoundValue(v1, v2)); } return(MoreValueParser.Parse(value, pos)); }
internal static MediaBlock ParseMediaDirective(ParserStream stream) { var start = stream.Position; var media = new StringBuilder(); stream.ScanUntil(media, '{'); var mediaStr = media.ToString().Trim(); if (mediaStr.IsNullOrEmpty()) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected media list"); throw new StoppedParsingException(); } var mediaQuery = MediaQueryParser.Parse(mediaStr, Position.Create(start, stream.Position, Current.CurrentFilePath)); var contained = new List <Block>(); char c; while ((c = stream.Peek()) != '}') { if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } // More directive (probably) if (c == '@') { contained.Add(ParseDirective(stream)); continue; } // Selector + block time! contained.Add(ParseSelectorAndBlock(stream)); } var notAllowed = contained.Where(x => !(x is SelectorAndBlock || x is MoreVariable)); foreach (var illegal in notAllowed) { Current.RecordError(ErrorType.Parser, illegal, "@media can only contain blocks and variable declarations"); } if (notAllowed.Count() != 0) { throw new StoppedParsingException(); } // Skip past } stream.Advance(); return(new MediaBlock(mediaQuery, contained, start, stream.Position, Current.CurrentFilePath)); }
internal static FontFaceBlock ParseFontFace(ParserStream stream, int start) { var ignored = new StringBuilder(); stream.ScanUntil(ignored, '{'); var rules = ParseCssRules(InvalidSelector.Singleton, stream); return(new FontFaceBlock(rules, start, stream.Position, Current.CurrentFilePath)); }
internal static Value ParseMoreValue(ParserStream stream) { var start = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = valueStr.ToString(); return(Value.Parse(value, start, stream.Position, Current.CurrentFilePath)); }
internal static GroupedValue ParseGroup(ParserStream stream, IPosition forPosition) { var toParse = new StringBuilder(); stream.Advance(); // skip ( stream.ScanUntilWithNesting(toParse, ')'); var group = toParse.ToString().Trim(); var ret = Parse(group, forPosition); return(new GroupedValue(ret)); }
private static QuotedStringValue ParseQuotedString(char quote, ParserStream stream, IPosition forPosition) { stream.Advance(); // skip the quote var buffer = new StringBuilder(); var x = stream.ScanUntil(buffer, quote); if (x == null) { Current.RecordError(ErrorType.Parser, forPosition, "Expected '" + quote + "'"); throw new StoppedParsingException(); } return(new QuotedStringValue(buffer.ToString())); }
internal static KeyFrame ParseKeyFrame(ParserStream stream) { var start = stream.Position; var buffer = new StringBuilder(); stream.ScanUntil(buffer, '{'); var percents = new List <decimal>(); var percentsStr = buffer.ToString(); foreach (var p in percentsStr.Split(',').Select(s => s.Trim())) { if (p.Length == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected `from`, `to`, or a percentage"); throw new StoppedParsingException(); } decimal percent; if (p.Equals("from", StringComparison.InvariantCultureIgnoreCase)) { percent = 0; } else { if (p.Equals("to", StringComparison.InvariantCultureIgnoreCase)) { percent = 100; } else { if (!p.EndsWith("%") || !decimal.TryParse(p.Substring(0, p.Length - 1), out percent)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected `from`, `to`, or a percentage. Found `" + p + "`"); throw new StoppedParsingException(); } } } percents.Add(percent); } var rules = ParseCssRules(InvalidSelector.Singleton, stream); return(new KeyFrame(percents, rules, start, stream.Position, Current.CurrentFilePath)); }
internal static Value ParseFontLikeValue(ParserStream stream) { var start = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = valueStr.ToString(); // The shorthand isn't in use, so we can handle this if (value.IndexOf('/') == -1) { return(MoreValueParser.Parse(value, Position.Create(start, stream.Position, Current.CurrentFilePath))); } return(new StringValue(value)); }
internal static SelectorAndBlock ParseSelectorAndBlock(ParserStream stream, string selectorStr = null) { var mark = stream.Position; int selectorStop, selectorStart; if (selectorStr.IsNullOrEmpty()) { selectorStart = stream.Position; var selector = new StringBuilder(); stream.ScanUntil(selector, '{'); selectorStop = stream.Position; selectorStr = selector.ToString().Trim(); if (selectorStr.IsNullOrEmpty()) { Current.RecordError(ErrorType.Parser, Position.Create(mark, stream.Position, Current.CurrentFilePath), "Expected selector"); throw new StoppedParsingException(); } } else { selectorStop = mark; selectorStart = selectorStop - selectorStr.Length; } var sel = Selector.Parse(selectorStr, selectorStart, selectorStop, Current.CurrentFilePath); var cssRules = ParseCssRules(sel, stream); // Bind @reset() properties to their location w.r.t. selectors cssRules = cssRules.Select( delegate(Property prop) { if (!(prop is ResetSelfProperty)) { return(prop); } return(((ResetSelfProperty)prop).BindToSelector(sel)); } ).ToList(); return(new SelectorAndBlock(sel, cssRules, null, mark, stream.Position, Current.CurrentFilePath)); }
internal static MoreVariable ParseMoreVariable(string name, ParserStream stream, int start) { name = name.Trim(); var valueStart = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = Value.Parse(valueStr.ToString().Trim(), valueStart, stream.Position, Current.CurrentFilePath, allowSelectorIncludes: true); if (name.ToLower().In(ReservedWords)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "'" + name + "' cannot be a variable name."); } return(new MoreVariable(name, value, start, stream.Position, Current.CurrentFilePath)); }
private static Value ParseImportant(ParserStream stream) { var buffer = new StringBuilder(); while (stream.HasMore() && (char.IsLetterOrDigit(stream.Peek()) || stream.Peek().In('!', '-', '_'))) { buffer.Append(stream.Read()); } var ret = buffer.ToString(); if (ret.Equals("!important", StringComparison.InvariantCultureIgnoreCase)) { return(ImportantValue.Singleton); } return(new StringValue(ret)); }
internal static SpriteRule ParseSpriteRule(ParserStream stream) { var start = stream.Position; if (stream.Peek() != '@') { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '@'"); throw new StoppedParsingException(); } stream.Advance(); // Advance past @ var name = new StringBuilder(); while (stream.HasMore() && stream.Peek() != '=') { name.Append(stream.Read()); } stream.AdvancePast("="); // Advance past = var ignored = new StringBuilder(); var quote = stream.ScanUntil(ignored, '\'', '"'); if (quote == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var valueStart = stream.Position; var valueStr = new StringBuilder(); valueStr.Append(quote.Value); stream.ScanUntil(valueStr, quote.Value); valueStr.Append(quote.Value); stream.AdvancePast(";"); var value = (QuotedStringValue)Value.Parse(valueStr.ToString(), valueStart, stream.Position, Current.CurrentFilePath); return(new SpriteRule(name.ToString().Trim(), value, start, stream.Position, Current.CurrentFilePath)); }
internal static List <Property> ParseCssRules(Selector selector, ParserStream stream) { var ret = new List <Property>(); while (stream.HasMore() && stream.Peek() != '}') { var c = stream.Peek(); if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); } else { ret.Add(ParseRule(stream)); } } stream.AdvancePast("}"); return(ret); }
public static Value Parse(string value, IPosition forPosition, bool allowSelectorIncludes = false) { var ret = new List<Value>(); using (var stream = new ParserStream(new StringReader(value))) { while (stream.HasMore()) { var buffer = new StringBuilder(); stream.ScanUntilWithNesting(buffer, ',', requireFound: false); using (var subStream = new ParserStream(new StringReader(buffer.ToString()))) { ret.Add(ParseImpl(subStream, forPosition, allowSelectorIncludes)); } } } if (ret.Count == 1) return ret[0]; return new CommaDelimittedValue(ret); }
public List <Block> Parse(string filePath, TextReader reader) { try { using (var stream = new ParserStream(reader)) { Current.SwitchToFile(filePath); var ret = new List <Block>(); while (stream.HasMore()) { char c = stream.Peek(); //Ignore white space if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } // More directive (probably) if (c == '@') { ret.Add(ParseDirective(stream)); continue; } // Selector + block time! ret.Add(ParseSelectorAndBlock(stream)); } return(ret); } } catch (StoppedParsingException) { return(null); } }
public List<Block> Parse(string filePath, TextReader reader) { try { using (var stream = new ParserStream(reader)) { Current.SwitchToFile(filePath); var ret = new List<Block>(); while (stream.HasMore()) { char c = stream.Peek(); //Ignore white space if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } // More directive (probably) if (c == '@') { ret.Add(ParseDirective(stream)); continue; } // Selector + block time! ret.Add(ParseSelectorAndBlock(stream)); } return ret; } } catch (StoppedParsingException) { return null; } }
internal static ColorValue ParseHashColor(ParserStream stream, IPosition forPosition) { stream.Advance(); // skip # var buffer = new StringBuilder(); while (buffer.Length < 6 && stream.HasMore() && char.ToLower(stream.Peek()).In('a', 'b', 'c', 'd', 'e', 'f', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0')) { buffer.Append(stream.Read()); } if (buffer.Length != 3 && buffer.Length != 6) { Current.RecordError(ErrorType.Parser, forPosition, "Expected 3 or 6 hexidecimal characters"); throw new StoppedParsingException(); } if (buffer.Length == 3) { return(HexTripleColorValue.Parse(buffer.ToString())); } return(HexSextupleColorValue.Parse(buffer.ToString())); }
internal static Using ParseUsingDirective(ParserStream stream) { var start = stream.Position; var ignored = new StringBuilder(); var quote = stream.ScanUntil(ignored, '"', '\''); if (quote == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var file = new StringBuilder(); stream.ScanUntil(file, quote.Value); int mediaStart = stream.Position; var mediaBuff = new StringBuilder(); stream.ScanUntil(mediaBuff, ';'); int mediaEnd = stream.Position; MediaQuery media; var mediaStr = mediaBuff.ToString().Trim(); if (mediaStr.Length > 0) { media = MediaQueryParser.Parse(mediaStr, Position.Create(mediaStart, mediaEnd, Current.CurrentFilePath)); } else { media = new MediaType(Media.all, Position.NoSite); } return(new Using(file.ToString(), media, start, stream.Position, Current.CurrentFilePath)); }
public static Value Parse(string value, IPosition forPosition, bool allowSelectorIncludes = false) { var ret = new List <Value>(); using (var stream = new ParserStream(new StringReader(value))) { while (stream.HasMore()) { var buffer = new StringBuilder(); stream.ScanUntilWithNesting(buffer, ',', requireFound: false); using (var subStream = new ParserStream(new StringReader(buffer.ToString()))) { ret.Add(ParseImpl(subStream, forPosition, allowSelectorIncludes)); } } } if (ret.Count == 1) { return(ret[0]); } return(new CommaDelimittedValue(ret)); }
internal static List <SpriteRule> ParseSpriteRules(ParserStream stream) { var ret = new List <SpriteRule>(); stream.AdvancePast("{"); while (stream.HasMore() && stream.Peek() != '}') { var c = stream.Peek(); if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); } else { ret.Add(ParseSpriteRule(stream)); } } stream.AdvancePast("}"); return(ret); }
internal static InnerMediaProperty ParseInnerMediaDirective(ParserStream stream) { var start = stream.Position; var media = new StringBuilder(); stream.ScanUntil(media, '{'); var mediaStr = media.ToString().Trim(); if (mediaStr.IsNullOrEmpty()) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected media list"); throw new StoppedParsingException(); } var mediaQuery = MediaQueryParser.Parse(mediaStr, Position.Create(start, stream.Position, Current.CurrentFilePath)); var props = ParseCssRules(InvalidSelector.Singleton, stream); var blockEquiv = new SelectorAndBlock(InvalidSelector.Singleton, props, null, start, stream.Position, Current.CurrentFilePath); return(new InnerMediaProperty(mediaQuery, blockEquiv, start, stream.Position, Current.CurrentFilePath)); }
internal static Block ParseDirective(ParserStream stream) { const string import = "import"; const string @using = "using"; const string sprite = "sprite"; const string charset = "charset"; const string media = "media"; const string keyframes = "keyframes"; const string mozKeyframes = "-moz-keyframes"; const string webKeyframes = "-webkit-keyframes"; const string fontFace = "font-face"; const string reset = "reset"; stream.Advance(); // Advance past @ var bufferStart = stream.Position; var buffer = new StringBuilder(); var next = stream.WhichNextInsensitive(buffer, @using, sprite, import, charset, media, keyframes, mozKeyframes, webKeyframes, fontFace, reset); if (next == @using) { return(ParseUsingDirective(stream)); } if (next == sprite) { return(ParseSpriteDeclaration(stream)); } if (next == import) { return(ParseImportDirective(stream)); } if (next == charset) { return(ParseCharsetDirective(stream)); } if (next == media) { return(ParseMediaDirective(stream)); } if (next.In(keyframes, mozKeyframes, webKeyframes)) { string prefix = ""; if (next == mozKeyframes) { prefix = "-moz-"; } if (next == webKeyframes) { prefix = "-webkit-"; } return(ParseKeyFramesDirective(prefix, stream, bufferStart)); } if (next == fontFace) { return(ParseFontFace(stream, bufferStart)); } if (next == reset) { return(ParseResetDirective(stream, bufferStart)); } stream.PushBack(buffer.ToString()); var leader = new StringBuilder(); var eqOrPara = stream.ScanUntil(leader, '=', '('); if (eqOrPara == '=') { return(ParseMoreVariable(leader.ToString(), stream, bufferStart)); } return(ParseMixinDeclaration(leader.ToString(), stream)); }
private static MathValue ParseMathValue(char op, Value lhs, ParserStream stream, IPosition forPosition) { stream.Advance(); // skip operator if (lhs == null) { Current.RecordError(ErrorType.Parser, forPosition, "Expected value, found '" + op + "'"); throw new StoppedParsingException(); } Operator @operator; switch (op) { case '+': @operator = Operator.Plus; break; case '-': @operator = Operator.Minus; break; case '*': @operator = Operator.Mult; break; case '/': @operator = Operator.Div; break; case '%': @operator = Operator.Mod; break; default: throw new InvalidOperationException("Unexpected operator [" + op + "]"); } var rhs = ParseImpl(stream, forPosition, allowSelectorIncludes: false); return new MathValue(lhs, @operator, rhs); }
internal static KeyFramesBlock ParseKeyFramesDirective(string prefix, ParserStream stream, int start) { var buffer = new StringBuilder(); stream.ScanUntil(buffer, '{'); var name = buffer.ToString().Trim(); if (name.Length == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected a name for the keyframe animation"); throw new StoppedParsingException(); } if ((name[0] != '-' && char.IsDigit(name[0])) || (name[0] == '-' && name.Length > 1 && char.IsDigit(name[1])) || name.Any(a => a != '-' && !char.IsLetterOrDigit(a))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Animation name `" + name + "` is not a valid identifier"); throw new StoppedParsingException(); } var frames = new List <KeyFrame>(); var rules = new List <VariableProperty>(); while (stream.HasMore()) { var c = stream.Peek(); if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } if (c == '}') { break; } if (c == '@') { var rule = ParseMixinOrVariableRule(stream); if (!(rule is VariableProperty)) { Current.RecordError(ErrorType.Parser, rule, "Expected variable declaration"); throw new StoppedParsingException(); } rules.Add((VariableProperty)rule); continue; } frames.Add(ParseKeyFrame(stream)); } if (stream.Peek() != '}') { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '}'"); throw new StoppedParsingException(); } stream.Advance(); // Skip } return(new KeyFramesBlock(prefix, name, frames, rules, start, stream.Position, Current.CurrentFilePath)); }
private static Value ParseString(ParserStream stream, IPosition forPosition) { var c = stream.Peek(); if (c == '!') { return ParseImportant(stream); } var buffer = new StringBuilder(); while (stream.HasMore() && !char.IsWhiteSpace(stream.Peek())) { buffer.Append(stream.Read()); if (buffer.Length == 3 && (stream.HasMore() && stream.Peek() == '(')) { var toDate = buffer.ToString(); if (toDate.Equals("rgb", StringComparison.InvariantCultureIgnoreCase) || toDate.Equals("hsl", StringComparison.InvariantCultureIgnoreCase)) { var group = ParseGroup(stream, forPosition); var @params = group.Value as CommaDelimittedValue; if (@params == null || @params.Values.Count() != 3) { Current.RecordError(ErrorType.Parser, forPosition, "Expected 3 parameters to '" + toDate + "'"); throw new StoppedParsingException(); } if(toDate == "rgb") { return new RGBColorValue( @params.Values.ElementAt(0), @params.Values.ElementAt(1), @params.Values.ElementAt(2) ); } if (toDate == "hsl") { return new HSLColorValue ( @params.Values.ElementAt(0), @params.Values.ElementAt(1), @params.Values.ElementAt(2) ); } } if (toDate.Equals("url", StringComparison.InvariantCultureIgnoreCase)) { var value = ParseGroup(stream, forPosition).Value; if(value is StringValue || value is QuotedStringValue) { return new UrlValue(value); } Current.RecordError(ErrorType.Parser, forPosition, "Expected string or quoted string"); throw new StoppedParsingException(); } } if (buffer.Length == 4 && (stream.HasMore() && stream.Peek() == '(')) { var toDate = buffer.ToString(); if (toDate.Equals("rgba", StringComparison.InvariantCultureIgnoreCase)) { var @params = ParseGroup(stream, forPosition).Value as CommaDelimittedValue; if (@params == null || @params.Values.Count() != 4) { Current.RecordError(ErrorType.Parser, forPosition, "Expected 4 parameters to '" + toDate + "'"); throw new StoppedParsingException(); } return new RGBAColorValue( @params.Values.ElementAt(0), @params.Values.ElementAt(1), @params.Values.ElementAt(2), @params.Values.ElementAt(3) ); } } if (buffer.Length == 5 && (stream.HasMore() && stream.Peek() == '(')) { var toDate = buffer.ToString(); if (toDate.Equals("local", StringComparison.InvariantCultureIgnoreCase)) { var val = ParseGroup(stream, forPosition).Value; var comma = val as CommaDelimittedValue; if (comma != null) { Current.RecordError(ErrorType.Parser, forPosition, "Expected 1 parameter to local() value, found "+comma.Values.Count()); throw new StoppedParsingException(); } return new LocalValue(val); } } if (buffer.Length == 6 && (stream.HasMore() && stream.Peek() == '(')) { var toDate = buffer.ToString(); if (toDate.Equals("format", StringComparison.InvariantCultureIgnoreCase)) { var val = ParseGroup(stream, forPosition).Value; var comma = val as CommaDelimittedValue; if (comma != null) { Current.RecordError(ErrorType.Parser, forPosition, "Expected 1 parameter to format() value, found " + comma.Values.Count()); throw new StoppedParsingException(); } return new FormatValue(val); } } } NamedColor color; if (Enum.TryParse<NamedColor>(buffer.ToString(), ignoreCase: true, result: out color)) { return new NamedColorValue(color); } var str = buffer.ToString(); if (str.Contains("!")) { var i = str.IndexOf('!'); var left = str.Substring(0, i); var right = str.Substring(i); var ret = new List<Value>(); var lhs = Parse(left, forPosition); var rhs = Parse(right, forPosition); if (lhs is CompoundValue) { ret.AddRange(((CompoundValue)lhs).Values); } else { ret.Add(lhs); } if (rhs is CompoundValue) { ret.AddRange(((CompoundValue)rhs).Values); } else { ret.Add(rhs); } return new CompoundValue(ret); } return new StringValue(buffer.ToString()); }
internal static SpriteBlock ParseSpriteDeclaration(ParserStream stream) { var start = stream.Position; stream.AdvancePast("("); var ignored = new StringBuilder(); var adTo = stream.ScanUntil(ignored, '"', '\''); if (ignored.ToString().Any(a => !char.IsWhiteSpace(a))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } if (adTo == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var nameStart = stream.Position; var name = new StringBuilder(); name.Append(adTo.Value); stream.ScanUntil(name, adTo.Value); name.Append(adTo.Value); var nameStop = stream.Position; stream.AdvancePast(")"); var rules = ParseSpriteRules(stream); return new SpriteBlock((QuotedStringValue)Value.Parse(name.ToString(), nameStart, nameStop, Current.CurrentFilePath), rules, start, stream.Position, Current.CurrentFilePath); }
internal static List<SpriteRule> ParseSpriteRules(ParserStream stream) { var ret = new List<SpriteRule>(); stream.AdvancePast("{"); while (stream.HasMore() && stream.Peek() != '}') { var c = stream.Peek(); if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); } else { ret.Add(ParseSpriteRule(stream)); } } stream.AdvancePast("}"); return ret; }
internal static MoreVariable ParseMoreVariable(string name, ParserStream stream, int start) { name = name.Trim(); var valueStart = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = Value.Parse(valueStr.ToString().Trim(), valueStart, stream.Position, Current.CurrentFilePath, allowSelectorIncludes: true); if (name.ToLower().In(ReservedWords)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "'" + name + "' cannot be a variable name."); } return new MoreVariable(name, value, start, stream.Position, Current.CurrentFilePath); }
internal static Value ParseMoreValue(ParserStream stream) { var start = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = valueStr.ToString(); return Value.Parse(value, start, stream.Position, Current.CurrentFilePath); }
internal static FontFaceBlock ParseFontFace(ParserStream stream, int start) { var ignored = new StringBuilder(); stream.ScanUntil(ignored, '{'); var rules = ParseCssRules(InvalidSelector.Singleton, stream); return new FontFaceBlock(rules, start, stream.Position, Current.CurrentFilePath); }
internal static Value ParseFontLikeValue(ParserStream stream) { var start = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = valueStr.ToString(); // The shorthand isn't in use, so we can handle this if(value.IndexOf('/') == -1) { return MoreValueParser.Parse(value, Position.Create(start, stream.Position, Current.CurrentFilePath)); } return new StringValue(value); }
internal static MediaBlock ParseMediaDirective(ParserStream stream) { var start = stream.Position; var media = new StringBuilder(); stream.ScanUntil(media, '{'); var mediaStr = media.ToString().Trim(); if (mediaStr.IsNullOrEmpty()) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected media list"); throw new StoppedParsingException(); } var mediaQuery = MediaQueryParser.Parse(mediaStr, Position.Create(start, stream.Position, Current.CurrentFilePath)); var contained = new List<Block>(); char c; while ((c = stream.Peek()) != '}') { if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } // More directive (probably) if (c == '@') { contained.Add(ParseDirective(stream)); continue; } // Selector + block time! contained.Add(ParseSelectorAndBlock(stream)); } var notAllowed = contained.Where(x => !(x is SelectorAndBlock || x is MoreVariable)); foreach (var illegal in notAllowed) { Current.RecordError(ErrorType.Parser, illegal, "@media can only contain blocks and variable declarations"); } if (notAllowed.Count() != 0) { throw new StoppedParsingException(); } // Skip past } stream.Advance(); return new MediaBlock(mediaQuery, contained, start, stream.Position, Current.CurrentFilePath); }
internal static KeyFramesBlock ParseKeyFramesDirective(string prefix, ParserStream stream, int start) { var buffer = new StringBuilder(); stream.ScanUntil(buffer, '{'); var name = buffer.ToString().Trim(); if (name.Length == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected a name for the keyframe animation"); throw new StoppedParsingException(); } if((name[0] != '-' && char.IsDigit(name[0])) || (name[0] == '-' && name.Length > 1 && char.IsDigit(name[1])) || name.Any(a => a != '-' && !char.IsLetterOrDigit(a))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Animation name `"+name+"` is not a valid identifier"); throw new StoppedParsingException(); } var frames = new List<KeyFrame>(); var rules = new List<VariableProperty>(); while (stream.HasMore()) { var c = stream.Peek(); if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } if (c == '}') { break; } if (c == '@') { var rule = ParseMixinOrVariableRule(stream); if (!(rule is VariableProperty)) { Current.RecordError(ErrorType.Parser, rule, "Expected variable declaration"); throw new StoppedParsingException(); } rules.Add((VariableProperty)rule); continue; } frames.Add(ParseKeyFrame(stream)); } if (stream.Peek() != '}') { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '}'"); throw new StoppedParsingException(); } stream.Advance(); // Skip } return new KeyFramesBlock(prefix, name, frames, rules, start, stream.Position, Current.CurrentFilePath); }
internal static KeyFrame ParseKeyFrame(ParserStream stream) { var start = stream.Position; var buffer = new StringBuilder(); stream.ScanUntil(buffer, '{'); var percents = new List<decimal>(); var percentsStr = buffer.ToString(); foreach (var p in percentsStr.Split(',').Select(s => s.Trim())) { if (p.Length == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected `from`, `to`, or a percentage"); throw new StoppedParsingException(); } decimal percent; if (p.Equals("from", StringComparison.InvariantCultureIgnoreCase)) { percent = 0; } else { if (p.Equals("to", StringComparison.InvariantCultureIgnoreCase)) { percent = 100; } else { if (!p.EndsWith("%") || !decimal.TryParse(p.Substring(0, p.Length -1 ), out percent)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected `from`, `to`, or a percentage. Found `" + p + "`"); throw new StoppedParsingException(); } } } percents.Add(percent); } var rules = ParseCssRules(InvalidSelector.Singleton, stream); return new KeyFrame(percents, rules, start, stream.Position, Current.CurrentFilePath); }
internal static Property ParseMixinOrVariableRule(ParserStream stream) { var start = stream.Position; var name = new StringBuilder(); stream.Advance(); // Skip @ bool trimmingWhiteSpace = false; while (stream.HasMore() && !stream.Peek().In('(', '=')) { // Check for nested media block syntax if (name.ToString().Equals("media", StringComparison.InvariantCultureIgnoreCase)) { return(ParseInnerMediaDirective(stream)); } var c = stream.Read(); if (char.IsWhiteSpace(c)) { trimmingWhiteSpace = true; continue; } if (trimmingWhiteSpace || (!char.IsLetterOrDigit(c) && !c.In('-', '_'))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Unexpected character '" + c + "'"); throw new StoppedParsingException(); } name.Append(c); } if (!stream.Peek().In('(', '=')) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '(' or '='"); throw new StoppedParsingException(); } if (stream.Peek() == '=') { stream.Advance(); var localValue = ParseMoreValue(stream); var varName = name.ToString().Trim(); if (varName.ToLower().In(ReservedWords)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "'" + varName + "' cannot be a variable name."); } return(new VariableProperty(varName, localValue, start, stream.Position, Current.CurrentFilePath)); } stream.Advance(); var startParams = stream.Position; var paramStart = stream.Position; var @params = new StringBuilder(); stream.ScanUntilWithNesting(@params, ')'); var paramStop = stream.Position; var options = new StringBuilder(); var optionsStart = stream.Position; stream.ScanUntil(options, ';'); var nameStr = name.ToString().Trim(); var paramsStr = @params.ToString().Trim(); var optionsStr = options.ToString().Trim(); var optional = optionsStr.Contains('?'); var overrides = optionsStr.Contains('!'); var unexpected = optionsStr.Where(c => !char.IsWhiteSpace(c) && c != '?' && c != '!'); if (unexpected.Count() != 0) { if (unexpected.Count() == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, optionsStart + options.Length, Current.CurrentFilePath), "Unexpected character '" + unexpected.ElementAt(0) + "'"); } else { Current.RecordError( ErrorType.Parser, Position.Create( start, optionsStart + options.Length, Current.CurrentFilePath ), "Unexpected characters " + string.Join(", ", unexpected.Select(c => "'" + c + "'")) ); } throw new StoppedParsingException(); } if (name.Length == 0) { if (optional) { Current.RecordWarning(ErrorType.Parser, Position.Create(start, optionsStart + options.Length, Current.CurrentFilePath), "Include directives are always optional, no trailing '?' is needed."); } return(new IncludeSelectorProperty(Selector.Parse(paramsStr, paramStart, paramStop, Current.CurrentFilePath), overrides, start, stream.Position, Current.CurrentFilePath)); } if (name.ToString().Trim().Equals("reset", StringComparison.InvariantCultureIgnoreCase)) { if (paramsStr.Trim().Length != 0) { return(new ResetProperty(Selector.Parse(paramsStr, paramStart, paramStop, Current.CurrentFilePath), start, stream.Position, Current.CurrentFilePath)); } return(new ResetSelfProperty(InvalidSelector.Singleton, start, stream.Position, Current.CurrentFilePath)); } return(new MixinApplicationProperty(nameStr, ParseApplicationParameters(paramsStr, startParams), optional: optional, overrides: overrides, start: start, stop: stream.Position, filePath: Current.CurrentFilePath)); }
internal static InnerMediaProperty ParseInnerMediaDirective(ParserStream stream) { var start = stream.Position; var media = new StringBuilder(); stream.ScanUntil(media, '{'); var mediaStr = media.ToString().Trim(); if (mediaStr.IsNullOrEmpty()) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected media list"); throw new StoppedParsingException(); } var mediaQuery = MediaQueryParser.Parse(mediaStr, Position.Create(start, stream.Position, Current.CurrentFilePath)); var props = ParseCssRules(InvalidSelector.Singleton, stream); var blockEquiv = new SelectorAndBlock(InvalidSelector.Singleton, props, null, start, stream.Position, Current.CurrentFilePath); return new InnerMediaProperty(mediaQuery, blockEquiv, start, stream.Position, Current.CurrentFilePath); }
internal static ResetBlock ParseResetDirective(ParserStream stream, int start) { var ignored = new StringBuilder(); stream.ScanUntil(ignored, '{'); var contained = new List<Block>(); char c; while ((c = stream.Peek()) != '}') { if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } // More directive (probably) if (c == '@') { contained.Add(ParseDirective(stream)); continue; } // Selector + block time! contained.Add(ParseSelectorAndBlock(stream)); } var notAllowed = contained.Where(x => !(x is SelectorAndBlock || x is MoreVariable)); foreach (var illegal in notAllowed) { Current.RecordError(ErrorType.Parser, illegal, "@reset can only contain blocks and variable declarations"); } if (notAllowed.Count() != 0) { throw new StoppedParsingException(); } // The whole @reset{} block disappears pretty quickly, but the actual // blocks need to know what they were "near" for variable resolution. var variables = contained.OfType<MoreVariable>(); var bound = new List<Block>(); foreach (var x in contained) { var asBlock = x as SelectorAndBlock; if (asBlock != null) { bound.Add(asBlock.InReset(variables)); } else { bound.Add(x); } } // Skip past } stream.Advance(); return new ResetBlock(bound, start, stream.Position, Current.CurrentFilePath); }
internal static SpriteRule ParseSpriteRule(ParserStream stream) { var start = stream.Position; if (stream.Peek() != '@') { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '@'"); throw new StoppedParsingException(); } stream.Advance(); // Advance past @ var name = new StringBuilder(); while (stream.HasMore() && stream.Peek() != '=') { name.Append(stream.Read()); } stream.AdvancePast("="); // Advance past = var ignored = new StringBuilder(); var quote = stream.ScanUntil(ignored, '\'', '"'); if (quote == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var valueStart = stream.Position; var valueStr = new StringBuilder(); valueStr.Append(quote.Value); stream.ScanUntil(valueStr, quote.Value); valueStr.Append(quote.Value); stream.AdvancePast(";"); var value = (QuotedStringValue)Value.Parse(valueStr.ToString(), valueStart, stream.Position, Current.CurrentFilePath); return new SpriteRule(name.ToString().Trim(), value, start, stream.Position, Current.CurrentFilePath); }
internal static Property ParseRule(ParserStream stream) { var start = stream.Position; if (stream.Peek() == '@') { return ParseMixinOrVariableRule(stream); } var ruleName = new StringBuilder(); var found = stream.ScanUntil(ruleName, ';', '{', '}'); // Final semi-colon in a block is optional, so inject a ';' if we encounter a '}' in this case if (found == '}') { found = ';'; // trailing semi-colon may be optional if (ruleName[ruleName.Length - 1] == ';') { ruleName = ruleName.Remove(ruleName.Length - 1, 1); } stream.PushBack(new[] { '}' }); } if (found == '{') { var nestedBlock = ParseSelectorAndBlock(stream, ruleName.ToString().Trim()); return new NestedBlockProperty(nestedBlock, start, stream.Position); } if (found == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected ';', '{', or '}'"); throw new StoppedParsingException(); } var colon = ruleName.ToString().IndexOf(':'); if (colon == -1) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected ':'"); throw new StoppedParsingException(); } var name = ruleName.ToString().Substring(0, colon).Trim(); var valStr = ruleName.ToString().Substring(colon + 1).Trim() + ";"; if (valStr == ";") { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected value"); throw new StoppedParsingException(); } stream.PushBack(valStr); Value value; if (name.Equals("font", StringComparison.InvariantCultureIgnoreCase)) { // font has a goofy shorthand, needs special treatment value = ParseFontLikeValue(stream); } else { if (name.Equals("background-position")) { // likewise, background-position can often look like a math statement value = ParseBackgroundPositionValue(stream); } else { if (name.Equals("border-radius")) { value = ParseFontLikeValue(stream); } else { value = ParseMoreValue(stream); } } } return new NameValueProperty(name, value, start, stream.Position, Current.CurrentFilePath); }
internal static SelectorAndBlock ParseSelectorAndBlock(ParserStream stream, string selectorStr = null) { var mark = stream.Position; int selectorStop, selectorStart; if (selectorStr.IsNullOrEmpty()) { selectorStart = stream.Position; var selector = new StringBuilder(); stream.ScanUntil(selector, '{'); selectorStop = stream.Position; selectorStr = selector.ToString().Trim(); if (selectorStr.IsNullOrEmpty()) { Current.RecordError(ErrorType.Parser, Position.Create(mark, stream.Position, Current.CurrentFilePath), "Expected selector"); throw new StoppedParsingException(); } } else { selectorStop = mark; selectorStart= selectorStop - selectorStr.Length; } var sel = Selector.Parse(selectorStr, selectorStart, selectorStop, Current.CurrentFilePath); var cssRules = ParseCssRules(sel, stream); // Bind @reset() properties to their location w.r.t. selectors cssRules = cssRules.Select( delegate(Property prop) { if (!(prop is ResetSelfProperty)) return prop; return ((ResetSelfProperty)prop).BindToSelector(sel); } ).ToList(); return new SelectorAndBlock(sel, cssRules, null, mark, stream.Position, Current.CurrentFilePath); }
internal static Property ParseRule(ParserStream stream) { var start = stream.Position; if (stream.Peek() == '@') { return(ParseMixinOrVariableRule(stream)); } var ruleName = new StringBuilder(); var found = stream.ScanUntil(ruleName, ';', '{', '}'); // Final semi-colon in a block is optional, so inject a ';' if we encounter a '}' in this case if (found == '}') { found = ';'; // trailing semi-colon may be optional if (ruleName[ruleName.Length - 1] == ';') { ruleName = ruleName.Remove(ruleName.Length - 1, 1); } stream.PushBack(new[] { '}' }); } if (found == '{') { var nestedBlock = ParseSelectorAndBlock(stream, ruleName.ToString().Trim()); return(new NestedBlockProperty(nestedBlock, start, stream.Position)); } if (found == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected ';', '{', or '}'"); throw new StoppedParsingException(); } var colon = ruleName.ToString().IndexOf(':'); if (colon == -1) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected ':'"); throw new StoppedParsingException(); } var name = ruleName.ToString().Substring(0, colon).Trim(); var valStr = ruleName.ToString().Substring(colon + 1).Trim() + ";"; if (valStr == ";") { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected value"); throw new StoppedParsingException(); } stream.PushBack(valStr); Value value; if (name.Equals("font", StringComparison.InvariantCultureIgnoreCase)) { // font has a goofy shorthand, needs special treatment value = ParseFontLikeValue(stream); } else { if (name.Equals("background-position")) { // likewise, background-position can often look like a math statement value = ParseBackgroundPositionValue(stream); } else { if (name.Equals("border-radius")) { value = ParseFontLikeValue(stream); } else { value = ParseMoreValue(stream); } } } return(new NameValueProperty(name, value, start, stream.Position, Current.CurrentFilePath)); }
internal static Value ParseFuncValue(ParserStream stream, IPosition forPosition, bool allowSelectorIncludes) { stream.Advance(); // skip @ var buffer = new StringBuilder(); var c = stream.Peek(); while (char.IsWhiteSpace(c)) { stream.Advance(); c = stream.Peek(); } if (!char.IsLetter(c) && c != '(') { Current.RecordError(ErrorType.Parser, forPosition, "Expected letter or '(', found '" + c + "'"); throw new StoppedParsingException(); } if (c != '(') { buffer.Append(stream.Read()); while (stream.HasMore() && char.IsLetterOrDigit(stream.Peek())) { buffer.Append(stream.Read()); } } var funcName = buffer.ToString(); buffer.Clear(); while (stream.HasMore() && char.IsWhiteSpace(stream.Peek())) { buffer.Append(stream.Read()); } if (stream.HasMore() && stream.Peek() == '(') { stream.Read(); // Skip ( buffer.Clear(); stream.ScanUntilWithNesting(buffer, ')'); if (funcName.Length == 0) { if (allowSelectorIncludes) { var sel = Selector.Parse(buffer.ToString(), forPosition.Start, forPosition.Stop, forPosition.FilePath); return new IncludeSelectorValue(sel); } return new StringValue("@(" + buffer + ")"); } var value = MoreValueParser.Parse(buffer.ToString().Trim(), forPosition, allowSelectorIncludes: true); if (value is NotFoundValue) throw new StoppedParsingException(); var @params = new List<Value>(); if (value is CommaDelimittedValue) { @params.AddRange(((CommaDelimittedValue)value).Values); } else { @params.Add(value); } return new FuncAppliationValue(funcName, @params); } return new FuncValue(funcName); }
internal static Block ParseDirective(ParserStream stream) { const string import = "import"; const string @using = "using"; const string sprite = "sprite"; const string charset = "charset"; const string media = "media"; const string keyframes = "keyframes"; const string mozKeyframes = "-moz-keyframes"; const string webKeyframes = "-webkit-keyframes"; const string fontFace = "font-face"; const string reset = "reset"; stream.Advance(); // Advance past @ var bufferStart = stream.Position; var buffer = new StringBuilder(); var next = stream.WhichNextInsensitive(buffer, @using, sprite, import, charset, media, keyframes, mozKeyframes, webKeyframes, fontFace, reset); if (next == @using) { return ParseUsingDirective(stream); } if (next == sprite) { return ParseSpriteDeclaration(stream); } if (next == import) { return ParseImportDirective(stream); } if (next == charset) { return ParseCharsetDirective(stream); } if (next == media) { return ParseMediaDirective(stream); } if (next.In(keyframes, mozKeyframes, webKeyframes)) { string prefix = ""; if (next == mozKeyframes) prefix = "-moz-"; if (next == webKeyframes) prefix = "-webkit-"; return ParseKeyFramesDirective(prefix, stream, bufferStart); } if (next == fontFace) { return ParseFontFace(stream, bufferStart); } if (next == reset) { return ParseResetDirective(stream, bufferStart); } stream.PushBack(buffer.ToString()); var leader = new StringBuilder(); var eqOrPara = stream.ScanUntil(leader, '=', '('); if (eqOrPara == '=') { return ParseMoreVariable(leader.ToString(), stream, bufferStart); } return ParseMixinDeclaration(leader.ToString(), stream); }
private static QuotedStringValue ParseQuotedString(char quote, ParserStream stream, IPosition forPosition) { stream.Advance(); // skip the quote var buffer = new StringBuilder(); var x = stream.ScanUntil(buffer, quote); if (x == null) { Current.RecordError(ErrorType.Parser, forPosition, "Expected '" + quote + "'"); throw new StoppedParsingException(); } return new QuotedStringValue(buffer.ToString()); }
internal static List<Property> ParseCssRules(Selector selector, ParserStream stream) { var ret = new List<Property>(); while (stream.HasMore() && stream.Peek() != '}') { var c = stream.Peek(); if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); } else { ret.Add(ParseRule(stream)); } } stream.AdvancePast("}"); return ret; }
private static Value ParseNumber(ParserStream stream, IPosition forPosition) { var pushbackBuffer = new StringBuilder(); var buffer = new StringBuilder(); bool negate = false; if (stream.Peek() == '-') { negate = true; stream.Advance(); pushbackBuffer.Append('-'); } bool decimalPassed = false; while (stream.HasMore() && (char.IsDigit(stream.Peek()) || (stream.Peek() == '.' && !decimalPassed))) { var c = stream.Read(); buffer.Append(c); pushbackBuffer.Append(c); if (c == '.') decimalPassed = true; } Unit? unit = null; decimal digit; if (!decimal.TryParse(buffer.ToString(), out digit)) { // Looked like a number, but wasn't! stream.PushBack(pushbackBuffer.ToString()); return ParseString(stream, forPosition); } if (negate) digit *= -1m; buffer.Clear(); while (stream.HasMore() && char.IsWhiteSpace(stream.Peek())) { buffer.Append(stream.Read()); } var nextFour = new StringBuilder(); for (int i = 0; i < 4; i++) { if (stream.HasMore()) { nextFour.Append(stream.Read()); } } var possibleUnit = nextFour.ToString(); List<char> pushBack; unit = ParsePossibleUnit(possibleUnit, out pushBack); if (unit == null) { stream.PushBack(nextFour.ToString()); stream.PushBack(buffer.ToString()); } else { stream.PushBack(pushBack); return new NumberWithUnitValue(digit, unit.Value); } return new NumberValue(digit); }
internal static CssCharset ParseCharsetDirective(ParserStream stream) { var start = stream.Position; var ignored = new StringBuilder(); var quote = stream.ScanUntil(ignored, '"', '\''); if (quote == null) { Current.RecordError(ErrorType.Parser, Model.Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var isoName = new StringBuilder(); stream.ScanUntil(isoName, quote.Value); stream.AdvancePast(";"); var isoNameStr = isoName.ToString(); if (!CssCharset.KnownCharset(isoNameStr)) { Current.RecordWarning(ErrorType.Parser, Model.Position.Create(start, stream.Position, Current.CurrentFilePath), "Unrecognized charset"); } return new CssCharset(new QuotedStringValue(isoNameStr), start, stream.Position); }
internal static ResetBlock ParseResetDirective(ParserStream stream, int start) { var ignored = new StringBuilder(); stream.ScanUntil(ignored, '{'); var contained = new List <Block>(); char c; while ((c = stream.Peek()) != '}') { if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } // More directive (probably) if (c == '@') { contained.Add(ParseDirective(stream)); continue; } // Selector + block time! contained.Add(ParseSelectorAndBlock(stream)); } var notAllowed = contained.Where(x => !(x is SelectorAndBlock || x is MoreVariable)); foreach (var illegal in notAllowed) { Current.RecordError(ErrorType.Parser, illegal, "@reset can only contain blocks and variable declarations"); } if (notAllowed.Count() != 0) { throw new StoppedParsingException(); } // The whole @reset{} block disappears pretty quickly, but the actual // blocks need to know what they were "near" for variable resolution. var variables = contained.OfType <MoreVariable>(); var bound = new List <Block>(); foreach (var x in contained) { var asBlock = x as SelectorAndBlock; if (asBlock != null) { bound.Add(asBlock.InReset(variables)); } else { bound.Add(x); } } // Skip past } stream.Advance(); return(new ResetBlock(bound, start, stream.Position, Current.CurrentFilePath)); }
// Basically, it's realy common for background-position to be something like "0px -20px", which shouldn't be interpretted as a math value // so detect the case where two number with unit values are separated by whitespace and treat them as a compound value instead // otherwise, fallback to normal parsing internal static Value ParseBackgroundPositionValue(ParserStream stream) { var start = stream.Position; var valueStr = new StringBuilder(); stream.ScanUntilWithNesting(valueStr, ';'); var value = valueStr.ToString(); var parts = value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var pos = Position.Create(start, stream.Position, Current.CurrentFilePath); // something's up if (parts.Length != 2) { return MoreValueParser.Parse(value, pos); } var p1 = parts[0]; var p2 = parts[1]; var v1 = MoreValueParser.Parse(p1, pos); var v2 = MoreValueParser.Parse(p2, pos); if (v1 is NumberWithUnitValue && v2 is NumberWithUnitValue) { return new CompoundValue(v1, v2); } return MoreValueParser.Parse(value, pos); }
internal static Value ParseImpl(ParserStream stream, IPosition forPosition, bool allowSelectorIncludes) { Value ret = null; while (stream.HasMore()) { var c = stream.Peek(); if (char.IsWhiteSpace(c)) { stream.AdvancePastWhiteSpace(); continue; } if (ret != null) { if (c.In('+', '-', '*', '/', '%')) { ret = ParseMathValue(c, ret, stream, forPosition); continue; } if (c == '?') { stream.Advance(); // skip ? if (stream.HasMore() && stream.Peek() == '?') { if (ret == null) { Current.RecordError(ErrorType.Parser, forPosition, "Expected value, found '??'"); throw new StoppedParsingException(); } stream.Advance(); // skip second ? var rhs = ParseImpl(stream, forPosition, allowSelectorIncludes); ret = new MathValue(ret, Operator.Take_Exists, rhs); continue; } ret = new LeftExistsValue(ret); continue; } } if (char.IsDigit(c) || c == '.' || c == '-') { ret = Combine(ret, ParseNumber(stream, forPosition)); continue; } if (c == '(') { ret = Combine(ret, ParseGroup(stream, forPosition)); continue; } if (c.In('\'', '"')) { ret = Combine(ret, ParseQuotedString(c, stream, forPosition)); continue; } if (c == '#') { ret = Combine(ret, ParseHashColor(stream, forPosition)); continue; } if (c == '@') { ret = Combine(ret, ParseFuncValue(stream, forPosition, allowSelectorIncludes)); continue; } ret = Combine(ret, ParseString(stream, forPosition)); } return(ret); }
internal static Using ParseUsingDirective(ParserStream stream) { var start = stream.Position; var ignored = new StringBuilder(); var quote = stream.ScanUntil(ignored, '"', '\''); if (quote == null) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quotation mark"); throw new StoppedParsingException(); } var file = new StringBuilder(); stream.ScanUntil(file, quote.Value); int mediaStart = stream.Position; var mediaBuff = new StringBuilder(); stream.ScanUntil(mediaBuff, ';'); int mediaEnd = stream.Position; MediaQuery media; var mediaStr = mediaBuff.ToString().Trim(); if (mediaStr.Length > 0) { media = MediaQueryParser.Parse(mediaStr, Position.Create(mediaStart, mediaEnd, Current.CurrentFilePath)); } else { media = new MediaType(Media.all, Position.NoSite); } return new Using(file.ToString(), media, start, stream.Position, Current.CurrentFilePath); }
internal static MixinBlock ParseMixinDeclaration(string name, ParserStream stream) { var start = stream.Position; var @params = new StringBuilder(); stream.ScanUntil(@params, ')'); name = name.Trim(); if (name.Length == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected mixin name"); throw new StoppedParsingException(); } if (name.ToLower().In(ReservedWords)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "'" + name + "' cannot be the name of a mixin."); } stream.AdvancePast("{"); return new MixinBlock( name, ParseMixinDeclarationParameter(@params.ToString(), start), ParseCssRules(InvalidSelector.Singleton, stream), start, stream.Position, Current.CurrentFilePath ); }
internal static Import ParseImportDirective(ParserStream stream) { var start = stream.Position; var buffer = new StringBuilder(); stream.ScanUntilWithNesting(buffer, ';'); var toParse = buffer.ToString().Trim(); Value val; MediaQuery media; string mediaStr; if (Regex.IsMatch(toParse, @"url\s*?\(", RegexOptions.IgnoreCase)) { var i = toParse.IndexOf(')'); if (i == -1) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected ')'"); ; throw new StoppedParsingException(); } val = Value.Parse(toParse.Substring(0, i + 1), start, start + i + 1, Current.CurrentFilePath); mediaStr = toParse.Substring(i + 1); } else { if (!(toParse.StartsWith("\"") || toParse.StartsWith("'"))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected quote"); throw new StoppedParsingException(); } var i = toParse.LastIndexOf(toParse[0]); if (i == -1) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '" + toParse[0] + "'"); throw new StoppedParsingException(); } val = Value.Parse(toParse.Substring(0, i + 1), start, start + i + 1, Current.CurrentFilePath); mediaStr = toParse.Substring(i + 1); } mediaStr = mediaStr.Trim(); if (mediaStr.Length > 0) { media = MediaQueryParser.Parse(mediaStr, Position.Create(start, stream.Position, Current.CurrentFilePath)); } else { media = new MediaType(Media.all, Position.Create(start, stream.Position, Current.CurrentFilePath)); } return new Import(val, media, start, stream.Position, Current.CurrentFilePath); }
public void StreamPushBack() { using (var stream = new ParserStream(new StringReader(""))) { var buffer = new StringBuilder(); stream.PushBack("hello world!"); stream.ScanUntil(buffer, '!'); Assert.AreEqual("hello world", buffer.ToString()); Assert.IsFalse(stream.HasMore()); buffer.Clear(); stream.PushBack("world!"); stream.PushBack("hello "); stream.ScanUntil(buffer, '!'); Assert.AreEqual("hello world", buffer.ToString()); Assert.IsFalse(stream.HasMore()); } }
internal static Property ParseMixinOrVariableRule(ParserStream stream) { var start = stream.Position; var name = new StringBuilder(); stream.Advance(); // Skip @ bool trimmingWhiteSpace = false; while (stream.HasMore() && !stream.Peek().In('(', '=')) { // Check for nested media block syntax if (name.ToString().Equals("media", StringComparison.InvariantCultureIgnoreCase)) { return ParseInnerMediaDirective(stream); } var c = stream.Read(); if (char.IsWhiteSpace(c)) { trimmingWhiteSpace = true; continue; } if (trimmingWhiteSpace || (!char.IsLetterOrDigit(c) && !c.In('-', '_'))) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Unexpected character '" + c + "'"); throw new StoppedParsingException(); } name.Append(c); } if (!stream.Peek().In('(', '=')) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected '(' or '='"); throw new StoppedParsingException(); } if (stream.Peek() == '=') { stream.Advance(); var localValue = ParseMoreValue(stream); var varName = name.ToString().Trim(); if (varName.ToLower().In(ReservedWords)) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "'" + varName + "' cannot be a variable name."); } return new VariableProperty(varName, localValue, start, stream.Position, Current.CurrentFilePath); } stream.Advance(); var startParams = stream.Position; var paramStart = stream.Position; var @params = new StringBuilder(); stream.ScanUntilWithNesting(@params, ')'); var paramStop = stream.Position; var options = new StringBuilder(); var optionsStart = stream.Position; stream.ScanUntil(options, ';'); var nameStr = name.ToString().Trim(); var paramsStr = @params.ToString().Trim(); var optionsStr = options.ToString().Trim(); var optional = optionsStr.Contains('?'); var overrides = optionsStr.Contains('!'); var unexpected = optionsStr.Where(c => !char.IsWhiteSpace(c) && c != '?' && c != '!'); if (unexpected.Count() != 0) { if (unexpected.Count() == 0) { Current.RecordError(ErrorType.Parser, Position.Create(start, optionsStart + options.Length, Current.CurrentFilePath), "Unexpected character '" + unexpected.ElementAt(0) + "'"); } else { Current.RecordError( ErrorType.Parser, Position.Create( start, optionsStart + options.Length, Current.CurrentFilePath ), "Unexpected characters "+ string.Join(", ", unexpected.Select(c => "'"+c+"'")) ); } throw new StoppedParsingException(); } if (name.Length == 0) { if (optional) { Current.RecordWarning(ErrorType.Parser, Position.Create(start, optionsStart + options.Length, Current.CurrentFilePath), "Include directives are always optional, no trailing '?' is needed."); } return new IncludeSelectorProperty(Selector.Parse(paramsStr, paramStart, paramStop, Current.CurrentFilePath), overrides, start, stream.Position, Current.CurrentFilePath); } if (name.ToString().Trim().Equals("reset", StringComparison.InvariantCultureIgnoreCase)) { if (paramsStr.Trim().Length != 0) { return new ResetProperty(Selector.Parse(paramsStr, paramStart, paramStop, Current.CurrentFilePath), start, stream.Position, Current.CurrentFilePath); } return new ResetSelfProperty(InvalidSelector.Singleton, start, stream.Position, Current.CurrentFilePath); } return new MixinApplicationProperty(nameStr, ParseApplicationParameters(paramsStr, startParams), optional: optional, overrides: overrides, start: start, stop: stream.Position, filePath: Current.CurrentFilePath); }