diff --git a/docu/docs/modul/descriptor.md b/docu/docs/modul/descriptor.md index 8a2ec4e..7eb62e4 100644 --- a/docu/docs/modul/descriptor.md +++ b/docu/docs/modul/descriptor.md @@ -2,9 +2,9 @@ --- ## Beschreibung -Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhänigkeit der enthaltenen Informationen hinzugefügt werden. +Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhängigkeit der enthaltenen Informationen hinzugefügt werden. -## Unterstütze Alarmtypen +## Unterstützte Alarmtypen - Fms - Pocsag - Zvei @@ -16,12 +16,12 @@ Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhänigke ## Konfiguration Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md) -**Achtung:** Zahlen welche führende Nullen entahlten müssen in Anführungszeichen gesetzt werden Bsp. `'0012345'` +**Achtung:** Zahlen, die führende Nullen enthalten, müssen in der YAML-Konfiguration in Anführungszeichen gesetzt werden, z.B. `'0012345'`. In CSV-Dateien ist dies nicht zwingend erforderlich. |Feld|Beschreibung|Default| |----|------------|-------| -|scanField|Feld des BW Pakets welches geprüft werden soll|| -|descrField|Name des Feldes im BW Paket in welchem die Beschreibung gespeichert werden soll|| +|scanField|Feld des BW Pakets, welches geprüft werden soll|| +|descrField|Name des Feldes im BW Paket, in welchem die Beschreibung gespeichert werden soll|| |wildcard|Optional: Es kann für das angelegte `descrField` automatisch ein Wildcard registriert werden|None| |descriptions|Liste der Beschreibungen|| |csvPath|Pfad der CSV-Datei (relativ zum Projektverzeichnis)|| @@ -29,9 +29,9 @@ Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md) #### `descriptions:` |Feld|Beschreibung|Default| |----|------------|-------| -|for|Inhalt im `scanField` auf welchem geprüft werden soll|| -|add|Beschreibungstext welcher im `descrField` hinterlegt werden soll|| -|isRegex|Muss explizit auf `true` gesetzt werden, falls RegEx verwendet wird|false| +|for|Wert im `scanField`, der geprüft werden soll|| +|add|Beschreibungstext, der im `descrField` hinterlegt werden soll|| +|isRegex|Muss explizit auf `true` gesetzt werden, wenn RegEx verwendet wird|false| **Beispiel:** ```yaml @@ -88,20 +88,25 @@ Eine neue CSV-Datei (z. B. `descriptions_tone.csv`) hat folgendes Format: for,add,isRegex 11111,KBI Landkreis Z,false 12345,FF A-Dorf,false -23456,FF B-Dorf,false -^3456[0-9]$,FF Grossdorf, true +23456,FF B-Dorf +^3456[0-9]$,FF Grossdorf,true ``` -In der Spalte isRegex kann **zusätzlich** angegeben werden, ob der Wert in for als regulärer Ausdruck interpretiert werden soll (true/false). Standardmäßig ist `false`. -Wenn `isRegex` auf `true` gesetzt ist, wird der Wert aus `for` als regulärer Ausdruck ausgewertet. +**Hinweis:** In CSV-Dateien müssen Werte mit führenden Nullen **nicht** in Anführungszeichen gesetzt werden (können aber, falls gewünscht). Die Spalte `isRegex` gibt an, ob der Wert in `for` als regulärer Ausdruck interpretiert werden soll (true/false). Falls kein Wert angegeben ist, ist die Paarung standardmäßig `false`, wie z.B. beim Eintrag der FF B-Dorf im obigen Beispiel. ### Kombination von YAML- und CSV-Konfiguration -Beide Varianten können parallel genutzt werden. In diesem Fall werden zuerst die Beschreibungen aus der YAML-Konfiguration und zusätzlich die Beschreibungen aus der angegebenen CSV-Datei geladen. +Beide Varianten können parallel genutzt werden. In diesem Fall werden die Beschreibungen aus der YAML-Konfiguration und aus der angegebenen CSV-Datei in einer gemeinsamen Datenbank zusammengeführt. -**Beispiel** +#### Matching-Reihenfolge und Priorität -``` +Das Modul wendet folgende Prioritäten beim Matching an: + +1. **Exakte Matches** (aus YAML und CSV) werden zuerst geprüft +2. **Regex-Matches** (aus YAML und CSV) werden nur geprüft, wenn kein exakter Match gefunden wurde + +**Beispiel für Kombination:** +```yaml - type: module res: descriptor config: @@ -110,12 +115,15 @@ Beide Varianten können parallel genutzt werden. In diesem Fall werden zuerst di wildcard: "{DESCR}" descriptions: - for: 12345 - add: FF YAML-Test - - for: '05678' # führende Nullen in '' ! - add: FF YAML-Nullen + add: FF YAML-Test (exakt) + - for: '123.*' + add: FF YAML-Regex + isRegex: true csvPath: "config/descriptions_tone.csv" ``` +Bei einem `scanField`-Wert von `12345` wird **immer** "FF YAML-Test (exakt)" verwendet, auch wenn der Regex ebenfalls zutreffen würde. Regex-Matches werden nur verwendet, wenn kein exakter Match gefunden wurde - unabhängig davon, ob die Einträge aus YAML oder CSV stammen. + --- ## Modul Abhängigkeiten - keine diff --git a/module/descriptor.py b/module/descriptor.py index b8b565c..eeda938 100644 --- a/module/descriptor.py +++ b/module/descriptor.py @@ -10,7 +10,7 @@ r"""! by Bastian Schroll @file: descriptor.py -@date: 04.08.2025 +@date: 03.12.2025 @author: Bastian Schroll @description: Module to add descriptions to bwPackets with CSV and Regex support """ @@ -89,11 +89,15 @@ class BoswatchModule(ModuleBase): reader = csv.DictReader(csvfile) for row in reader: # Set default values if columns are missing + raw_for = str(row.get('for', '')).strip() + # Remove enclosing quotes + clean_for = raw_for.strip().strip('"').strip("'") entry = { - 'for': str(row.get('for', '')), - 'add': row.get('add', ''), + 'for': clean_for, + 'add': row.get('add', '').strip(), 'isRegex': row.get('isRegex', 'false').lower() == 'true' # Default: False } + logging.debug("CSV row read: %s", row) self.unified_cache[descriptor_key].append(entry) csv_count += 1 @@ -103,41 +107,56 @@ class BoswatchModule(ModuleBase): logging.error("Error loading CSV file %s: %s", csv_path, str(e)) def _find_description(self, descriptor_key, scan_value, bw_packet): - r"""!Find matching description for a scan value with Regex group support.""" + r"""!Find matching description for a scan value with Regex group support. + + The search is performed in two passes for performance optimization: + 1. First pass: Check for exact string matches (fast, no regex compilation) + 2. Second pass: Check regex patterns only if no exact match was found + + Regex patterns support capture groups that can be referenced in the description + using standard regex backreferences (\1, \2, etc.) via match.expand(). + + Example: + Pattern: r"(\d{7})" + Input: "1234567" + Description template: "RIC: \1" + Result: "RIC: 1234567" + + @param descriptor_key: Cache key identifying the descriptor configuration + @param scan_value: Value to search for in the descriptor cache + @param bw_packet: BOSWatch packet for wildcard replacement + @return: Matched description string or None if no match found + """ + descriptions = self.unified_cache.get(descriptor_key, []) - scan_value_str = str(scan_value) + scan_value_str = str(scan_value).strip() - # Search for matching description + # First pass: Search for exact matches (performance optimization) + # Exact matches are checked first because they don't require regex compilation for desc in descriptions: - description_text = desc.get('add', '') - match_pattern = desc.get('for', '') - is_regex = desc.get('isRegex', False) + if not desc.get('isRegex', False): + if desc['for'] == scan_value_str: + description_text = desc.get('add', '') + final_description = self._replace_wildcards(description_text, bw_packet) + return final_description - if is_regex: - # Regex matching + # Second pass: Search for regex matches + # Only executed if no exact match was found in the first pass + for desc in descriptions: + if desc.get('isRegex', False): + match_pattern = desc.get('for', '') try: match = re.search(match_pattern, scan_value_str) if match: - # Expand regex groups (\1, \2) in the description + description_text = desc.get('add', '') + # match.expand() replaces backreferences (\1, \2, etc.) with captured groups + # Example: pattern="(\d+)-(\d+)", input="123-456", template="First: \1, Second: \2" + # result="First: 123, Second: 456" expanded_description = match.expand(description_text) - - # Replace standard wildcards like {TONE} final_description = self._replace_wildcards(expanded_description, bw_packet) - - logging.debug("Regex match '%s' -> '%s' for descriptor '%s'", - match_pattern, final_description, descriptor_key) return final_description except re.error as e: - logging.error("Invalid regex pattern '%s': %s", match_pattern, str(e)) - continue - else: - # Exact match - if match_pattern == scan_value_str: - # Replace standard wildcards like {TONE} - final_description = self._replace_wildcards(description_text, bw_packet) - logging.debug("Exact match '%s' -> '%s' for descriptor '%s'", - match_pattern, final_description, descriptor_key) - return final_description + logging.error("Invalid regex pattern '%s': %s", match_pattern, e) return None