//******************************************************************************************************
// PhysicalParser.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 05/03/2012 - Stephen C. Wills, Grid Protection Alliance
// Generated original version of source code.
// 12/17/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using GSF.IO.Checksums;
using Ionic.Zlib;
namespace GSF.PQDIF.Physical
{
#region [ Enumerations ]
///
/// Enumeration which defines the types of compression used in PQDIF files.
///
public enum CompressionStyle : uint
{
///
/// No compression.
///
None = 0,
///
/// Compress the entire file after the container record.
/// This compression style is deprecated and is currently
/// not supported by this PQDIF library.
///
TotalFile = 1,
///
/// Compress the body of each record.
///
RecordLevel = 2
}
///
/// Enumeration which defines the algorithms used to compress PQDIF files.
///
public enum CompressionAlgorithm : uint
{
///
/// No compression.
///
None = 0,
///
/// Zlib compression.
/// http://www.zlib.net/
///
Zlib = 1,
///
/// PKZIP compression.
/// This compression algorithm is deprecated and
/// is currently not supported by this PQDIF library.
///
PKZIP = 64
}
#endregion
///
/// Represents a parser which parses the physical structure of a PQDIF file.
///
///
///
/// This class is used internally by the to read the
/// physical structure of the PQDIF file. If your goal is to read data from a PQD file into
/// an application, you probably do not want to instantiate the physical parser directly.
/// Instead, the logical parser will apply the rules governing the logical structure of the PQDIF
/// file to make the data more readily usable by an application that will be processing that data.
///
///
///
/// The following example of usage was adapted from the PQDIFDump utility, which represents a
/// bare minimum level of effort to read an uncompressed PQDIF file and display its contents
/// in a console application.
///
///
///
/// string fileName = args[0];
///
/// using (PhysicalParser parser = new PhysicalParser(fileName))
/// {
/// parser.Open();
///
/// while (parser.HasNextRecord())
/// {
/// Record record = parser.NextRecord();
/// Console.WriteLine(record);
/// Console.WriteLine();
/// }
/// }
///
///
public class PhysicalParser : IDisposable
{
#region [ Members ]
// Nested Types
private class UnknownElement : Element
{
private ElementType m_typeOfElement;
public UnknownElement(ElementType typeOfElement)
{
m_typeOfElement = typeOfElement;
}
public override ElementType TypeOfElement => m_typeOfElement;
}
private BinaryReader m_fileReader;
private CompressionStyle m_compressionStyle;
private CompressionAlgorithm m_compressionAlgorithm;
private bool m_hasNextRecord;
private HashSet m_headerAddresses;
private List m_exceptionList;
private int m_maximumExceptionsAllowed = 100;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
/// Path to the PQDIF file to be parsed.
public PhysicalParser(string filePath)
: this()
{
FilePath = filePath;
}
///
/// Creates a new instance of the class.
///
/// The stream containing the PQDIF file data.
/// True to leave the stream open when closing the parser; otherwise false.
/// is null.
/// is not both readable and seekable.
public PhysicalParser(Stream stream, bool leaveOpen = false)
: this()
{
Open(stream, leaveOpen);
}
private PhysicalParser()
{
m_headerAddresses = new HashSet();
m_exceptionList = new List();
}
#endregion
#region [ Properties ]
///
/// Gets or sets the file path (not just the name) of the PQDIF file to be parsed.
/// Obsolete in favor of .
///
[Obsolete("Property is deprecated. Please use FilePath instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public string FileName
{
get { return FilePath; }
set { FilePath = value; }
}
///
/// Gets or sets the file path of the PQDIF file to be parsed.
///
public string FilePath { get; set; }
///
/// Gets all the exceptions encountered while parsing.
///
public List ExceptionList
{
get
{
return m_exceptionList;
}
}
///
/// Gets or sets the compression style used by the PQDIF file.
///
/// compression has been deprecated by the standard and is not supported by this parser.
public CompressionStyle CompressionStyle
{
get
{
return m_compressionStyle;
}
set
{
if (value == CompressionStyle.TotalFile)
throw new NotSupportedException("Total file compression has been deprecated and is not supported");
m_compressionStyle = value;
}
}
///
/// Gets or sets the compression algorithm used by the PQDIF file.
///
/// compression has been deprecated by the standard and is not supported by this parser.
public CompressionAlgorithm CompressionAlgorithm
{
get
{
return m_compressionAlgorithm;
}
set
{
if (value == CompressionAlgorithm.PKZIP)
throw new NotSupportedException("PKZIP compression has been deprecated and is not supported");
m_compressionAlgorithm = value;
}
}
///
/// Gets or sets the maximum number of exceptions
/// in the exception list before parser will quit.
///
/// Enter a negative value to disable this safeguard.
public int MaximumExceptionsAllowed
{
get
{
return m_maximumExceptionsAllowed;
}
set
{
m_maximumExceptionsAllowed = value;
}
}
///
/// Gets a value that indicates whether the maximum number of exceptions has been reached.
///
public bool MaximumExceptionsReached
{
get
{
return (m_maximumExceptionsAllowed >= 0) && (m_exceptionList.Count > m_maximumExceptionsAllowed);
}
}
#endregion
#region [ Methods ]
///
/// Opens the PQDIF file.
///
/// has not been defined.
public void Open()
{
if ((object)FilePath == null)
throw new InvalidOperationException("Unable to open PQDIF file when no file name has been defined.");
using (m_fileReader)
{
m_fileReader = new BinaryReader(File.OpenRead(FilePath));
}
m_hasNextRecord = true;
}
///
/// Opens a PQDIF file from a stream of data.
///
/// The stream from which to read the PQDIF file.
/// True to leave the stream open when closing the parser; false otherwise.
/// is null.
/// is not both readable and seekable.
public void Open(Stream stream, bool leaveOpen = false)
{
if ((object)stream == null)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanRead)
throw new InvalidOperationException("Stream must be readable in order to parse PQDIF file data.");
if (!stream.CanSeek)
throw new InvalidOperationException("Stream must be seekable in order to parse PQDIF file data.");
using (m_fileReader)
{
m_fileReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen);
}
m_hasNextRecord = true;
}
///
/// Returns true if this parser has not reached the end of the PQDIF file.
///
/// false if the end of the file has been reached; true otherwise
public bool HasNextRecord()
{
return m_hasNextRecord;
}
///
/// Reads the next record from the PQDIF file.
///
/// The next record to be parsed from the PQDIF file.
public Record NextRecord()
{
RecordHeader header;
RecordBody body;
if (!m_hasNextRecord)
Reset();
header = ReadRecordHeader();
body = ReadRecordBody(header.BodySize);
if ((object)body != null)
body.Collection.TagOfElement = header.RecordTypeTag;
m_hasNextRecord =
header.NextRecordPosition > 0 &&
header.NextRecordPosition < m_fileReader.BaseStream.Length &&
m_headerAddresses.Add(header.NextRecordPosition) &&
!MaximumExceptionsReached;
m_fileReader.BaseStream.Seek(header.NextRecordPosition, SeekOrigin.Begin);
return new Record(header, body);
}
///
/// Jumps in the file to the location of the record with the given header.
///
/// The header to seek to.
public void Seek(RecordHeader header)
{
m_fileReader.BaseStream.Seek(header.Position, SeekOrigin.Begin);
}
///
/// Sets the parser back to the beginning of the file.
///
public void Reset()
{
m_compressionAlgorithm = CompressionAlgorithm.None;
m_compressionStyle = CompressionStyle.None;
m_fileReader.BaseStream.Seek(0, SeekOrigin.Begin);
m_hasNextRecord = true;
m_headerAddresses.Clear();
m_exceptionList.Clear();
}
///
/// Closes the PQDIF file.
///
public void Close()
{
m_fileReader.Close();
m_hasNextRecord = false;
}
///
/// Releases all resources held by this parser.
///
public void Dispose()
{
if ((object)m_fileReader != null)
{
m_fileReader.Dispose();
m_fileReader = null;
}
m_hasNextRecord = false;
}
// Reads the header of a record from the PQDIF file.
private RecordHeader ReadRecordHeader()
{
return new RecordHeader()
{
Position = (int)m_fileReader.BaseStream.Position,
RecordSignature = new Guid(m_fileReader.ReadBytes(16)),
RecordTypeTag = new Guid(m_fileReader.ReadBytes(16)),
HeaderSize = m_fileReader.ReadInt32(),
BodySize = m_fileReader.ReadInt32(),
NextRecordPosition = m_fileReader.ReadInt32(),
Checksum = m_fileReader.ReadUInt32(),
Reserved = m_fileReader.ReadBytes(16)
};
}
// Reads the body of a record from the PQDIF file.
private RecordBody ReadRecordBody(int byteSize)
{
byte[] bytes;
Adler32 checksum;
if (byteSize == 0)
return null;
bytes = m_fileReader.ReadBytes(byteSize);
checksum = new Adler32();
checksum.Update(bytes);
if (m_compressionAlgorithm == CompressionAlgorithm.Zlib && m_compressionStyle != CompressionStyle.None)
bytes = ZlibStream.UncompressBuffer(bytes);
using (MemoryStream stream = new MemoryStream(bytes))
using (BinaryReader reader = new BinaryReader(stream))
{
return new RecordBody()
{
Collection = ReadCollection(reader),
Checksum = checksum.Value
};
}
}
// Reads an element from the PQDIF file.
private Element ReadElement(BinaryReader recordBodyReader)
{
Element element;
Guid tagOfElement = Guid.Empty;
ElementType typeOfElement = 0;
PhysicalType typeOfValue = 0;
bool isEmbedded;
// Calculate the location of the next element
// after this one in case we encounter any errors
long nextLink = recordBodyReader.BaseStream.Position + 28L;
try
{
tagOfElement = new Guid(recordBodyReader.ReadBytes(16));
typeOfElement = (ElementType)recordBodyReader.ReadByte();
typeOfValue = (PhysicalType)recordBodyReader.ReadByte();
isEmbedded = recordBodyReader.ReadByte() != 0;
// Read reserved byte
recordBodyReader.ReadByte();
long link;
long returnLink;
returnLink = recordBodyReader.BaseStream.Position + 8L;
if (!isEmbedded || typeOfElement != ElementType.Scalar)
{
link = recordBodyReader.ReadInt32();
if (link < 0 || link >= recordBodyReader.BaseStream.Length)
throw new InvalidOperationException("Element link is outside the bounds of the file");
recordBodyReader.BaseStream.Seek(link, SeekOrigin.Begin);
}
switch (typeOfElement)
{
case ElementType.Collection:
element = ReadCollection(recordBodyReader);
break;
case ElementType.Scalar:
element = ReadScalar(recordBodyReader, typeOfValue);
break;
case ElementType.Vector:
element = ReadVector(recordBodyReader, typeOfValue);
break;
default:
element = new UnknownElement(typeOfElement);
element.TypeOfValue = typeOfValue;
break;
}
element.TagOfElement = tagOfElement;
recordBodyReader.BaseStream.Seek(returnLink, SeekOrigin.Begin);
return element;
}
catch (Exception ex)
{
m_exceptionList.Add(ex);
// Jump to the location of the next element after this one
if (nextLink < recordBodyReader.BaseStream.Length)
recordBodyReader.BaseStream.Seek(nextLink, SeekOrigin.Begin);
else
recordBodyReader.BaseStream.Seek(0L, SeekOrigin.End);
return new ErrorElement(typeOfElement, ex)
{
TagOfElement = tagOfElement,
TypeOfValue = typeOfValue
};
}
}
// Reads a collection element from the PQDIF file.
private CollectionElement ReadCollection(BinaryReader recordBodyReader)
{
int size = recordBodyReader.ReadInt32();
CollectionElement collection = new CollectionElement();
collection.ReadSize = size;
for (int i = 0; i < size; i++)
{
collection.AddElement(ReadElement(recordBodyReader));
if (recordBodyReader.BaseStream.Position >= recordBodyReader.BaseStream.Length || MaximumExceptionsReached)
break;
}
return collection;
}
// Reads a vector element from the PQDIF file.
private VectorElement ReadVector(BinaryReader recordBodyReader, PhysicalType typeOfValue)
{
VectorElement element = new VectorElement()
{
Size = recordBodyReader.ReadInt32(),
TypeOfValue = typeOfValue
};
byte[] values = recordBodyReader.ReadBytes(element.Size * typeOfValue.GetByteSize());
element.SetValues(values, 0);
return element;
}
// Reads a scalar element from the PQDIF file.
private ScalarElement ReadScalar(BinaryReader recordBodyReader, PhysicalType typeOfValue)
{
ScalarElement element = new ScalarElement()
{
TypeOfValue = typeOfValue
};
byte[] value = recordBodyReader.ReadBytes(typeOfValue.GetByteSize());
element.SetValue(value, 0);
return element;
}
#endregion
}
}