Java als Scriptsprache

 30. Juli 2020 •  Ekkart •  java, script •  ToDo

Mal etwas anderes als Filme: Seit Java 11 kann man Java als Scriptsprache einsetzen, das heißt, direkt eine Java-Datei aufrufen.

Im Endeffekt schreibt man eine Klasse mit main-Methode, die die Arbeit erledigt (man kann wohl mehrere Klassen in eine Datei packen, aber wer macht sowas).

Was rauskommt, ist typisches Scripting der ersten Stunde: Spaghetticode, der eine Aufgabe erledigt und nicht wiederverwendet werden kann. Wenn man sich dessen bewusst ist, ist das tatsächlich hilfreich.

Vorteil: das Script ist plattformübergreifend nutzbar Nachteil: Java ist nicht für Scripting entworfen, der Code ist grauenhaft, es ist nur eine Datei erlaubt, also keine Wiederverwendung

Ich habe das jetzt mal ausprobiert, da ich schnell eine Lösung brauchte und mich in Python etc. immer erst einarbeiten muss und auf meinen Rechnern überall Java installiert ist.

Ob ich das dauerhaft Bash-Scripts (die echt nicht schön aussehen) und Ant-Scripts vorziehe – mal so, mal so, vermute ich. Alles mit Aufruf von Programmen auf dem Rechner wird wohl bash oder Ant bleiben, bei dem Rest muss ich gucken.

Mein aktuelles Beispiel: neuen Dateinamen für eine Datei finden mit Informationen, die aus dem XML (kml) geholt werden. Dafür sieht es ganz ok aus…

Aufruf: java RenameKML.java <path> <color> <width>

import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.Writer;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

public class RenameKML {

  private final static String EXTENSION = ".kml";

  /**
   * Main method.
   */
  public static void main(String[] args) {

    System.out.println("Rename KML");

    try {

      if (args.length &lt; 3) {
        throw new Exception("Missing parameters.");
      }

      Path pathSource = Path.of(args[0]);
      if (Files.exists(pathSource)) {
        try (Stream&lt;Path> paths = Files.walk(pathSource)) {
          paths
              .filter(Files::isRegularFile)
              .filter(path -> path.toString().endsWith(EXTENSION))
              .forEach(path -> processFile(path, args[1], Integer.parseInt(args[2])));
        }
      } else {
        throw new FileNotFoundException(pathSource.toString());
      }

    } catch (Exception e) {

      System.err.println();
      e.printStackTrace();
      System.err.println();
      printUsage();

      System.exit(1);

    }

  }

  /**
   * Usage information.
   */
  private static void printUsage() {

    System.out.println("Usage:\tjava RenameKML.java &lt;path> &lt;color> &lt;width>");
    System.out.println("\tcolor: RRGGBBAA");
    System.out.println("");

  }

  /**
   * Process single file.
   *
   * A normal Exception cannot be declared, because this method is called from
   * withni Stream.forEach, only RuntimeException is allowed there.
   */
  private static void processFile(
      final Path theXMLFile,
      final String theColor,
      final int theWidth
  ) {

    System.out.println(String.format("Processing %s with color %s and width %d", theXMLFile.toString(), theColor, theWidth));

    try {

      String sXMLContent = Files.readString(theXMLFile);
      sXMLContent = cleanKML(sXMLContent, theColor, theWidth);


      Path pathOutFile = Path.of(theXMLFile.getParent().toAbsolutePath().toString(), String.format("%s%s", getNewFilename(sXMLContent), EXTENSION));

      writeKMLFile(pathOutFile, sXMLContent);

      if (!theXMLFile.toAbsolutePath().equals(pathOutFile.toAbsolutePath())) {
        System.out.println(String.format("\tDeleting: %s", theXMLFile.toString()));
        Files.delete(theXMLFile);
      }

    } catch (Exception e) {
      throw new RuntimeException(e);
    }

  }

