From cc10a1e7b4ee30e50767762d0f23e3847cec8758 Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Sun, 15 Jun 2025 21:09:58 +0200 Subject: [PATCH] Parse table columns --- fmp/fmp_const.go | 129 ++++++++++++++++++++++++++++++---------------- fmp/fmp_sector.go | 2 +- fmp/fmp_table.go | 49 ++++++++++++++++-- fmp/fmp_test.go | 26 ++++++++++ fmp/fmp_util.go | 6 +-- 5 files changed, 159 insertions(+), 53 deletions(-) diff --git a/fmp/fmp_const.go b/fmp/fmp_const.go index 421d065..7eb8ecc 100644 --- a/fmp/fmp_const.go +++ b/fmp/fmp_const.go @@ -1,8 +1,5 @@ package fmp -type FmpChunkType uint8 -type FmpCollation uint8 -type FmpScriptStepType uint8 type FmpError string func (e FmpError) Error() string { return string(e) } @@ -17,50 +14,94 @@ var ( ErrBadChunk = FmpError("bad chunk") ) -const ( - FmpChunkSimpleData FmpChunkType = iota - FmpChunkSegmentedData FmpChunkType = iota - FmpChunkSimpleKeyValue FmpChunkType = iota - FmpChunkLongKeyValue FmpChunkType = iota - FmpChunkPathPush FmpChunkType = iota - FmpChunkPathPushLong FmpChunkType = iota - FmpChunkPathPop FmpChunkType = iota - FmpChunkNoop FmpChunkType = iota -) +type FmpChunkType uint8 const ( - FmpCollationEnglish FmpCollation = 0x00 - FmpCollationFrench FmpCollation = 0x01 - FmpCollationGerman FmpCollation = 0x03 - FmpCollationItalian FmpCollation = 0x04 - FmpCollationDutch FmpCollation = 0x05 - FmpCollationSwedish FmpCollation = 0x07 - FmpCollationSpanish FmpCollation = 0x08 - FmpCollationDanish FmpCollation = 0x09 - FmpCollationPortuguese FmpCollation = 0x0A - FmpCollationNorwegian FmpCollation = 0x0C - FmpCollationFinnish FmpCollation = 0x11 - FmpCollationGreek FmpCollation = 0x14 - FmpCollationIcelandic FmpCollation = 0x15 - FmpCollationTurkish FmpCollation = 0x18 - FmpCollationRomanian FmpCollation = 0x27 - FmpCollationPolish FmpCollation = 0x2a - FmpCollationHungarian FmpCollation = 0x2b - FmpCollationRussian FmpCollation = 0x31 - FmpCollationCzech FmpCollation = 0x38 - FmpCollationUkrainian FmpCollation = 0x3e - FmpCollationCroatian FmpCollation = 0x42 - FmpCollationCatalan FmpCollation = 0x49 - FmpCollationFinnishAlt FmpCollation = 0x62 - FmpCollationSwedishAlt FmpCollation = 0x63 - FmpCollationGermanAlt FmpCollation = 0x64 - FmpCollationSpanishAlt FmpCollation = 0x65 - FmpCollationAscii FmpCollation = 0x66 + FmpChunkSimpleData FmpChunkType = iota + FmpChunkSegmentedData + FmpChunkSimpleKeyValue + FmpChunkLongKeyValue + FmpChunkPathPush + FmpChunkPathPushLong + FmpChunkPathPop + FmpChunkNoop ) +type FmpFieldType uint8 + +const ( + FmpFieldSimple FmpFieldType = 1 + FmpFieldCalculation FmpFieldType = 2 + FmpFieldScript FmpFieldType = 3 +) + +type FmpFieldStorageType uint8 + +const ( + FmpFieldStorageRegular FmpFieldStorageType = 0 + FmpFieldStorageGlobal FmpFieldStorageType = 1 + FmpFieldStorageCalculation FmpFieldStorageType = 8 + FmpFieldStorageUnstoredCalculation FmpFieldStorageType = 10 +) + +type FmpDataType uint8 + +const ( + FmpDataText FmpDataType = 1 + FmpDataNumber FmpDataType = 2 + FmpDataDate FmpDataType = 3 + FmpDataTime FmpDataType = 4 + FmpDataTS FmpDataType = 5 + FmpDataContainer FmpDataType = 6 +) + +type FmpAutoEnterOption uint8 + +const ( + FmpAutoEnterData FmpAutoEnterOption = iota + FmpAutoEnterSerialNumber + FmpAutoEnterCalculation + FmpAutoEnterCalculationReplacingExistingValue + FmpAutoEnterFromLastVisitedRecord + FmpAutoEnterCreateDate + FmpAutoEnterCreateTime + FmpAutoEnterCreateTS + FmpAutoEnterCreateName + FmpAutoEnterCreateAccountName + FmpAutoEnterModDate + FmpAutoEnterModTime + FmpAutoEnterModTS + FmpAutoEnterModName + FmpAutoEnterModAccountName +) + +var autoEnterPresetMap = map[uint8]FmpAutoEnterOption{ + 0: FmpAutoEnterCreateDate, + 1: FmpAutoEnterCreateTime, + 2: FmpAutoEnterCreateTS, + 3: FmpAutoEnterCreateName, + 4: FmpAutoEnterCreateAccountName, + 5: FmpAutoEnterModDate, + 6: FmpAutoEnterModTime, + 7: FmpAutoEnterModTS, + 8: FmpAutoEnterModName, + 9: FmpAutoEnterModAccountName, +} + +var autoEnterOptionMap = map[uint8]FmpAutoEnterOption{ + 2: FmpAutoEnterSerialNumber, + 4: FmpAutoEnterData, + 8: FmpAutoEnterCalculation, + 16: FmpAutoEnterFromLastVisitedRecord, + 32: FmpAutoEnterCalculation, + 136: FmpAutoEnterCalculationReplacingExistingValue, +} + +type FmpScriptStepType uint64 + const ( FmpScriptPerformScript FmpScriptStepType = 1 - FmpScriptSaveACopyAsXml FmpScriptStepType = 3 + FmpScriptSaveCopyAsXML FmpScriptStepType = 3 FmpScriptGoToNextField FmpScriptStepType = 4 FmpScriptGoToPreviousField FmpScriptStepType = 5 FmpScriptGoToLayout FmpScriptStepType = 6 @@ -115,7 +156,7 @@ const ( FmpScriptInsertText FmpScriptStepType = 61 FmpScriptPauseResumeScript FmpScriptStepType = 62 FmpScriptSendMail FmpScriptStepType = 63 - FmpScriptSendDdeExecute FmpScriptStepType = 64 + FmpScriptSendDDEExecute FmpScriptStepType = 64 FmpScriptDialPhone FmpScriptStepType = 65 FmpScriptSpeak FmpScriptStepType = 66 FmpScriptPerformApplescript FmpScriptStepType = 67 @@ -165,7 +206,7 @@ const ( FmpScriptOpenFileOptions FmpScriptStepType = 114 FmpScriptAllowFormattingBar FmpScriptStepType = 115 FmpScriptSetNextSerialValue FmpScriptStepType = 116 - FmpScriptExecuteSql FmpScriptStepType = 117 + FmpScriptExecuteSQL FmpScriptStepType = 117 FmpScriptOpenHosts FmpScriptStepType = 118 FmpScriptMoveResizeWindow FmpScriptStepType = 119 FmpScriptArrangeAllWindows FmpScriptStepType = 120 @@ -242,7 +283,7 @@ const ( FmpScriptSetErrorLogging FmpScriptStepType = 200 FmpScriptConfigureNfcReading FmpScriptStepType = 201 FmpScriptConfigureMachineLearningModel FmpScriptStepType = 202 - FmpScriptExecuteFilemakerDataApi FmpScriptStepType = 203 + FmpScriptExecuteFileMakerDataAPI FmpScriptStepType = 203 FmpScriptOpenTransaction FmpScriptStepType = 205 FmpScriptCommitTransaction FmpScriptStepType = 206 FmpScriptRevertTransaction FmpScriptStepType = 207 diff --git a/fmp/fmp_sector.go b/fmp/fmp_sector.go index fa96cb4..5a18127 100644 --- a/fmp/fmp_sector.go +++ b/fmp/fmp_sector.go @@ -85,7 +85,7 @@ func (sect *FmpSector) processChunks(dict *FmpDict) error { // Todo: take index into account dict.SetValue( currentPath, - append(dict.GetValue(currentPath), chunk.Value...), + append(dict.GetValue(currentPath...), chunk.Value...), ) case FmpChunkSimpleKeyValue: diff --git a/fmp/fmp_table.go b/fmp/fmp_table.go index 1ecd11b..cccce24 100644 --- a/fmp/fmp_table.go +++ b/fmp/fmp_table.go @@ -1,21 +1,60 @@ package fmp type FmpTable struct { - ID uint64 - Name string + ID uint64 + Name string + Columns []FmpColumn +} + +type FmpColumn struct { + ID uint64 + Name string + Type FmpFieldType + DataType FmpDataType + StorageType FmpFieldStorageType + AutoEnter FmpAutoEnterOption + Indexed bool } func (ctx *FmpFile) Tables() []*FmpTable { tables := make([]*FmpTable, 0) - ent := ctx.Dictionary.GetEntry([]uint64{3, 16, 5}) + ent := ctx.Dictionary.GetEntry(3, 16, 5) for path, tableEnt := range *ent.Children { if path < 128 { continue } - name := decodeByteSeq(tableEnt.Children.GetValue([]uint64{16})) - table := &FmpTable{ID: path, Name: name} + + table := &FmpTable{ + ID: path, + Name: decodeByteSeq(tableEnt.Children.GetValue(16)), + Columns: make([]FmpColumn, 0), + } + tables = append(tables, table) + colEnt := ctx.Dictionary.GetEntry(table.ID, 3, 5) + + for colPath, colEnt := range *colEnt.Children { + name := decodeByteSeq(colEnt.Children.GetValue(16)) + flags := colEnt.Children.GetValue(2) + + column := FmpColumn{ + ID: colPath, + Name: name, + Type: FmpFieldType(flags[0]), + DataType: FmpDataType(flags[1]), + StorageType: FmpFieldStorageType(flags[9]), + Indexed: flags[8] == 128, + } + + if flags[11] == 1 { + column.AutoEnter = autoEnterPresetMap[flags[4]] + } else { + column.AutoEnter = autoEnterOptionMap[flags[11]] + } + + table.Columns = append(table.Columns, column) + } } return tables diff --git a/fmp/fmp_test.go b/fmp/fmp_test.go index 5ccce03..57776ae 100644 --- a/fmp/fmp_test.go +++ b/fmp/fmp_test.go @@ -38,4 +38,30 @@ func TestTables(t *testing.T) { if !slicesHaveSameElements(tableNames, expectedNames) { t.Errorf("tables do not match") } + + var field FmpColumn + for _, table := range tables { + for _, column := range table.Columns { + if column.Name == "PrimaryKey" { + field = column + break + } + } + } + + if field.Type != FmpFieldSimple { + t.Errorf("expected field type to be simple, but it is not") + } + if field.DataType != FmpDataText { + t.Errorf("expected field data type to be text, but it is not") + } + if field.StorageType != FmpFieldStorageRegular { + t.Errorf("expected field storage type to be regular, but it is not") + } + if !field.Indexed { + t.Errorf("expected field to be indexed, but it is not") + } + if field.AutoEnter != FmpAutoEnterCalculationReplacingExistingValue { + t.Errorf("expected field to have auto enter calculation replacing existing value, but it does not") + } } diff --git a/fmp/fmp_util.go b/fmp/fmp_util.go index 98f303b..495b12f 100644 --- a/fmp/fmp_util.go +++ b/fmp/fmp_util.go @@ -9,7 +9,7 @@ type FmpDictEntry struct { Children *FmpDict } -func (dict *FmpDict) GetEntry(path []uint64) *FmpDictEntry { +func (dict *FmpDict) GetEntry(path ...uint64) *FmpDictEntry { for i, key := range path { _, ok := (*dict)[key] if !ok { @@ -28,8 +28,8 @@ func (dict *FmpDict) GetEntry(path []uint64) *FmpDictEntry { return nil } -func (dict *FmpDict) GetValue(path []uint64) []byte { - ent := dict.GetEntry(path) +func (dict *FmpDict) GetValue(path ...uint64) []byte { + ent := dict.GetEntry(path...) if ent != nil { return ent.Value }