git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@2318 52acb1d6-8a22-11de-b505-999d5b087335
652 lines
15 KiB
C#
652 lines
15 KiB
C#
#region Copyright
|
|
//
|
|
// Nini Configuration Project.
|
|
// Copyright (C) 2006 Brent R. Matzelle. All rights reserved.
|
|
//
|
|
// This software is published under the terms of the MIT X11 license, a copy of
|
|
// which has been included with this distribution in the LICENSE.txt file.
|
|
//
|
|
#endregion
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Collections;
|
|
|
|
namespace ExtensionLoader.Ini
|
|
{
|
|
#region IniReadState enumeration
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniReadState"]/docs/*' />
|
|
public enum IniReadState : int
|
|
{
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniReadState"]/Value[@name="Closed"]/docs/*' />
|
|
Closed,
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniReadState"]/Value[@name="EndOfFile"]/docs/*' />
|
|
EndOfFile,
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniReadState"]/Value[@name="Error"]/docs/*' />
|
|
Error,
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniReadState"]/Value[@name="Initial"]/docs/*' />
|
|
Initial,
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniReadState"]/Value[@name="Interactive"]/docs/*' />
|
|
Interactive
|
|
};
|
|
#endregion
|
|
|
|
#region IniType enumeration
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniType"]/docs/*' />
|
|
public enum IniType : int
|
|
{
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniType"]/Value[@name="Section"]/docs/*' />
|
|
Section,
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniType"]/Value[@name="Key"]/docs/*' />
|
|
Key,
|
|
/// <include file='IniReader.xml' path='//Enum[@name="IniType"]/Value[@name="Empty"]/docs/*' />
|
|
Empty
|
|
}
|
|
#endregion
|
|
|
|
/// <include file='IniReader.xml' path='//Class[@name="IniReader"]/docs/*' />
|
|
public class IniReader : IDisposable
|
|
{
|
|
#region Private variables
|
|
int lineNumber = 1;
|
|
int column = 1;
|
|
IniType iniType = IniType.Empty;
|
|
TextReader textReader = null;
|
|
bool ignoreComments = false;
|
|
StringBuilder name = new StringBuilder ();
|
|
StringBuilder value = new StringBuilder ();
|
|
StringBuilder comment = new StringBuilder ();
|
|
IniReadState readState = IniReadState.Initial;
|
|
bool hasComment = false;
|
|
bool disposed = false;
|
|
bool lineContinuation = false;
|
|
bool acceptCommentAfterKey = true;
|
|
bool acceptNoAssignmentOperator = false;
|
|
bool consumeAllKeyText = false;
|
|
char[] commentDelimiters = new char[] { ';' };
|
|
char[] assignDelimiters = new char[] { '=' };
|
|
#endregion
|
|
|
|
#region Public properties
|
|
/// <include file='IniReader.xml' path='//Property[@name="Name"]/docs/*' />
|
|
public string Name
|
|
{
|
|
get { return this.name.ToString (); }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="Value"]/docs/*' />
|
|
public string Value
|
|
{
|
|
get { return this.value.ToString (); }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="Type"]/docs/*' />
|
|
public IniType Type
|
|
{
|
|
get { return iniType; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="Comment"]/docs/*' />
|
|
public string Comment
|
|
{
|
|
get { return (hasComment) ? this.comment.ToString () : null; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="LineNumber"]/docs/*' />
|
|
public int LineNumber
|
|
{
|
|
get { return lineNumber; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="LinePosition"]/docs/*' />
|
|
public int LinePosition
|
|
{
|
|
get { return column; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="IgnoreComments"]/docs/*' />
|
|
public bool IgnoreComments
|
|
{
|
|
get { return ignoreComments; }
|
|
set { ignoreComments = value; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="ReadState"]/docs/*' />
|
|
public IniReadState ReadState
|
|
{
|
|
get { return readState; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="LineContinuation"]/docs/*' />
|
|
public bool LineContinuation
|
|
{
|
|
get { return lineContinuation; }
|
|
set { lineContinuation = value; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="AcceptCommentAfterKey"]/docs/*' />
|
|
public bool AcceptCommentAfterKey
|
|
{
|
|
get { return acceptCommentAfterKey; }
|
|
set { acceptCommentAfterKey = value; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="AcceptNoAssignmentOperator"]/docs/*' />
|
|
public bool AcceptNoAssignmentOperator
|
|
{
|
|
get { return acceptNoAssignmentOperator; }
|
|
set { acceptNoAssignmentOperator = value; }
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Property[@name="ConsumeAllKeyText"]/docs/*' />
|
|
public bool ConsumeAllKeyText
|
|
{
|
|
get { return consumeAllKeyText; }
|
|
set { consumeAllKeyText = value; }
|
|
}
|
|
#endregion
|
|
|
|
#region Constructors
|
|
/// <include file='IniReader.xml' path='//Constructor[@name="ConstructorPath"]/docs/*' />
|
|
public IniReader (string filePath)
|
|
{
|
|
textReader = new StreamReader (filePath);
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Constructor[@name="ConstructorTextReader"]/docs/*' />
|
|
public IniReader (TextReader reader)
|
|
{
|
|
textReader = reader;
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Constructor[@name="ConstructorStream"]/docs/*' />
|
|
public IniReader (Stream stream)
|
|
: this (new StreamReader (stream))
|
|
{
|
|
}
|
|
#endregion
|
|
|
|
#region Public methods
|
|
/// <include file='IniReader.xml' path='//Method[@name="Read"]/docs/*' />
|
|
public bool Read ()
|
|
{
|
|
bool result = false;
|
|
|
|
if (readState != IniReadState.EndOfFile
|
|
|| readState != IniReadState.Closed) {
|
|
readState = IniReadState.Interactive;
|
|
result = ReadNext ();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="MoveToNextSection"]/docs/*' />
|
|
public bool MoveToNextSection ()
|
|
{
|
|
bool result = false;
|
|
|
|
while (true)
|
|
{
|
|
result = Read ();
|
|
|
|
if (iniType == IniType.Section || !result) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="MoveToNextKey"]/docs/*' />
|
|
public bool MoveToNextKey ()
|
|
{
|
|
bool result = false;
|
|
|
|
while (true)
|
|
{
|
|
result = Read ();
|
|
|
|
if (iniType == IniType.Section) {
|
|
result = false;
|
|
break;
|
|
}
|
|
if (iniType == IniType.Key || !result) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="Close"]/docs/*' />
|
|
public void Close ()
|
|
{
|
|
Reset ();
|
|
readState = IniReadState.Closed;
|
|
|
|
if (textReader != null) {
|
|
textReader.Close ();
|
|
}
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="Dispose"]/docs/*' />
|
|
public void Dispose ()
|
|
{
|
|
Dispose (true);
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="GetCommentDelimiters"]/docs/*' />
|
|
public char[] GetCommentDelimiters ()
|
|
{
|
|
char[] result = new char[commentDelimiters.Length];
|
|
Array.Copy (commentDelimiters, 0, result, 0, commentDelimiters.Length);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="SetCommentDelimiters"]/docs/*' />
|
|
public void SetCommentDelimiters (char[] delimiters)
|
|
{
|
|
if (delimiters.Length < 1) {
|
|
throw new ArgumentException ("Must supply at least one delimiter");
|
|
}
|
|
|
|
commentDelimiters = delimiters;
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="GetAssignDelimiters"]/docs/*' />
|
|
public char[] GetAssignDelimiters ()
|
|
{
|
|
char[] result = new char[assignDelimiters.Length];
|
|
Array.Copy (assignDelimiters, 0, result, 0, assignDelimiters.Length);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <include file='IniReader.xml' path='//Method[@name="SetAssignDelimiters"]/docs/*' />
|
|
public void SetAssignDelimiters (char[] delimiters)
|
|
{
|
|
if (delimiters.Length < 1) {
|
|
throw new ArgumentException ("Must supply at least one delimiter");
|
|
}
|
|
|
|
assignDelimiters = delimiters;
|
|
}
|
|
#endregion
|
|
|
|
#region Protected methods
|
|
/// <include file='IniReader.xml' path='//Method[@name="DisposeBoolean"]/docs/*' />
|
|
protected virtual void Dispose (bool disposing)
|
|
{
|
|
if (!disposed) {
|
|
textReader.Close ();
|
|
disposed = true;
|
|
|
|
if (disposing) {
|
|
GC.SuppressFinalize (this);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Private methods
|
|
/// <summary>
|
|
/// Destructor.
|
|
/// </summary>
|
|
~IniReader ()
|
|
{
|
|
Dispose (false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets all of the current INI line data.
|
|
/// </summary>
|
|
private void Reset ()
|
|
{
|
|
this.name.Remove (0, this.name.Length);
|
|
this.value.Remove (0, this.value.Length);
|
|
this.comment.Remove (0, this.comment.Length);
|
|
iniType = IniType.Empty;
|
|
hasComment = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the next INI line item.
|
|
/// </summary>
|
|
private bool ReadNext ()
|
|
{
|
|
bool result = true;
|
|
int ch = PeekChar ();
|
|
Reset ();
|
|
|
|
if (IsComment (ch)) {
|
|
iniType = IniType.Empty;
|
|
ReadChar (); // consume comment character
|
|
ReadComment ();
|
|
|
|
return result;
|
|
}
|
|
|
|
switch (ch)
|
|
{
|
|
case ' ':
|
|
case '\t':
|
|
case '\r':
|
|
SkipWhitespace ();
|
|
ReadNext ();
|
|
break;
|
|
case '\n':
|
|
ReadChar ();
|
|
break;
|
|
case '[':
|
|
ReadSection ();
|
|
break;
|
|
case -1:
|
|
readState = IniReadState.EndOfFile;
|
|
result = false;
|
|
break;
|
|
default:
|
|
ReadKey ();
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a comment. Must start after the comment delimiter.
|
|
/// </summary>
|
|
private void ReadComment ()
|
|
{
|
|
int ch = -1;
|
|
SkipWhitespace ();
|
|
hasComment = true;
|
|
|
|
do
|
|
{
|
|
ch = ReadChar ();
|
|
this.comment.Append ((char)ch);
|
|
} while (!EndOfLine (ch));
|
|
|
|
RemoveTrailingWhitespace (this.comment);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes trailing whitespace from a StringBuilder.
|
|
/// </summary>
|
|
private void RemoveTrailingWhitespace (StringBuilder builder)
|
|
{
|
|
string temp = builder.ToString ();
|
|
|
|
builder.Remove (0, builder.Length);
|
|
builder.Append (temp.TrimEnd (null));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a key.
|
|
/// </summary>
|
|
private void ReadKey ()
|
|
{
|
|
int ch = -1;
|
|
iniType = IniType.Key;
|
|
|
|
while (true)
|
|
{
|
|
ch = PeekChar ();
|
|
|
|
if (IsAssign (ch)) {
|
|
ReadChar ();
|
|
break;
|
|
}
|
|
|
|
if (EndOfLine (ch)) {
|
|
if (acceptNoAssignmentOperator) {
|
|
break;
|
|
}
|
|
throw new IniException (this,
|
|
String.Format ("Expected assignment operator ({0})",
|
|
assignDelimiters[0]));
|
|
}
|
|
|
|
this.name.Append ((char)ReadChar ());
|
|
}
|
|
|
|
ReadKeyValue ();
|
|
SearchForComment ();
|
|
RemoveTrailingWhitespace (this.name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the value of a key.
|
|
/// </summary>
|
|
private void ReadKeyValue ()
|
|
{
|
|
int ch = -1;
|
|
bool foundQuote = false;
|
|
int characters = 0;
|
|
SkipWhitespace ();
|
|
|
|
while (true)
|
|
{
|
|
ch = PeekChar ();
|
|
|
|
if (!IsWhitespace (ch)) {
|
|
characters++;
|
|
}
|
|
|
|
if (!this.ConsumeAllKeyText && ch == '"') {
|
|
ReadChar ();
|
|
|
|
if (!foundQuote && characters == 1) {
|
|
foundQuote = true;
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundQuote && EndOfLine (ch)) {
|
|
throw new IniException (this, "Expected closing quote (\")");
|
|
}
|
|
|
|
// Handle line continuation
|
|
if (lineContinuation && ch == '\\')
|
|
{
|
|
StringBuilder buffer = new StringBuilder ();
|
|
buffer.Append ((char)ReadChar ()); // append '\'
|
|
|
|
while (PeekChar () != '\n' && IsWhitespace (PeekChar ()))
|
|
{
|
|
if (PeekChar () != '\r') {
|
|
buffer.Append ((char)ReadChar ());
|
|
} else {
|
|
ReadChar (); // consume '\r'
|
|
}
|
|
}
|
|
|
|
if (PeekChar () == '\n') {
|
|
// continue reading key value on next line
|
|
ReadChar ();
|
|
continue;
|
|
} else {
|
|
// Replace consumed characters
|
|
this.value.Append (buffer.ToString ());
|
|
}
|
|
}
|
|
|
|
if (!this.ConsumeAllKeyText) {
|
|
// If accepting comments then don't consume as key value
|
|
if (acceptCommentAfterKey && IsComment (ch) && !foundQuote) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Always break at end of line
|
|
if (EndOfLine (ch)) {
|
|
break;
|
|
}
|
|
|
|
this.value.Append ((char)ReadChar ());
|
|
}
|
|
|
|
if (!foundQuote) {
|
|
RemoveTrailingWhitespace (this.value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads an INI section.
|
|
/// </summary>
|
|
private void ReadSection ()
|
|
{
|
|
int ch = -1;
|
|
iniType = IniType.Section;
|
|
ch = ReadChar (); // consume "["
|
|
|
|
while (true)
|
|
{
|
|
ch = PeekChar ();
|
|
if (ch == ']') {
|
|
break;
|
|
}
|
|
if (EndOfLine (ch)) {
|
|
throw new IniException (this, "Expected section end (])");
|
|
}
|
|
|
|
this.name.Append ((char)ReadChar ());
|
|
}
|
|
|
|
ConsumeToEnd (); // all after '[' is garbage
|
|
RemoveTrailingWhitespace (this.name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Looks for a comment.
|
|
/// </summary>
|
|
private void SearchForComment ()
|
|
{
|
|
int ch = ReadChar ();
|
|
|
|
while (!EndOfLine (ch))
|
|
{
|
|
if (IsComment (ch)) {
|
|
if (ignoreComments) {
|
|
ConsumeToEnd ();
|
|
} else {
|
|
ReadComment ();
|
|
}
|
|
break;
|
|
}
|
|
ch = ReadChar ();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes all data until the end of a line.
|
|
/// </summary>
|
|
private void ConsumeToEnd ()
|
|
{
|
|
int ch = -1;
|
|
|
|
do
|
|
{
|
|
ch = ReadChar ();
|
|
} while (!EndOfLine (ch));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns and consumes the next character from the stream.
|
|
/// </summary>
|
|
private int ReadChar ()
|
|
{
|
|
int result = textReader.Read ();
|
|
|
|
if (result == '\n') {
|
|
lineNumber++;
|
|
column = 1;
|
|
} else {
|
|
column++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the next upcoming character from the stream.
|
|
/// </summary>
|
|
private int PeekChar ()
|
|
{
|
|
return textReader.Peek ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if a comment character is found.
|
|
/// </summary>
|
|
private bool IsComment (int ch)
|
|
{
|
|
return HasCharacter (commentDelimiters, ch);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if character is an assign character.
|
|
/// </summary>
|
|
private bool IsAssign (int ch)
|
|
{
|
|
return HasCharacter (assignDelimiters, ch);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the character is found in the given array.
|
|
/// </summary>
|
|
private bool HasCharacter (char[] characters, int ch)
|
|
{
|
|
bool result = false;
|
|
|
|
for (int i = 0; i < characters.Length; i++)
|
|
{
|
|
if (ch == characters[i])
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if a value is whitespace.
|
|
/// </summary>
|
|
private bool IsWhitespace (int ch)
|
|
{
|
|
return ch == 0x20 || ch == 0x9 || ch == 0xD || ch == 0xA;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skips all whitespace.
|
|
/// </summary>
|
|
private void SkipWhitespace ()
|
|
{
|
|
while (IsWhitespace (PeekChar ()))
|
|
{
|
|
if (EndOfLine (PeekChar ())) {
|
|
break;
|
|
}
|
|
|
|
ReadChar ();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if an end of line is found. End of line
|
|
/// includes both an end of line or end of file.
|
|
/// </summary>
|
|
private bool EndOfLine (int ch)
|
|
{
|
|
return (ch == '\n' || ch == -1);
|
|
}
|
|
#endregion
|
|
}
|
|
} |