/// <summary> /// Remove column from header definition. /// </summary> /// <param name="nIndex"></param> public void RemoveColumn(int nIndex) { //throw exception if the header is locked if (mLocked) { throw new InvalidOperationException("This header is locked and can not be modified. Modifying the header would result in a corrupt DBF file. You can unlock the header by calling UnLock() method."); } DbfColumn oColRemove = mFields[nIndex]; mFields.RemoveAt(nIndex); oColRemove.mDataAddress = 0; mRecordLength -= oColRemove.Length; mHeaderLength -= ColumnDescriptorSize; //if you remove a column offset shift for each of the columns //following the one removed, we need to update those offsets. int nRemovedColLen = oColRemove.Length; for (int i = nIndex; i < mFields.Count; i++) { mFields[i].mDataAddress -= nRemovedColLen; } //clear the empty record mEmptyRecord = null; //set dirty bit mIsDirty = true; }
public bool ReadValue(int rowIndex, int columnIndex, out string result) { result = String.Empty; DbfColumn ocol = _header[columnIndex]; //move to the specified record, note that an exception will be thrown is stream is not seekable! //This is ok, since we provide a function to check whether the stream is seekable. long nSeekToPosition = _header.HeaderLength + (rowIndex * _header.RecordLength) + ocol.DataAddress; //check whether requested record exists. Subtract 1 from file length (there is a terminating character 1A at the end of the file) //so if we hit end of file, there are no more records, so return false; if (rowIndex < 0 || _dbfFile.Length - 1 <= nSeekToPosition) { return(false); } //move to position and read _dbfFile.Seek(nSeekToPosition, SeekOrigin.Begin); //read the value byte[] data = new byte[ocol.Length]; _dbfFile.Read(data, 0, ocol.Length); result = new string(encoding.GetChars(data, 0, ocol.Length)); return(true); }
/// <summary> /// Add a new column to the DBF header. /// </summary> /// <param name="oNewCol"></param> public void AddColumn(DbfColumn oNewCol) { //throw exception if the header is locked if (mLocked) { throw new InvalidOperationException("This header is locked and can not be modified. Modifying the header would result in a corrupt DBF file. You can unlock the header by calling UnLock() method."); } //since we are breaking the spec rules about max number of fields, we should at least //check that the record length stays within a number that can be recorded in the header! //we have 2 unsigned bytes for record length for a maximum of 65535. if (mRecordLength + oNewCol.Length > 65535) { throw new ArgumentOutOfRangeException("oNewCol", "Unable to add new column. Adding this column puts the record length over the maximum (which is 65535 bytes)."); } //add the column mFields.Add(oNewCol); //update offset bits, record and header lengths oNewCol.mDataAddress = mRecordLength; mRecordLength += oNewCol.Length; mHeaderLength += ColumnDescriptorSize; //clear empty record mEmptyRecord = null; //set dirty bit mIsDirty = true; }
/// <summary> /// Get date value. /// </summary> /// <param name="nColIndex"></param> /// <returns></returns> public DateTime GetDateValue(int nColIndex) { DbfColumn ocol = mHeader[nColIndex]; if (ocol.ColumnType == DbfColumn.DbfColumnType.Date) { string sDateVal = encoding.GetString(mData, ocol.DataAddress, ocol.Length); return(DateTime.ParseExact(sDateVal, "yyyyMMdd", CultureInfo.InvariantCulture)); } throw new InvalidDataException("Invalid data type. Column '" + ocol.Name + "' is not a date column."); }
/// <summary> /// Get date value. /// </summary> /// <param name="nColIndex"></param> /// <returns></returns> public void SetDateValue(int nColIndex, DateTime value) { DbfColumn ocol = mHeader[nColIndex]; DbfColumn.DbfColumnType ocolType = ocol.ColumnType; if (ocolType == DbfColumn.DbfColumnType.Date) { //Format date and set value, date format is like this: yyyyMMdd //------------------------------------------------------------- encoding.GetBytes(value.ToString("yyyyMMdd"), 0, ocol.Length, mData, ocol.DataAddress); } else { throw new Exception("Invalid data type. Column is of '" + ocol.ColumnType.ToString() + "' type, not date."); } }
/// <summary> /// Read header data, make sure the stream is positioned at the start of the file to read the header otherwise you will get an exception. /// When this function is done the position will be the first record. /// </summary> /// <param name="reader"></param> public void Read(BinaryReader reader) { // type of reader. int nFileType = reader.ReadByte(); if (nFileType != 0x03) { throw new NotSupportedException("Unsupported DBF reader Type " + nFileType); } // parse the update date information. int year = reader.ReadByte(); int month = reader.ReadByte(); int day = reader.ReadByte(); mUpdateDate = new DateTime(year + 1900, month, day); // read the number of records. mNumRecords = reader.ReadUInt32(); // read the length of the header structure. mHeaderLength = reader.ReadUInt16(); // read the length of a record mRecordLength = reader.ReadInt16(); // skip the reserved bytes in the header. reader.ReadBytes(20); // calculate the number of Fields in the header int nNumFields = (mHeaderLength - FileDescriptorSize) / ColumnDescriptorSize; //offset from start of record, start at 1 because that's the delete flag. int nDataOffset = 1; // read all of the header records mFields = new List <DbfColumn>(nNumFields); for (int i = 0; i < nNumFields; i++) { // read the field name var buffer = new char[11]; buffer = reader.ReadChars(11); var sFieldName = new string(buffer); int nullPoint = sFieldName.IndexOf((char)0); if (nullPoint != -1) { sFieldName = sFieldName.Substring(0, nullPoint); } //read the field type var cDbaseType = (char)reader.ReadByte(); // read the field data address, offset from the start of the record. int nFieldDataAddress = reader.ReadInt32(); //read the field length in bytes //if field type is char, then read FieldLength and Decimal count as one number to allow char fields to be //longer than 256 bytes (ASCII char). This is the way Clipper and FoxPro do it, and there is really no downside //since for char fields decimal count should be zero for other versions that do not support this extended functionality. //----------------------------------------------------------------------------------------------------------------------- int nFieldLength = 0; int nDecimals = 0; if (cDbaseType == 'C' || cDbaseType == 'c') { //treat decimal count as high byte nFieldLength = reader.ReadUInt16(); } else { //read field length as an unsigned byte. nFieldLength = reader.ReadByte(); //read decimal count as one byte nDecimals = reader.ReadByte(); } //read the reserved bytes. reader.ReadBytes(14); //Create and add field to collection mFields.Add(new DbfColumn(sFieldName, DbfColumn.GetDbaseType(cDbaseType), nFieldLength, nDecimals, nDataOffset)); // add up address information, you can not trust the address recorded in the DBF file... nDataOffset += nFieldLength; } // Last byte is a marker for the end of the field definitions. reader.ReadBytes(1); //read any extra header bytes...move to first record //equivalent to reader.BaseStream.Seek(mHeaderLength, SeekOrigin.Begin) except that we are not using the seek function since //we need to support streams that can not seek like web connections. int nExtraReadBytes = mHeaderLength - (FileDescriptorSize + (ColumnDescriptorSize * mFields.Count)); if (nExtraReadBytes > 0) { reader.ReadBytes(nExtraReadBytes); } //if the stream is not forward-only, calculate number of records using file size, //sometimes the header does not contain the correct record count //if we are reading the file from the web, we have to use ReadNext() functions anyway so //Number of records is not so important and we can trust the DBF to have it stored correctly. if (reader.BaseStream.CanSeek && mNumRecords == 0) { //notice here that we subtract file end byte which is supposed to be 0x1A, //but some DBF files are incorrectly written without this byte, so we round off to nearest integer. //that gives a correct result with or without ending byte. if (mRecordLength > 0) { mNumRecords = (uint)Math.Round(((double)(reader.BaseStream.Length - mHeaderLength - 1) / mRecordLength)); } } //lock header since it was read from a file. we don't want it modified because that would corrupt the file. //user can override this lock if really necessary by calling UnLock() method. mLocked = true; //clear dirty bit mIsDirty = false; }
/// <summary> /// Guards against incorrect column value access. /// </summary> /// <param name="methodName">The name of the calling method.</param> /// <param name="ordinal">The zero based column ordinal.</param> /// <param name="dbfColumnType">The type of the column.</param> private void GuardGetValue(string methodName, int ordinal, DbfColumn.DbfColumnType dbfColumnType) { this.GuardOpen(methodName); this.GuardOrdinal(methodName, ordinal); this.GuardColumnType(methodName, ordinal, dbfColumnType); }
// ReSharper restore UnusedParameter.Local /// <summary> /// Guards against accessing a column as an invalid type. /// </summary> /// <param name="methodName">The name of the calling method.</param> /// <param name="ordinal">The zero based column ordinal.</param> /// <param name="dbfColumnType">The type of the column.</param> // ReSharper disable UnusedParameter.Local private void GuardColumnType(string methodName, int ordinal, DbfColumn.DbfColumnType dbfColumnType) { if (this.dbfFile.Header[ordinal].ColumnType != dbfColumnType) { throw new InvalidOperationException(methodName); } }
/// <summary> /// Set string data to a column, if the string is longer than specified column length it will be truncated! /// If dbf column type is not a string, input will be treated as dbf column /// type and if longer than length an exception will be thrown. /// </summary> /// <param name="nColIndex"></param> /// <returns></returns> public string this[int nColIndex] { set { DbfColumn ocol = _header[nColIndex]; DbfColumn.DbfColumnType ocolType = ocol.ColumnType; // //if an empty value is passed, we just clear the data, and leave it blank. //note: test have shown that testing for null and checking length is faster than comparing to "" empty str :) //------------------------------------------------------------------------------------------------------------ if (string.IsNullOrEmpty(value)) { //this is like NULL data, set it to empty. i looked at SAS DBF output when a null value exists //and empty data are output. we get the same result, so this looks good. Buffer.BlockCopy(_emptyRecord, ocol.DataAddress, _data, ocol.DataAddress, ocol.Length); } else { //set values according to data type: //------------------------------------------------------------- if (ocolType == DbfColumn.DbfColumnType.Character) { if (!_allowStringTruncate && value.Length > ocol.Length) { throw new DbfDataTruncateException("Value not set. String truncation would occur and AllowStringTruncate flag is set to false. To supress this exception change AllowStringTruncate to true."); } //BlockCopy copies bytes. First clear the previous value, then set the new one. Buffer.BlockCopy(_emptyRecord, ocol.DataAddress, _data, ocol.DataAddress, ocol.Length); encoding.GetBytes(value, 0, value.Length > ocol.Length ? ocol.Length : value.Length, _data, ocol.DataAddress); } else if (ocolType == DbfColumn.DbfColumnType.Number) { if (ocol.DecimalCount == 0) { //integers //---------------------------------- //throw an exception if integer overflow would occur if (!_allowIntegerTruncate && value.Length > ocol.Length) { throw new DbfDataTruncateException("Value not set. Integer does not fit and would be truncated. AllowIntegerTruncate is set to false. To supress this exception set AllowIntegerTruncate to true, although that is not recomended."); } //clear all numbers, set to [space]. //----------------------------------------------------- Buffer.BlockCopy(_emptyRecord, 0, _data, ocol.DataAddress, ocol.Length); //set integer part, CAREFUL not to overflow buffer! (truncate instead) //----------------------------------------------------------------------- int nNumLen = value.Length > ocol.Length ? ocol.Length : value.Length; encoding.GetBytes(value, 0, nNumLen, _data, (ocol.DataAddress + ocol.Length - nNumLen)); } else { ///TODO: we can improve perfomance here by not using temp char arrays cDec and cNum, ///simply directly copy from source string using encoding! //break value down into integer and decimal portions //-------------------------------------------------------------------------- int nidxDecimal = value.IndexOf('.'); //index where the decimal point occurs char[] cDec = null; //decimal portion of the number char[] cNum = null; //integer portion if (nidxDecimal > -1) { cDec = value.Substring(nidxDecimal + 1).Trim().ToCharArray(); cNum = value.Substring(0, nidxDecimal).ToCharArray(); //throw an exception if decimal overflow would occur if (!_allowDecimalTruncate && cDec.Length > ocol.DecimalCount) { throw new DbfDataTruncateException("Value not set. Decimal does not fit and would be truncated. AllowDecimalTruncate is set to false. To supress this exception set AllowDecimalTruncate to true."); } } else { cNum = value.ToCharArray(); } //throw an exception if integer overflow would occur if (!_allowIntegerTruncate && cNum.Length > ocol.Length - ocol.DecimalCount - 1) { throw new DbfDataTruncateException("Value not set. Integer does not fit and would be truncated. AllowIntegerTruncate is set to false. To supress this exception set AllowIntegerTruncate to true, although that is not recomended."); } //------------------------------------------------------------------------------------------------------------------ // NUMERIC TYPE //------------------------------------------------------------------------------------------------------------------ //clear all decimals, set to 0. //----------------------------------------------------- Buffer.BlockCopy(_decimalClear, 0, _data, (ocol.DataAddress + ocol.Length - ocol.DecimalCount), ocol.DecimalCount); //clear all numbers, set to [space]. Buffer.BlockCopy(_emptyRecord, 0, _data, ocol.DataAddress, (ocol.Length - ocol.DecimalCount)); //set decimal numbers, CAREFUL not to overflow buffer! (truncate instead) //----------------------------------------------------------------------- if (nidxDecimal > -1) { int nLen = cDec.Length > ocol.DecimalCount ? ocol.DecimalCount : cDec.Length; encoding.GetBytes(cDec, 0, nLen, _data, (ocol.DataAddress + ocol.Length - ocol.DecimalCount)); } //set integer part, CAREFUL not to overflow buffer! (truncate instead) //----------------------------------------------------------------------- int nNumLen = cNum.Length > ocol.Length - ocol.DecimalCount - 1 ? (ocol.Length - ocol.DecimalCount - 1) : cNum.Length; encoding.GetBytes(cNum, 0, nNumLen, _data, ocol.DataAddress + ocol.Length - ocol.DecimalCount - nNumLen - 1); //set decimal point //----------------------------------------------------------------------- _data[ocol.DataAddress + ocol.Length - ocol.DecimalCount - 1] = (byte)'.'; } } else if (ocolType == DbfColumn.DbfColumnType.Float) { //------------------------------------------------------------------------------------------------------------------ // FLOAT TYPE // example: value=" 2.40000000000e+001" Length=19 Decimal-Count=11 //------------------------------------------------------------------------------------------------------------------ // check size, throw exception if value won't fit: if (value.Length > ocol.Length) { throw new DbfDataTruncateException("Value not set. Float value does not fit and would be truncated."); } double parsed_value; if (!Double.TryParse(value, out parsed_value)) { //value did not parse, input is not correct. throw new DbfDataTruncateException("Value not set. Float value format is bad: '" + value + "' expected format: ' 2.40000000000e+001'"); } //clear value that was present previously Buffer.BlockCopy(_decimalClear, 0, _data, ocol.DataAddress, ocol.Length); //copy new value at location char[] valueAsCharArray = value.ToCharArray(); encoding.GetBytes(valueAsCharArray, 0, valueAsCharArray.Length, _data, ocol.DataAddress); } else if (ocolType == DbfColumn.DbfColumnType.Integer) { //note this is a binary Integer type! //---------------------------------------------- ///TODO: maybe there is a better way to copy 4 bytes from int to byte array. Some memory function or something. _tempIntVal[0] = Convert.ToInt32(value); Buffer.BlockCopy(_tempIntVal, 0, _data, ocol.DataAddress, 4); } else if (ocolType == DbfColumn.DbfColumnType.Memo) { //copy 10 digits... ///TODO: implement MEMO throw new NotImplementedException("Memo data type functionality not implemented yet!"); } else if (ocolType == DbfColumn.DbfColumnType.Boolean) { if (String.Compare(value, "true", true) == 0 || String.Compare(value, "1", true) == 0 || String.Compare(value, "T", true) == 0 || String.Compare(value, "yes", true) == 0 || String.Compare(value, "Y", true) == 0) { _data[ocol.DataAddress] = (byte)'T'; } else if (value == " " || value == "?") { _data[ocol.DataAddress] = (byte)'?'; } else { _data[ocol.DataAddress] = (byte)'F'; } } else if (ocolType == DbfColumn.DbfColumnType.Date) { //try to parse out date value using Date.Parse() function, then set the value DateTime dateval; if (DateTime.TryParse(value, out dateval)) { SetDateValue(nColIndex, dateval); } else { throw new InvalidOperationException("Date could not be parsed from source string! Please parse the Date and set the value (you can try using DateTime.Parse() or DateTime.TryParse() functions)."); } } else if (ocolType == DbfColumn.DbfColumnType.Binary) { throw new InvalidOperationException("Can not use string source to set binary data. Use SetBinaryValue() and GetBinaryValue() functions instead."); } else { throw new InvalidDataException("Unrecognized data type: " + ocolType.ToString()); } } } get { DbfColumn ocol = _header[nColIndex]; return(new string(encoding.GetChars(_data, ocol.DataAddress, ocol.Length))); } }
/// <summary> /// Add a new column to the DBF header. /// </summary> /// <param name="oNewCol"></param> public void AddColumn(DbfColumn oNewCol) { //throw exception if the header is locked if (_locked) throw new InvalidOperationException("This header is locked and can not be modified. Modifying the header would result in a corrupt DBF file. You can unlock the header by calling UnLock() method."); //since we are breaking the spec rules about max number of fields, we should at least //check that the record length stays within a number that can be recorded in the header! //we have 2 unsigned bytes for record length for a maximum of 65535. if (_recordLength + oNewCol.Length > 65535) throw new ArgumentOutOfRangeException("oNewCol", "Unable to add new column. Adding this column puts the record length over the maximum (which is 65535 bytes)."); //add the column _fields.Add(oNewCol); //update offset bits, record and header lengths oNewCol._dataAddress = _recordLength; _recordLength += oNewCol.Length; _headerLength += ColumnDescriptorSize; //clear empty record _emptyRecord = null; //set dirty bit _isDirty = true; _columnNameIndex = null; }
public void DbfColumn_Construct_NameNull_ThrowsException() { var dbfColumn = new DbfColumn(null, default(DbfColumn.DbfColumnType)); }
public void DbfColumn_Construct_NameLongerThan11Chars_ThrowsException() { var dbfColumn = new DbfColumn("TheseAreMoreThan11Chars", default(DbfColumn.DbfColumnType)); }
public void DbfColumn_Construct_NameEmpty_ThrowsException() { var dbfColumn = new DbfColumn(string.Empty, default(DbfColumn.DbfColumnType)); }
public void DbfColumn_Construct_DbfColumnTypeNumber_NoLenghtNorDecimalPrecisionSpecified_ThrowsException() { var dbfColumn = new DbfColumn("COLUMN", DbfColumn.DbfColumnType.Number); }
public void DbfColumn_Construct_DbfColumnTypeNumber_LenghtBiggerThanDecimalPrecision_ThrowsException() { var dbfColumn = new DbfColumn("COLUMN", DbfColumn.DbfColumnType.Number, 1, 2); }
/// <exception cref="Exception">Если файл не содержит необходимого столбца или имеет не верный формат</exception> protected static int FindColumnOrThrow(DbfHeader header, String columnName, DbfColumn.DbfColumnType? columnType = DbfColumn.DbfColumnType.Character, int? maxLen = null) { var i = header.FindColumn(columnName); if (i < 0) throw new Exception(ClassName + "Отсутсвует столбец " + columnName); var column = header[i]; if (columnType.HasValue && column.ColumnType != columnType) throw new Exception(String.Format(ClassName + "Тип столбца {0} должен быть {1}", columnName, columnType)); if (maxLen.HasValue && column.Length > maxLen) throw new Exception(String.Format(ClassName + "Длина столбца {0} не должна превышать {1}", columnName, maxLen)); return i; }
/// <summary> /// Create and add a new column with specified name and type. /// </summary> /// <param name="sName"></param> /// <param name="type"></param> public void AddColumn(string sName, DbfColumn.DbfColumnType type) { AddColumn(new DbfColumn(sName, type)); }
protected internal string ReadValue(Stream obr, int colIndex) { DbfColumn ocol = _header[colIndex]; return(new string(encoding.GetChars(_data, ocol.DataAddress, ocol.Length))); }
/// <summary> /// Create and add a new column with specified name, type, length, and decimal precision. /// </summary> /// <param name="sName">Field name. Uniqueness is not enforced.</param> /// <param name="type"></param> /// <param name="nLength">Length of the field including decimal point and decimal numbers</param> /// <param name="nDecimals">Number of decimal places to keep.</param> public void AddColumn(string sName, DbfColumn.DbfColumnType type, int nLength, int nDecimals) { AddColumn(new DbfColumn(sName, type, nLength, nDecimals)); }