DocSpace-client/products/ASC.Calendar/Server/iCalParser/Scanner.cs
2020-02-13 12:18:13 +03:00

527 lines
14 KiB
C#

/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
/*
* ICalParser is a general purpose .Net parser for iCalendar format files (RFC 2445)
*
* Copyright (C) 2004 J. Tim Spurway
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
using System;
using System.Collections;
using System.IO;
using System.Text;
namespace ASC.Calendar.iCalParser
{
public enum ScannerState { ParseSimple, ParseID, ParseParms, ParseValue, ParseKey };
/// <summary>
/// The scanner is responsible for tokenizing iCalendar (RFC2445) files for use by the
/// parser.
/// </summary>
public class Scanner : IEnumerable
{
private string fileName = null;
private Stream stream = null;
private TextReader rdr = null;
private int lineNumber = -1;
private bool newlineFound = false;
private class Enumerator : IEnumerator
{
Scanner scanner;
Token current;
internal Enumerator(Scanner _scanner)
{
scanner = _scanner;
current = null;
}
public bool MoveNext()
{
current = scanner.GetNextToken();
return current != null;
}
public object Current
{
get { return current; }
}
public void Reset()
{
scanner.Close();
}
}
public Scanner(string _fileName)
{
fileName = _fileName;
}
public Scanner(Stream _stream)
{
stream = _stream;
}
public Scanner(TextReader _reader)
{
rdr = _reader;
}
public IEnumerator GetEnumerator()
{
return new Enumerator(this);
}
public Token GetNextToken()
{
return GetNextToken(ScannerState.ParseValue);
}
/// <summary>
/// Returns the next token in the file. Returns null on EOF.
/// </summary>
/// <returns></returns>
public Token GetNextToken(ScannerState state)
{
switch (state)
{
case ScannerState.ParseKey:
eatWhitespace();
return GetNextID();
case ScannerState.ParseID:
return GetNextID();
case ScannerState.ParseParms:
return GetNextParms();
case ScannerState.ParseSimple:
eatWhitespace();
return GetNextSimple();
case ScannerState.ParseValue:
Token rval = GetNextValue();
//ConsumeToEOL();
return rval;
}
return null;
}
public Token GetNextID()
{
StringBuilder buff = new StringBuilder();
char c;
int i;
if ((i = Peek()) == -1)
return null;
else
c = (char)i;
if (Char.IsLetter(c))
{
buff.Append(c);
Read();
goto getID;
}
return null; // IDs need to start with a letter
getID:
if ((i = Peek()) == -1)
return new Token(buff.ToString(), ScannerState.ParseID);
else
c = (char)i;
if (Char.IsLetterOrDigit(c) || c == '_' || c == '-')
{
// and may be composed of letter, digit, _, - characters
buff.Append(c);
Read();
goto getID;
}
else if (c == '\n')
{
Read(); // consume it and go
}
return new Token(buff.ToString(), ScannerState.ParseID);
}
public Token GetNextParms()
{
StringBuilder buff = new StringBuilder();
char c;
int i;
if ((i = Peek()) == -1)
return null;
start:
if ((i = Peek()) == -1)
return new Token(buff.ToString(), ScannerState.ParseParms);
else
c = (char)i;
if (c == ';' || c == ':')
{
return new Token(buff.ToString(), ScannerState.ParseParms);
}
else if (c == '\n')
{
// parameters must end in a semi-colon or colon
return null;
}
else if (c == '"')
{
buff.Append(c);
Read();
goto quote;
}
else
{
buff.Append(c);
Read();
goto start;
}
quote:
if ((i = Peek()) == -1)
return null;
else
c = (char)i;
if (c == '\\')
{
buff.Append(c);
Read();
goto quoteChar;
}
else if (c == '\n')
{
return null;
}
else if (c == '"')
{
buff.Append(c);
Read();
return new Token(buff.ToString(), ScannerState.ParseParms, true);
}
else
{
buff.Append(c);
Read();
goto quote;
}
quoteChar:
if ((i = Read()) == -1)
return null;
else
c = (char)i;
if (c == '\n')
{
// can't backslash quote a newline - have to use continuation char
return null;
}
buff.Append(c);
goto quote;
}
public Token GetNextSimple()
{
char c;
int i;
if ((i = Peek()) == -1)
return null;
else
c = (char)i;
if (c == ';')
{
Read();
return new Token(TokenValue.SemiColon);
}
else if (c == ':')
{
Read();
return new Token(TokenValue.Colon);
}
else if (c == '=')
{
Read();
return new Token(TokenValue.Equals);
}
else if (c == ',')
{
Read();
return new Token(TokenValue.Comma);
}
else if (c == '-')
{
Read();
return new Token(TokenValue.Hyphen);
}
else
{
return null;
}
}
public Token GetNextValue()
{
StringBuilder buff = new StringBuilder();
char c;
int i;
if ((i = Peek()) == -1)
return null;
start:
if ((i = Peek()) == -1)
return new Token(buff.ToString(), ScannerState.ParseValue);
else
c = (char)i;
if (c == '\n')
{
// values always end in a newline
Read();
return new Token(buff.ToString(), ScannerState.ParseValue);
}
else if (c == '\\')
{
Read(); // consume the backslash - it's a quote character unused in RDF/Xml
goto quoteChar;
}
else
{
//if( c == '\'' || c == '\\' || c == '\"' )
//buff.Append( '\\' );
buff.Append(c);
Read();
goto start;
}
quoteChar:
// handle newlines and returns by putting the actual control characters
// in the output - other quoted character (such as commas) are just dumped out
if ((i = Read()) == -1)
return new Token(buff.ToString(), ScannerState.ParseValue);
else
c = (char)i;
if (c == '\n')
{
// can't backslash quote a newline - have to use continuation char
return null;
}
else if (c == 'n')
{
buff.Append('\n');
}
else if (c == 'r')
{
buff.Append('\r');
}
else
{
buff.Append(c);
}
goto start;
}
private void eatWhitespace()
{
int i;
char c;
while (true)
{
if ((i = Peek()) == -1)
return;
else
c = (char)i;
//if( Char.IsWhiteSpace( c ))
if (c == ' ' || c == '\t') // only eat spaces and tabs
{
Read();
}
else
{
return;
}
}
}
/// <summary>
/// This method is used for error recovery, get the rest of the line, including
/// folded lines, and position on a fresh line or EOF
/// </summary>
public void ConsumeToEOL()
{
int i;
char c;
while (true)
{
if ((i = Peek()) == -1)
return;
else
c = (char)i;
if (c == '\n')
{
Read();
return;
}
Read();
}
}
public bool isEOF()
{
return Peek() == -1;
}
private TextReader reader
{
get
{
if (rdr == null)
{
if (stream == null)
{
// open file
stream = File.OpenRead(fileName);
}
// create reader
rdr = new StreamReader(stream, Encoding.UTF8);
lineNumber = 0;
}
return rdr;
}
}
public void Close()
{
if (reader != null)
{
reader.Close();
rdr = null;
stream = null;
}
}
public int LineNumber
{
get { return lineNumber; }
}
private void consumeFolding()
{
if ((char)HardPeek() == '\n')
{
HardRead(); // read to EOL
newlineFound = true;
if ((char)HardPeek() == ' ')
{
// continuation char, consume it
newlineFound = false;
HardRead();
}
}
}
private int Read()
{
consumeFolding();
if (newlineFound)
{
newlineFound = false;
return (int)'\n';
}
return HardRead();
}
private int Peek()
{
consumeFolding();
if (newlineFound)
{
//newlineFound = false;
return (int)'\n';
}
return HardPeek();
}
private int HardPeek()
{
//while( (char)reader.Peek() == '\r' )
while (IsControlChar(reader.Peek()))
{
reader.Read();
}
return reader.Peek();
}
private int HardRead()
{
//while( (char)reader.Peek() == '\r' )
while (IsControlChar(reader.Peek()))
{
reader.Read();
}
return reader.Read();
}
private bool IsControlChar(int theChar)
{
// don't filtre out newlines or tabs - depends on ASCII
char c = (char)theChar;
return char.IsControl(c) && c != '\n' && c != '\t';
}
}
}