# Htmlcode abfragen und ausgeben



## seahawk (17. Aug 2019)

Hallo alle zusammen,
ich bin noch ein Java-Anfänger möchte aber eine kleine Android-App programmieren, welche den html-code eines Vertretungsplans aufruft, das ganze in eine csv-datei schreibt, bzw mir spezifisch für eine Klasse alles wichtige raussucht.
Mein Htmlcode sieht so aus:

```
<tr class="normal">
<td class="VBlock">7d</td>
<td class="VBlock">Mi 1.</td>
<td class="VBlock">xxxxx / E</td>
<td class="VBlock">entfällt</td>
<td class="VBlock"></td>
<td class="VBlock"></td>
<td class="VBlock" style="white-space:normal"></td>
</tr>
<tr class="normal Leselinie">
<td class="VBlock">7d</td>
<td class="VBlock">Mi 2.</td>
<td class="VBlock">xxxxx / E</td>
<td class="VBlock">entfällt</td>
<td class="VBlock"></td>
<td class="VBlock"></td>
<td class="VBlock" style="white-space:normal"></td>
</tr>
```
usw..und jetzt würde ich halt gerne zB nur alles anzeigen was für die 7d entfällt.
Mein Code sieht bisher so aus:

```
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.io.IOException;


public class Main2Activity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Reading();
    }

    protected void Reading()throws IOException{
        URL url = new URL("https://list-gymnasium.de/vertretungsplan/upload/Vertretungen-Sa.html");
        InputStream in = url.openStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));

        String s;

        while((s = reader.readLine()) != null){
            System.out.println(s);
        }
    }
}
```
Kann mir jemand sagen, was die beste Methode ist, das ganze zu "filtern"?
Vielen Dank schonmal im Voraus!
seahawk


----------



## Robat (17. Aug 2019)

Bevor du dir anfängst deinen eigenen Parser zu bauen solltest du ggf. lieber auf eine Library wie JSoup zurückgreifen.
Damit kannst du ganz einfach auf bestimmte Elemente aus deiner HTML Datei zugreifen und so zum Beispiel über alle Reihen iterieren und dir von jeder Reihe alle Spalteninhalte holen.


----------



## mihe7 (17. Aug 2019)

Als HTML-Parser kannst Du https://jsoup.org/ verwenden. Als Selector (https://jsoup.org/cookbook/extracting-data/selector-syntax) kannst Du ggf. `tr:has(td.VBlock:containsOwn('7d'))` verwenden, um die passenden tr-Elemente zu erhalten. Müsstest Du mal ausprobieren.


----------



## Xyz1 (17. Aug 2019)

Wieso ist bei mir der Body die Seite leer?

```
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
                      "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Vertretungsplan</title>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<style type="text/css">
# hier Style-Informationen
</style>
<link rel="stylesheet" type="text/css" href="Vertretungen-S.CSS"/>
</head>
<body>

</body>
</html>
```


Nichtsdestominder ist JSoup eine gute , erste Anlaufstelle...
Wenn Du mir den Vertretungsplan schicken magst, kann ich mal schauen, wie man daraus die relevanten Daten extrahieren und weiter-bearbeiten könnte. 
Mich würd auch _nicht_ interessieren, wie oft welcher Unterricht bei welchem Lehrkörper entfällt.... Ich weiß ja gar nicht, wo List liegt.


----------



## seahawk (17. Aug 2019)

Erstmal vielen dank für eure schnellen Antworten!
@Tobias-nrw ja das liegt daran, dass der Vertretungsplan momentan offline ist, da Ferien sind. Ich habe allerdings einen alten Code gespeichert..hab ich auch oben einen Ausschnitt gepostet..brauchst du mehr?
Ich hab das mit jsoup versucht und denk auch, dass das die beste Art ist..aber irgendwie kann ich Jsoup nicht importieren. Ich arbeite in Android-Studio. Mein Code:

