/* DBFWriter Class for defining a DBF structure and addin data to that structure and finally writing it to an OutputStream. This file is part of DotNetDBF packege. original author (javadbf): anil@linuxense.com 2004/03/31 license: LGPL (http://www.gnu.org/copyleft/lesser.html) ported to C# (DotNetDBF): Jay Tuley 6/28/2007 */ using System; using System.Collections; using System.Collections.Generic; using System.IO; namespace DotNetDBF { public class DBFWriter : DBFBase, IDisposable { private DBFHeader header; private Stream raf; private int recordCount; private List v_records = new List(); private Stream _dataMemo; private string _dataMemoLoc; /// Creates an empty Object. public DBFWriter() { header = new DBFHeader(); } /// Creates a DBFWriter which can append to records to an existing DBF file. /// @param dbfFile. The file passed in should be a valid DBF file. /// @exception Throws DBFException if the passed in file does exist but not a valid DBF file, or if an IO error occurs. public DBFWriter(string dbfFile) { try { raf = File.Open(dbfFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); DataMemoLoc = Path.ChangeExtension(dbfFile, "dbt"); /* before proceeding check whether the passed in File object is an empty/non-existent file or not. */ if (raf.Length == 0) { header = new DBFHeader(); return; } header = new DBFHeader(); header.Read(new BinaryReader(raf)); /* position file pointer at the end of the raf */ raf.Seek(-1, SeekOrigin.End); /* check whether the last byte is 0x1A (end of file marker for dbf files) - in this case move 1 byte back to ignore it when writing new records */ var lastByte = raf.ReadByte(); /* Advances to end of stream */ if (lastByte == DBFFieldType.EndOfData) { raf.Seek(-1, SeekOrigin.End); } } catch (FileNotFoundException e) { throw new DBFException("Specified file is not found. ", e); } catch (IOException e) { throw new DBFException(" while reading header", e); } recordCount = header.NumberOfRecords; } public DBFWriter(Stream dbfFile) { raf = dbfFile; /* before proceeding check whether the passed in File object is an empty/non-existent file or not. */ if (raf.Length == 0) { header = new DBFHeader(); return; } header = new DBFHeader(); header.Read(new BinaryReader(raf)); /* position file pointer at the end of the raf */ raf.Seek(-1, SeekOrigin.End); /* check whether the last byte is 0x1A (end of file marker for dbf files) - in this case move 1 byte back to ignore it when writing new records */ var lastByte = raf.ReadByte(); /* Advances to end of stream */ if (lastByte == DBFFieldType.EndOfData) { raf.Seek(-1, SeekOrigin.End); } recordCount = header.NumberOfRecords; } public byte Signature { get => header.Signature; set => header.Signature = value; } public string DataMemoLoc { get => _dataMemoLoc; set { _dataMemoLoc = value; _dataMemo?.Close(); _dataMemo = File.Open(_dataMemoLoc, FileMode.OpenOrCreate, FileAccess.ReadWrite); } } public Stream DataMemo { get => _dataMemo; set => _dataMemo = value; } public byte LanguageDriver { set { if (header.LanguageDriver != 0x00) { throw new DBFException("LanguageDriver has already been set"); } header.LanguageDriver = value; } } public DBFField[] Fields { get => header.FieldArray; set { if (header.FieldArray != null) { throw new DBFException("Fields has already been set"); } if (value == null || value.Length == 0) { throw new DBFException("Should have at least one field"); } for (var i = 0; i < value.Length; i++) { if (value[i] == null) { throw new DBFException("Field " + (i + 1) + " is null"); } } header.FieldArray = value; try { if (raf != null && raf.Length == 0) { /* this is a new/non-existent file. So write header before proceeding */ header.Write(new BinaryWriter(raf)); } } catch (IOException e) { throw new DBFException("Error accessing file", e); } } } #region IDisposable Members /// Performs application-defined tasks associated with freeing, releasing, /// or resetting unmanaged resources. /// 2 public void Dispose() { Close(); } #endregion /** Add a record. */ public void WriteRecord(params object[] values) { if (raf == null) { throw new DBFException( "Not initialized with file for WriteRecord use, use AddRecord instead"); } AddRecord(values, true); } public void AddRecord(params object[] values) { if (raf != null) { throw new DBFException( "Appending to a file, requires using WriteRecord instead"); } AddRecord(values, false); } private void AddRecord(object[] values, bool writeImmediately) { if (header.FieldArray == null) { throw new DBFException( "Fields should be set before adding records"); } if (values == null) { throw new DBFException("Null cannot be added as row"); } if (values.Length != header.FieldArray.Length) { throw new DBFException( "Invalid record. Invalid number of fields in row"); } for (var i = 0; i < header.FieldArray.Length; i++) { var fld = header.FieldArray[i]; var val = values[i]; if (val is null || val is DBNull) { continue; } void ThrowErrorIfNot() { if (!(val is T)) { throw new DBFRecordException($"Invalid value '{val}' for field {i}({fld.Name}{fld.DataType})", 0); } } switch (fld.DataType) { case NativeDbType.Char: //ignore all objects have ToString() break; case NativeDbType.Logical: ThrowErrorIfNot(); break; case NativeDbType.Numeric: ThrowErrorIfNot(); break; case NativeDbType.Date: ThrowErrorIfNot(); break; case NativeDbType.Float: ThrowErrorIfNot(); break; case NativeDbType.Memo: ThrowErrorIfNot(); break; default: throw new ArgumentOutOfRangeException(); } } if (!writeImmediately) { v_records.Add(values); } else { try { WriteRecord(new BinaryWriter(raf), values); recordCount++; } catch (IOException e) { throw new DBFException( "Error occured while writing record. ", e); } } } ///Writes the set data to the OutputStream. public void Write(Stream tOut) { try { var outStream = new BinaryWriter(tOut); header.NumberOfRecords = v_records.Count; header.Write(outStream); /* Now write all the records */ var t_recCount = v_records.Count; for (var i = 0; i < t_recCount; i++) { /* iterate through records */ var t_values = (object[]) v_records[i]; WriteRecord(outStream, t_values); } outStream.Write(DBFFieldType.EndOfData); outStream.Flush(); } catch (IOException e) { throw new DBFException("Error Writing", e); } } public void Close() { /* everything is written already. just update the header for record count and the END_OF_DATA mark */ header.NumberOfRecords = recordCount; if (raf != null) { raf.Seek(0, SeekOrigin.Begin); header.Write(new BinaryWriter(raf)); raf.Seek(0, SeekOrigin.End); raf.WriteByte(DBFFieldType.EndOfData); raf.Close(); _dataMemo?.Close(); } else if (!string.IsNullOrEmpty(DataMemoLoc)) { DataMemo.Close(); } } private void WriteRecord(BinaryWriter dataOutput, object[] objectArray) { dataOutput.Write((byte) ' '); for (var j = 0; j < header.FieldArray.Length; j++) { /* iterate through fields */ switch (header.FieldArray[j].DataType) { case NativeDbType.Char: if (objectArray[j] != null && objectArray[j] != DBNull.Value) { var str_value = objectArray[j].ToString(); dataOutput.Write( Utils.textPadding(str_value, CharEncoding, header.FieldArray[j]. FieldLength ) ); } else { dataOutput.Write( Utils.textPadding("", CharEncoding, header.FieldArray[j]. FieldLength ) ); } break; case NativeDbType.Date: if (objectArray[j] != null && objectArray[j] != DBNull.Value) { var tDate = (DateTime) objectArray[j]; dataOutput.Write( CharEncoding.GetBytes(tDate.ToString("yyyyMMdd"))); } else { dataOutput.Write( Utils.FillArray(new byte[8], DBFFieldType.Space)); } break; case NativeDbType.Float: if (objectArray[j] != null && objectArray[j] != DBNull.Value) { var tDouble = Convert.ToDecimal(objectArray[j]); dataOutput.Write( Utils.NumericFormating( tDouble, CharEncoding, header.FieldArray[j].FieldLength, header.FieldArray[j].DecimalCount ) ); } else { dataOutput.Write( Utils.textPadding( NullSymbol, CharEncoding, header.FieldArray[j].FieldLength, Utils.ALIGN_RIGHT ) ); } break; case NativeDbType.Numeric: if (objectArray[j] != null && objectArray[j] != DBNull.Value) { var tDecimal = Convert.ToDecimal(objectArray[j]); dataOutput.Write( Utils.NumericFormating( tDecimal, CharEncoding, header.FieldArray[j].FieldLength, header.FieldArray[j].DecimalCount ) ); } else { dataOutput.Write( Utils.textPadding( NullSymbol, CharEncoding, header.FieldArray[j].FieldLength, Utils.ALIGN_RIGHT ) ); } break; case NativeDbType.Logical: if (objectArray[j] != null && objectArray[j] != DBNull.Value) { if ((bool) objectArray[j]) { dataOutput.Write(DBFFieldType.True); } else { dataOutput.Write(DBFFieldType.False); } } else { dataOutput.Write(DBFFieldType.UnknownByte); } break; case NativeDbType.Memo: if (objectArray[j] != null && objectArray[j] != DBNull.Value) { var tMemoValue = ((MemoValue) objectArray[j]); tMemoValue.Write(this); dataOutput.Write(Utils.NumericFormating(tMemoValue.Block, CharEncoding, 10, 0)); } else { dataOutput.Write( Utils.textPadding("", CharEncoding, 10 ) ); } break; default: throw new DBFException("Unknown field type " + header.FieldArray[j].DataType); } } /* iterating through the fields */ } } }