// NOTE: we capture entire lines as "tokens"; this class's job is to classify individual line entries (and tag comment/whitespace trivia), not to parse raw tokens public IniToken GetNextToken() { var leadingTrivia = new List <IniTrivia>(); // NOTE: this loop will continue until we parse a line or read eof of file while (true) { var nextLine = this.ReadIniTextLine(); if (nextLine is null) { return(new IniToken(IniTokenKind.EndOfFile, new List <char>() /* empty lexeme, rather than null */, IniLineTerminatorOption.None, leadingTrivia, null)); } var lexeme = nextLine.Text !; var lineTerminator = nextLine.LineTerminator; // check for empty lines if (lexeme.Count == 0) { // empty line; treat this as trivia var whitespaceTrivia = new IniTrivia(IniTriviaKind.Whitespace, lexeme, lineTerminator); leadingTrivia.Add(whitespaceTrivia); // capture another line of text continue; } else { // check for comment lines if (lexeme.First() == ';') { // comment; capture this as trivia var commentTrivia = new IniTrivia(IniTriviaKind.Comment, lexeme, lineTerminator); leadingTrivia.Add(commentTrivia); // capture another line of text continue; } // check for whitespace lines var lineIsWhitespace = lexeme.All(ch => { switch (ch) { case ' ': case '\t': return(true); default: return(false); } }); if (lineIsWhitespace == true) { var whitespaceTrivia = new IniTrivia(IniTriviaKind.Whitespace, lexeme, lineTerminator); leadingTrivia.Add(whitespaceTrivia); // capture another line of text continue; } // check for an EOF marker if (lexeme.Count == 1 && lexeme.First() == (char)0x1A /* EOF marker */) { // EOF marker; this will end our text capture return(new IniToken(IniTokenKind.EndOfFile, lexeme, lineTerminator, leadingTrivia, null)); } // now categorize our lexeme (or mark it as invalid) char?firstNonWhitespaceCharacter = null; foreach (var ch in lexeme) { switch (ch) { case ' ': case '\t': continue; default: firstNonWhitespaceCharacter = ch; break; } if (firstNonWhitespaceCharacter is not null) { break; } } // check for sections if (firstNonWhitespaceCharacter == '[') { // make sure that the line contains an ending (right) bracket var indexOfRightBracket = lexeme.IndexOf(']'); if (indexOfRightBracket > 0) { // found right bracket; make sure that the line does not contain any non-whitespace characters after the bracket for (var index = indexOfRightBracket + 1; index < lexeme.Count; index += 1) { switch (lexeme[index]) { case ' ': case '\t': // whitespace break; default: // if we encounter any non-whitespace characters, this an invalid token return(new IniToken(IniTokenKind.Invalid, lexeme, lineTerminator, leadingTrivia, null)); } } // this section is valid; return it now return(new IniToken(IniTokenKind.Section, lexeme, lineTerminator, leadingTrivia, null)); } else { // this line does not have a closing bracket for the section return(new IniToken(IniTokenKind.Invalid, lexeme, lineTerminator, leadingTrivia, null)); } } // NOTE: at this point, the line should be a property (i.e. key/value pair); anything else is invalid // check for an equals sign; we will treat anything to the left as the key and anything to the right as the value var indexOfEqualsCharacter = lexeme.IndexOf('='); if (indexOfEqualsCharacter >= 0) { // key/value pair // NOTE: our parser is responsible for parsing out the key and value (and dealing with leading/trailing whitespace, double quotes around values, etc.) return(new IniToken(IniTokenKind.Property, lexeme, lineTerminator, leadingTrivia, null)); } // anything else is an invalid token return(new IniToken(IniTokenKind.Invalid, lexeme, lineTerminator, leadingTrivia, null)); } } }
private static void AppendTriviaToStringBuilder(IniTrivia trivia, ref StringBuilder builder) { builder.Append(trivia.Lexeme.ToArray()); IniFile.AppendLineTerminatorToStringBuilder(trivia.LineTerminator ?? IniLineTerminatorOption.None, ref builder); }