```
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import org.jsoup.nodes.Elements;


public class Main2Activity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Reading();
    }

    protected void Reading(){
        //Document doc = Jsoup.connect("http://example.com/").get(); das nehm ich wieder, wenn die website wieder online ist
        File input = new File("/Desktop/input.html");
        Document doc = Jsoup.parse(input, "UTF-8", "");
        String treffer = doc.select("tr:has(td.VBlock:containsOwn(klasse))");
        System.out.println(treffer);
    }
}
```
mein Error:

```
error: package org.jsoup does not exist   
error: package org.jsoup.nodes does not exist   
error: package org.jsoup.nodes does not exist   
error: cannot find symbol class File   
error: cannot find symbol class File   
error: cannot find symbol class Document   
error: cannot find symbol variable Jsoup
```
Ich mach glaub irgendwas beim Importieren falsch..und bei der Kodierung. Was für eine muss ich da nehmen, dass Umlaute angezeigt werden?


----------



## M.L. (17. Aug 2019)

> ..aber irgendwie kann ich Jsoup nicht importieren. Ich arbeite in Android-Studio.


Android Studio wird JSoup wohl kaum kennen, wenn es noch nicht heruntergealden und installiert wurde: https://jsoup.org/download   (vrmtl. via Gradle)


----------



## Xyz1 (17. Aug 2019)

Tobias-nrw hat gesagt.:


> der Body die Seite leer?


Oh man, ich sollte auch mal lesen, was ich so schreibe. 



Tobias-nrw hat gesagt.:


> wo List liegt


... auch peinlich, schließlich weiß ich ungefähr, wo Reutlingen liegt... und das nachdem ich selber zu einem Gymi ging das nach einem meiner Vorfahren benannt wurde. 



seahawk hat gesagt.:


> brauchst du mehr


um näher helfen zu können, ja



seahawk hat gesagt.:


> aber irgendwie kann ich Jsoup nicht importieren


pom.xml, innerhalb von <dependencies>:

```
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.12.1</version>
</dependency>
```




seahawk hat gesagt.:


> Ich arbeite in Android-Studio


Das habe ich zurzeit nicht installiert...


----------



## seahawk (18. Aug 2019)

ok, also ich hab das jetzt zum Laufen gebracht..

```
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.content.Intent;
import android.widget.Button;
import android.widget.EditText;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

import java.io.IOException;


public class MainActivity extends AppCompatActivity {
    private Button button;
    private String klasse;
    private EditText etklasse;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                etklasse = (EditText)findViewById(R.id.editText);
                klasse = etklasse.getText().toString();
                openActivity2();
                Reading();
            }
        });
    }

    public void openActivity2() {
        Intent intent = new Intent(this, Main2Activity.class);
        startActivity(intent);
    }

    public static void Reading()throws IOException {
        //Document doc = Jsoup.connect("http://example.com/").get(); //das nehm ich wieder, wenn die website wieder online ist
        File input = new File("/Desktop/input.html"); //ist die htmldatei die ich schonmal gepostet habe
        Document doc = Jsoup.parse(input, "UTF-8");
        Elements treffer = doc.select("tr:has(td.VBlock:containsOwn(klasse))");
        System.out.println(treffer);
    }
}
```
mein .get() wirft mir allerdings eine Exception, die ich dann oben ja auch reingeschrieben habe..allerdings kann ich die Methode gar nicht aufrufen, weil die onCreate Methode keine Exceptions haben darf


----------



## Robat (18. Aug 2019)

Generell darfst du Netzwerk-Operationen in Android sowieso nicht auf den Haupthread ausführen, sonder musst es auf einen anderen Thread auslagern. Dazu eignen sich in Android der so genannte AsnycTask. 

```
public class ReadHtmlTask extends AsyncTask<String, Void, Elements> {
    @Override
    protected Elements doInBackground(String... urls) {
        try {
            Document document = JSoup.connect(urls.get(0)).get();
            Elements treffer = document.select("tr:has(td.VBlock:containsOwn(klasse))");
            return treffer;
        } catch (IOException e) {
           e.printStackTrace();
           return null;
        }
        return null;
    }
    @Override
     protected void onPostExecute(Elements result) {
         // display result ... 
     }
}
```
Benutzen kannst du das ganze in deiner Activity dann wie folgt:

