parseParams: Sondersituation

Begonnen von Dr. Boris Neubert, 22 März 2018, 21:10:17

Vorheriges Thema - Nächstes Thema

Dr. Boris Neubert

Hallo,

ich beabsichtige folgende Parametersyntax bei einem meiner Module einzuführen:

key:value

Mehrere solche Paare sind durch Leerzeichen getrennt möglich. Geparst wird mit

parseParams(\@a, " ", " ", ":")

Problem: wenn value einen in Gänsefüßchen eingeschlossenen String mit Leerzeichen enthält, wird der String nicht als solcher erkannt und der hintere Teil abgetrennt. Beispiel:

format:custom={"foo bar"}

wird in 'format' => 'custom={"foo' und 'bar"}' zerlegt.

Lässt sich das einfach in parseParams() einbauen oder muss ich meinen eigenen Parser schreiben?

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Phill

#1
Parseparams schützt den Separator nur wenn das Value mit einem Anführungszeichen beginnt.

Werden die Parameter wirklich als Array-Referenz übergeben? Weil in dem Fall wird eigentlich kein Split mehr am Separator durchgeführt und das Problem sollte nicht bestehen.
Aber genau das ist eigentlich auch die Lösung. Und ich würde hier ParseWords empfehlen.
use Text::ParseWords;
my  @a = quotewords('\s+', 1, 'format:custom={"foo bar"} a:b={foo\ bar} c:"d={foo bar}" x:y="{foo bar}"');
parseParams(\@a, undef,undef, ":");

Frage ist nur ob der Split am Doppelpunkt noch mit parseParams gemacht werden muss.
my %h = map { split (":", $_, 2) } @a ;
Macht das selbe, wenn es nur den Typ "key:value" gibt.
Sollen die Anführungszeichen bestehen bleiben? Andernfalls muss quotewords 0 im 2. Parameter übergeben werden.

Quotewords (mit Parameter 0) könnte in parseParams den Split ersetzen. 
Ich kann es leider gerade nicht testen.

Gruß
Homebrew 1-Wire / HomeMatic Mix - Cubietruck mit FHEM als Server - Raspberry PI 3 als Informationsanzeige im MagicMirror Stil - Raspberry Pi 1 als Klingelanlage - VDR

Mein Modul: Talk2Fhem - Mein Tipp: https://forum.fhem.de/index.php/topic,82442.0.html

justme1968

die syntax die ich zugrunde gelegt hatte ist im thread mit dem vorschlag zu paraeParams beschrieben.

leerzeichen werden geschützt in dem der ganze string mit anführungszeichen umschlossen wird.

wenn innerhalb des strings selber anführungszeichen nötigste sind kann man wie bei perl zwischen einfachen und doppelten wechseln. eine sorte ganz außen und die andere dann innen im string.

die geschweiften klammern werden aber eigentlich auch als gruppierung erkannt und dein beispiel sollte funktionieren. so eins ist bei meinen test beispielen auch dabei.

vielleicht liegt es an der wahl des : als key value trenner. ursprünglich war hier nur = vorgesehen. geht es damit?

soll der value bei dir echter perl code sein? wenn ja: in readingsGroup mache ich das an mehreren stellen und das geht problemlos wenn man die regel mit den anführungszeichen von oben beachtet.

ich schaue nachher noch mal genau was bei deinem beispiel nicht passt.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Hallo Phill und Andre,

Danke für Eure Antworten. Ich gebe Euch mal zwei volle Beispiele, die ich parsen möchte, und werde heute Abend weiter herumdoktern:

Aus
format:custom="$T1 $D $S" filter:mode=upcoming|alarmed
soll ein Hash mit format und filter als Keys und den Zeichenfolgen nach dem Doppelpunkt als Value werden. Dito für

format:custom={ sprintf("%10d %s", $self->{t1}, $self->{summary}) } filter:from=-10d,to=5d

(Das weitere Zerlegen des Value geht dann gut mit dem Match-Operator und regulären Ausdrücken.)

