pathfinder: Spezielle Textdatei mit PHP auslesen und speichern

Hallo,

habe hier Daten in einer Textdatei (siehe unten), welche etwas merkwürdig mittels Leerzeichen und Punkten strukturiert sind. Würde diese zur Weiterverarbeitung gerne mittels PHP auslesen... aber irgendwie fehlt mir hierzu ein Lösungsansatz, wie man das möglichst Sinnvoll (beispielsweise in ein mehrdimensionales array) bewerkstelligen kann. Schließlich soll das am Ende ja ggf. wieder in dem selben Format als Textdatei gespeichert werden können. Eventuell kennt ja jemand diese Art der Datenspeicherung oder/und hat Idee wie man das mit mögl. wenig Aufwand geschickt ausgelesen und wieder gespeichert bekommt!?

VG

[datensatz]
version
 .minor=2
session
 .id=32
datensatz
 .name=Muster
 .orientierung=1
 .uid=0x1
 .adresse=0x1
 .typ=A
 .icon=list
 .symbol=2
 .xy=8
 .xz=4
 .max=50
 .min=5
 .funktion
 ..nr=0
 ..typ=100
 ..wert=1
 .funktion
 ..nr=1
 ..typ=120
 ..wert=5
 .funktion
 ..nr=2
 ..typ=141
 .funktion
 ..nr=3
 ..typ=142
 .funktion
 ..nr=4
 .funktion
 ..nr=5
 datensatz
 .name=Beispiel
 .uid=0x2005
 .adresse=0x5
 .typ=B
 .sid=0x5
 .icon=block
 .symbol=5
 .xy=6
 .xz=5
 .max=80
 .min=10
 .funktion
 ..nr=0
 ..typ=1
 .funktion
 ..nr=1
 ..typ=40
 .funktion
 ..nr=2
 ..typ=20
 ..wert=1
 .funktion
 ..nr=3
 ..typ=18
 .funktion
 ..nr=4
 .funktion
 ..nr=5
 ..typ=142
 datensatz
 .name=Test
 .orientierung=1
 .uid=0x2006
 .adresse=0x6
 .typ=B
 .sid=0x6
 .icon=tab
 .symbol=3
 .xy=15
 .xz=15
 .max=255
 .min=255
 .funktion
 ..nr=0
 ..typ=1
 .funktion
 ..nr=1
 .funktion
 ..nr=2
 ..typ=200
 .funktion
 ..nr=3
 .funktion
 ..nr=5
 ..typ=18
 ..wert=99
 .funktion
 ..nr=6
 ..typ=48
  1. Lieber pathfinder,

    woher hast Du diese Datei? Sie ähnelt dem .ini-Format. Keine Ahnung, ob Dir da parse_ini_file() helfen kann.

    Liebe Grüße

    Felix Riesterer

    1. Hallo Felix,

      das dachte ich auch zuerst, aber dieses Punkt und Punktpunkt-Format ist doch abweichend. Das ist kein reines INI-Format. Ich habe sowas noch nie gesehen und mein schneller Versuch, etwas über "ini-files with dots" oder "hierarchical ini file" zu finden, führte nicht zum Ziel.

      Es gibt ein hierarchisches Ini-Format, aber da wird vor den Punkten jeweils der Sektionsname wiederholt. PHP kennt das nicht. Und es versteht auch keine Zeilen, die nur einen Sektionsnamen enthalten, ohne Gleichheitszeichen.

      Man muss es wohl selbst bauen. Aber das ist nicht trivial, und es wird durch die PHP-Technik des "copy on write" für Arrays auch nicht einfacher. Spontan herunterschreiben kann ich das nicht, da bräuchte ich sicherlich eine Stunde oder zwei für. Sag ich jetzt. 4 Stunden später bin ich vermutlich am Rande des Nervenzusammenbruchs und frickele den siebzehnten Sonderfall hinein 😉

      pathfinder - hast Du sachdienliche Hinweise auf die Anwendung, die eine Ini-Datei in diesem Format erwartet?

      Rolf

      --
      sumpsi - posui - obstruxi
  2. Moin,

    ist das eventuell eine Baumstruktur, in der die Punkte Zweige darstellen? Mich erinnert das eine mögliche Darstellung von Hierarchien – wenn ich das umformatierte:

    [datensatz]
    version
        minor=2
    session
        id=32
    datensatz
        name=Muster
        orientierung=1
        uid=0x1
        adresse=0x1
        typ=A
        icon=list
        symbol=2
        xy=8
        xz=4
        max=50
        min=5
        funktion
            nr=0
            typ=100
            wert=1
        funktion
            nr=1
            typ=120
            wert=5
    …
    

    Viele Grüße
    Robert

  3. Aloha ;)

    Rolfs Schätzung mit den zwei Stunden war ganz gut - ich habe etwa zweieinhalb gebraucht, dafür aber auch gleich die Funktion zum Re-Codieren fertig gemacht.

    Ist jetzt nicht so als wär mir langweilig, aber irgendwie hats mich gejuckt.

    Das Re-Encodierte Ergebnis:

    [datensatz]
     version
     .minor=2
     session
     .id=32
     datensatz
     .name=Muster
     .orientierung=1
     .uid=0x1
     .adresse=0x1
     .typ=A
     .icon=list
     .symbol=2
     .xy=8
     .xz=4
     .max=50
     .min=5
     .funktion
     ..nr=0
     ..typ=100
     ..wert=1
     .funktion
     ..nr=1
     ..typ=120
     ..wert=5
     .funktion
     ..nr=2
     ..typ=141
     .funktion
     ..nr=3
     ..typ=142
     .funktion
     ..nr=4
     .funktion
     ..nr=5
     datensatz
     .name=Beispiel
     .uid=0x2005
     .adresse=0x5
     .typ=B
     .sid=0x5
     .icon=block
     .symbol=5
     .xy=6
     .xz=5
     .max=80
     .min=10
     .funktion
     ..nr=0
     ..typ=1
     .funktion
     ..nr=1
     ..typ=40
     .funktion
     ..nr=2
     ..typ=20
     ..wert=1
     .funktion
     ..nr=3
     ..typ=18
     .funktion
     ..nr=4
     .funktion
     ..nr=5
     ..typ=142
     datensatz
     .name=Test
     .orientierung=1
     .uid=0x2006
     .adresse=0x6
     .typ=B
     .sid=0x6
     .icon=tab
     .symbol=3
     .xy=15
     .xz=15
     .max=255
     .min=255
     .funktion
     ..nr=0
     ..typ=1
     .funktion
     ..nr=1
     .funktion
     ..nr=2
     ..typ=200
     .funktion
     ..nr=3
     .funktion
     ..nr=5
     ..typ=18
     ..wert=99
     .funktion
     ..nr=6
     ..typ=48
    

    Eine Kleinigkeit ist anders - die Leerzeichen vor den Elementen der ersten Hierarchieebene sind jetzt einheitlich vorhanden, das waren sie im Beispiel nicht vollständig, ich vermute das war ein Eingabefehler hier. Ansonsten müsste das deckungsgleich sein.

    Das daraus von PHP geparste Array:

    Array
    (
        [datensatz] => Array
            (
                [version] => Array
                    (
                        [minor] => 2
                    )
    
                [session] => Array
                    (
                        [id] => 32
                    )
    
                [datensatz] => Array
                    (
                        [0] => Array
                            (
                                [name] => Muster
                                [orientierung] => 1
                                [uid] => 0x1
                                [adresse] => 0x1
                                [typ] => A
                                [icon] => list
                                [symbol] => 2
                                [xy] => 8
                                [xz] => 4
                                [max] => 50
                                [min] => 5
                                [funktion] => Array
                                    (
                                        [0] => Array
                                            (
                                                [nr] => 0
                                                [typ] => 100
                                                [wert] => 1
                                            )
    
                                        [1] => Array
                                            (
                                                [nr] => 1
                                                [typ] => 120
                                                [wert] => 5
                                            )
    
                                        [2] => Array
                                            (
                                                [nr] => 2
                                                [typ] => 141
                                            )
    
                                        [3] => Array
                                            (
                                                [nr] => 3
                                                [typ] => 142
                                            )
    
                                        [4] => Array
                                            (
                                                [nr] => 4
                                            )
    
                                        [5] => Array
                                            (
                                                [nr] => 5
                                            )
    
                                    )
    
                            )
    
                        [1] => Array
                            (
                                [name] => Beispiel
                                [uid] => 0x2005
                                [adresse] => 0x5
                                [typ] => B
                                [sid] => 0x5
                                [icon] => block
                                [symbol] => 5
                                [xy] => 6
                                [xz] => 5
                                [max] => 80
                                [min] => 10
                                [funktion] => Array
                                    (
                                        [0] => Array
                                            (
                                                [nr] => 0
                                                [typ] => 1
                                            )
    
                                        [1] => Array
                                            (
                                                [nr] => 1
                                                [typ] => 40
                                            )
    
                                        [2] => Array
                                            (
                                                [nr] => 2
                                                [typ] => 20
                                                [wert] => 1
                                            )
    
                                        [3] => Array
                                            (
                                                [nr] => 3
                                                [typ] => 18
                                            )
    
                                        [4] => Array
                                            (
                                                [nr] => 4
                                            )
    
                                        [5] => Array
                                            (
                                                [nr] => 5
                                                [typ] => 142
                                            )
    
                                    )
    
                            )
    
                        [2] => Array
                            (
                                [name] => Test
                                [orientierung] => 1
                                [uid] => 0x2006
                                [adresse] => 0x6
                                [typ] => B
                                [sid] => 0x6
                                [icon] => tab
                                [symbol] => 3
                                [xy] => 15
                                [xz] => 15
                                [max] => 255
                                [min] => 255
                                [funktion] => Array
                                    (
                                        [0] => Array
                                            (
                                                [nr] => 0
                                                [typ] => 1
                                            )
    
                                        [1] => Array
                                            (
                                                [nr] => 1
                                            )
    
                                        [2] => Array
                                            (
                                                [nr] => 2
                                                [typ] => 200
                                            )
    
                                        [3] => Array
                                            (
                                                [nr] => 3
                                            )
    
                                        [4] => Array
                                            (
                                                [nr] => 5
                                                [typ] => 18
                                                [wert] => 99
                                            )
    
                                        [5] => Array
                                            (
                                                [nr] => 6
                                                [typ] => 48
                                            )
    
                                    )
    
                            )
    
                    )
    
            )
    
    )
    

    ...und hier der PHP-Code mit rekursiv aufgerufenen Closures:

    // das Posting war zu lang
    

    Vielleicht (hoffentlich?) trägt diese Lösung auch jenseits der Möglichkeit zum Copy+Paste zu Erkenntnissen bei. Falls es Fragen dazu gibt beantworte ich die jedenfalls gerne.

    Grüße,

    RIDER

    --
    Camping_RIDER a.k.a. Riders Flame a.k.a. Janosch Albers-Zoller
    # Twitter # Steam # YouTube # Self-Wiki # Selfcode: sh:) fo:) ch:| rl:) br:^ n4:? ie:% mo:| va:) js:) de:> zu:} fl:( ss:) ls:[
    1. Aloha ;)

      ...und hier der PHP-Code mit rekursiv aufgerufenen Closures:

      // das Posting war zu lang
      
      <?php
      
      function decode_strange($raw) {
      	$decoded = [];
      	
      	$lines = explode("\n", $raw);
      	
      	// remove "\r" character if present
      	foreach ($lines as $i=>$line) {
      		
      		$lines[$i] = (strrpos($line, "\r") === strlen($line)-1)
      			? substr($lines[$i], 0, -1)
      			: $lines[$i];
      			
      		// better-to-parse formatting: 
      		// "[category]" -> "category"
      		// "data"/" data" -> ".data"
      		// ".prop" -> "..prop" and so on
      		if (preg_match('~^\[([^\]]+)\]$~', trim($lines[$i]), $matches)) {
      			$lines[$i] = $matches[1];
      		} else {
      			// remove starting whitespaces
      			if (preg_match('~^\s*(.+)$~', $lines[$i], $matches)) {
      				// add another dot in front to make space for category
      				$lines[$i] = '.'.$matches[1];
      			}
      		}
      	}
      	
      	$parse_layer = function ($layer_lines, $depth, $func) {
      		$layer = [];
      		$sub_heading = '';
      		$sub_lines = [];
      		foreach ($layer_lines as $line) {
      			// count starting dots
      			if (preg_match('~^(\.*)([^\.].*)$~', $line, $matches)) {
      				$dotcount = strlen($matches[1]);
      				// if count of starting dots matches depth, this is a new heading or property
      				if ($dotcount == $depth) {
      					if (preg_match('~(.+)=(.*)~', $matches[2], $prop_matches)) {
      						// this is a property
      						// arrayfy for case of duplicate headings
      						if (!array_key_exists($prop_matches[1], $layer)) {
      							$layer[$prop_matches[1]] = [];
      						}
      						$layer[$prop_matches[1]][] = $prop_matches[2];
      					} else {
      						// if we already had a heading, save old heading in layer
      						if ($sub_heading !== '') {
      							// arrayfy for case of duplicate headings
      							if (!array_key_exists($sub_heading, $layer)) {
      								$layer[$sub_heading] = [];
      							}
      							$layer[$sub_heading][] = $func($sub_lines, $depth + 1, $func);
      						}
      						$sub_heading = $matches[2];
      						$sub_lines = [];
      					}
      				} elseif ($dotcount > $depth) {
      					$sub_lines[] = $line;
      				} else {
      					throw new Exception('Dot count does not match: Found '.$dotcount.' dots in depth '.$depth);
      				}
      			}
      		}
      		
      		// if there is a leftover heading, save it
      		if ($sub_heading !== '') {
      			// arrayfy for case of duplicate headings
      			if (!array_key_exists($sub_heading, $layer)) {
      				$layer[$sub_heading] = [];
      			}
      			$layer[$sub_heading][] = $func($sub_lines, $depth + 1, $func);
      		} else {
      			// this means there was not even one heading!
      		}
      		
      		// de-arrayfy where possible
      		foreach ($layer as $li => $larr) {
      			if (count($larr) == 1) {
      				$layer[$li] = $layer[$li][0];
      			}
      		}
      		
      		return $layer;
      	};
      	
      	return $parse_layer($lines, 0, $parse_layer);
      }
      
      function encode_strange ($data, $newline = "\r\n") {
      	$parse_layer = function($layer_data, $depth, $newline, $func) {
      		$depth_prefix = ($depth >= 1)
      			? ' ' : '';
      		for ($i = 1; $i < $depth; $i++) {
      			$depth_prefix .= '.';
      		}
      		$depth_suffix = '';
      
      		if ($depth == 0) {
      			$depth_prefix = '[';
      			$depth_suffix = ']';
      		}
      		
      		$ltext = '';
      		foreach ($layer_data as $ltitle => $lelm) {
      			// only properties are non-arrays
      			if (!is_array($lelm)) {
      				$ltext .= $depth_prefix.$ltitle.$depth_suffix.'='.$lelm.$newline;
      			} else {
      				// numeric indices mean this is a list
      				$fully_numeric = true;
      				foreach (array_keys($lelm) as $lek) {
      					$fully_numeric = $fully_numeric && is_numeric($lek);
      				}
      				if ($fully_numeric) {
      					// add each list element
      					foreach ($lelm as $llistelm) {
      						$ltext .= $depth_prefix.$ltitle.$depth_suffix.$newline;
      						$ltext .= $func($llistelm, $depth+1, $newline, $func);
      					}
      				} else {
      					// add this single element
      					$ltext .= $depth_prefix.$ltitle.$depth_suffix.$newline;
      					$ltext .= $func($lelm, $depth+1, $newline, $func);
      				}
      			}
      		}
      		return $ltext;
      	};
      	
      	return $parse_layer($data, 0, $newline, $parse_layer);
      }
      
      $decoded = decode_strange(file_get_contents('./strangefile.txt'));
      
      echo '<pre>';
      echo print_r($decoded, true);
      echo '</pre>';
      
      file_put_contents('./strangefile_reencoded.txt', encode_strange($decoded));
      
      ?>
      
      1. Hallo zusammen,

        war zwei Tage unterwegs und konnte mich daher erst jetz wieder damit befassen. Danke für eure Hilfe und besonders @Camping_RIDER für seine 1A Lösung 👍🍺

        Habe etwas gebraucht um dahinter zu steigen, da ich reguläre Ausdrücke nicht immer gleich kappiere 😵

        Sorry für meinen kleinen Eingabefehler mit dem beiden Leerzeichen vor dem 2. und 3. datensatz. Die gehören da nicht hin, wodurch dann natürlich decode_strange eine falsche Ausgabe liefert. Ist aber keine große Sache, habe einfach $depth >= 1 das = geklaut und schon ist es valide.

        VG

        1. Aloha ;)

          Sehr gerne. Freut mich, dass es geholfen hat!

          Grüße,

          RIDER

          --
          Camping_RIDER a.k.a. Riders Flame a.k.a. Janosch Albers-Zoller
          # Twitter # Steam # YouTube # Self-Wiki # Selfcode: sh:) fo:) ch:| rl:) br:^ n4:? ie:% mo:| va:) js:) de:> zu:} fl:( ss:) ls:[