  /**
   * Clean KML from unused or strange tags/content.
   */
  private static String cleanKML(
      final String theKML,
      final String theColor,
      final int theWidth
  ) throws Exception {

    String sKMLColor = theColor.replaceAll("(..)(..)(..)(..)", "$4$3$2$1");

    String sKMLCleaned = theKML;
    // IconStyles
    sKMLCleaned = sKMLCleaned.replaceAll(
      "&lt;Style id=\".*\">&lt;IconStyle>.*?&lt;/IconStyle>&lt;/Style>",
      ""
    );
    // Folder
    sKMLCleaned = sKMLCleaned.replaceAll(
      "&lt;Folder>(.*)&lt;/Folder>",
      "$1"
    );
    // Recording Placemarks
    sKMLCleaned = sKMLCleaned.replaceAll(
      "&lt;Placemark>.*?Recording.*?&lt;/Placemark>",
      ""
    );
    // Fill name, remove other
    sKMLCleaned = sKMLCleaned.replaceAll(
      "&lt;name>.*&lt;description>(.*)&lt;/description>(&lt;Placemark>)",
      "$2&lt;name>$1&lt;/name>"
    );
    // Remove double names
    sKMLCleaned = sKMLCleaned.replaceAll(
      "&lt;name>(.*)&lt;/name>&lt;name>.*?&lt;/name>",
      "&lt;name>$1&lt;/name>"
    );
    // Remove last name/description
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(&lt;/Placemark>)&lt;name>.*&lt;/description>",
      "$1"
    );
    // Remove strange underlines
    sKMLCleaned = sKMLCleaned.replaceAll(
      "&lt;u>(.*?)&lt;/u>",
      "$1"
    );

    // reformatting description

    // time conversion (strangely complicated)
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(\\d{2}\\:\\d{2}\\:\\d{2}) vorm\\. ",
      "$1 "
    );
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(\\d{1}\\:\\d{2}\\:\\d{2}) vorm\\. ",
      "0$1 "
    );
    Matcher matchTime = Pattern.compile("(\\d{1,2})(\\:\\d{2}\\:\\d{2}) nachm\\. ").matcher(sKMLCleaned);
    while (matchTime.find()) {
      sKMLCleaned = sKMLCleaned.replaceAll(
        String.format("%s%s nachm\\. ", matchTime.group(1), matchTime.group(2)),
        String.format("%d%s ", Integer.parseInt(matchTime.group(1)) + 12, matchTime.group(2))
      );
    }

    // Duration
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(Duration\\: )(\\d{1}h\\d{2}'\\d{2}\")",
      "$10$2"
    );
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(Duration\\: )(\\d{2})h(\\d{2})'(\\d{2})\"",
      "$1$2:$3:$4"
    );
    // Description itself
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(&lt;description>&lt;!\\[CDATA\\[)Start\\: (\\d{2}\\.\\d{2}\\.\\d{4}).*?(\\d{2}\\:\\d{2})\\:\\d{2} .*?End\\: \\d{2}\\.\\d{2}\\.\\d{4}.*?(\\d{2}\\:\\d{2})\\:\\d{2} .*?Duration\\: (\\d{2}\\:\\d{2})\\:\\d{2}.*?Length\\: (.*?)km.*?Avg Speed\\: (.*?)km h.*?Max Speed\\: (.*?)km h.*?Low\\: (.*?)m.*?High\\: (.*?)m.*?Gain\\: (.*?)m.*(\\]\\]>&lt;/description>)",
      "$1Am $2 von $3 bis $4 Uhr, Dauer: $5 h, Strecke: $6 km, Ø: $7 km/h, v_max: $8 km/h, Höhenmeter: $11 m von $9 m bis $10 m$12"
    );

    // /reformatting description

    // Single TimeStamp
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(&lt;/styleUrl>)(&lt;gx:Track>)(&lt;altitudeMode>.*&lt;/altitudeMode>)(&lt;when>.*?&lt;/when>)",
      "$1&lt;TimeStamp>$4&lt;/TimeStamp>$2$3"
    );
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(&lt;altitudeMode>.*&lt;/altitudeMode>)&lt;when>.*&lt;/when>",
      "$1"
    );

    // Change color
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(&lt;color>).{8}(&lt;/color>)",
      String.format("$1%s$2", sKMLColor)
    );

    // Change width
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(&lt;width>).(&lt;/width>)",
      String.format("$1%d$2", theWidth)
    );

    return sKMLCleaned;

  }

  /**
   * Write KML file, tidy up first.
   */
  private static void writeKMLFile(
      final Path theOutFile,
      final String theXMLContent
  ) throws Exception {

    System.out.println(String.format("\tWriting: %s", theOutFile.toString()));
    Files.writeString(theOutFile, tidyXML(theXMLContent), StandardOpenOption.CREATE, StandardOpenOption.WRITE);

  }

  /**
   * Tidy up XML.
   */
  private static String tidyXML(
      final String theXMLContent
  ) throws Exception {

    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(theXMLContent.getBytes()));
    doc.getDocumentElement().normalize();

    Transformer transformer = TransformerFactory.newInstance().newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    DOMSource source = new DOMSource(doc);

    String sResult = null;

    try (Writer swXML = new StringWriter()) {

      transformer.transform(source, new StreamResult(swXML));
      sResult = swXML.toString();

    }

    return sResult;

  }

  /**
   * Get new filename from name and date within KML file.
   */
  private static String getNewFilename(final String theXML) throws Exception {

    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(theXML.getBytes()));
    doc.getDocumentElement().normalize();


    NodeList nList = doc.getElementsByTagName("name");

    if (nList.getLength() != 1) {
      throw new Exception("more than one or no name tag");
    }

    String sName = nList.item(0).getTextContent();


    nList = doc.getElementsByTagName("when");

    if (nList.getLength() != 1) {
      throw new Exception("more than one or no when tag");
    }

    LocalDateTime dteWhen = LocalDateTime.parse(nList.item(0).getTextContent(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);

    return String.format("%s_%s",
      dteWhen.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
      cleanFilename(sName, true)
    );

  }

  /**
   * Clean filename from special characters.
   */
  private static String cleanFilename(
      final String theFilename,
      final boolean convertUmlauts
  ) {

    String sConverted = theFilename;

    sConverted = sConverted
        .replace(" ", "_")
        .replace("?", "_")
        .replace(":", "_")
        .replace(";", "_")
        .replace(",", "_")
        .replace("/", "_")
        .replace("\\", "_")
        .replace("*", "_")
        .replace("(", "_")
        .replace(")", "_")
        .replace("[", "_")
        .replace("]", "_")
        .replace("{", "_")
        .replace("}", "_")
        ;

    if (convertUmlauts) {
      sConverted = sConverted
          .replace("ä", "ae")
          .replace("Ä", "Ae")
          .replace("ö", "oe")
          .replace("Ö", "Oe")
          .replace("ü", "ue")
          .replace("Ü", "Ue")
          .replace("ß", "ss")
          ;
    }

    return sConverted;

  }

}