@Andre: Deinen ursprünglichen Beitrag habe ich gestern schon nur noch mühsam wiedergefunden. Ich würde gerne im Wiki einen Developer-Eintrag dazu schreiben. Würdest Du denn dann reviewen und ergänzen wollen?

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

ich denke ich sehe das problem.

die syntax die dir vorschwebt passt nicht ganz zu den ursprünglichen annahmen. ,eigentlich' besteht deine syntax aus drei statt zwei teilen: key:modifier=value.

das problem ist: paraeParams geht davon aus das die zeichen zum ,klammern' des value ganz außen links und rechts stehen. bei dir ist das nicht der fall. das öffnende zeichen kommt erst nach dem =. deshalb ,eigentlich' drei statt zwei komponenten.


zwei wege fallen mir auf die schnelle ein:
- wie oben vorgeschlagen mit einer zusätzlichen ebene anführungszeichen arbeiten dir den value komplett umschließen
- nicht : sondern = als trenner verwenden. dann wird der abc:def teil zusammenhängend als key erkannt und der value teil kann so wie in deinen beispielen aufgebaut sein. in einer schleife müsste man dann noch ein mal alle erkannten keys durchgehen und den modifier abzuschneiden und vor den value zu setzen.

ersteres ist für den anwender doof, letzteres auch nicht wirklich intuitiv.


wenn du im wiki noch mehr dokumentieren magst schaue ich es mir gerne an.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Zitat von: justme1968 am 23 März 2018, 09:06:31
- nicht : sondern = als trenner verwenden. dann wird der abc:def teil zusammenhängend als key erkannt und der value teil kann so wie in deinen beispielen aufgebaut sein. in einer schleife müsste man dann noch ein mal alle erkannten keys durchgehen und den modifier abzuschneiden und vor den value zu setzen.

Diese Option ist mir gestern Abend auch durch den Kopf gegangen. Die Alternative wäre, mir aus $hash->{CL} die Kommandozeile zu ziehen und selbst zu zerlegen. Mal sehen, für welche Variante ich mich entscheide.

Zitat
wenn du im wiki noch mehr dokumentieren magst schaue ich es mir gerne an.

Danke für den Wink. Anscheinend habe ich gestern Abend die Wiki-Suche nicht richtig bedient. Steht ja schon alles im Wiki  :-[

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Phill

Ich glaube ein manueller RegExp-Parser sollte das recht leicht lösen können.
my $t ='format:custom={ sprintf("%10d %s", $self->{t1}, $self->{summary}) } filter:from=-10d,to=5d';

my %h;
while($t =~ /(\S+):(.*?)(?=(\s\S+:|$))/g) {
  $h{$1} =$2;
}

print Dumper \%h;

$VAR1 = {
          'filter' => 'from=-10d,to=5d',
          'format' => 'custom={ sprintf("%10d %s", $self->{t1}, $self->{summary}) }'
        };

Gruß
Homebrew 1-Wire / HomeMatic Mix - Cubietruck mit FHEM als Server - Raspberry PI 3 als Informationsanzeige im MagicMirror Stil - Raspberry Pi 1 als Klingelanlage - VDR

Mein Modul: Talk2Fhem - Mein Tipp: https://forum.fhem.de/index.php/topic,82442.0.html

justme1968

so einfach ist es leider nicht :)

deine einfache regex funktioniert nicht wenn im value ein : vorkommt. z.b. so etwas: format:custom={ sprintf("%10d: %s", $self->{t1}, $self->{summary}) }

einfache regex parser haben wegen fehlendem look ahead/look behind und fehlender möglichkeit zu zählen immer das problem das sie nicht mit beliebigen verschachtelungen klar kommen. auch das unterscheiden von klammer ebenen ist nicht oder nur mit klimmzügen möglich.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Phill

#8
Zitat von: justme1968 am 23 März 2018, 10:17:38
einfache regex parser haben wegen fehlendem look ahead/look behind und fehlender möglichkeit zu zählen immer das problem das sie nicht mit beliebigen verschachtelungen klar kommen.