```
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                etklasse = (EditText)findViewById(R.id.editText);
                klasse = etklasse.getText().toString();
                ReadHtmlTask task = new ReadHtmlTask();
                task.execute("http://www.example.com");
            }
        });
    }
```
Nebenbei gesagt macht deine aktuelle Logik recht wenig Sinn. Du startest erst eine neue Activity und rufst danach Reading() auf.


----------



## seahawk (18. Aug 2019)

Also wenn ich dich richtig verstanden habe, müsste das ja so aussehen:

```
public class Main2Activity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                etklasse = (EditText)findViewById(R.id.editText);
                klasse = etklasse.getText().toString();
                ReadHtmlTask task = new ReadHtmlTask();
                task.execute("http://www.example.com");
            }
        });
    }
}

public class ReadHtmlTask extends AsyncTask<String, Void, Elements> {
    @Override
    protected Elements doInBackground(String... urls) {
        try {
            Document document = JSoup.connect(urls.get(0)).get();
            Elements treffer = document.select("tr:has(td.VBlock:containsOwn(klasse))");
            return treffer;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return null;
    }
    @Override
    protected void onPostExecute(Elements result) {
        // display result ...
    }
}
```
Muss ich für das AsnycTask irgendwas importieren, weil momentan zeigt er mir viele Errors


----------



## Robat (18. Aug 2019)

Sieht deine Java Datei denn genau so aus wie du sie gepostet hast? Die ReadHtmlTask Klasse sollte schon eine eigene Java Datei oder eine innere Klasse der Activity sein. Ansonsten kommt AsyncTask aus dem Package `android.os.*;`

Mehr kann man nicht sagen .. da müsstest du schon mal preisgeben, was für Fehler das sind

PS: Der Code war auch nicht dazu gedacht dass du ihn einfach kopierst. Du solltest dich schon damit beschäftigen und verstehen was dort passiert.


----------



## seahawk (20. Aug 2019)

Oki, danke erstmal für die Tipps! Ich hab mir AsyncTask angeschaut und verstanden. Hab jetzt das ganze auch nochmal überarbeitet.
Eine Frage ist mir aber geblieben..


seahawk hat gesagt.:


> // display result ...


also wenn ich das richtig verstanden habe, gibt die onPostExecute-Methode ihr result gleich an den UI-Thread weiter. Wenn ich jetzt mit den Daten aber erstmal weiterabreiten möchte..zb erstmal in variablen speichern, wie mach ich das dann?
Und ich kann ja auch nichts anfänglich übergeben, weil ich mir das ja eigentlich in genau dem Schritt erst hole. Und nochmal zur Absicherung..Netzwerkoperationen NUR und GANZ in einem Asynctask?


----------



## Robat (20. Aug 2019)

Weitergeben tut die nichts. Es ist aber sichergestellt, dass diese Methode im UI Thread aufgerufen wird. Somit kannst du dort ohne Sorgen das Resultat anzeigen. 
Wenn du vorher mit den Daten noch etwas machen möchtest kannst du das natürlich gerne tun. Darin hundert dich niemand 

Und ja: Netzwerkoperationen sollten ausschließlich in einem separaten Thread laufen


----------



## mihe7 (20. Aug 2019)

Robat hat gesagt.:


> Somit kannst du dort ohne Sorgen das Resultat anzeigen.


Ohne Sorgen würde ich bei Android grds. nicht behaupten  

Korrigier mich, wenn ich falsch liege: der AsyncTask muss recht kurz laufen, damit man Probleme (z. B. configuration change) minimiert.


----------



## seahawk (20. Aug 2019)

Robat hat gesagt.:


> vorher mit den Daten noch etwas machen


Darf ich das dann in der Activity mit dem AsyncTask machen oder in der Main Activity


----------



## Robat (20. Aug 2019)

mihe7 hat gesagt.:


