Algo zum Parsen von .txt-Datei

Moin, gegeben sei folgende Datei:

hier
steht
viel

[FATIGUE_COEF]
;comp 0
RegularFactor00=0.1
SpecificFactor00=0.1
BadWeather0=0.1
;comp1
RegularFactor10=0.1
RegularFactor11=0.1
SpecificFactor10=0.1
SpecificFactor11=0.1
BadWeather1=0.1
;comp2
RegularFactor20=0.1
RegularFactor21=0.1
SpecificFactor20=0.1
SpecificFactor21=0.1
BadWeather2=0.1
;comp3
RegularFactor30=0.1
RegularFactor31=0.1
SpecificFactor30=0.1
SpecificFactor31=0.1
BadWeather3=0.1
;comp4
RegularFactor40=0.1
RegularFactor41=0.1
SpecificFactor40=0.1
SpecificFactor41=0.1
BadWeather4=0.1
;comp5
RegularFactor50=0.1
RegularFactor51=0.1
SpecificFactor50=0.1
SpecificFactor51=0.1
BadWeather5=0.1
;comp6
SpecificFactor60=0.1
SpecificFactor61=0.1
;comp7
SpecificFactor70=0.1
SpecificFactor71=0.1
;comp8
RegularFactor80=0.1
RegularFactor81=0.1
SpecificFactor80=0.1
SpecificFactor81=0.1
BadWeather8=0.1
;comp9
RegularFactor90=0.1
SpecificFactor90=0.1
BadWeather9=0.1
;comp10
RegularFactor100=0.1
SpecificFactor100=0.1
BadWeather10=0.1
;comp 11
RegularFactor110=0.1
RegularFactor111=0.1
SpecificFactor110=0.1
SpecificFactor111=0.1
BadWeather11=0.1

hier
steht
noch
mehr

Alle Werte innerhalb von FATIGUE_COEF und ;compXyz sollen nun durch zufällige Werte ersetzt werden.

