This class as the title implies reads fixed length files into classes. The classes use attributes to define themselves as belonging to the Fixed Length File. Sorry, the comments in this case are still mostly in Dutch, you might be able to filter the examples if you don't speak the language, but if you do, that would be easier ;-)
Will try to do the translation later on.
using System;
using System.Collections;
using System.Reflection;
using System.IO;
using ImportExport;
using System.Globalization;
using System.Collections.Generic;
namespace ImportExport
{
///
<summary>
/// Just a base class to include statics and classes that shouldn't be included
/// in the generic FixedLengthReader.
/// </summary>
public abstract class FixedLengthReader{
public
readonly Type Base;
public FixedLengthReader(
Type Base,
int? Version)
{
this.Base = Base;
}
#region Version
/// <summary>
/// Optional.
/// Use this property to indicate the version of the file.
/// If one of the fieldattributes (<see cref="FLRAttribute"/> contains a specific
/// version, that field is only used if it corresponds with this number.
///
/// NB: changing this value after the constructor has finished, does not do anything for
/// the field versions
/// </summary>
protected int? version;
/// <summary>
/// Optional.
/// Use this property to indicate the version of the file.
/// If one of the fieldattributes (<see cref="FLRAttribute"/> contains a specific
/// version, that field is only used if it corresponds with this number
/// </summary>
public int? Version { get { return version; } }
#endregion
///
<summary>
/// Represents a field of the RecordType of the FixedLengthReader
/// </summary>
protected class Field{
public Field(
FieldInfo fi,
FLRFieldAttribute fa)
{
this.FieldInfo = fi;
if (fa.Parser !=
null)
this.Parser = ParserBase.GetParser(fa.Parser);
}
public
ParserBase Parser;
public int Position = -1;
public int Length = -1;
public int FirstPositionNextField
{
get { return Position + Length; }
}
public readonly FieldInfo FieldInfo;
public Field Next;public
override int GetHashCode()
{
return Position;
}}
protected
abstract class ChildBase{
public
readonly FieldInfo FieldInfo;
public readonly ChildAttribute ca;
public ChildBase(
FieldInfo fi,
ChildAttribute ca)
{
this.FieldInfo = fi;
this.ca = ca;
}public abstract bool Fill(object ObjectToFill, Stream s);
public
static ChildBase CreateChild(
FieldInfo fi,
ChildAttribute ca)
{
return (
ChildBase)
Activator.CreateInstance(
typeof(Child<>).MakeGenericType(fi.FieldType), fi, ca);
}}
protected
class Child<RecordType>:
ChildBasewhere RecordType:new()
{public
readonly FixedLengthReader<RecordType> flr;
public Child(
FieldInfo fi,
ChildAttribute ca):
base(fi,ca)
{
this.flr = new FixedLengthReader<RecordType>();
}public
override bool Fill(
object ObjectToFill,
Stream s)
{
return Fill((RecordType)ObjectToFill, s);
}
public virtual bool Fill(RecordType ObjectToFill, Stream s)
{if (ca.IsMatch(s))
{
//TODO: collecties...
RecordType o = flr.getInstance();
flr.Fill(o, s);
FieldInfo.SetValue(ObjectToFill, o);
return true;
}
elsereturn false;
}}
protected
class ParserCollection{
struct
ParserInstance{
public
readonly int TypeCode;
public readonly Type TargetType;
Type parsertype;
public Type ParserType
{
get {
return parsertype; }
set{
if (value == parsertype) return;
if (value == null) throw new ArgumentNullException();
ParserBase.CheckType(value);
parsertype = value;
instance = null;
} }
public ParserInstance(Type TargetType, Type ParserType)
{this.TargetType = TargetType;
this.parsertype = null;
this.instance = null;
this.TypeCode = TargetType.GetHashCode();
this.ParserType = ParserType;
}
ParserBase instance;
public ParserBase GetInstance()
{
if (instance == null) instance = (ParserBase)Activator.CreateInstance(parsertype);
return instance;
}}
List<ParserInstance> list = new List<ParserInstance>();
public ParserCollection()
{
//asign default type parsers (these can be overriden by using the defaultparser attributes)
Type pt = typeof(TextParser);
list.Add(new ParserInstance(typeof(string), pt));
pt = typeof(IntegerParser);
list.Add(new ParserInstance(typeof(int), pt));
list.Add(new ParserInstance(typeof(long), pt));
pt = typeof(DateTimeExactParser);
list.Add(new ParserInstance(typeof(DateTime), pt));
pt = typeof(BoolParser);
list.Add(new ParserInstance(typeof(bool), pt));
}
public void Add(
Type TargetType,
Type ParserType)
{
this[TargetType] = ParserType;
}public
int IndexOf(
Type TargetType)
{
for (
int i = 0; i < list.Count; i++)
{
if (list[i].TargetType == TargetType)
return i;
}
return -1; }public
Type this[
Type TargetType]
{
get
{
int i = IndexOf(TargetType);
if (i == -1) return null;
return list[i].ParserType;
}
set
{int i = IndexOf(TargetType);
ParserInstance pi =
new ParserInstance(TargetType,
value);
if (i == -1)
list.Add(pi);
else
list[i] = pi;
} }public
ParserBase GetParser<T>()
{
return this[typeof(T)];
}internal
ParserBase GetParser(
Type TargetType)
{
int i = IndexOf(TargetType);
if (i == -1)
throw new Exception("No parser assigned for type: " + TargetType.FullName);
return list[i].GetInstance();
}internal
bool TryGetParser(
Type TargetType,
out ParserBase parser)
{
try
{
parser = GetParser(TargetType);
return true;
}
catch{
parser = null;
return false;
}
}}
}
/// <summary>Algemene klasse om snel 'fixed length' tekstbestanden in te kunnen lezen
/// Elke klasse die velden bevat waaraan ofwel het PositionAttribute, ofwel het FixedLengthAttribute zijn toegekend
/// kunnen gebruikt worden om mbv deze klasse in te lezen</summary>
///
/// Een heel bestand kan worden ingelezen met de ReadFile methode. Dit is tevens de snelste methode om
/// een bestand in te lezen, omdat per blok alleen de vereiste bytes worden ingezen en dus geen strings
/// op en neer hoeven te worden gestuurd en het kopieren van strings voorkomen kan worden (.net behandeld
/// strings als valuetypes. Een toewijzing van string a = string b veroorzaakt al een kopie)
///
/// De eerste stap is een klasse die kan worden ingelezen, dit is simpelweg een kwestie van
/// een FixedLengthAttribute toekennen aan de juiste velden.
/// <remarks></remarks>
/// <example>
/// class AClass
/// {
///        [FixedLength(35)]
///        public string AStringvalue;
///        [FixedLength(3)]
///        public double AdoubleValue;
///        //etc.
/// }
///
/// //om de volledige lijst terug te krijgen:
/// ArrayList AClasses = FixedLengthReader.ReadFile(typeof(AClass),"NameOfTheFile");
///
/// //da's al :)
/// //uiteraard kan ook een instantie worden gecreeerd van de FLR en daarmee worden gelezen,
/// //maar in het algemeen zal dit voldoen en uiteraard worden straks generics gebruikt ;-)
///
/// //Een enkele regel kan uitgelezen worden met ReadLine() Dit kan rechstreeks van de stream, maar ook van een string
///
///
/// //Als een regel in het bestand een vaste lengte heeft kan aan de klasse zelf een RowLengthAttribute
/// //toegekend worden. bijv.
/// [RowLength(2048)]
/// class AClass
/// {
/// }
///
/// /*
/// Numerieke waarden omzetten. bij numerieke waarde gaat het natuurlijk vooral om de gebroken getallen
/// Standaard wordt de <see cref="FloatParser"/> gebruikt voor gebroken getallen, maar die kan of per veld
/// of op klasse niveau anders toegekend worden. Op klasse niveau: */
/// [DefaultFloatParser(typeof(FloatDivide100))]
/// class AClass
/// {
/// }
///
/// // Per Veld kan ook een specifieke parser worden toegekend (dit kan niet alleen voor de gebroken
/// // getallen, maar voor alle velden), bijv
/// [FixedLength(4,Parser = typeof(EenDoubleParser))]
/// public double AdoubleValue;
///
/// // Ten slotte kan ook op klasse niveau een parser toegekend worden voor een bepaald type mbv
/// // het <see cref="DefaultParserAttribute"/>. Het 1e type is het type veld voor welke
/// // die parser gebruikt moet worden, het 2e type het type van de parser.
/// // Uiteraard geldt ook hier weer, dat Parsers toegekend per veld voorrang krijgen.
/// // Mbv de DefaultParserAttribute, kan bijv een Parser worden toegekend voor alle DateTime velden
/// </example>
public class FixedLengthReader<RecordType>:
FixedLengthReader//where RecordType : new()
{        
readonly Field firstfield;
readonly List<
ChildBase> children;
readonly int RowLength=-1;        
readonly ErrorMode emode;
readonly bool ReadToEndOfLine =
true;
/// <summary>
/// This field contains the default <see cref="ErrorMode"/> for newly created
/// <see cref="FixedLengthReader"/>s when no errormode is specified
/// </summary>
public static ErrorMode DefaultErrorMode =
ErrorMode.Throw;
public FixedLengthReader():
this(
null)
{
}
public FixedLengthReader(
ErrorMode ErrorMode):
this(ErrorMode,
null)
{
}
public FixedLengthReader(
int? Version)
: this(DefaultErrorMode, Version)
{
}
#region supporting attribute functions
T GetAttribute<T>()
where T:FLRAttribute
{return GetAttribute<T>(Base);
}static T GetAttribute<T>(
MemberInfo mi)
where T:FLRAttribute
{foreach (T t
in GetAttributes<T>(mi))
{
return t;
}
return null; }IEnumerable<T> GetAttributes<T>()
where T : FLRAttribute
{return GetAttributes<T>(Base);
}IEnumerable<
FLRAttribute> GetAttributes()
{
return GetAttributes<FLRAttribute>();
}static
IEnumerable<T> GetAttributes<T>(
MemberInfo mi)
where T : FLRAttribute
{foreach (
object o
in mi.GetCustomAttributes(
typeof(T),
true))
{
yield return (T)o;
} }#endregion
public FixedLengthReader(
ErrorMode ErrorMode,
int? Version)
: base(typeof(RecordType),Version)
{
emode = ErrorMode;
//check if an attribute is set
VersionAttribute va = GetAttribute<
VersionAttribute>();
if (va !=
null)
{
if (version.HasValue && va.Version != version)
throw
new Exception(
string.Format(
"Version was set in the constructor but also with a VersionAttribute on the recordtypes class. Versions can not be diffferent\nVersion in constructor:{0}\nVersion on {1}: {2}"
, version, Base.FullName, va.Version));
this.version = va.Version;
}//check class level attributes
foreach(
FLRAttribute fa
in GetAttributes())
if (fa
is VersionAttribute || ! fa.CheckVersion(version))
{
//skip this attribute: wrong version
//or in case of fa is versionattribute: already handled
}
else if(fa is RowLengthAttribute)
{this.RowLength = (fa as RowLengthAttribute).RowLength;
}
else if(fa is DefaultFloatParserAttribute)
{fparser = (fa as DefaultFloatParserAttribute).ParserType;
}
else if(fa is DefaultParserAttribute)
{DefaultParserAttribute dp = fa as DefaultParserAttribute;
typeparsers[dp.FieldType] = dp.ParserType;
}
else if (fa is ReadToEndOfLineAttribute)
{ReadToEndOfLine = (fa as ReadToEndOfLineAttribute).Enabled;
}//floating type parsers vullen
typeparsers[
typeof(
double)]
= typeparsers[typeof(float)]
= typeparsers[typeof(decimal)]
= fparser;
//Velden bepalen
List<
Field> fields =
new List<
Field>();
Type pat =
typeof(
PositionAttribute);
Field f;
bool needsort =
false;
foreach(
FieldInfo fi
in Base.GetFields())
{
FLRAttribute a = GetAttribute<
FLRAttribute>(fi);
if(a!=
null)
{
if (!a.CheckVersion(version))
{
//attribute does not apply to this version
continue;
}if(a
is ChildAttribute)
{
if(children==
null)
children = new List<ChildBase>();
children.Add(
ChildBase.CreateChild(fi,a
as ChildAttribute));
}
else
{f =
new Field(fi,a
as FLRFieldAttribute);
if (a
is FixedLengthAttribute)
{
f.Length = (a as FixedLengthAttribute).Length;
}
else if(a
is PositionAttribute)
{
PositionAttribute pa = a as PositionAttribute;
f.Position = pa.Position;
f.Length = pa.Length;
needsort = true;
}//assign a parser if none was assigned in a previous step
if(f.Parser==
null)
{
if(typeparsers.TryGetParser(f.FieldInfo.FieldType,
out f.Parser))
{
//no further action necessary , a default parser was assigned in the action above
}
else if (f.FieldInfo.FieldType.IsEnum)
{//enumerations get our special love and attention
typeparsers.Add(f.FieldInfo.FieldType,
typeof(EnumParser<>).MakeGenericType(f.FieldInfo.FieldType));
f.Parser = typeparsers.GetParser(f.FieldInfo.FieldType); }
else//unknown type or no default parser available:
//assign textparser
f.Parser = new TextParser();
}
fields.Add(f);
}
}
}
if(fields.Count==0)
throw
new Exception(
string.Format(
"[{0}] is not a valid source.\nThe generic field type does not contain any position attributes. Check if the type contains position attributes. If you use versioning, please check the version numbers of the attributes (Version of this Reader instance is{1})"
,typeof(RecordType).FullName,
Version==null ? " not set" : ": " + Version
));
if(needsort)fields.Sort();
int to = fields.Count -1;
for(
int i = 0 ; i <=to; i++ )
{
f = fields[i]
as Field;
if(i < to)
{
f.Next = fields[i+1] as Field;
if(f.Length==-1 ) f.Length = f.Next.Position - f.Position;
}
if(f.Position==-1)
{
f.Position = i==0 ? 0 : (fields[i-1] as Field).FirstPositionNextField;
}
}
firstfield = fields[0] as Field;
HasChildren = children != null;
}
readonly ParserCollection typeparsers = new ParserCollection();
/// <summary>
/// The default floating type parser. This parser
/// can be overriden by using the proper attributes
/// </summary>
readonly Type fparser = typeof(FloatParser);
public readonly bool HasChildren;
public RecordType ReadLine(
string Line)
{
RecordType o = getInstance();
Fill(o,Line);
return o;
}
ConstructorInfo ci;
internal RecordType getInstance()
{
if (ci ==
null)
ci = typeof(RecordType).GetConstructor(Type.EmptyTypes);
if (ci ==
null)
throw
new Exception(
"To use this functionality the RecordType should contain an accessible parameterless constructor. "+ typeof(RecordType).FullName + " does not contain such a constructor");
return (RecordType)ci.Invoke(
null);
//didn't include the generic restraint 'new()' because the class should
//be usable for filling of existing objects as well
//return new RecordType() ; }
public RecordType ReadLine(Stream stream)
{RecordType o = getInstance();
Fill(o,stream);
return o;
}public bool HasLineFeeds = true;
///
<summary>Leest een heel bestand uit en geeft een arraylist terug
/// met de gevulde instanties van het 'Base' type</summary>
/// <param name="File" type="string"></param>
/// <returns></returns>
public List<RecordType> ReadFile(
string File)
{
using(
StreamReader sr =
new StreamReader(File))
{
List<RecordType> al = ReadFile(sr);
sr.Close();
return al;
} }
public List<RecordType> ReadFile(StreamReader sr)
{List<RecordType> al =
new List<RecordType>();
long pos = 0;
Stream s = sr.BaseStream;                
while((
int)( s.Length - s.Position) > firstfield.Length)
{
try
{
pos = s.Position;
al.Add(ReadLine(s));
}
catch{
if(RollBack)s.Position = pos;
if(SkipLine)MoveToNextLine(s);
if(ThrowExceptions)throw;
} }
return al; }#region Errormode
bool checkEmode(
ErrorMode em)
{
return (emode & em) > 0;
}
bool ThrowExceptions
{get{return checkEmode(ErrorMode.Throw);}
}
bool RollBack
{get{return checkEmode(ErrorMode.RollBack);}
}
bool SkipLine
{get{return checkEmode(ErrorMode.SkipLine);}
}
#endregion/*
public static List<RecordType> ReadFile(string File)
{
return ReadFile(File,ErrorMode.SkipLine);
}*/
public static List<RecordType> ReadFile(
string File,
ErrorMode em)
{
return new FixedLengthReader<RecordType>(em).ReadFile(File);
}int b;
public void MoveToNextLine(
Stream stream)
{
search(stream,false);
ConsumeLineFeeds(stream);
}
void search(
Stream stream,
bool linefeeds)
{
while(stream.Position < stream.Length)
{
b = stream.ReadByte();
if(
(b== '\r' || b== '\n') == linefeeds
)continue;
stream.Position--;
break;
}
}
public void ConsumeLineFeeds(Stream stream)
{search(stream,true);
}public
void Fill(RecordType ObjectToFill,
string Line)
{
Fill(ObjectToFill,Line,null);
}
/// <summary>de algemene functie, of Line of stream wordt gebruikt, maar niet beide</summary>
/// <param name="ObjectToFill" type="object"></param>
/// <param name="Line" type="string"></param>
/// <param name="stream" type="System.IO.Stream"></param>
/// <returns></returns>
void Fill(RecordType ObjectToFill,string Line, Stream stream)
{
Field f = firstfield;
int b;
while(f !=
null)
{
string s;
if(f.Length==-1)
{
if(f.Next !=
null)
f.Length = f.Next.Position - f.Position;
else
{
if(Line !=
null)
f.Length = Line.Length - f.Position;
else
{
long pos = stream.Position;                            
while((b=stream.ReadByte()) != '\r' && b != '\n'){}
f.Length =(int)( stream.Position - pos);
stream.Position = pos;
} } }
if(stream==null)s= Line.Substring(f.Position,f.Length);
else{
byte[] buffer = new byte[f.Length];
stream.Read(buffer,0,f.Length);
s = System.Text.Encoding.ASCII.GetString(buffer);
}
f.FieldInfo.SetValue(ObjectToFill,ParseField(f,ref s));
if(!f.Parser.Success)
{if(ThrowExceptions)
throw
new Exception(
string.Format("Value '{0}' could not be parsed in column {1} (position {2} to {3})"
,s,f.FieldInfo.Name,f.Position,f.FirstPositionNextField-1));
}
f= f.Next; }// onderstaande functionaliteit is alleen beschikbaar als een
//stream wordt gelezen. Als maar 1 regel ingelezen wordt -> exit
if(stream==null)return;
//in het geval van een vaste rijlengte, de positie verder zetten
if(EmptyLength > 0)
{
stream.Position += EmptyLength;
}
//Indien nodig, linefeeds ook lezen om de positie goed te zetten
if(ReadToEndOfLine)
MoveToNextLine(stream);
else
if(HasLineFeeds)
{
ConsumeLineFeeds(stream);
}
//Als het Base type subregels bevat, die inlezen
//dit kan alleen als via een stream ingelezen wordt
if(HasChildren)
{
foreach(
ChildBase c
in children)
{
while(c.Fill(ObjectToFill,stream)){}
} }if(ObjectToFill
is IAfterFill)
(ObjectToFill as IAfterFill).onAfterFill();
}
int emptlen=-1;
/// <summary>
/// De niet gebruikte opvulling ingeval een vaste rijlengte gebruikt wordt
/// </summary>
int EmptyLength
{
get
{
if(emptlen==-1)
{
if(RowLength > 0)
{
emptlen = RowLength;
Field f = firstfield;
while(f!=
null)
{
emptlen -= f.Length;
f = f.Next;
}
}
elseemptlen = 0;
}
return emptlen; } }protected
virtual object ParseField(
Field f,
ref string s)
{    
return
Convert.ChangeType(
f.Parser.Parse(ref s),
f.FieldInfo.FieldType);    
}
///
<summary>Betere methode om te vullen: rechtstreeks van een (file)stream, zodat al de 'string' stukjes overgelagen kunnen worden</summary>
/// <param name="ObjectToFill" type="object"></param>
/// <param name="stream" type="System.IO.Stream"></param>
/// <returns></returns>
public void Fill(RecordType ObjectToFill,
Stream stream)
{
Fill(ObjectToFill,null, stream);
}
}
///
<summary>
/// interface die een klasse kan implementeren om nadat de FLR klaar is met inlezen
/// custom code uit te laten voeren
/// </summary>
public interface IAfterFill{
void onAfterFill();
}///
<summary>
/// Used in the <see cref="FixedLengthReader"/> to indicate how errors should be handled
/// </summary>[
Flags]
public enum ErrorMode{
Silent,
Throw =1,
RollBack = 2,
SkipLine = 4 | RollBack
}
#region Attributes
/// <summary>
/// This base class but serves for recognizing
/// the FRL attributes.
/// </summary>
public abstract class FLRAttribute:
Attribute{
#region Version
private
int? version;
/// <summary>
/// Use this property if the attribute only applies to a specific version
/// NB: this does NOT set the version of the class this attribute applies to. The only
/// exception being the <see cref="VersionAttribute"/> where the Version does do exactly that
/// </summary>
public int Version
{
get { return version.HasValue ? version.Value : 0; }
set { version = value; }
}private VersionComparissons verscomp;
bool verscompset;
///
<summary>
/// Determines how versions are checked. Only applies if a <see cref="Version"/> has
/// been set
/// </summary>
public VersionComparissons VersionComparisson
{
get {
return verscomp; }
set{
verscomp = value;
verscompset = true;
}
}///
<summary>
/// This value can't be set directly through the attribute (inullable members are not
/// allowed as properties in attribute setting it seems)
/// </summary>
internal int? VersionNumber
{
get { return version; }
set { version = value; }
}///
<summary>
/// Checks if the attribute corresponds with the current version.
/// If no version is applied to the <see cref="FLRAttribute"/>, it is
/// valid for all versions
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
internal bool CheckVersion(
int? FLRVersion)
{
if (version ==
null || version==FLRVersion)
return true;
if (FLRVersion.HasValue && verscompset)
{
if (verscomp ==
VersionComparissons.From)
return FLRVersion.Value >= version.Value;
if (verscomp ==
VersionComparissons.UpUntil)
return FLRVersion.Value <= version.Value;
}
return false; }
#endregion}
#region Fields (attributes to identify the columns in the file)
/// <summary>
/// De basis voor een attribuut voor de velden
/// </summary>[
AttributeUsage(
AttributeTargets.Field)]
public abstract class FLRFieldAttribute:
FLRAttribute{
Type parser;
/// <summary>
/// Geeft de mogelijkheid om een aangepast type Parser toe te kennen. Een parser zet de
/// waarde uit het bestand (tekst dus) om naar de uiteindelijke waarde.
/// Als deze niet specifiek is opgegeven, wordt gekeken naar het velddtype en geprobeerd
/// zelf de juiste Parser toe te kennen.
///
/// NB: Type moet afgeleid zijn van het type <see cref="ImportExport.Parsers.Parser"/> en de klasse moet een parameterloze
/// constructor bevatten
/// </summary>
public Type Parser
{
get{
return parser;}
set{
ImportExport.ParserBase.CheckType(value);
parser = value;
}
}}
///
<summary>
/// Dit attribuut kan worden gebruikt om aan te geven dat 1. een veld moet meegenomen worden
/// bij gebruik van een <see cref="FixedLengthReader"/> en 2. Hoe lang dat veld dan wel niet is.
///
/// NB: de velden moeten uiteraard wel in de goede volgorde in de klasse staan.
/// </summary>
public class FixedLengthAttribute:
FLRFieldAttribute{
public
readonly int Length;
public FixedLengthAttribute(
int Length)
{
this.Length = Length;
}}
///
<summary>
/// Alternatief voor velden in de goede volgorde aanhouden. <see cref="FixedLengthAttribute"/>
/// heeft absoluut de voorkeur voor zijn overzichtelijkheid, maar indien die methode niet afdoende
/// is, is er ook de mogelijkheid om de positie aan te geven
/// </summary>
/// <remarks></remarks>
public class PositionAttribute:
FLRFieldAttribute{
public readonly int Position;
public readonly int Length;
public PositionAttribute(int Position):this(Position,-1)
{
}
public PositionAttribute(
int Position,
int Length)
{
this.Position= Position;
this.Length = Length;
}}
#endregion
#region Childs (attributes to indicate a class field is a container that holds child rows)
/// <summary>
/// Attribute to indicate Child rows
/// </summary>
public class ChildAttribute:
FLRAttribute{
public readonly string StartsWith;
public readonly int Count = 1;        
/// <summary>
/// Dit attribuut zonder argumenten geeft simpelweg aan, dat de regel onder de hoofdregel
/// (of onder een bovenliggende regel met ChildAttribute) de regel is voor het veld (toewijzen aan
/// een collectie is met deze overload natuurlijk niet mogelijk) waaraan dit attribuut is toegekend.
/// </summary>
public ChildAttribute()
{
}
///
<summary>
/// Als dit attribuut wordt toegekend, moet een regel in het bestand beginnen met de
/// waarde opgegeven in "StartsWith" om te voldoen aan dit type en dan ingelezen te worden.
/// Dit kunnen meerdere regels onder elkaar zijn.
///
/// NB: Als StartsWith is ingevuld, gaat de Reader ervan uit dat dit een prefix is, waarna
/// welke de werkelijke gegevens beginnen. Als de positie niet vooruitgeschoven moet worden en
/// deze waarde alsnog ingelezen moet worden, zorg dan dat ConsumePrefix = false gezet wordt
/// (normaal attribuut: [Child("bla")]  , wordt dan [Child("bla", ConsumePrefix = false)]
/// </summary>
public ChildAttribute(
string StartsWith)
{
this.StartsWith = StartsWith;
}bool consumepre =
true;
public bool ConsumePrefix
{
get{return consumepre;}
set{consumepre = value;}
}///
<summary>
/// Geeft aan dat de <c>Count</c> regels onder de bovenliggende regel (de hoofdregel, danwel
/// een andere subregel) horen bij deze collectie</summary>
/// <param name="Count" type="int"></param>
/// <returns></returns>
public ChildAttribute(
int Count)
{
this.Count = Count;
}internal
bool IsMatch(
Stream s)
{
if(StartsWith!=
null)
{
for(
int i=0 ; i < StartsWith.Length ; i++)
{
if(s.ReadByte() != StartsWith[i])
{
s.Position -= ++i;
return false;
}
}
if(!consumepre)s.Position -= StartsWith.Length;
}//als tot hier gekomen is: is het een match
return true;
}
}
#endregion
#region Class Attributes (these attributes further set properties of a FixedLengthReader)
/// <summary>
/// Basisklasse voor attributen voor een rij-klasse (een klasse die de equivalent is voor
/// een rij in een fixed-length bestand)
/// </summary>[
AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct)]    
public abstract class FLRClassAttribute:
FLRAttribute{
}
/// <summary>
/// Met deze waarde kan een vaste rijlengte worden aangegeven, in geval een extra lege ruimte
/// is toegevoegd achter het laatste veld.
/// </summary>    
public class RowLengthAttribute:
FLRClassAttribute{
public
readonly int RowLength = -1;
/// <summary>
/// Met deze waarde kan een vaste rijlengte worden aangegeven, in geval een extra lege ruimte
/// is toegevoegd achter het laatste veld.
/// </summary>
public RowLengthAttribute(
int RowLength)
{
this.RowLength=RowLength;
} }public
class ReadToEndOfLineAttribute:
FLRClassAttribute{
public
readonly bool Enabled;
/// <summary>
/// Door dit attribuut toe te kennen wordt aangegeven of dat na het inlezen van het laatste
/// veld, doorgelezen moet worden tot aan het begin van de volgende rij.
/// (Als het om een vaste rij lengte gaat, kan ook het <see cref="RowLengthAttribute"/> attribuut
/// gebruikt worden)
/// Standaard wordt deze methodiek gebruikt door de <see cref="FixedLengthReader"/>, maar
/// met dit attribuut kan dit worden uitgezet of vastzetten
/// </summary>
public ReadToEndOfLineAttribute(
bool Enabled)
{
this.Enabled=Enabled;
} }
/*/// <summary>
/// Met deze waarde kan een minimum lengte
/// </summary>    
public class MinimumRowLengthAttribute:FLRClassAttribute
{
public readonly int MinimumRowLength = -1;
/// <summary>
/// Met deze waarde kan een vaste rijlengte worden aangegeven, in geval een extra lege ruimte
/// is toegevoegd achter het laatste veld.
/// </summary>
public MinimumRowLengthAttribute(int RowLength)
{
this.MinimumRowLength=RowLength;
}
}
*/
#region Versioning
///
<summary>
/// The kind of version comparisson that is used when checking if an <see cref="FLRAttribute"/> should
/// be used in a <see cref="FixedLengthReader"/>
/// </summary>
public enum VersionComparissons{
/// <summary>
/// Only the specific version is used (default)
/// </summary>
EqualsTo,
/// <summary>
/// All versions equal to or greater than the specified version are used
/// </summary>
From,
/// <summary>
/// Only versions smaller than or equal to this version are allowed.
/// </summary>
UpUntil
}
/// <summary>
/// An attribute to set the version of a class. This does the same
/// as including a version in the constructor of a new instance of a fixedlenghtreader,
/// but this way you can indicate a class (eg an inherited one) this class
/// will fixate the version when a <see cref="FixedLenghtReader"/> for this
/// type of class is created.
/// NB, using this class means that the <see cref="Version"/> property points
/// to the version of the class, not to the version when this attribute should be
/// used, as it means in the other attributes
/// </summary>
public class VersionAttribute:FLRClassAttribute
{///
<summary>
/// Sets this class to hold a fixed version number. Newly created instances
/// <see cref="FixedLengthReader"/>s using this class will use the specified version
/// </summary>
/// <param name="Version"></param>
public VersionAttribute(
int Version)
{
this.Version = Version;
} }#endregion
#endregion
#region ImportExport.Parsers
public abstract class ParserBase
{
///
<summary>
/// Checks if the given type is a valid ParserBase type
/// </summary>
/// <param name="value"></param>
internal static void CheckType(
Type value)
{
if (value !=
null)
{
if (!
typeof(
ParserBase).IsAssignableFrom(value))
throw new Exception("Type must inherit from Parser");
if (value.GetConstructor(
Type.EmptyTypes) ==
null)
throw new Exception("Type must implement a parameterless constructor");
if (value.IsAbstract)
throw new Exception("Type can not be abstract");
}
}
public
object Parse(
ref string value)
{
if (value[0] == ' ' || value[value.Length - 1] == ' ') value = value.Trim();
success = true;
return Parse(ref value, ref success);
}
protected abstract object Parse(ref string value, ref bool success);
public abstract Type TargetType { get;}
bool success;
/// <summary>
/// Indicates if the last called <see cref="Parse"/> was successful
/// </summary>
public bool Success
{
get { return success; }
}public
static implicit operator ParserBase(
Type type)
{
return GetParser(type);
}
/// <summary>
/// Returns the default parser for the specified type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
internal static ParserBase GetParser(Type type)
{if (type ==
null)
return null;
return (
ParserBase)
Activator.CreateInstance(
typeof(Parser<>).MakeGenericType(type));
}}
///
<summary>
/// Basis-klasse om een waarde te parsen
/// </summary>
public abstract class Parser<T>:
ParserBase{
protected
override object Parse(
ref string value,
ref bool success)
{
return parse(ref value, ref success);
}
public abstract T parse(ref string value, ref bool Success);public
override Type TargetType
{
get { return typeof(T); }
}}
///
<summary>
/// Geeft de mogelijkheid om voor een bepaald type veld een parser toe te kennen. Dit geldt
/// dus voor alle velden van dat type, behalve als voor dat veld weer een andere parser
/// is toegekend.
///
/// Omdat floating waarde meerdere typen kan zijn en omdat deze zoveel voorkomt, bestaat daarvoor
/// een apart attribuut (<see cref="DefaultFloatParserAttribute"/>)
/// </summary>
public class DefaultParserAttribute:
FLRClassAttribute{
public
readonly Type FieldType,ParserType;
public DefaultParserAttribute(
Type FieldType,
Type ParserType)
{
ParserBase.CheckType(ParserType);
this.ParserType=ParserType;
this.FieldType=FieldType;
} }///
<summary>
/// Geeft de mogelijkheid om een parser toe te kennen voor alle float - waarde velden
/// in de klasse. Per veld kan alsnog een Parser worden aangegeven, maar met behulp
/// van dit attribuut kan voor alle floats tegelijk 1 worden toegekend om herhaling te voorkomen.
/// (NB, Parsers per veld aangegeven gaan dus voor)
/// </summary>
public class DefaultFloatParserAttribute:
DefaultParserAttribute{
/// <summary>
/// Geeft de mogelijkheid om een parser toe te kennen voor alle float - waarde velden
/// in de klasse. Per veld kan alsnog een Parser worden aangegeven, maar met behulp
/// van dit attribuut kan voor alle floats tegelijk 1 worden toegekend om herhaling te voorkomen.
/// (NB, Parsers per veld aangegeven gaan dus voor)
/// </summary>
public DefaultFloatParserAttribute(Type ParserType):base(typeof(ValueType),ParserType)
{
}
}
///
<summary>
/// Klasse die dient om een waarde rechtstreeks tekst om te zetten. Dit doet dus inderdaad
/// niet erg veel ;-)
/// </summary>
public class TextParser:
Parser<
string>
{
public
override string parse(
ref string value,
ref bool Success)
{
return value;
} }///
<summary>
/// Klasse die dient om een waarde rechtstreeks naar een Integer waarde te parsen (dat is:
/// naar een niet gebroken waarde, niet perse naar het Type int.
/// </summary>
public class IntegerParser:
Parser<
long>
{
public
override long parse(
ref string value,
ref bool Success)
{
return ParseString(ref value, ref Success);
}public
static long ParseString(
ref string value,
ref bool Success)
{
for (
int i = 0; i < value.Length; i++)
if (!
char.IsDigit(value[i]))
{
Success = false;
return 0;
}
return long.Parse(value);
}
}
public
class EnumParser<EnumType> :
Parser<EnumType>
{
public override EnumType parse(
ref string value,
ref bool Success)
{
return (EnumType)Enum.Parse(typeof(EnumType), value, true);
}}
///
<summary>
/// Basis voor floating values parsen. Deze standaard variant gebruikt een "." voor
/// decimaal scheidingsteken en gaat uit van geen duizendtal scheidings teken
/// </summary>
public class FloatParser:
Parser<
double>
{
public
readonly NumberFormatInfo nfi;
public readonly NumberStyles ns;
public FloatParser()
{
nfi = new NumberFormatInfo();
nfi.NumberDecimalSeparator = decimalSeparator();
nfi.NumberGroupSeparator = thousandSeparator();
if(nfi.NumberDecimalSeparator.Length>0)
ns |= NumberStyles.AllowDecimalPoint;
if(nfi.NumberGroupSeparator.Length>0)
ns |= NumberStyles.AllowThousands;
}
public
virtual string decimalSeparator()
{
return "."
}public
virtual string thousandSeparator()
{
return ""
}public
override double parse(
ref string value,
ref bool Success)
{
double d;
Success =
double.TryParse(
value,
ns,
nfi
,out d);
return d;
}}
///
<summary>
/// Deze floatparser gebruikt de instellingen van de actieve cultuur om de waarde
/// om te zetten
/// </summary>    
public class FloatCultureParser:
FloatParser{
public
override string decimalSeparator()
{
return CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
}public
override string thousandSeparator()
{
return CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
}
}
///
<summary>
/// Standaard functionaliteit als een gebroken waarde als gehele waarde wordt weggeschreven of
/// als zodanig moet worden behandeld en daarna door 10^x gedeeld moet worden
/// </summary>
public class FloatDivideParser:
Parser<
double>
{
public FloatDivideParser(
int DecimalCount)
{
this.DecimalCount = DecimalCount;
}
/// <summary>
/// Het aantal decimalen waardoor de integer waardes gedeeld moet worden
/// </summary>
public readonly int DecimalCount;public
override double parse(
ref string value,
ref bool Success)
{
value = value.Replace(",",null).Replace(".",null);
double d = (double)IntegerParser.ParseString(ref value,ref Success);
if(Success)d/= Math.Pow(10,DecimalCount);
return d;
}
}
public
class FloatDivide100:
FloatDivideParser{
public FloatDivide100() : base(2) { }
}public
class FloatDivide1000:
FloatDivideParser{
public FloatDivide1000() : base(3) { }
}///
<summary>
/// Probeert een datetime te parsen met de huidige cultuur instellingen.
/// NB: dit is wel de standaard voor een cultuur, maar niet de standaard van de
/// <see cref="FixedLengthReader"/>. De FLR gebruikt standaard voor DateTime velden
/// de <see cref="DateTimeExactParser"/>
/// </summary>
public class DateTimeParser:
Parser<
DateTime>
{
public
override DateTime parse(
ref string value,
ref bool Success)
{
DateTime dt;
try{
dt =DateTime.Parse(value);
}
catch{
dt = DateTime.MinValue;
Success = false;
}
return dt;            
}}
///
<summary>
/// Probeert een waarde om te zetten vanuit een vast formaat. Afgeleide klassen kunnen
/// dit formaat overschrijven.
/// Voor deze klasse is dat formaat: "yyyyMMdd"
/// </summary>
public class DateTimeExactParser:
Parser<
DateTime>
{
string format;
public DateTimeExactParser()
{
format = GetFormat();
}
public override DateTime parse(
ref string value,
ref bool Success)
{
try
{
return DateTime.ParseExact(value,format,null);
}
catch
{Success = false;
return DateTime.MinValue;
} }///
<summary>
/// Formaat waarmee getracht wordt de string te parsen
/// </summary>
/// <returns></returns>
protected virtual string GetFormat()
{
return "yyyyMMdd"
}}
///
<summary>
/// If the value contains "1" or "true", the text is considered true, otherwise false
/// </summary>
public class BoolParser :
Parser<
bool>
{
public
override bool parse(
ref string value,
ref bool Success)
{
if (value ==
null)
return false;
switch (value.Length)
{
case 1:
return value == "1"
case 4:
return value.ToLower() == "true"
default:
return false;
} }}
///
<summary>
/// If the value contains text (any text), the value is considered true.
/// If a field contains no text-> false
/// </summary>
public class BoolAnyTextParser:
Parser<
bool>
{
public
override bool parse(
ref string value,
ref bool Success)
{
return value.Length>0;
}}
#endregion
#endregion
}
. . .