diff --git a/files/Untitled.fmp12 b/files/Untitled.fmp12 index 71e81f6..fdcd0ce 100644 Binary files a/files/Untitled.fmp12 and b/files/Untitled.fmp12 differ diff --git a/fmp/fmp_debug.go b/fmp/fmp_debug.go index 333376c..90eb3f0 100644 --- a/fmp/fmp_debug.go +++ b/fmp/fmp_debug.go @@ -3,6 +3,7 @@ package fmp import ( "fmt" "os" + "slices" ) const debugging = false @@ -80,7 +81,15 @@ func (c *FmpChunk) String() string { func (dict *FmpDict) string(parentPath string) string { s := "" - for k, v := range *dict { + keys := make([]uint64, 0, len(*dict)) + + for k := range *dict { + keys = append(keys, k) + } + slices.Sort(keys) + + for _, k := range keys { + v := (*dict)[k] s += fmt.Sprintf("%v%v: %v\n", parentPath, k, string(v.Value)) if v.Children != nil { diff --git a/fmp/fmp_table.go b/fmp/fmp_table.go index 305d75e..8b36d8e 100644 --- a/fmp/fmp_table.go +++ b/fmp/fmp_table.go @@ -3,11 +3,12 @@ package fmp type FmpTable struct { ID uint64 Name string - Columns []FmpColumn + Columns map[uint64]FmpColumn + Records map[uint64]FmpRecord } type FmpColumn struct { - ID uint64 + Index uint64 Name string Type FmpFieldType DataType FmpDataType @@ -17,6 +18,11 @@ type FmpColumn struct { Indexed bool } +type FmpRecord struct { + Index uint64 + Values map[uint64]string +} + func (ctx *FmpFile) Tables() []*FmpTable { tables := make([]*FmpTable, 0) ent := ctx.Dictionary.GetEntry(3, 16, 5) @@ -28,19 +34,19 @@ func (ctx *FmpFile) Tables() []*FmpTable { table := &FmpTable{ ID: path, - Name: decodeByteSeq(tableEnt.Children.GetValue(16)), - Columns: make([]FmpColumn, 0), + Name: decodeFmpString(tableEnt.Children.GetValue(16)), + Columns: map[uint64]FmpColumn{}, + Records: map[uint64]FmpRecord{}, } tables = append(tables, table) - colEnt := ctx.Dictionary.GetEntry(table.ID, 3, 5) - for colPath, colEnt := range *colEnt.Children { - name := decodeByteSeq(colEnt.Children.GetValue(16)) + for colPath, colEnt := range *ctx.Dictionary.GetChildren(table.ID, 3, 5) { + name := decodeFmpString(colEnt.Children.GetValue(16)) flags := colEnt.Children.GetValue(2) column := FmpColumn{ - ID: colPath, + Index: colPath, Name: name, Type: FmpFieldType(flags[0]), DataType: FmpDataType(flags[1]), @@ -55,7 +61,16 @@ func (ctx *FmpFile) Tables() []*FmpTable { column.AutoEnter = autoEnterOptionMap[flags[11]] } - table.Columns = append(table.Columns, column) + table.Columns[column.Index] = column + } + + for recPath, recEnt := range *ctx.Dictionary.GetChildren(table.ID, 5) { + record := FmpRecord{Index: recPath, Values: make(map[uint64]string)} + table.Records[record.Index] = record + + for colIndex, value := range *recEnt.Children { + record.Values[colIndex] = decodeFmpString(value.Value) + } } } diff --git a/fmp/fmp_test.go b/fmp/fmp_test.go index 1d7536b..1f40275 100644 --- a/fmp/fmp_test.go +++ b/fmp/fmp_test.go @@ -7,11 +7,11 @@ func TestOpenFile(t *testing.T) { if err != nil { t.Fatal(err) } - if f.FileSize != 393216 { + if f.FileSize != 229376 { t.Errorf("expected file size to be 393216, got %d", f.FileSize) } - if f.numSectors != 95 { - t.Errorf("expected number of sectors to be 95, got %d", f.numSectors) + if f.numSectors != 55 { + t.Errorf("expected number of sectors to be 55, got %d", f.numSectors) } if f.CreatorName != "Pro 12.0" { t.Errorf("expected application name to be 'Pro 12.0', got '%s'", f.CreatorName) @@ -29,7 +29,7 @@ func TestTables(t *testing.T) { } tables := f.Tables() - expectedNames := []string{"Untitled", "WayDomains", "WayProcesses"} + expectedNames := []string{"Untitled"} tableNames := []string{} for _, table := range tables { tableNames = append(tableNames, table.Name) @@ -67,4 +67,10 @@ func TestTables(t *testing.T) { if field.AutoEnter != FmpAutoEnterCalculationReplacingExistingValue { t.Errorf("expected field to have auto enter calculation replacing existing value, but it does not") } + if len(tables[0].Records) != 3 { + t.Errorf("expected table to have 3 records, but it has %d", len(tables[0].Records)) + } + if tables[0].Records[1].Values[1] != "629FAA83-50D8-401F-A560-C8D45217D17B" { + t.Errorf("first record has an incorrect ID '%s'", tables[0].Records[0].Values[0]) + } } diff --git a/fmp/fmp_util.go b/fmp/fmp_util.go index 495b12f..f523dee 100644 --- a/fmp/fmp_util.go +++ b/fmp/fmp_util.go @@ -36,6 +36,14 @@ func (dict *FmpDict) GetValue(path ...uint64) []byte { return nil } +func (dict *FmpDict) GetChildren(path ...uint64) *FmpDict { + ent := dict.GetEntry(path...) + if ent != nil { + return ent.Children + } + return &FmpDict{} +} + func (dict *FmpDict) SetValue(path []uint64, value []byte) { for i, key := range path { _, ok := (*dict)[key] @@ -61,7 +69,7 @@ func parseVarUint64(payload []byte) uint64 { return length } -func decodeByteSeq(payload []byte) string { +func decodeFmpString(payload []byte) string { result := "" for i := range payload { result += string(payload[i] ^ 0x5A)