Also hab ich mir geschrieben:

        BufferedReader br = new BufferedReader(new FileReader(f));
        String line;
        while ((line = br.readLine()) != null) {
            if (line.equals("[FATIGUE_COEF]")) {
                line = br.readLine();
                while (line.startsWith(";comp")) {
                    System.out.println(line);
                    while ((line = br.readLine()) != null && line.matches("[^=]+=[^=]+")) {
                        Pattern p = Pattern.compile("([^=]+)=[^=]+");
                        Matcher m = p.matcher(line);
                        m.find();
                        System.out.println(m.group(1) + "=0.1");
                    }
                }
            }
        }
        br.close();```

um die Paare zu finden und auszugeben. Der Pfad wäre:

[FATIGUE_COEF] -> ;comp* -> *

Jetzt wollte ich fragen, wie man auch alle * (Paare) eines wesentlich komplizierteren Pfades in einer .txt-Datei finden kann und ersetzten kann? Es muss doch einen guten Ansatz dafür geben?

Deine Datei ist im Standard INI Format.
Um das schnell und einfach zu parsen/bearbeiten kannst du folgendes nutzen:
https://commons.apache.org/proper/commons-configuration/

Was wäre mit einem Array:

String[][] stringArray = {{"2", ""}, {"1", "[FATIGUE_COEF]"}, {"2", ";comp"}, {"3", "[^=]+=[^=]+"}};

1 := exakt
2 := Anfang
3 := regulärer Ausdruck

liegt so eins vor, gehe ich weiter oder bleibe bei last, liegt so eins nicht vor, dann gehe ich zurück. Wäre das so richtig?

Noch ein Tipp zum effizienten Umgang mit Regexen.

  • Wenn die Regex -wie hier- statisch ist, kompiliere das Pattern nur einmal und merke es Dir in einer Variablen/Konstanten. Nur die Matcher werden öfter erzeugt.
  • Das dann gemerkte Pattern kann auch für das Line-Matching verwendet werden. Auch das vermeidet das unnötige Kompilieren von Patterns (hier dann unter der Haube durch die Klasse String in der Methode line.matches).
  • Lektüre der Javadocs der Klasse Matcher (https://docs.oracle.com/javase/7/docs/api/java/util/regex/Matcher.html) erleichtert die korrekte Auswahl der benötigten Methoden.
    Die entsprechenden Codeschnipsel:
static final Pattern KEY_VALUE = Pattern.compile("([^=]+)=(.+)");
...
Matcher m = KEY_VALUE.matcher(line);
if(m.matches())
  System.out.println("key=" + m.group(1));
   System.out.println("value=" + m.group(2));
}

Ich habe die Schnipsel nicht in Deine Schleifen eingebaut, weil ich glaube, dass die komplett überarbeitet werden müssten. Vierfach-Verschachteltung ist (jedenfalls für meine Birne) nicht mehr handelbar. Hier unbedingt mit Methoden arbeiten. Und sie sehen auch so aus, als würden sie (jedenfalls unter gewissen Umständen) nicht wie gewünscht durchlaufen.

Ok, wenn das Format ungültig ist, müsste ich das bei jedem read prüfen.

Aber angenommen, der Pfad ist länger, dann wird mind. 1 zusätzliche Schleife doch unübersichtlich.

Pattern. check

Oder [ini4j] - Java API for handling Windows ini file format nehmen. Bitte nicht selbst rumfriemeln, das ist einfach doof…

So meinte ich das, aber ich weiß nicht, ob man diesen Aufwand hätte machen sollen, um 48 key-values zu ersetzen:

import java.util.regex.*;

public class BestimmteKlasse {

    private static Pattern pattern = Pattern.compile("([^=]+)=([^=]+)");
    private static String[][] phat = {{"2", ""}, {"1", "[FATIGUE_COEF]"}, {"2", ";comp"}, {"3", "([^=]+)=([^=]+)"}};
    private static String replaceValue = "-0.1";
    private static int index = 0;

    public static String parse(File f) throws IOException {
        StringBuilder builder = new StringBuilder();
        BufferedReader br = new BufferedReader(new FileReader(f));
        String line;
        while ((line = br.readLine()) != null) {
            builder.append(processLine(line));
            builder.append(System.lineSeparator());
        }
        br.close();
        return builder.toString();
    }

    private static String processLine(String line) {
        if (line.isEmpty()) {
            return line;
        }
        if (index == -1) {
            index++;
        }
        switch (Integer.parseInt(phat[index][0])) {
            case 1:
                if (line.equals(phat[index][1])) {
                    index++;
                } else {
                    index--;
                    return processLine(line);
                }
                break;
            case 2:
                if (line.startsWith(phat[index][1])) {
                    index++;
                } else {
                    index--;
                    return processLine(line);
                }
                break;
            case 3:
                if (line.matches(phat[index][1])) {
                    index++;
                } else {
                    index--;
                    return processLine(line);
                }
                break;
            default:
                throw new AssertionError();
        }
        if (index == phat.length) {
            index--;
            Matcher m = pattern.matcher(line);
            if (m.find()) {
                return m.group(1) + "=" + replaceValue;
            }
        }
        return line;
    }

    public static void main(String[] args) throws IOException {
        File f = new File(" bestimmter Pfad ");
        System.out.println(parse(f));
    }
}```

Gibt es da neuere Möglichkeiten in Java 7, 8, 9? :rolleyes:

Soweit ich weiß nicht. Aber anstatt das Rad neu zu erfinden würde ich auf bereits bestehende Lösungen zurückgreifen, wie zum Beispiel Apache Commons Configuration. Leute haben das schon erledigt. Warum nicht einfach benutzen? Einer der größten plus Punkte ist, dass diese Libraries von tausenden Leuten benutzt werden. Kleinste Fehler werden schnell gefunden und auch schnell behoben!

Alles richtig, aber um das selber zu schreiben brauche ich Zeit, aber um:

  1. Lib zu finden,
  2. Lib zu laden,
  3. Lib zu „installieren“,
  4. Syntax für Pfad anzueignen,
  5. zu Testen usw
    brauche ich auch Zeit.

Finde XyzPath, um alle key-values zu finden, ersetze alle values → Doku lesen, komplizierte Systax lernen, alle kompliziert

Man macht nicht mit Stock und Wolle Feuer, wenn das Feuerzeug halbwegs einfach ist, aber wenn ich erst 500 Seiten lesen muss, dann nehme ich doch Stock Bogen Wolle. → http://www.allmystery.de/i/typOdo6_feuerbohrer.jpg

