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

516 lines
19 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.Collections;
using System.IO;
using System.Text;
namespace ASC.Calendar.iCalParser
{
/// <summary>
/// Parse iCalendar rfc2445 streams and convert to another format based on the emitter used.
/// </summary>
///
/// <remarks>
/// This class is the main entry point for the ICalParser library. A parser is created
/// with a TextReader that contains the iCalendar stream to be parsed, and an IEmitter, which
/// is used to transform the iCalendar into another format.
///
/// Each iCalendar format file is in the form:
///
/// ID[[;attr1;attr2;attr3;...;attrn]:value]
///
/// where ID is the main keyword identifying the iCalendar entry, followed optionally by a
/// set of attributes and a single value. The parser works by identifying the specific IDs,
/// attributes and values, categorizing them based on similar 'behaviour' (as defined in the <code>Token</code>
/// class) and passing on recognized symbols to the emitter for further processing.
///
/// The error recovery policy of the parser is pretty simple. When an error is detected, it is recorded,
/// and the rest of the (possibly folded) line is read, and parsing continues.
/// </remarks>
///
/// <example>
/// The following snippet will read the contents of the file 'myCalendar.ics', which the
/// parser will expect to contain iCalendar statements, and will write the RdfICalendar
/// equivalent to standard output.
/// <code>
/// RDFEmitter emitter = new RDFEmitter( );
/// StreamReader reader = new StreamReader( "myCalendar.ics" );
/// Parser parser = new Parser( reader, emitter );
/// parser.Parse( );
/// Console.WriteLine( emitter.Rdf );
/// </code>
/// </example>
///
public class Parser
{
ArrayList errors;
Scanner scanner;
Stack stack, attributes;
IEmitter emitter;
int linenumber;
Token id, iprop; // id is the current ID for the current line
/// <summary>
/// Create a new iCalendar parser.
/// </summary>
/// <param name="reader">The reader that contains the stream of text iCalendar</param>
/// <param name="_emitter">The emitter that will transform the iCalendar elements</param>
public Parser(TextReader reader, IEmitter _emitter)
{
scanner = new Scanner(reader);
emitter = _emitter;
emitter.VParser = this;
errors = new ArrayList();
}
public ArrayList Errors
{
get
{
return errors;
}
}
public string ErrorString
{
get
{
if (!HasErrors)
{
return "";
}
StringBuilder rval = new StringBuilder();
foreach (ParserError error in errors)
{
rval.Append(error.ToString()).Append("\r\n");
}
return rval.ToString();
}
}
public bool HasErrors
{
get { return errors.Count > 0; }
}
/*/// <summary>
/// Give public access to the parse stack.
/// </summary>
private Stack VStack
{
get { return stack; }
}*/
/// <summary>
/// Main entry point for starting the Parser.
/// </summary>
public void Parse()
{
Parse(true);
}
/// <summary>
/// Alternate entry point for starting the parser.
/// </summary>
/// <param name="emitHandT">Indicates if the emitter should be told to emit headers
/// and trailers before and after emitting the iCalendar body</param>
public void Parse(bool emitHandT)
{
stack = new Stack();
linenumber = 0;
attributes = new Stack(); // a stack of key-value pairs (implemented as a stack of DitionaryEntry)
if (emitHandT)
{
emitter.doIntro();
}
// each time through the loop will get a single (maybe folded) line
while (true)
{
// check for termination condition
if (scanner.isEOF())
{
// end of file - do cleanup and go
break;
}
// empty the attribute stack and the iprop value...
attributes.Clear();
iprop = null;
id = null;
//FIXME: linenumber doesn't really keep track of actual line numbers because
// it is not aware of folded lines...
linenumber++;
//DEBUG: emit line number
//emitter.emit( linenumber + ". " );
if (!parseID())
{
continue;
}
// we now have to parse a set of attributes (semi-colon separated) or
// a value (delimited by a colon)
Token sep = scanner.GetNextToken(ScannerState.ParseSimple);
if (sep == null || sep.TokenVal == TokenValue.Error)
{
// some kind of error - skip rest of line and continue
reportError(scanner, " expecting : or ; after id - found nothing.");
continue;
}
else if (sep.TokenVal == TokenValue.SemiColon)
{
if (!parseAttributes(scanner))
{
continue;
}
// now we have to parse the value
sep = scanner.GetNextToken(ScannerState.ParseSimple);
if (!parseValue())
{
continue;
}
}
else if (sep.TokenVal == TokenValue.Colon)
{
if (!parseValue())
{
continue;
}
}
else
{
reportError(scanner, "expecting : or ; after id - found: " + sep.TokenText);
continue;
}
// now sploosh out the attributes (if any) and finish the ID tag
while (attributes.Count > 0)
{
DictionaryEntry entry = (DictionaryEntry)attributes.Pop();
Token key = (Token)entry.Key;
Token val = (Token)entry.Value;
emitter.doAttribute(key, val);
}
emitter.doEnd(id);
}
if (emitHandT)
{
emitter.doOutro();
}
}
protected void reportError(Scanner s, string msg)
{
s.ConsumeToEOL();
errors.Add(new ParserError(linenumber, msg));
}
/// <summary>
/// Parse the first field (ID) of the line. Returns a boolean on weather or not the
/// method sucesfully recognized an ID. If not, the method insures that the scanner
/// will start at the beginning of a new line.
/// </summary>
/// <returns></returns>
protected virtual bool parseID()
{
Token t; // re-usable token variable
id = scanner.GetNextToken(ScannerState.ParseID);
if (id == null || id.TokenVal == TokenValue.Error)
{
// some kind of error - skip rest of line and continue
reportError(scanner, "expecting ID - found nothing.");
return false;
}
switch (id.TokenVal)
{
case TokenValue.Tbegin:
t = scanner.GetNextToken(ScannerState.ParseSimple);
if (t == null || t.isError() || t.TokenVal != TokenValue.Colon)
{
if (t == null)
reportError(scanner, " expecting : - found nothing.");
else
reportError(scanner, " expecting : - found " + t.TokenText);
return false;
}
t = scanner.GetNextToken(ScannerState.ParseID);
if (t == null || t.isError() || (!t.isBeginEndValue() && !t.isResourceProperty()))
{
if (t == null)
reportError(scanner, " expecting a valid beginend value - found nothing.");
else
reportError(scanner, " expecting a valid beginend value - found " + t.TokenText);
return false;
}
// check for the different types of begin tags
if (t.isResourceProperty())
{
emitter.doResourceBegin(t);
}
else if (t.isComponent())
{
emitter.doComponent();
emitter.doComponentBegin(t);
}
else if (t.TokenVal == TokenValue.Tvcalendar)
{
emitter.doComponentBegin(t);
}
else
{
emitter.doBegin(t);
}
stack.Push(t); // to match up to the corresponding end value
//scanner.ConsumeToEOL();
return false;
case TokenValue.Tend:
t = scanner.GetNextToken(ScannerState.ParseSimple);
if (t == null || t.isError() || t.TokenVal != TokenValue.Colon)
{
if (t == null)
reportError(scanner, " expecting : - found nothing.");
else
reportError(scanner, " expecting : - found " + t.TokenText);
return false;
}
t = scanner.GetNextToken(ScannerState.ParseID);
if (t == null || t.isError() || (!t.isBeginEndValue() && !t.isResourceProperty()))
{
if (t == null)
reportError(scanner, " expecting a valid beginend value - found nothing.");
else
reportError(scanner, " expecting a valid beginend value - found " + t.TokenText);
return false;
}
// the end is easier - ignore the last one...
if (stack.Count != 0)
{
emitter.doEnd(t);
if (t.isComponent())
{
emitter.doEndComponent();
}
stack.Pop();
}
else
{
reportError(scanner, "stack stuff is weird - probably illformed .ics file - parsing " + id.TokenText);
}
//scanner.ConsumeToEOL();
return false;
case TokenValue.Trrule:
emitter.doResourceBegin(id);
break;
default:
emitter.doID(id);
break;
}
return true;
}
/// <summary>
/// Parse the list of attributes - separated by ';'s. Attributes always are in the
/// form 'id=value' and indicate key/value pairs in the iCalendar attribute format.
/// </summary>
/// <returns></returns>
protected virtual bool parseAttributes(Scanner scan)
{
Token key = scan.GetNextToken(ScannerState.ParseKey);
if (key == null || key.TokenVal == TokenValue.Error)
{
// some kind of error - skip rest of line and continue
if (key == null)
reportError(scanner, " expecting ID - found nothing.");
else
reportError(scanner, " expecting ID - found " + key.TokenText);
return false;
}
Token sep = scan.GetNextToken(ScannerState.ParseSimple);
if (sep == null || sep.TokenVal != TokenValue.Equals)
{
// some kind of error - skip rest of line and continue
if (sep == null)
reportError(scanner, " expecting = - found nothing.");
else
reportError(scanner, " expecting = - found " + sep.TokenText);
return false;
}
Token val = scan.GetNextToken(ScannerState.ParseParms);
if (val == null || val.TokenVal == TokenValue.Error)
{
// some kind of error - skip rest of line and continue
if (val == null)
reportError(scanner, " expecting parameter - found nothing.");
else
reportError(scanner, " expecting parameter - found " + val.TokenText);
return false;
}
if (key.TokenVal == TokenValue.Tvalue && scanner == scan)
{
// it's an IPROP - don't ask...
iprop = val;
}
else
{
attributes.Push(new DictionaryEntry(key, val));
}
// do a recursive case to identify all of the attributes
sep = scan.GetNextToken(ScannerState.ParseSimple);
if (sep == null || sep.TokenVal == TokenValue.Error)
{
// if we are parsing an rrule - this is the line termination
if (scanner != scan)
{
return true;
}
// some kind of error - skip rest of line and continue
if (sep == null)
reportError(scanner, " expecting : or ; - found nothing.");
else
reportError(scanner, " expecting : or ; - found " + sep.TokenText);
return false;
}
if (sep.TokenVal == TokenValue.Colon)
{
// termination case
return true;
}
else if (sep.TokenVal == TokenValue.SemiColon)
{
// recursive case
return parseAttributes(scan);
}
return true;
}
/// <summary>
/// Parse the value. The value is the last data item on a iCalendar input line.
/// </summary>
/// <returns></returns>
protected virtual bool parseValue()
{
Token val = scanner.GetNextToken(ScannerState.ParseValue);
if (val == null || val.TokenVal == TokenValue.Error)
{
// some kind of error - skip rest of line and continue
if (val == null)
reportError(scanner, " expecting value - found nothing.");
else
reportError(scanner, " expecting value - found " + val.TokenText);
return false;
}
// the emmision of code for the value will depend on the ID for this line
if (id.isSymbolicProperty())
{
emitter.doSymbolic(val);
return false; // because this ends the tag
}
else if (id.isMailtoProperty())
{
emitter.doMailto(val);
}
else if (id.isValueProperty())
{
if (id.TokenVal == TokenValue.Trrule)
{
// this is a special case - the value will be an attribute list...
parseAttributes(new Scanner(new StringReader(val.TokenText)));
}
else
{
emitter.doValueProperty(val, iprop);
}
}
else if (iprop != null && id.TokenVal != TokenValue.Xtension)
{
if (iprop.TokenText.ToLowerInvariant() == "uri")
{
// special case
emitter.doURIResource(val);
}
else
{
emitter.doIprop(val, iprop);
}
return false;
}
else
{
if (id.TokenVal == TokenValue.TrecurrenceId)
val.FormatDateTime(); // if this is a recurrence id, then format the date so that it is a legal date type for RDF and RQL
emitter.doRest(val, id);
return false;
}
return true;
}
}
}