/* EOF */

Als Vergleich hier das Ant-Script, dass die XML-Umwandlung vornimmt, könnt Ihr ja selbst sehen, was Ihr lesbarer findet…

&lt;?xml version="1.0" encoding="UTF-8" ?>

&lt;project name="kml" default="help" basedir=".">

	&lt;import file="/home/ekleinod/working/git/edgeutils/ant/ant-commons.xml"/>

	&lt;target name="kml" depends="clearLog" description="Clean all KML files in directory.">

		&lt;fail message="Pfad für die KML-Dateien nicht gesetzt (Farbe: RRGGBBTT): ant kml -Dkml:path=&quot;.&quot; -Dkml:color=&quot;14C8F0CE&quot; -Dkml:width=&quot;5&quot;" unless="kml:path" />
		&lt;fail message="Farbe für die KML-Dateien nicht gesetzt (Farbe: RRGGBBTT): ant kml -Dkml:path=&quot;.&quot; -Dkml:color=&quot;14C8F0CE&quot; -Dkml:width=&quot;5&quot;" unless="kml:color" />
		&lt;fail message="Breite für die KML-Dateien nicht gesetzt (Farbe: RRGGBBTT): ant kml -Dkml:path=&quot;.&quot; -Dkml:color=&quot;14C8F0CE&quot; -Dkml:width=&quot;5&quot;" unless="kml:width" />

		&lt;echo message="Bearbeite KML-Dateien in '${kml:path}', Farbe '${kml:color}', Breite '${kml:width}'" />

		&lt;echo message="Remove IconStyles" />
		&lt;replaceregexp
				encoding="utf-8"
				match="&lt;Style id=&quot;.*&quot;&gt;&lt;IconStyle&gt;.*&lt;/IconStyle&gt;&lt;/Style&gt;"
				replace=""
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Remove Folder" />
		&lt;replaceregexp
				encoding="utf-8"
				match="&lt;Folder&gt;(.*)&lt;/Folder&gt;"
				replace="\1"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Remove Recording Placemarks" />
		&lt;replaceregexp
				encoding="utf-8"
				match="&lt;Placemark&gt;&lt;TimeStamp&gt;.*&lt;/TimeStamp&gt;&lt;/Placemark&gt;"
				replace=""
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Fill name, remove other" />
		&lt;replaceregexp
				encoding="utf-8"
				match="&lt;name&gt;.*&lt;description&gt;(.*)&lt;/description&gt;(&lt;Placemark&gt;)"
				replace="\2&lt;name&gt;\1&lt;/name&gt;"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Remove last name/description" />
		&lt;replaceregexp
				encoding="utf-8"
				match="(&lt;/Placemark&gt;)&lt;name&gt;.*&lt;/description&gt;"
				replace="\1"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Remove double names" />
		&lt;replaceregexp
				encoding="utf-8"
				match="&lt;name&gt;(.*)&lt;/name&gt;&lt;name&gt;.*&lt;/name&gt;"
				replace="&lt;name&gt;\1&lt;/name&gt;"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Remove strange underlines" />
		&lt;replaceregexp
				encoding="utf-8"
				match="&lt;u&gt;(.*)&lt;/u&gt;"
				replace="\1"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;antcall target="timeconversion" />

		&lt;echo message="Reformat description" />
		&lt;replaceregexp
				encoding="utf-8"
				match="(Duration\: )(\d{1}h\d{2}'\d{2}&quot;)"
				replace="\10\2"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="(Duration\: )(\d{2})h(\d{2})'(\d{2})&quot;"
				replace="\1\2:\3:\4"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="(&lt;description&gt;&lt;!\[CDATA\[)Start\: (\d{2}\.\d{2}\.\d{4}).*?(\d{2}\:\d{2})\:\d{2} .*?End\: \d{2}\.\d{2}\.\d{4}.*?(\d{2}\:\d{2})\:\d{2} .*?Duration\: (\d{2}\:\d{2})\:\d{2}.*?Length\: (.*?)km.*?Avg Speed\: (.*?)km h.*?Max Speed\: (.*?)km h.*?Low\: (.*?)m.*?High\: (.*?)m.*?Gain\: (.*?)m.*(\]\]&gt;&lt;/description&gt;)"
				replace="\1Am \2 von \3 bis \4 Uhr, Dauer: \5 h, Strecke: \6 km, Ø: \7 km/h, v_max: \8 km/h, Höhenmeter: \11 m von \9 m bis \10 m\12"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Insert TimeStamp" />
		&lt;replaceregexp
				encoding="utf-8"
				match="(&lt;/styleUrl&gt;)(&lt;gx:Track&gt;)(&lt;altitudeMode&gt;.*&lt;/altitudeMode&gt;)(&lt;when&gt;.*?&lt;/when&gt;)"
				replace="\1&lt;TimeStamp&gt;\4&lt;/TimeStamp&gt;\2\3"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Remove other TimeStamps" />
		&lt;replaceregexp
				encoding="utf-8"
				match="(&lt;altitudeMode&gt;.*&lt;/altitudeMode&gt;)&lt;when&gt;.*&lt;/when&gt;"
				replace="\1"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Fill color (RGBT)" />
		&lt;replaceregexp
				encoding="utf-8"
				match="(&lt;color&gt;).{8}(&lt;/color&gt;)"
				replace="\1${kml:color}\2"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;echo message="Reformat color (RGBT -> TBGR)" />
		&lt;replaceregexp
				encoding="utf-8"
				match="(&lt;color&gt;)(..)(..)(..)(..)(&lt;/color&gt;)"
				replace="\1\5\4\3\2\6"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Fill width" />
		&lt;replaceregexp
				encoding="utf-8"
				match="(&lt;width&gt;).(&lt;/width&gt;)"
				replace="\1${kml:width}\2"
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

		&lt;echo message="Tidy XML" />
		&lt;apply executable="xmllint" parallel="false" force="true" dir="${kml:path}" relative="true">
			&lt;arg value="--format" />
			&lt;arg value="--output" />
			&lt;targetfile />
			&lt;srcfile />
			&lt;fileset dir="${kml:path}" includes="*.kml" />
			&lt;mapper type="identity" />
		&lt;/apply>

	&lt;/target>

	&lt;target name="timeconversion" depends="clearLog">

		&lt;echo message="Reformat time" />

		&lt;replaceregexp
				encoding="utf-8"
				match="(\d{1}\:\d{2}\:\d{2}) vorm\. "
				replace="0\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="(\d{2}\:\d{2}\:\d{2}) vorm\. "
				replace="\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="1(\:\d{2}\:\d{2}) nachm\. "
				replace="13\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="2(\:\d{2}\:\d{2}) nachm\. "
				replace="14\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="3(\:\d{2}\:\d{2}) nachm\. "
				replace="15\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="4(\:\d{2}\:\d{2}) nachm\. "
				replace="16\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="5(\:\d{2}\:\d{2}) nachm\. "
				replace="17\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="6(\:\d{2}\:\d{2}) nachm\. "
				replace="18\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="7(\:\d{2}\:\d{2}) nachm\. "
				replace="19\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="8(\:\d{2}\:\d{2}) nachm\. "
				replace="20\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="9(\:\d{2}\:\d{2}) nachm\. "
				replace="21\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="10(\:\d{2}\:\d{2}) nachm\. "
				replace="22\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="11(\:\d{2}\:\d{2}) nachm\. "
				replace="23\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>
		&lt;replaceregexp
				encoding="utf-8"
				match="12(\:\d{2}\:\d{2}) nachm\. "
				replace="00\1 "
				flags="g">

			&lt;fileset dir="${kml:path}" includes="*.kml" />

		&lt;/replaceregexp>

	&lt;/target>

&lt;/project>

&lt;!-- EOF -->