public static PropertySheet Parse(string propertySheetText, string originalFilename, PropertySheet propertySheet = null ) { var p = new PropertySheetParser(propertySheetText,originalFilename,propertySheet ?? new PropertySheet()); return p.Parse(); }
public void AddImportedSheet(PropertySheet importedSheet) { _importedSheets.Insert(0, importedSheet); }
protected PropertySheetParser(string text, string originalFilename,PropertySheet propertySheet) { _propertySheetText = text; _propertySheet = propertySheet; _filename = originalFilename; }
protected bool Import(string importFilename, string folder) { string filename = importFilename.GetCustomFilePathOrWalkUp(folder) ?? importFilename; if( string.IsNullOrEmpty(filename) || !File.Exists(filename)) { return false; } string document = File.ReadAllText(filename); if (!string.IsNullOrEmpty(document)) { var includedSheet = new PropertySheet(); // parse the contents of that file into the current property sheet. new PropertySheetParser(document, filename, includedSheet).Parse(); foreach (var r in includedSheet.Rules) { r.ParentPropertySheet = _propertySheet; } _propertySheet.ImportedSheets.Add(filename, includedSheet); } return true; }
/// <summary> /// Rules must be created by the property sheet only. /// </summary> /// <param name="propertySheet"> </param> internal Rule(PropertySheet propertySheet) { Name = "*"; ParentPropertySheet = propertySheet; }
private PropertySheet ParseIncludedSheet(string importedAsFilename, string actualFilename, string textContent) { var includedSheet = new PropertySheet() { Filename = actualFilename, ImportedAsFilename = importedAsFilename }; // parse the contents of that file into the current property sheet. new PropertySheetParser(textContent, actualFilename, includedSheet).Parse(); // make sure each rule has the parent propertysheet set to the master. // since the exposed Rules collection is recursive, this sets the value all the way down... foreach (var r in includedSheet.Rules) { r.ParentPropertySheet = _propertySheet; } return includedSheet; }
protected PropertySheet Parse() { var tokenStream = PropertySheetTokenizer.Tokenize(_propertySheetText); var state = ParseState.Global; var enumerator = tokenStream.GetEnumerator(); var importFilename = string.Empty; Token token; Rule rule = null; string ruleName = "*"; string ruleParameter = null; string ruleClass = null; string ruleId = null; PropertyRule property = null; var sourceLocation = new SourceLocation { Row=0, Column = 0, SourceFile = null, }; string propertyName = null; string propertyLabelText = null; string presentlyUnknownValue = null; string collectionName = null; enumerator.MoveNext(); do { token = enumerator.Current; switch (token.Type) { // regardless where we are, skip over whitespace, etc. case TokenType.WhiteSpace: case TokenType.LineComment: case TokenType.MultilineComment: continue; } // System.Console.WriteLine("Token : {0} == [{1}]", token.Type, token.Data); switch (state) { case ParseState.Global: sourceLocation = new SourceLocation { // will be the start of the next new rule. Row = token.Row, Column = token.Column, SourceFile = _filename, }; switch (token.Type) { case TokenType.Identifier: // look for identifier as the start of a selector if( token.Data == "@import" ) { // special case to handle @import rules state = ParseState.Import; continue; } state = ParseState.Selector; ruleName = token.Data; continue; case TokenType.Dot: state = ParseState.SelectorDot; ruleName = "*"; // take next identifier as the classname continue; case TokenType.Pound: state = ParseState.SelectorPound; ruleName = "*"; // take next identifier as the id continue; case TokenType.Semicolon: // tolerate extra semicolons. continue; default: throw new EndUserParseException(token, _filename, "PSP 100", "Expected one of '.' , '#' or identifier"); } case ParseState.Import : switch (token.Type) { case TokenType.StringLiteral: case TokenType.Identifier: state = ParseState.ImportFilename; importFilename = token.Data; continue; default: throw new EndUserParseException(token, _filename, "PSP 121", "Expected a string literal for filename (Missing semicolon?)"); } case ParseState.ImportFilename: switch (token.Type) { case TokenType.Semicolon: // import the specified file and insert it's rules into this sheet. string document = null; string filename = null; if (!string.IsNullOrEmpty(_filename)) { // it's either a full path to a file, or a relative path to the current document. try { var folder = Path.GetDirectoryName(_filename.GetFullPath()); filename = Path.Combine(folder, importFilename); if (File.Exists(filename)) { document = File.ReadAllText(filename); } } catch { } // hmm that didn't work. I guess just try the filename... } if( document == null ) { // without a filename for the current document, all we can do is hope that there is a file at the specified string filename = importFilename.GetFullPath(); if (!File.Exists(filename)) { throw new EndUserParseException(token, _filename, "PSP 122", "Imported file '{0}' not found", filename); } document = File.ReadAllText(filename); } var includedSheet = new PropertySheet(); // parse the contents of that file into the current property sheet. new PropertySheetParser(document, filename, includedSheet).Parse(); foreach( var r in includedSheet.Rules) { r.ParentPropertySheet = _propertySheet; } _propertySheet.ImportedSheets.Add(filename, includedSheet); state = ParseState.Global; continue; default: throw new EndUserParseException(token, _filename, "PSP 121", "Expected a string literal for filename"); } case ParseState.Selector: switch (token.Type) { case TokenType.Dot: state = ParseState.SelectorDot; continue; case TokenType.Pound: state = ParseState.SelectorPound; continue; case TokenType.SelectorParameter: ruleParameter = token.Data; if( ruleParameter.IndexOfAny("\r\n".ToCharArray()) >= 0) { throw new EndUserParseException(token, _filename, "PSP 123", "Selector parameter may not contain CR/LFs (missing close bracket?): {0} ", Rule.CreateSelectorString(ruleName, ruleParameter,ruleClass, ruleId )); } continue; case TokenType.OpenBrace: state = ParseState.InRule; // property sheets now merge rules when redefined. // if( _propertySheet.HasRule(ruleName, ruleParameter, ruleClass, ruleId) ) { // throw new EndUserParseException(token, _filename, "PSP 113", "Duplicate rule with identical selector not allowed: {0} ", Rule.CreateSelectorString(ruleName, ruleParameter,ruleClass, ruleId )); // } rule = _propertySheet.GetRule(ruleName, ruleParameter, ruleClass, ruleId); ruleName = null; ruleParameter = null; ruleClass = null; ruleId = null; rule.SourceLocation = sourceLocation; continue; default: throw new EndUserParseException(token, _filename, "PSP 101", "Expected one of '.' , '#' , '[' or '{{' ." ); } case ParseState.SelectorDot: switch (token.Type) { case TokenType.Identifier: ruleClass = token.Data; state = ParseState.Selector; continue; default: throw new EndUserParseException(token, _filename, "PSP 102", "Expected identifier"); } case ParseState.SelectorPound: switch (token.Type) { case TokenType.Identifier: ruleId = token.Data; state = ParseState.Selector; continue; default: throw new EndUserParseException(token, _filename, "PSP 103", "Expected identifier"); } case ParseState.InRule: switch (token.Type) { case TokenType.Semicolon: // extra semicolons are tolerated. continue; case TokenType.StringLiteral: case TokenType.Identifier: propertyName = token.Data; state = ParseState.HavePropertyName; sourceLocation = new SourceLocation { Row = token.Row, Column = token.Column, SourceFile = _filename, }; continue; case TokenType.CloseBrace: // this rule is DONE. rule = null; // set this to null, so that we don't accidentally add new stuff to this rule. state = ParseState.Global; continue; default: throw new EndUserParseException(token, _filename, "PSP 104", "In rule, expected semi-colon ';', close-brace '}}' or value." ); } case ParseState.HavePropertyName: switch (token.Type) { case TokenType.Colon: state = ParseState.HavePropertySeparator; property = rule.GetRuleProperty(propertyName); continue; default: throw new EndUserParseException(token, _filename, "PSP 105", "Found rule property name, expected colon ':'." ); } case ParseState.HavePropertySeparator: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: state = ParseState.HavePropertyLabel; if ("@Literal" == token.RawData) { propertyLabelText = token.Data; } else { propertyLabelText = token.Data; } continue; case TokenType.Identifier: state = ParseState.HavePropertyLabel; propertyLabelText = token.Data; continue; case TokenType.OpenBrace: state = ParseState.InPropertyCollectionWithoutLabel; continue; default: throw new EndUserParseException(token, _filename, "PSP 106", "After rule property name, expected value, open-brace '{{' or open-parenthesis '('." ); } case ParseState.InPropertyCollectionWithoutLabel: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: // at this point it could be a collection, a label, or a value. presentlyUnknownValue = token.Data; state = ParseState.InPropertyCollectionWithoutLableButHaveSomething; continue; case TokenType.CloseBrace: state = ParseState.HavePropertyCompleted; continue; default: throw new EndUserParseException(token, _filename, "PSP 107", "In property collection, expected value or close brace '}}'"); } case ParseState.InPropertyCollectionWithoutLableButHaveSomething: switch (token.Type) { case TokenType.Lambda: collectionName = presentlyUnknownValue; presentlyUnknownValue = null; state = ParseState.HasLambda; continue; case TokenType.Equal: // looks like it's gonna be a label = value type. propertyLabelText = presentlyUnknownValue; presentlyUnknownValue = null; state = ParseState.HasEqualsInCollection; continue; case TokenType.Comma: { // turns out its a simple collection item. var pv = property.GetPropertyValue(string.Empty); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; presentlyUnknownValue = null; state = ParseState.InPropertyCollectionWithoutLabel; } continue; case TokenType.CloseBrace: { // turns out its a simple collection item. var pv = property.GetPropertyValue(string.Empty); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; presentlyUnknownValue = null; state = ParseState.HavePropertyCompleted; } continue; default: throw new EndUserParseException(token, _filename, "PSP 114", "after an value or identifier in a collection expected a '=>' or '=' or ',' ."); } case ParseState.HasEqualsInCollection : switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: { var pv = property.GetPropertyValue(propertyLabelText); pv.Add(token.Data); pv.SourceLocation = sourceLocation; state = ParseState.InPropertyCollectionWithoutLabelWaitingForComma; } continue; default: throw new EndUserParseException(token, _filename, "PSP 119", "after an equals '=' in a collection, expected a value or identifier."); } case ParseState.HasLambda: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: propertyLabelText = token.Data; state = ParseState.HasLambdaAndLabel; continue; default: throw new EndUserParseException(token, _filename, "PSP 115", "After the '=>' in a collection, expected value or identifier."); } case ParseState.HasLambdaAndLabel: switch (token.Type) { case TokenType.Equal: state = ParseState.HasLambdaAndLabelAndEquals; continue; case TokenType.Semicolon: case TokenType.Comma : case TokenType.CloseBrace: { // assumes "${DEFAULTLAMBDAVALUE}" for the lamda value var pv = property.GetPropertyValue(propertyLabelText, collectionName); pv.Add("${DEFAULTLAMBDAVALUE}"); pv.SourceLocation = sourceLocation; collectionName = propertyLabelText = null; state = ParseState.InPropertyCollectionWithoutLabelWaitingForComma; } if (token.Type == TokenType.CloseBrace) { // and, we're closing out this property. state = ParseState.HavePropertyCompleted; } continue; default: throw new EndUserParseException(token, _filename, "PSP 116", "After the '{0} => {1}' in collection, expected '=' ", collectionName, propertyLabelText); } case ParseState.HasLambdaAndLabelAndEquals: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: { var pv = property.GetPropertyValue(propertyLabelText, collectionName); pv.Add(token.Data); pv.SourceLocation = sourceLocation; collectionName = propertyLabelText = null; state = ParseState.InPropertyCollectionWithoutLabelWaitingForComma; } continue; default: throw new EndUserParseException(token, _filename, "PSP 117", "After the '{0} => {1} = ' in collection, expected a value or identifier" ,collectionName, propertyLabelText); } case ParseState.InPropertyCollectionWithoutLabelWaitingForComma: switch (token.Type) { case TokenType.Comma: case TokenType.Semicolon: state = ParseState.InPropertyCollectionWithoutLabel; continue; case TokenType.CloseBrace: state = ParseState.HavePropertyCompleted; continue; default: throw new EndUserParseException(token, _filename, "PSP 118", "After complete expression or value in a collection, expected ',' or '}'."); } case ParseState.InPropertyCollectionWithLabel: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: presentlyUnknownValue = token.Data; state = ParseState.HaveCollectionValue; continue; case TokenType.CloseBrace: state = ParseState.HavePropertyCompleted; continue; default: throw new EndUserParseException(token, _filename, "PSP 107", "In property collection, expected value or close brace '}}'" ); } case ParseState.HaveCollectionValue: switch (token.Type) { case TokenType.Comma: { var pv = property.GetPropertyValue(propertyLabelText); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; collectionName = propertyLabelText = null; state = ParseState.InPropertyCollectionWithLabel; } continue; case TokenType.CloseBrace: { var pv = property.GetPropertyValue(propertyLabelText); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; collectionName = propertyLabelText = null; state = ParseState.HavePropertyCompleted; } continue; default: throw new EndUserParseException(token, _filename, "PSP 108", "With property collection value, expected comma ',' or close-brace '}}'."); } case ParseState.HavePropertyLabel: switch (token.Type) { case TokenType.Equal: state = ParseState.HavePropertyEquals; continue; case TokenType.Dot: var t = SkipToNext(ref enumerator); if( !t.HasValue ) { throw new EndUserParseException(token, _filename, "PSP 109", "Unexpected end of Token stream [HavePropertyLabel]"); } token = t.Value; if (token.Type == TokenType.Identifier || token.Type == TokenType.NumericLiteral) { propertyLabelText += "." + token.Data; } else throw new EndUserParseException(token, _filename, "PSP 110", "Expected identifier or numeric literal after Dot '.'."); continue; case TokenType.Semicolon: { // it turns out that what we thought the label was, is really the property value, // the label is an empty string var pv = property.GetPropertyValue(string.Empty); pv.Add(propertyLabelText); pv.SourceLocation = sourceLocation; propertyName = propertyLabelText = null; state = ParseState.InRule; } continue; default: throw new EndUserParseException(token, _filename, "PSP 111", "After property value, expected semi-colon ';' or equals '='." ); } case ParseState.HavePropertyEquals: switch (token.Type) { case TokenType.Identifier: case TokenType.StringLiteral: case TokenType.NumericLiteral: { // found our property-value. add it, and move along. var pv = property.GetPropertyValue(propertyLabelText); pv.Add(token.Data); pv.SourceLocation = sourceLocation; propertyName = propertyLabelText = null; state = ParseState.HavePropertyCompleted; } continue; case TokenType.OpenBrace: // we're starting a new collection (where we have the label already). state = ParseState.InPropertyCollectionWithLabel; continue; default: throw new EndUserParseException(token, _filename, "PSP 112", "After equals in property, expected value or close-brace '{B{'." ); } case ParseState.HavePropertyCompleted: switch (token.Type) { case TokenType.Semicolon: state = ParseState.InRule; continue; default: throw new EndUserParseException(token, _filename, "PSP 113", "After property completed, expected semi-colon ';'."); } default: throw new EndUserParseException(token, _filename, "PSP 120", "CATS AND DOGS, LIVINGTOGETHER..."); } } while (enumerator.MoveNext()); return _propertySheet; }