local abc abc=repl('x', 256) abcshort = '1234546' a2345678900=.f. a2345678901=.t. a2345678902=804.3 a2345678903={^2012/01/01} a2345678904={^2012/01/01 01:02:03} a2345678905=$-1234.5678 a2345678906 = null dime aa[2] aa[1] = 1 aa[2] = 2 dime LongNameArray[4, 3] local i, j for i = 1 to alen(LongNameArray, 1) for j = 1 to alen(LongNameArray, 2) LongNameArray[i, j] = i + j/100 endfor endfor save to 'd:\tmp\1.txt'Code to produce the output below
class VfpVariableTest { public static void Main() { byte[] bytes = File.ReadAllBytes(@"d:\tmp\1.txt"); Dictionary<String, VfpVariable> dict = VfpMemoryRestore.GetVariables(bytes, false); foreach (var v in dict) { Console.Write("Name: {0} Type: {1}", v.Value.Name, v.Value.Type); if (v.Value.Type == VfpValueType.Array) { if (v.Value.Value is VfpVariable[]) { // one dimensional array VfpVariable[] vfpArray = v.Value.Value as VfpVariable[]; Console.WriteLine(); for (int i = 0; i < vfpArray.Length; i++) Console.WriteLine("{0}[{1}] = {2}", vfpArray[i].Name, i, vfpArray[i].Value); } else if (v.Value.Value is VfpVariable[,]) { // two dimensional array VfpVariable[,] vfpArray = v.Value.Value as VfpVariable[,]; Console.WriteLine(); for (int i = 0; i <= vfpArray.GetUpperBound(0); i++) for (int j = 0; j <= vfpArray.GetUpperBound(1); j++) Console.WriteLine("{0}[{1},{2}] = {3}", vfpArray[i, j].Name, i, j, vfpArray[i, j].Value); } } else if (v.Value.Value == null) { Console.WriteLine(" Value: {0}", "null"); } else if (v.Value.Type == VfpValueType.Char) { // treat as chars here byte[] s = (byte[])v.Value.Value; Console.WriteLine(" Value: {0}", Encoding.Default.GetString(s, 0, s.Length)); } else // no array { Console.WriteLine(" Value: {0}", v.Value.Value); } } Console.ReadLine(); } }output
Name: ABC Type: Char Value: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Name: A2345678901 Type: Logical Value: True Name: A2345678902 Type: Numeric Value: 804.3 Name: A2345678903 Type: Date Value: 01/01/2012 00:00:00 Name: A2345678904 Type: DateTime Value: 01/01/2012 01:02:03 Name: I Type: Numeric Value: 5 Name: AA Type: Array AA[0] = 1 AA[1] = 2 Name: J Type: Numeric Value: 4 Name: ABCSHORT Type: Char Value: 1234546 Name: A2345678905 Type: Currency Value: -1234.5678 Name: A2345678900 Type: Logical Value: False Name: A2345678906 Type: Logical Value: null Name: LONGNAMEARRAY Type: Array LONGNAMEARRAY[0,0] = 1.01 LONGNAMEARRAY[0,1] = 1.02 LONGNAMEARRAY[0,2] = 1.03 LONGNAMEARRAY[1,0] = 2.01 LONGNAMEARRAY[1,1] = 2.02 LONGNAMEARRAY[1,2] = 2.03 LONGNAMEARRAY[2,0] = 3.01 LONGNAMEARRAY[2,1] = 3.02 LONGNAMEARRAY[2,2] = 3.03 LONGNAMEARRAY[3,0] = 4.01 LONGNAMEARRAY[3,1] = 4.02 LONGNAMEARRAY[3,2] = 4.03
(2) Explanation, design, choicesWe start off with a class
class VfpVariable { public readonly string Name; public VfpValueType Type { get; private set; } public VfpVariableScope Scope { get; private set; } private object _Value; public object Value { get; private set;} // rest of code is at the end }VfpValueType is an enum
enum VfpValueType { Logical = 0, // bool Numeric, // double Currency, // decimal Char, // byte[] Date, // datetime DateTime, // datetime Array, // VfpVariable[] or VfpVariable[ , ] Object // not implemented }The scope is an enum - but I doubt you will care
enum VfpVariableScope { Public, Private, Local }The Value is implemented as an object - could have subclassed - but I found it too much of a burden
static class VfpMemoryRestore { // rest of the code at the end internal static Dictionary<String, VfpVariable> GetVariables(byte[] buffer, bool isMemoField) { / code at the end } }
(3) Full code at the end
enum VfpValueType { Logical = 0, // bool Numeric, // double Currency, // decimal Char, // byte[] Date, // datetime DateTime, // datetime Array, // VfpVariable[] or VfpVariable[ , ] Object // not implemented }
enum VfpVariableScope { Public, Private, Local }
class VfpVariable { public readonly string Name; public VfpValueType Type { get; private set; } public VfpVariableScope Scope { get; private set; } private object _Value; public object Value { get { return _Value; } private set { if (value == null) ; // ok else switch(Type) { case VfpValueType.Array: if( !(value is VfpVariable[]) && !(value is VfpVariable[,])) throw new ArgumentException("Wrong value for type"); break; case VfpValueType.Char: if( !(value is byte[]) ) throw new ArgumentException("Wrong value for type"); break; case VfpValueType.Currency: if( ! (value is decimal) ) throw new ArgumentException("Wrong value for type"); break; case VfpValueType.Date: if( !(value is DateTime) || ((DateTime)value).Date != (DateTime)value) throw new ArgumentException("Wrong value for type"); break; case VfpValueType.DateTime: if( !(value is DateTime) ) throw new ArgumentException("Wrong value for type"); break; case VfpValueType.Logical: if( !(value is bool) ) throw new ArgumentException("Wrong value for type"); break; case VfpValueType.Numeric: if (!(value is int) && !(value is uint) && !(value is Double)) throw new ArgumentException("Wrong value for type"); break; case VfpValueType.Object: throw new NotImplementedException("Object type"); break; default: throw new NotImplementedException("Type"); } _Value = value; } } //______________________________________________________________________ public VfpVariable(string name, VfpValueType type, object value, VfpVariableScope scope) { CheckName(name); Name = name; Type = type; // type must be assiged before value (see set accessor of Value) Value = value; Scope = scope; } //______________________________________________________________________ // other checks to add private void CheckName(string name) { if (String.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name"); } //______________________________________________________________________ }
static class VfpMemoryRestore { /* * 00-10 char[11] upper(name) '\0 terminated * 11-11 char Type, if lower(): extended name, except 0 (Null) * 12-15 length strings > 254 ( type H, includes \0) * 16-16 Length for * - char <= 255 chars ( includes \0' ) * * * not for * - logical * - numeric * - date * - dateTime * - null - not always * * * * 17-17 precision for numeric * 20-23 ?? * 24-24 0x00 = Private/public, 0x01 = local * 25-25 constant : 0x03 * 26-31 ? && case extended name * 32-33 len Name if extended name * 34-... name * ..-.. Value && otherwise * 32 - Value (or type if 11-11 == '0' * */ private const int EndOfFileMarker = 0x1a; private const int VariableTypeOffset = 11; private const int ValueOffset = 0x20; private const int ScopeOffset = 24; private const int ContentLengthOffset = 16; private const int LongStringContentLengthOffset = 12; //______________________________________________________________________ internal static Dictionary<String, VfpVariable> GetVariables(byte[] buffer, bool isMemoField) { Dictionary<String, VfpVariable> dict = new Dictionary<string, VfpVariable>(); int offset = isMemoField ? 2 : 0; VfpVariable v; while (offset < buffer.Length) { if (offset == buffer.Length - 1 && buffer[offset] == EndOfFileMarker) break; v = GetVariable(buffer, ref offset); dict.Add(v.Name, v); } return dict; } //______________________________________________________________________ private static VfpVariable GetVariable(byte[] buffer, ref int offset) { int valueOffset; // Name // valueOffset depends on the length of the name string name = GetName(buffer, offset, out valueOffset); // Scope VfpVariableScope scope = GetScope(buffer[offset + ScopeOffset]); //Type and value VfpValueType type; object value; int newOffset; byte[] x; // for strings int contentLength; byte varType = buffer[offset + VariableTypeOffset]; if (varType == (byte)'0') { type = GetVarType(buffer[offset + valueOffset]); contentLength = 1; value = null; newOffset = offset + valueOffset + contentLength; } else { type = GetVarType(varType); switch (type) { case VfpValueType.Char: if (varType == (byte)'C' || varType == (byte)'c') { contentLength = buffer[offset + ContentLengthOffset]; x = new byte[contentLength - 1]; Array.Copy(buffer, offset + valueOffset, x, 0, contentLength - 1); value = x; } else { contentLength = BitConverter.ToInt32(buffer, offset + LongStringContentLengthOffset); x = new byte[contentLength - 1]; Array.Copy(buffer, offset + valueOffset, x, 0, contentLength - 1); value = x; } newOffset = offset + valueOffset + contentLength; break; case VfpValueType.Logical: contentLength = 1; value = (buffer[offset +valueOffset] != 0x00); newOffset = offset + valueOffset + contentLength; break; case VfpValueType.Numeric: contentLength = 8; value = BitConverter.ToDouble(buffer, offset + valueOffset); newOffset = offset + valueOffset + contentLength; break; case VfpValueType.Date : case VfpValueType.DateTime: contentLength = 8; double d = BitConverter.ToDouble(buffer, offset + valueOffset); if (d == 0.0) value = null; else { if (type == VfpValueType.Date) d = d < 0.0 ? Math.Ceiling(d) : Math.Floor(d); value = DateTime.FromOADate(d - 2415019.0); //2415019 is julian day of 1899 dec 30 } newOffset = offset + valueOffset + contentLength; break; case VfpValueType.Currency: contentLength = 8; Int64 v64 = BitConverter.ToInt64(buffer, offset + valueOffset); value = (decimal)v64 / 10000.0m; newOffset = offset + valueOffset + contentLength; break; case VfpValueType.Array: contentLength = 4; // for rowCount and columnCount uint rowCount = BitConverter.ToUInt16(buffer, offset + valueOffset); uint colCount = BitConverter.ToUInt16(buffer, offset + valueOffset + sizeof(UInt16)); newOffset = offset + valueOffset + contentLength; if (colCount == 0) { // one dimensional array VfpVariable[] array = new VfpVariable[rowCount]; for (int i = 0; i < rowCount; i++) array[i] = GetVariable(buffer, ref newOffset); value = array; } else { // two dimensional array VfpVariable[,] array = new VfpVariable[rowCount, colCount]; for (int i = 0; i < rowCount; i++) for( int j = 0; j < colCount; j++) array[i, j] = GetVariable(buffer, ref newOffset); value = array; } break; default: throw new NotImplementedException(String.Format("Program error: type {0}", type)); } } VfpVariable v = new VfpVariable(name, type, value, scope); offset = newOffset; return v; } //______________________________________________________________________ private static VfpValueType GetVarType(byte varType) { VfpValueType type; switch (varType) { case (byte)'C': case (byte)'c': case (byte)'H': case (byte)'h': type = VfpValueType.Char; break; case (byte)'L': case (byte)'l': type = VfpValueType.Logical; break; case (byte)'N': case (byte)'n': type = VfpValueType.Numeric; break; case (byte)'D': case (byte)'d': type = VfpValueType.Date; break; case (byte)'T': case (byte)'t': type = VfpValueType.DateTime; break; case (byte)'Y': case (byte)'y': type = VfpValueType.Currency; break; case (byte)'A': case (byte)'a': type = VfpValueType.Array; break; default: throw new NotImplementedException(String.Format("Program error: GetVarType {0}", varType.ToString())); } return type; } //______________________________________________________________________ private static string GetName(byte[] buffer, int offset, out int valueOffset) { int nameOffset, count; if (buffer[offset] != 0) //short name, null terminated { // name <= 10 bytes valueOffset = ValueOffset; nameOffset = offset; count = Array.IndexOf<byte>(buffer, 0, nameOffset) - nameOffset; if (count < 0 || count > 10) throw new Exception("Name inconsistency"); } else { count = BitConverter.ToInt16(buffer, offset + ValueOffset); valueOffset = ValueOffset + count + 2; nameOffset = offset + ValueOffset + 2; } string name = Encoding.Default.GetString(buffer, nameOffset, count); return name; } //______________________________________________________________________ private static VfpVariableScope GetScope(byte b) { VfpVariableScope scope; switch (b) { case 0x00: scope = VfpVariableScope.Private; break; case 0x01: scope = VfpVariableScope.Local; break; default: throw new NotImplementedException(String.Format("Program error: Scope {0}", b.ToString())); } return scope; } //______________________________________________________________________ }