Das ist zum Teil richtig! Ich verwende doch positive look behind in der RegExp. "(?=...)" und selbst Verschachtelungen lassen sich mit durch die Recrusivepattern (?R) irgendwie lösen, habe ich aber noch nie gebraucht/verstanden.  ;)
Aber du hast recht das sollte etwas expliziter geschrieben werden.

my $t ='format:custom={ sprintf("%10d: %s", $self->{t1}, $self->{summary}) } filter:from=-10d,to=5d';

my %h;
while ($t =~ /(\w+):(.*?)(?=(\s+\w+:\w+=|$))/g) {
$h{$1} = $2;
}

print Dumper \%h;

$VAR1 = {
          'format' => 'custom={ sprintf("%10d: %s", $self->{t1}, $self->{summary}) }',
          'filter' => 'from=-10d,to=5d'
        };

Hat natürlich wieder seine Grenze, wenn innerhalb des values nochmal ein abc:def= auftritt.
Gruß
Homebrew 1-Wire / HomeMatic Mix - Cubietruck mit FHEM als Server - Raspberry PI 3 als Informationsanzeige im MagicMirror Stil - Raspberry Pi 1 als Klingelanlage - VDR

Mein Modul: Talk2Fhem - Mein Tipp: https://forum.fhem.de/index.php/topic,82442.0.html

justme1968

du hast recht das lookahead und lookbehind geht (?= ist lookahead).

das 'reicht' in diesem fall vermutlich auch mehr oder weniger da es recht unwahrscheinlich ist das ein abc:def= pattern tatsächlich im value auftaucht. zumindest nach boris beispielen.

bei der 'einfachen' form die parseParams eigentlich erwartet (nur key=value) reicht es aber nicht, da key=value in so ziemlich jedem sinnvollen perl ausdruck vorkommt. und in einem etwas längeren stück code sogar mehrfach.

das zählen bzw. die rekursion ist dann nötig wenn man den fall der verschachtelten anführungszeichen oder {} klammern behandeln und hierbei die richtige anzahl zusammengehörender klammern berücksichtigen will. kombiniert mit dem einfachen key=value fall funktioniert das dann nicht mehr.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Hallo,

ich habe mich jetzt entschieden, eine eigene Routine zu schreiben, die eine Zeile nach Perl-Regeln in Teile zerlegt. Da die Teile dann alle von der Form Wort:Rest sind, lässt sich das dann sehr leicht weiter zerlegen.

Viele Grüße
Boris

# everything within matching single or double quotes is literally copied
# everything within braces is literally copied, nesting braces is allowed
# use \ to mask quotes and braces
# parts are separated by one or more spaces
sub simpleParseWords {
  my ($p)= @_;

  my $quote= undef;
  my $braces= 0;
  my @parts= (); # resultant array of space-separated parts
  my @chars= split(//, $p); # split into characters
  my $escape= 0; # escape mode off
  my @part= (); # the current part
  for my $c (@chars) {
    #Debug "checking $c, quote is " . (defined($quote) ? $quote : "empty") . ", braces is $braces";
    push @part, $c; # append the character to the current part
    if($escape) { $escape= 0; next; } # continue and turn escape mode off if escape mode is on
    if(($c eq " ")  && !$braces && !defined($quote)) { # we have encountered a space outside quotes and braces
      #Debug " break";
      pop @part; # remove the space
      push @parts, join("", @part) if(@part);  # add the completed part if non-empty
      @part= ();
      next;
    }
    $escape= ($c eq "\\"); next if($escape); # escape mode on
    #Debug " not escaped";
    if(($c eq "\"") || ($c eq "\'")) {
      #Debug " quote";
      if(defined($quote)) {
        if($c eq $quote) { $quote= undef; }
      } else {
        $quote= $c;
      }
      next;
    }
    next if defined($quote);
    if($c eq "{") { $braces++; next; } # opening brace
    if($c eq "}") { # closing brace
      return("closing brace without matching opening brace", undef) unless($braces);
      $braces--;
    }
  }
  return("opening quote $quote without matching closing quote", undef) if(defined($quote));
  return("$braces opening brace(s) without matching closing brace(s)", undef) if($braces);
  push @parts, join("", @part) if(@part);  # add the completed part
  return(undef, \@parts);
}
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!