Hallo,
ich brauchte für ein aktuelles Projekt eine Regelverarbeitung. Die Regeln sollten menschenlesbar gestaltet sein. Meinen Ansatz und Umsetzung wollte ich hier kurz vorstellen mit der Bitte mir zu zeigen ob ich das so richtig gemacht habe, oder wie man da sonst rangeht. Ich will einfach lernen
Also es ging darum voneinander abhängige Kategorien gegen einen Regelsatz zu prüfen. Die Kategorienhirarchie sieht exemplarisch so aus:
Cat => Cat1
Subcat => Cat2
SubSubCat => Cat3
SubSubSubCat => Cat4
SubSubSubSubCat => Cat5
Also fünfstufig (derzeit). Das was hier als "Cat" steht ist einmal eine ID ("kategorieid1") und einmal ein Label ("Meine Hauptkategorie"). In den zu prüfenden Objekten sind die IDs enthalten, der Mensch sieht im WebGui die Label und formuliert auch damit die Regeln.
Die gewählten Kategorien werden ihrer Reihenfolge nach in einen String abgelegt, durch '/' getrennt, also z.B. "/Label Cat1/Label Cat2/Label Cat3". Der "Pfad" enthält nur die gewählten Kategorien (sind alle optional).
Das Regelwerk soll in der Lage sein auf bestimmte Kategorielabel zu machten, oder diese durch einen Wildcard ('*') zu ersetzen. Dabei sollen die Regeln von vorn nach hinten durchgearbeitet werden (first rule matches). In der Regeldatei wird dann noch eine auszuführende Aktion hinterlegt, wenn die Regel zutrifft. Das ist hier aber nicht von Belang.
Hier ein paar Beispiele für Regeln und was sie matchen sollen:
"/Label Cat1/*" => soll auf alle Objekte zutreffen, die "Label Cat1" als Hauptkategorie eingestellt haben und jede weitere darunter beliebig ist. Wichtig: Es muss wenigstens die nachfolgende Kategorie auch gewählt sein!
"/Label Cat1/Label Cat2" => hier soll nur das Objekt gewählt werden was exakt diese beiden Kategorien ausgewählt hat, keine mehr.
"/Label Cat1/*/Label Cat3" => Die zweite gewählte Kategorie ist beliebig.
"/Label Cat1/*/Label Cat3/*" => Die zweite gewählte Kategorie ist beliebig sowie ab der vierten, es muss aber wenigstens eine vierte gewählt sein.
"/*" triff auf alle Kategorien zu (Fallback/Default).
Die Anforderung und geplante Umsetzung dürfte jetzt nicht so überraschend und ungewöhnlich sein
Ich übersetze die Regeldatei mit folgendem Code in ein Array:
Anschließend habe ich in $map eine solche Struktur:
Nun kommt ein Aufruf von einem zu prüfenden Objekt mit einem bereits als IDs vorliegenden Kategoriestruktur, also
Mit folgender Iteration prüfe ich dann:
Am Schluß soll $portal entweder leer sein (keine Regel traf zu) oder einen String enthalten.
Die Idee hinter diesem Code war folgender:
1. Es wird Regel für Regel gegen die im Objekt vorliegende Kategoriestruktur geprüft
2. Wenn die Anzahl der Elemente von Objekt-Kategorie und Regel-Teilen unterschiedlich ist und am Ende der Regel kein '*' steht, kann es kein Treffer sein
3. Gibt es mehr Elemente in der Objekt-Kategorie als in der Regel und das letzte Regel-Element ist ein '*', dann ersetze den Teil der Regel ab dem Stern mit den Werten des Objekt-Kategorie ab dieser Position
4. Ersetze ggf. vorhandene Wildcards ('*') in der Regel mit dem an gleicher Position vorkommenden Kategorie des Objekts
5. Vergleiche den so modifizierte Regelsatz mit der aktuellen Objektkategorie. Sind sie gleich, trifft die Regel zu und die Iteration wird beendet.
Ich bin über mehrere Ansätze dort hin gekommen, aber das war für mich der effektivste Code. Womöglich ist das ja alles totaler Unfug und jemand mit höherer Schulbildung oder Wissenstand kann mir erklären wie man solche Probleme richtig löst?!
Sage schonmal danke fürs lesen und mitdenken!
ich brauchte für ein aktuelles Projekt eine Regelverarbeitung. Die Regeln sollten menschenlesbar gestaltet sein. Meinen Ansatz und Umsetzung wollte ich hier kurz vorstellen mit der Bitte mir zu zeigen ob ich das so richtig gemacht habe, oder wie man da sonst rangeht. Ich will einfach lernen
Also es ging darum voneinander abhängige Kategorien gegen einen Regelsatz zu prüfen. Die Kategorienhirarchie sieht exemplarisch so aus:
Cat => Cat1
Subcat => Cat2
SubSubCat => Cat3
SubSubSubCat => Cat4
SubSubSubSubCat => Cat5
Also fünfstufig (derzeit). Das was hier als "Cat" steht ist einmal eine ID ("kategorieid1") und einmal ein Label ("Meine Hauptkategorie"). In den zu prüfenden Objekten sind die IDs enthalten, der Mensch sieht im WebGui die Label und formuliert auch damit die Regeln.
Die gewählten Kategorien werden ihrer Reihenfolge nach in einen String abgelegt, durch '/' getrennt, also z.B. "/Label Cat1/Label Cat2/Label Cat3". Der "Pfad" enthält nur die gewählten Kategorien (sind alle optional).
Das Regelwerk soll in der Lage sein auf bestimmte Kategorielabel zu machten, oder diese durch einen Wildcard ('*') zu ersetzen. Dabei sollen die Regeln von vorn nach hinten durchgearbeitet werden (first rule matches). In der Regeldatei wird dann noch eine auszuführende Aktion hinterlegt, wenn die Regel zutrifft. Das ist hier aber nicht von Belang.
Hier ein paar Beispiele für Regeln und was sie matchen sollen:
"/Label Cat1/*" => soll auf alle Objekte zutreffen, die "Label Cat1" als Hauptkategorie eingestellt haben und jede weitere darunter beliebig ist. Wichtig: Es muss wenigstens die nachfolgende Kategorie auch gewählt sein!
"/Label Cat1/Label Cat2" => hier soll nur das Objekt gewählt werden was exakt diese beiden Kategorien ausgewählt hat, keine mehr.
"/Label Cat1/*/Label Cat3" => Die zweite gewählte Kategorie ist beliebig.
"/Label Cat1/*/Label Cat3/*" => Die zweite gewählte Kategorie ist beliebig sowie ab der vierten, es muss aber wenigstens eine vierte gewählt sein.
"/*" triff auf alle Kategorien zu (Fallback/Default).
Die Anforderung und geplante Umsetzung dürfte jetzt nicht so überraschend und ungewöhnlich sein
Ich übersetze die Regeldatei mit folgendem Code in ein Array:
PHP-Code:
// load mapping from file
$file = $RUNTIME['maps']['portalredir_file'];
if ( ! is_file($file)) {
throw new Exception("Portalredir map not found: ".$file, 10);
}
$txt = @file_get_contents($file);
if ( ! $txt) {
throw new Exception("Portalredir map is empty: ".$file, 10);
}
// convert to array and resolve names to IDs
$map = array();
$txt = explode("\n", $txt);
foreach ($txt as $line)
{
$line = trim($line);
if ($line == '') continue; // ignore empty lines
if (substr($line,0,1) == '#') continue; // ignore comments
$s = explode('=>', $line);
if (count($s) != 2) {
throw new Exception("Bad characters or missing delimiter in portalredir map: ".$file, 10);
}
$name = trim($s[0]);
$portal = trim($s[1]);
$names = explode('/', substr($name, 1));
$ids = $ecms->categoryIds($names, $chan); // Return category ID from label text
$ids = $ids + $names;
# $id = '/' . implode('/', $ids);
$rule = array(
'match' => $ids,
'target' => $portal,
);
$map[] = $rule;
}
Code:
Array ( 0 => array( 'match' => array( 0 => 'catid1', 1 => '*', 2 => 'catid3', ), 'target' => "...." ), 1 => array( ... )
Code:
Array ( 0 => 'catid1', 1 => 'catid2', 2 => 'catid3', )
PHP-Code:
// check for matching rule
$jobcat_ids = explode('/', substr($jobinfo['category'],1));
$jlast = count($jobcat_ids) - 1;
$portal = '';
foreach ($map as $rule)
{
// get index of last element of rule
$rlast = count($rule['match']) - 1;
// if number of elements in jobcats and current rule differ,
// and last element of rule is not a wildcard ('*'), it is no match!
if ($jlast != $rlast && $rule['match'][$rlast] != '*') {
continue;
}
// if there are more jobcat-elements than rule-elements, and
// last rule-element is wildcard ('*') replace it with slice of jobcat-elements
if ($jlast > $rlast && $rule['match'][$rlast] == '*') {
$rule['match'] = array_slice($rule['match'], 0, $rlast - 1) + array_slice($jobcat_ids, $rlast);
}
// now, replace all inner wildcards
foreach ($rule['match'] as $i => $part)
{
if ($part == '*') {
$rule['match'][$i] = $jobcat_ids[$i];
}
}
// at least compare rule with jobcat for match
if ($rule['match'] == $jobcat_ids) {
$portal = $rule['target'];
break;
}
}
Die Idee hinter diesem Code war folgender:
1. Es wird Regel für Regel gegen die im Objekt vorliegende Kategoriestruktur geprüft
2. Wenn die Anzahl der Elemente von Objekt-Kategorie und Regel-Teilen unterschiedlich ist und am Ende der Regel kein '*' steht, kann es kein Treffer sein
3. Gibt es mehr Elemente in der Objekt-Kategorie als in der Regel und das letzte Regel-Element ist ein '*', dann ersetze den Teil der Regel ab dem Stern mit den Werten des Objekt-Kategorie ab dieser Position
4. Ersetze ggf. vorhandene Wildcards ('*') in der Regel mit dem an gleicher Position vorkommenden Kategorie des Objekts
5. Vergleiche den so modifizierte Regelsatz mit der aktuellen Objektkategorie. Sind sie gleich, trifft die Regel zu und die Iteration wird beendet.
Ich bin über mehrere Ansätze dort hin gekommen, aber das war für mich der effektivste Code. Womöglich ist das ja alles totaler Unfug und jemand mit höherer Schulbildung oder Wissenstand kann mir erklären wie man solche Probleme richtig löst?!
Sage schonmal danke fürs lesen und mitdenken!
Kommentar