Jetzt mal unabhängig davon, es wird wahrscheinlich ein Übungszettel bald sein. :frowning:

[QUOTE=Unregistered]Alles richtig, aber um das selber zu schreiben brauche ich Zeit, aber um:

  1. Lib zu finden,
  2. Lib zu laden,
  3. Lib zu “installieren”,
  4. Syntax für Pfad anzueignen,
  5. zu Testen usw
    brauche ich auch Zeit.[/quote]
    im normfall ist das eine Sache von 5min - testen brauchst du nicht viel, was willst du testen ? Holen, reintun, benutzen.
    Mit Maven etc ists meistens ne Sache von 2min max.

Unsinn - man schaut sich wenn dann kurz die Beispiele an, aller höchstens noch die javadoc für das was man braucht. Du bindest nicht Photoshop ein, sondern ne simple lib.

[quote=Unregistered]1) Lib zu finden,
2) Lib zu laden,
3) Lib zu “installieren”,
4) Syntax für Pfad anzueignen,
5) zu Testen usw
brauche ich auch Zeit.[/quote]

  1. libs wurden dir genannt … mit links
  2. Witz? So groß sind die nicht. Klickst drauf und dann sind die auf deinem Rechner. Da hast du mehr Zeit für die Punkteliste benötigt als der Download gebraucht hätte
  3. Wenn man seine IDE oder Maven kennt ist das eine arbeit von Sekunden bis gar keine.
  4. Was?
  5. Nope. Das ist nicht deine Aufgabe, wenn man eine Lib verwendet geht man von aus das die funktioniert.

Ginge es bei dem ganzen Ding um einen Übungszweck, dann wäre es natürlich eine andere Sache das selber zu machen. Aber selbst wenn doch. Bevor du mit sowas wie Regex anfängst solltest du erstmal deine Werkzeuge kennen lernen. Dazu gehört auch libs einzubinden oder zu wissen, wie man sich davon die basics aneignet. Denn da hast du ein komplett falsches Bild von.

Ja, zumindest ab Java 8 kannst Du mit der Klasse Files und sogenannten Funktionalen Interfaces arbeiten.

Path path = Paths.get("<pfadZurDatei>");
Stream<String> lines = Files.lines(path);
lines.forEach(lineProcessor);

Da sparst Du Dir den Code für die Reader (aufmachen, schließen) und die eine Schleife für das Lesen solange nicht null. Wenn Die Datei nicht zu groß ist und Du gerne alle lines in Gänze hättest (kann sein, dass das Deine Aufgabe vereinfacht), dann findest Du evtl auch https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#readAllLines-java.nio.file.Path- nett.

dafür braucht es doch kein Java 8,
schon von Java 1(.1) an konnte man sich einfache Hilfmethoden schreiben die etwa einen Iterator (ok, 1.2) liefern, jedes next eine neue Zeile

selbst mutmaßlich CyborgBeta kam hier auf die Idee, eine recht saubere Methode
private static String processLine(String line) zu haben, der Standardcode außerhalb davon

die Frage bezog sich auf besseres Möglichkeiten des Processing, nicht des Dateiöffnens :wink:

Deswegen Java 8 und funktionale Interfaces. Die Musik steckt in den drei Punktender Zeile
Consumer<String> lineProcessor = ...;
Mit funktionalen Interfaces lässt sich Code zur Auswahl, des Behandlungscodes abhängig vom Inhalt der Zeile schreiben. Der Behandlungscode selbst ist dann jeweils auch wieder die Implementierung eines funktionalen Interfaces. Das ist am Ende sogar NOCH sauberer als durchdachte Hilfsmethoden. Und ein Teil der Probleme entsteht hier, weil man die Struktur nicht im Griff hat.

Das ganze erleichtert die Isolation der einzelnen zu lösenden fachlichen Probleme. Mit deren Lösung muss man sich selbst rumschlagen. Insofern gebe ich Dir Recht, dass auch die neueren Versionen von Java nichts mehr bieten als die älteren.

Naja, ich muss mir eine völlig neue, komplizierte Syntax aneignen, in welcher ich den gewünschten Pfad zu den key-value-Pairs angeben kann, denke ich, solange das nicht standardisiert wurde, was es whrs. ist. Verstehst du, was ich meine?

@nillehammer : Neuere Möglichkeiten, eleganterer Code. :frowning: