/// <summary> /// (BNF) declaration : property ':' S* value; /// (BNF) property : IDENT S*; /// </summary> /// <returns></returns> private CssDeclaration ParseDeclaration() { CssDeclaration declaration = new CssDeclaration(); char ch; while (this.Read(out ch) && (Char.IsWhiteSpace(ch) || ch == ';')) { // skip whitespace, and empty declarations } // consume property name switch (ch) { case '{': case ':': //case ';': { throw new SyntaxError("Declaration missing property name", this.reader.FilePath, this.reader.Line, this.reader.Column); } case '}': { // no more declarations return null; } } // read property, starting with current char int start = this.Position; while (this.Read(out ch) && !Char.IsWhiteSpace(ch) && ch != ':') { // consume property name switch (ch) { case '{': //case ':': case ';': { throw new SyntaxError("Invalid CSS property name: "+this.Copy(start), this.reader.FilePath, this.reader.Line, this.reader.Column); } case '}': { this.PutBack(); goto case ';'; } } } declaration.Property = this.Copy(start); if (Char.IsWhiteSpace(ch)) { while (this.Read(out ch) && (Char.IsWhiteSpace(ch))) { // skip whitespace } } if (ch != ':') { // missing the property delim and value if (ch == ';' || ch == '}') { // these are good chars for resyncing // so put them back on the stream to // not create subsequent errors this.PutBack(); } throw new SyntaxError("Expected <property> : <value>", this.reader.FilePath, this.reader.Line, this.reader.Column); } CssValueList value = this.ParseValue(); declaration.Value = value; return declaration; }
/* * /// <summary> * /// (BNF) block : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*; * /// </summary> * /// <returns></returns> * private CssBlock ParseBlock() * { * CssBlock block = new CssBlock(); * int start = this.Position;// start with current char * * char ch; * while (this.Read(out ch)) * { * switch (ch) * { * case '@': * { * // copy anything before * string value = this.Copy(start); * if (value != null && !String.IsNullOrEmpty(value = value.Trim())) * { * CssString any = new CssString(); * any.Value = value; * block.Values.Add(any); * } * * // parse inner block * CssAtRule atRule = this.ParseAtRule(); * block.Values.Add(atRule); * * // reset start with current char * start = this.Position; * break; * } * case '{': * { * // copy anything before * string value = this.Copy(start); * if (value != null && !String.IsNullOrEmpty(value = value.Trim())) * { * CssString any = new CssString(); * any.Value = value; * block.Values.Add(any); * } * * // parse inner block * CssBlock innerBlock = this.ParseBlock(); * block.Values.Add(innerBlock); * * // reset start with current char * start = this.Position; * break; * } * case '}': * { * // copy anything before * string value = this.Copy(start); * if (value != null && !String.IsNullOrEmpty(value = value.Trim())) * { * CssString any = new CssString(); * any.Value = value; * block.Values.Add(any); * } * * return block; * } * } * } * * throw new UnexpectedEndOfFile("Unclosed block", this.reader.FilePath, this.reader.Line, this.reader.Column); * } */ #endregion Block #region RuleSet /// <summary> /// (BNF) ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; /// </summary> /// <returns></returns> private CssRuleSet ParseRuleSet() { char ch; CssRuleSet ruleSet = new CssRuleSet(); ParseSelectors: while (true) { try { CssSelector selector = this.ParseSelector(); if (selector == null) { break; } ruleSet.Selectors.Add(selector); } catch (ParseException ex) { this.errors.Add(ex); while (this.Read(out ch)) { // restabalize on next rulset switch (ch) { case ',': { // continue parsing rest of Selectors goto ParseSelectors; } case '{': { goto ParseDeclarations; } //case ':':// keep going case ';': case '}': { throw new SyntaxError("Invalid selector list", this.reader.FilePath, this.reader.Line, this.reader.Column); } } } } } ParseDeclarations: while (true) { try { CssDeclaration declaration = this.ParseDeclaration(); if (declaration == null) { break; } ruleSet.Declarations.Add(declaration); } catch (ParseException ex) { this.errors.Add(ex); while (this.Read(out ch)) { // restabalize on next declaration switch (ch) { case '{': { throw new SyntaxError("Invalid ruleset", this.reader.FilePath, this.reader.Line, this.reader.Column); } //case ':':// keep going case ';': { // continue parsing rest of delcarations goto ParseDeclarations; } case '}': { // no more declarations return(ruleSet); } } } } } return(ruleSet); }
/// <summary> /// (BNF) declaration : property ':' S* value; /// (BNF) property : IDENT S*; /// </summary> /// <returns></returns> private CssDeclaration ParseDeclaration() { CssDeclaration declaration = new CssDeclaration(); char ch; while (this.Read(out ch) && (Char.IsWhiteSpace(ch) || ch == ';')) { // skip whitespace, and empty declarations } // consume property name switch (ch) { case '{': case ':': //case ';': { throw new SyntaxError("Declaration missing property name", this.reader.FilePath, this.reader.Line, this.reader.Column); } case '}': { // no more declarations return(null); } } // read property, starting with current char int start = this.Position; while (this.Read(out ch) && !Char.IsWhiteSpace(ch) && ch != ':') { // consume property name switch (ch) { case '{': //case ':': case ';': { throw new SyntaxError("Invalid CSS property name: " + this.Copy(start), this.reader.FilePath, this.reader.Line, this.reader.Column); } case '}': { this.PutBack(); goto case ';'; } } } declaration.Property = this.Copy(start); if (Char.IsWhiteSpace(ch)) { while (this.Read(out ch) && (Char.IsWhiteSpace(ch))) { // skip whitespace } } if (ch != ':') { // missing the property delim and value if (ch == ';' || ch == '}') { // these are good chars for resyncing // so put them back on the stream to // not create subsequent errors this.PutBack(); } throw new SyntaxError("Expected <property> : <value>", this.reader.FilePath, this.reader.Line, this.reader.Column); } CssValueList value = this.ParseValue(); declaration.Value = value; return(declaration); }
/// <summary> /// (BNF) at-rule : ATKEYWORD S* any* [ block | ';' S* ]; /// </summary> /// <returns></returns> /// <remarks> /// NOTE: each at-rule might parse differently according to CSS3 /// The @media block for example contains a block of statements /// while other at-rules with a block contain a block of declarations /// </remarks> private CssAtRule ParseAtRule() { CssAtRule atRule = new CssAtRule(); int start = this.Position + 1; // start with first char of ident char ch; while (this.Read(out ch) && !Char.IsWhiteSpace(ch)) { // continue consuming } atRule.Ident = this.Copy(start); while (this.Read(out ch) && Char.IsWhiteSpace(ch)) { // consuming whitespace } start = this.Position; // start with current char do { switch (ch) { case '{': //Block Begin { atRule.Value = this.Copy(start); bool containsRuleSets = String.Equals(atRule.Ident, CssAtRule.MediaIdent, StringComparison.Ordinal); while (true) { while (this.Read(out ch) && Char.IsWhiteSpace(ch)) { // consume whitespace } if (ch == '}') { break; } try { if (containsRuleSets) { // includes @media CssStatement statement = this.ParseStatement(); atRule.Block.Values.Add(statement); } else { // includes @font-face, @page this.PutBack(); CssDeclaration declaration = this.ParseDeclaration(); atRule.Block.Values.Add(declaration); } } catch (ParseException ex) { this.errors.Add(ex); while (this.Read(out ch) && ch != '}') { // restabilize on block end } break; } } return(atRule); } case ';': //At-Rule End { atRule.Value = this.Copy(start); return(atRule); } } } while (this.Read(out ch)); throw new UnexpectedEndOfFile("Unclosed At-Rule", this.reader.FilePath, this.reader.Line, this.reader.Column); }