1 # UVrules: Module with rule functions for usevote
2 # Used by uvvote.pl, UVconfig.pm
7 use vars qw (@ISA @EXPORT $VERSION @rules);
18 # ---------------------------------------------------------------------
19 # Erlaeuterung zur Regelpruefung (to be translated)
20 # ---------------------------------------------------------------------
21 # Um Stimmen mit multiplen Abstimmungspunkten auf ihre Sinnfaelligkeit
22 # pruefen zu koennen, koennen in Usevote verschiedenste Regeln
23 # fuer solche Pruefungen definiert werden.
25 # Die Regeln bestehen aus zwei Teilen. Einer IF-Klausel und einer THEN-
26 # Klausel. Die IF-Klausel bestimmt, ob die Stimme mit der THEN-Klausel
27 # verglichen werden soll. Passt sie auf diese, ist die Stimme in Ordnung,
28 # wenn nicht liegt ein Fehler vor.
30 # Ein kleines Beispiel: "IF S.. THEN .SS"
31 # Wenn beim ersten Punkt mit Ja oder Nein gestimmt wurde, dann muss
32 # bei den anderen beiden Punkten auch ein Ja oder Nein vorliegen.
34 # Die Stimmabgabe JNE wuerde also gegen die obige Regel verstossen,
35 # JJN nicht. EEJ wuerde ebenfalls gueltig sein, da die Regel nicht unter
36 # die IF-Klausel faellt und somit keine Ueberpruefung der THEN-Klausel
40 # ---------------------------------------------------------------------
42 # ---------------------------------------------------------------------
43 # Um eine moeglichst einfache Ueberpruefung der Stimmen vorzunehmen,
44 # bietet es sich an, aus den beiden Klauseln regulaere Ausdruecke zu
45 # generieren. Diese wuerden dann auf die Stimme angewandt werden.
47 # Bei der Umwandlung in regulaere Audruecke kommt uns die Notation
48 # der Regeln bereits entgegen. So kann der Punkt als beliebige Stimme
49 # beibehalten werden. Die grossen Buchstaben bleiben ebenfalls bis
50 # auf S erhalten, da die zu pruefenden Stimmen aus den Buchstaben
53 # So muessen wir zur Ueberpruefung von direkten Stimmen nur 'S' in
54 # eine Klasse mit [JN] und I in eine Klasse mit [EN] umwandeln.
56 # 'J..' => 'J..', 'NNE' => 'NNE', 'S..' => '[JN]..'
58 # Bei den indirekten Stimmabgaben wird es schon schwieriger. Hier
59 # muessten alle Moeglichkeiten eines Strings gebaut werden, um zu
60 # testen ob mindestens eine Version matcht.
62 # '.jjj' => '.(j..|.j.|..j)
64 # Je komplexer die Regeln, um so mehr Moeglichkeiten muessten
65 # konstruiert werden, um einen geschlossenen regulaeren Ausdruck
68 # Wir koennen den Regex aber auch einfach aufbauen, in dem wir
69 # nicht alle Faelle betrachten die moeglich sind, sondern nur die
70 # Faelle die nicht erlaubt sind.
72 # D.h. soll an einer Stelle ein Ja stehen, erlauben wir dort
73 # nur Nein und Enthaltungen. Passt eine Stimme auf diesen Regex,
74 # kann sie unmoeglich die Vorgabe enthalten.
76 # 'nnnn' => '[JE][JE][JE][JE]'
78 # Besteht eine Stimme also nur aus Ja und Enthaltung, wissen wir
79 # das kein einziges Nein enthalten seien kann. Die Stimme passt
80 # also nicht auf unser Muster.
82 # Tritt hingegen nur ein einziges J auf, passt der regulaere Ausdruck
83 # nicht mehr, und wir wissen, dass die Stimme die Regel erfuellt.
85 # Wie wir sehen koennen, ist der negative Ausdruck leichter zu
86 # bilden als der positive.
89 # Da eine Stimme nun sowohl aus direkten, als auch indirekten
90 # Stimmen bestehen kann (z.B. 'Jnnn..') muessen wir die Stimme
91 # zerlegen. Wir bilden einen positiven Regex fuer die Grossbuch-
92 # staben und einen negativen Regex fuer die kleinen.
94 # Passt eine Stimme dann auf den positiven Regex und nicht auf
95 # den negativen Regex, so entspricht sie der urspruenglichen
98 # Ein Beispiel: 'Sss..' (Der erste Punkt und der zweite oder dritte
99 # Punkt muessen ein Ja oder Nein sein.)
101 # positiver Regex: '[JN]...' muss erfuellt werden
102 # negativer Regex: '.EE.' darf nicht erfuellt werden
104 # JJNN => positiv matcht => negativ matcht nicht => Regel erfuellt
105 # ENJE => positiv matcht nicht => Regel nicht erfuellt
106 # NEEJ => positiv matcht => negativ matcht => Regel nicht erfuellt
109 # Mit Hilfe dieser Technik, lassen sich einfach Regex bilden, die
110 # ebenso einfach ueberprueft werden koennen.
113 ##############################################################################
114 # Read usevote.rul and check rules for correct syntax #
115 ##############################################################################
120 open (RULES, "<$config{rulefile}")
121 or die UVmessage::get("RULES_ERROPENFILE", (FILE => $config{rulefile})) . "\n\n";
125 s/#.*$//; # delete comments
127 # does line match correct if-then syntax?
128 if (/^\s*if\s+(\S+)\s+then\s+(\S+)\s*$/) {
132 # $num contains the rule's array index
135 # check for correct length of condition
137 if (length($if) < @groups) {
138 $errortext = UVmessage::get("RULES_TOOSHORT", (NUM=>$num+1, TYPE=>"if"));
140 } elsif (length($if) > @groups) {
141 $errortext = UVmessage::get("RULES_TOOLONG", (NUM=>$num+1, TYPE=>"if"));
143 } elsif (length($then) < @groups) {
144 $errortext = UVmessage::get("RULES_TOOSHORT", (NUM=>$num+1, TYPE=>"then"));
146 } elsif (length($then) > @groups) {
147 $errortext = UVmessage::get("RULES_TOOLONG", (NUM=>$num+1, TYPE=>"then"));
149 die $errortext . ": $_\n\n" if ($errortext);
151 # check for correct characters in conditions
152 if ($if !~ /^[JjNnEeSsHhIi\.]+$/) {
153 die UVmessage::get ("RULES_INVCHARS", (NUM=>$num+1, TYPE=>"if")) . ": $if\n\n";
155 } elsif ($then !~ /^[JjNnEeSsHhIi\.]+$/) {
156 die UVmessage::get ("RULES_INVCHARS",
157 (NUM=>$num+1, TYPE=>"if")) . ": $then\n\n";
160 # Zur Speicherung der Regeln (to be translated):
161 # - if_compl und then_compl sind die kompletten Bedingungen als Strings,
162 # werden fuer die Sprachausgabe der Regeln benoetigt
163 # - zusaetzlich werden der if- und then-Teil fuer die einfachere
164 # Verarbeitung in zwei Teile gesplittet: Eine Positiv-Regex, die auf
165 # die Grossbuchstaben (explizite Forderungen, UND-Verknuepfungen)
166 # matched, und eine Negativ-Regex, die bei den Kleinbuchstaben
167 # (optionale Felder, ODER-Verknuepfungen) verwendet wird.
169 my %rule = ( if_compl => $if,
170 if_pos => make_regex_pos($if),
171 if_neg => make_regex_neg($if),
173 then_pos => make_regex_pos($then),
174 then_neg => make_regex_neg($then) );
176 push (@rules, \%rule);
183 ##############################################################################
184 # Generates a RegEx for positive matching of the rules #
186 # All lower case characters are replaced with dots, as they are to be #
187 # matched by the negativ RegEx. Furthermore the symbol S is replaced by [JN] #
188 # and I is replaced by [EN] (for use in combined votings when only one #
189 # option may be accepted and the others must be rejected or abstained. #
190 # As a result we have a regular expression that can be matched against the #
192 ##############################################################################
197 $pat =~ s/[hijens]/./g;
206 ##############################################################################
207 # Generates a RegEx for negative matching of the rules #
209 # All upper case characters are replaced with dots, as they are to be #
210 # matched by the positiv RegEx. If lower case characters are found the #
211 # condition is reversed, so that we are able to match votes *not* #
212 # corresponding to this rule #
213 ##############################################################################
218 # upper case characters are replaced with dots
219 # (are covered by make_regex_pos)
220 $pat =~ s/[HIJENS]/./g;
222 # reverse lower case characters
230 # If the string contained only upper case characters they are now all
231 # replaced with dots and the RegEx would match everything, i.e. declare
232 # every vote as invalid. In this case an empty pattern is returned.
239 ##############################################################################
240 # Check a voting for rule compliance #
241 # Parameters: Votes (Reference to Array) #
242 # Return value: Number of violated rule or 0 (everything OK) #
243 # (Internally rules are saved with indexes starting at 0) #
244 ##############################################################################
249 # Turn array reference into a string
250 my $vote = join ('', @$voteref);
252 # For compliance with the rules every rule has to be matched against the
253 # the vote. If the IF clause matches but not the THEN clause the vote is
254 # invalid and the rule number is returned.
256 for (my $n = 0; $n < @rules; $n++) {
257 return $n+1 if ($vote =~ m/^$rules[$n]->{if_pos}$/ &&
258 $vote !~ m/^$rules[$n]->{if_neg}$/ &&
259 not($vote =~ m/^$rules[$n]->{then_pos}$/ &&
260 $vote !~ m/^$rules[$n]->{then_neg}$/ ));
267 ##############################################################################
268 # Print rules in human readable format #
269 # Parameter: rule number #
270 # Return value: rule text #
271 ##############################################################################
276 my $and = UVmessage::get ("RULES_AND");
277 my $or = UVmessage::get ("RULES_OR");
278 my $yes = UVmessage::get ("RULES_YES");
279 my $no = UVmessage::get ("RULES_NO");
280 my $abst = UVmessage::get ("RULES_ABSTAIN");
283 my $text = UVmessage::get ("RULES_RULE") . " #$n:\n";
284 $text .= " " . UVmessage::get ("RULES_IF") . "\n";
286 my @rule = split (//, $rules[$n-1]->{if_compl});
290 for (my $i=0; $i<@rule; $i++) {
293 if ($rule[$i] eq 'J') {
295 $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
296 } elsif ($rule[$i] eq 'N') {
298 $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
299 } elsif ($rule[$i] eq 'E') {
301 $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
302 } elsif ($rule[$i] eq 'S') {
304 $text1 = UVmessage::get ("RULES_IFCLAUSE",
305 (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
306 } elsif ($rule[$i] eq 'H') {
308 $text1 = UVmessage::get ("RULES_IFCLAUSE",
309 (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
310 } elsif ($rule[$i] eq 'I') {
312 $text1 = UVmessage::get ("RULES_IFCLAUSE",
313 (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
314 } elsif ($rule[$i] eq 'j') {
316 $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
317 } elsif ($rule[$i] eq 'n') {
319 $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
320 } elsif ($rule[$i] eq 'e') {
322 $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
323 } elsif ($rule[$i] eq 's') {
325 $text1 = UVmessage::get ("RULES_IFCLAUSE",
326 (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
327 } elsif ($rule[$i] eq 'h') {
329 $text1 = UVmessage::get ("RULES_IFCLAUSE",
330 (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
331 } elsif ($rule[$i] eq 'i') {
333 $text1 = UVmessage::get ("RULES_IFCLAUSE",
334 (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
339 $text .= " " . $text1 . "\n";
342 $text .= $fill . $text1 . "\n";
347 @rule = split (//, $rules[$n-1]->{then_compl});
348 $text .= " ..." . UVmessage::get ("RULES_THEN") . "\n";
351 for (my $i=0; $i<@rule; $i++) {
353 if ($rule[$i] eq 'J') {
355 $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
356 } elsif ($rule[$i] eq 'N') {
358 $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
359 } elsif ($rule[$i] eq 'E') {
361 $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
362 } elsif ($rule[$i] eq 'S') {
364 $text1 = UVmessage::get ("RULES_THENCLAUSE",
365 (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
366 } elsif ($rule[$i] eq 'H') {
368 $text1 = UVmessage::get ("RULES_THENCLAUSE",
369 (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
370 } elsif ($rule[$i] eq 'I') {
372 $text1 = UVmessage::get ("RULES_THENCLAUSE",
373 (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
374 } elsif ($rule[$i] eq 'j') {
376 $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
377 } elsif ($rule[$i] eq 'n') {
379 $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
380 } elsif ($rule[$i] eq 'e') {
382 $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
383 } elsif ($rule[$i] eq 's') {
385 $text1 = UVmessage::get ("RULES_THENCLAUSE",
386 (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
387 } elsif ($rule[$i] eq 'h') {
389 $text1 = UVmessage::get ("RULES_THENCLAUSE",
390 (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
391 } elsif ($rule[$i] eq 'i') {
393 $text1 = UVmessage::get ("RULES_THENCLAUSE",
394 (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
399 $text .= " " . $text1 . "\n";
402 $text .= $fill . $text1 . "\n";