> der AsyncTask muss recht kurz laufen, damit man Probleme (z. B. configuration change) minimiert.


Ja meine Aussage war etwas salop, da hast du natürlich recht. Aber so ein Vertretungsplan sollte jetzt nicht unendlich lange zum parsen brauchen.


seahawk hat gesagt.:


> Darf ich das dann in der Activity mit dem AsyncTask machen oder in der Main Activity


Das kannst du ruhig im AsyncTask machen. Das ist btw keine Activity sondern eine Klasse


----------



## seahawk (20. Aug 2019)

Wieso wirft er mir hier einen Error?

```
@Override
    protected Elements doInBackground(String... urls) {
        try {
            Document document = Jsoup.connect("http://vertretungsplan.jkg-reutlingen.de/Vertretungen-Sa.html").get();
            System.out.print(document);
            Elements treffer = document.select("tr:has(td.normal:containsOwn(Aufbau))");
            System.out.print(treffer);
            return treffer;

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return null;
    }
```
Er sagt bei dem unteren return..es sei ein unreachable statment


----------



## Robat (20. Aug 2019)

Weil es niemals ausgeführt wird. Bei deinem try-catch Block gibt es genau 2 Fälle die eintreten können. Entweder geht alles gut, dann gibst du treffer zurück. Wenn eine Exception fliegt geht er in den catch-Block und gibt dort null zurück. 
Es gibt also keine Möglichkeit wie die Zeile nach deinem try-catch Block ausgeführt werden kann


----------



## seahawk (21. Aug 2019)

Mmh ja so weit war ich auch..nur dann wirft er mir unzählige errors wegen JSoup

```
Duplicate class org.jsoup.parser.HtmlTreeBuilderState$6 found in modules jsoup-1.12.1.jar (jsoup-1.12.1.jar) and jsoup-1.12.1.jar (org.jsoup:jsoup:1.12.1)
Duplicate class org.jsoup.parser.HtmlTreeBuilderState$7 found in modules jsoup-1.12.1.jar (jsoup-1.12.1.jar) and jsoup-1.12.1.jar (org.jsoup:jsoup:1.12.1)
Duplicate class org.jsoup.parser.HtmlTreeBuilderState$8 found in modules jsoup-1.12.1.jar (jsoup-1.12.1.jar) and jsoup-1.12.1.jar (org.jsoup:jsoup:1.12.1)
Duplicate class org.jsoup.parser.HtmlTreeBuilderState$9 found in modules jsoup-1.12.1.jar (jsoup-1.12.1.jar) and jsoup-1.12.1.jar (org.jsoup:jsoup:1.12.1)
Duplicate class org.jsoup.parser.HtmlTreeBuilderState$Constants found in modules jsoup-1.12.1.jar (jsoup-1.12.1.jar) and jsoup-1.12.1.jar (org.jsoup:jsoup:1.12.1)
```
usw


----------



## mrBrown (21. Aug 2019)

Wie hast du jsoup eingebunden?


----------



## seahawk (21. Aug 2019)

In der build.gradle mit: implementation 'org.jsoup:jsoup:1.12.1'
Und im Ordner libs hab ich natürlich die .jar-Datei drin


----------



## mrBrown (21. Aug 2019)

Wenn du die Datei per Hand in den lib-Ordner gelegt hast: lösch sie daraus wieder


----------



## seahawk (21. Aug 2019)

oh perfekt, hat funktioniert, danke!


----------



## seahawk (21. Aug 2019)

Wie kann ich jetzt aber, wenn ich in meiner doInBackground-Methode eine Variable mit Daten erstelle, aus der onExecute_Methode darauf zugreifen um bspw ein TextView mit den Daten zu machen


----------



## mihe7 (21. Aug 2019)

onPostExecute bekommt doch das Ergebnis von doInBackground. Wenn Du Daten weitergeben willst, gibst Du diese einfach zurück (ggf. musst Du den Typ anpassen).


----------



## RangerBa (3. Okt 2019)

Da was schon gefunden?


----------

