Wie ich den Zeitplan analysiert habe

Hallo Habr!

Lieber Leser! Wenn Sie sich für HTML-Analyse und Android-Entwicklung interessieren, ist dieser Artikel genau das Richtige für Sie. Ich hoffe, Sie finden darin viele interessante und nützliche Dinge. Darin möchte ich meine Erfahrungen auf diesem Gebiet teilen.

Problembeschreibung


Ein bisschen über mich. Ich bin ein Student im dritten Jahr der ITA SFU. Wie alle Schüler muss ich mir jeden Tag den Stundenplan ansehen. Und ich muss den Zeitplan nicht nur am nächsten Tag kennen, sondern auch ein oder zwei Wochen im Voraus.

Es scheint, warum nicht einfach den Zeitplan speichern und ihn verwenden? Leider gibt es eine Reihe von Gründen, die dies verhindern:

  • Der Zeitplan für eine Woche kann sich stark vom Zeitplan für eine andere Woche unterscheiden
  • Der Zeitplan ist nicht konstant und kann sich ändern

Natürlich gibt es eine Site mit einem Zeitplan, aber das ist nicht sehr praktisch, da nur eine unformatierte Tabelle mit einem Zeitplan für 20 Wochen angezeigt wird. Der Schüler muss eine große Seite umblättern und nach einem Zeitplan für den gewünschten Tag suchen. Darüber hinaus ist der Zeitplan im Offlinemodus nicht mehr verfügbar.
Ich beschloss, eine kleine Anwendung zu erstellen, die die Site mit dem Zeitplan meines Instituts parsen und die folgenden Goodies enthalten würde:

  • Anzeige: aktuelle Wochennummer, Datum, Wochentag und Zeitplan für diesen Tag
  • Die Möglichkeit, mit den Schaltflächen "Zurück" und "Weiter" durch den Zeitplan zu scrollen
  • Wenn kein Internet verfügbar ist, zeigen Sie die zuletzt heruntergeladene Offline-Version des Zeitplans an

Fahren Sie mit der Ausführung fort


Also krempelte ich die Ärmel hoch und machte mich an die Arbeit. Sie müssen klein anfangen. Nämlich von der Bearbeitung der Manifestdatei. Es ist zu beachten, dass unsere Anwendung mit dem Internet funktioniert und es für uns sehr wichtig ist, die entsprechende Erlaubnis einzuholen:

Manifest-Datei
Gehen Sie zu manifestests-> AndroidManifest.xml. Berechtigung hinzufügen. Das Ergebnis ist ungefähr so:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapplication"> <uses-permission android:name="android.permission.INTERNET" /> ... </manifest> 

Kommen wir nun zur Benutzeroberfläche. Konzentrieren wir uns zunächst auf die Funktionalität und nicht auf den Missbrauch von Widgets. Daher habe ich nur vier Widgets platziert: Titel, Textfeld und Schaltflächen: vor und zurück.

Aktivitäts-Markup
 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/WeekNumber" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/timetable" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="100dp" android:ems="10" android:inputType="textMultiLine" android:text="" app:layout_constraintTop_toBottomOf="@+id/WeekNumber" tools:layout_editor_absoluteX="0dp" /> <Button android:id="@+id/next" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" app:layout_constraintBottom_toBottomOf="parent" tools:layout_editor_absoluteX="0dp"></Button> <Button android:id="@+id/down" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" app:layout_constraintBottom_toTopOf="@+id/next" tools:layout_editor_absoluteX="0dp"></Button> </androidx.constraintlayout.widget.ConstraintLayout> 

Beginnen wir jetzt mit dem Parsen. Hier hilft uns der wunderbare Open Source Parser Jsoup. Ich habe die Option mit WebView sofort verworfen, da ich diese Methode als äußerst unpraktisch empfand. Außerdem wollte ich eigentlich kein zusätzliches Widget verwenden, auf das man problemlos verzichten kann.

Jsoup-Verbindung
Fügen Sie die Abhängigkeit zu build.gradle hinzu:
 implementation 'org.jsoup:jsoup:1.11.1' 


Vergessen Sie nicht, dass die Arbeit mit Web für Android eine schwierige Aufgabe ist. Um zu verhindern, dass die Anwendung hängen bleibt, müssen Sie mit dem Web außerhalb des UI-Streams arbeiten. Daher verwenden wir die AsyncTask-Klasse. Wir werden die grundlegenden Funktionen einbinden und die Daten dann einfach in den UI-Stream übertragen.

Für diejenigen, die nicht mit AsyncTask vertraut sind, möchte ich sagen, dass sich diese Klasse in der Klasse Ihrer Aktivität befinden sollte. Die Klasse selbst ist unten gezeigt.

Aktivitätscode mit AsyncTask-Klasse
 package com.example.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class MainActivity extends AppCompatActivity { public boolean offline; public String request; public String WeekNumber; public int count; // public TextView weeknumber; public EditText timetable; public Button next; public Button down; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); offline = false;//     count = 0;//      weeknumber = findViewById(R.id.WeekNumber); timetable = findViewById(R.id.timetable); next = findViewById(R.id.next); down = findViewById(R.id.down); getting AsyncTask = new getting(); AsyncTask.execute(); } class getting extends AsyncTask<String, String, String> { @Override protected void onPreExecute() { super.onPreExecute(); //         } @Override protected String doInBackground(String... params) { /*             html  */ String answer = "";//         .     String url = "https://ictis.sfedu.ru/rasp/HTML/82.htm";//     Document document = null; try { document = Jsoup.connect(url).get();//     answer = document.body().html();//     body  } catch (IOException e) { //   ,   ,     //    answer    txt  try { BufferedReader read = new BufferedReader(new InputStreamReader(openFileInput("timetable.txt"))); String str = ""; while ((str = read.readLine())!=null){ answer +=str; } read.close(); offline = true;//    } catch (FileNotFoundException ex) { //     ,    answer  answer = ""; ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } //    html // html      nolessone //   answer = answer.replace("","") .replace("","") .replace("<br>","br") .replace("<font face=\"Arial\" size=\"1\"></font><p align=\"CENTER\"><font face=\"Arial\" size=\"1\"></font>","nolessone")// ""    nolessone .replace(" ",""); return Jsoup.parse(answer).text();//      answer    UI- } @Override protected void onPostExecute(String result) { super.onPostExecute(result); /*            */ request = "";//   String temp = result.toString();//   //  ,   timetable.txt,        try { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(openFileOutput("timetable.txt",MODE_PRIVATE))); writer.write(temp); writer.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } boolean start = false; for(String str:temp.split(": ")){ if(start) { //      newweek    request request += "newweek"+str.split("")[0] + "\n"; } start = true; } //      newday,     request = request.replace("","newday").replace("","newday") .replace("","newday").replace("","newday") .replace("","newday").replace("","newday"); /*    count = 0,       count = -1,    count = 1,    . */ Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); //  timetable.setText(request); if(offline && !temp.equals("")){ // ,      Toast.makeText(getApplicationContext(),"   !",Toast.LENGTH_LONG).show(); } //    ,    if(temp.equals("")){ Toast.makeText(getApplicationContext()," !",Toast.LENGTH_LONG).show(); } } } } 


Als Ergebnis erhalten wir die Daten in dieser Form:

Zeitplan für eine Woche


Lassen Sie uns die von uns verwendeten Methoden analysieren:

Erstellen Sie ein Element vom Typ Dokument

 Document document = null; 

Wir bekommen die Seite

 document = Jsoup.connect(url).get(); 

Jetzt erhalten wir den Inhalt des Body-Tags

 answer = document.body().html(); 

Jsoup kann auch den Inhalt anderer Core-Tags abrufen. Beispielsweise können Sie den Seitentitel mit der title () -Methode usw. abrufen. Html () -Methode Gibt HTML-Code zurück, und text () ist einfacher Text ohne HTML-Tags.

Nachdem Sie den HTML-Code erhalten haben, können Sie ihn in einfachen Text konvertieren und alle Tags entfernen. Dies kann mit parse (htmlcode) .text () geschehen:

 return Jsoup.parse(answer).text(); 

Ich möchte einige weitere nützliche Jsoup-Methoden vorstellen, die nicht verwendet wurden:

 Element link = document.select("tag");//    String url = link.attr("attribute"); //    

Das Bild oben im Spoiler ist ein Beispiel für einen einwöchigen Zeitplan. Tatsächlich werden 20 solcher Wochen an uns zurückgegeben. Jetzt ist es unsere Aufgabe, heute in diesem Datensatz zu suchen und anzuzeigen.

Denken Sie daran


Also, was haben wir? Wir haben gelernt, den HTML-Code einer Seite in eine Zeichenfolge umzuwandeln, die leicht analysiert werden kann. Dies kann einfach mit den String-Methoden .split () und .replace () durchgeführt werden.

Im Allgemeinen sieht der Algorithmus so aus.

Zuerst bekommen wir den gewünschten Termin von Android. Dann machen wir zwei ineinander verschachtelte Zyklen. Der erste Zyklus durchläuft die Wochen, der zweite, der sich darin befindet, durchläuft die Wochentage. Wenn das Datum des Tages mit dem von Android erhaltenen Datum übereinstimmt, wird der Zeitplan für diesen Tag im Textfeld angezeigt. Jeder kann diesen Algorithmus jedoch auf seine eigene Weise schreiben. Ich habe meine Version der Implementierung angehängt.

Vollständiger Aktivitätscode
 package com.example.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class MainActivity extends AppCompatActivity { public boolean offline; public String request; public String WeekNumber; public int count; // public TextView weeknumber; public EditText timetable; public Button next; public Button down; public void formating(String day){ String DayTimetable = ""; String[] weeks = request.split("newweek"); String DayData = "";//      /*                 less      tich       aud  ,      ( -230) */ String less[] = new String[7]; String tich[] = new String[7]; String aud[] = new String[7]; for(String thisweek:weeks){//   if(thisweek.indexOf(day) != -1) {//        ... WeekNumber = thisweek.split(" ")[0];//   for(String thisday:thisweek.split("newday")){//      if(thisday.indexOf(day) != -1) {//       , ... // ,       newless //      . . .  . thisday = thisday.replace("no","newless") .replace(".","newless.") .replace(".","newless.") .replace(".","newless."); int i = 0; for(String thislessone:thisday.split("newless")) {//      if(i != 0) { String[] ScienceInformation = thislessone.replace("br ","").split("br"); String science = ScienceInformation[0]; science = science.replace("lessone",""); String ticher = ""; if(ScienceInformation.length > 1) ticher = ScienceInformation[1]; DayTimetable += i + "-:  - " + science+"\n"+ticher+"\n\n"; ticher = ticher.replace("-","@-").replace("-","@-") .replace("-","@-").replace("-","@-") .replace("-","@-").replace("-","@-") .replace("-","@-").replace("K-","@K-"); String Auditory; if(ticher.split("@").length == 2){ Auditory = ": "+ticher.split("@")[1]; }else Auditory = ": ";//     ticher = ticher.split("@")[0]; if(ticher.length() >0){ ticher = ": "+ticher; }else{ ticher = ""; } if(i==1){ less[i-1] = "1- (8:00-9:35) "+science; } if(i==2){ less[i-1] = "2- (9:50-11:25) "+science; } if(i==3){ less[i-1] = "3- (11:55-13:30) "+science; } if(i==4){ less[i-1] = "4- (13:45-15:20) "+science; } if(i==5){ less[i-1] = "5- (15:50-17:25) "+science; } if(i==6){ less[i-1] = "6- (17:40-19:15) "+science; } if(i==7){ less[i-1] = "7- (19:30-21:05) "+science; } tich[i-1] = ticher; aud[i-1] = Auditory; }else DayData = thislessone;// i=0  thislessone     i++; } } } } } timetable.setText(DayData);//  for(int i = 0; i <=6; i++){ timetable.setText(timetable.getText()+"\n"+less[i]+tich[i]+aud[i]);// ,      (   ) } weeknumber.setText(" "+ WeekNumber + " ");//   } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); offline = false;//     count = 0; weeknumber = findViewById(R.id.WeekNumber); timetable = findViewById(R.id.timetable); next = findViewById(R.id.next); down = findViewById(R.id.down); getting getting = new getting(); getting.execute(); //      next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { count++; Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); formating(format.format(dayformat)); } }); down.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { count--; Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); formating(format.format(dayformat)); } }); } class getting extends AsyncTask<String, String, String> { @Override protected void onPreExecute() { super.onPreExecute(); //         getSupportActionBar().setTitle("..."); } @Override protected String doInBackground(String... params) { /*             html  */ String answer = "";//         .     String url = "https://ictis.sfedu.ru/rasp/HTML/82.htm";//     Document document = null; try { document = Jsoup.connect(url).get();//     answer = document.body().html();//     body  } catch (IOException e) { //   ,   ,     //    answer    txt  try { BufferedReader read = new BufferedReader(new InputStreamReader(openFileInput("timetable.txt"))); String str = ""; while ((str = read.readLine())!=null){ answer +=str; } read.close(); offline = true;//    } catch (FileNotFoundException ex) { //     ,    answer  answer = ""; ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } //    html // html      nolessone //   answer = answer.replace("","") .replace("","") .replace("<br>","br") .replace("<font face=\"Arial\" size=\"1\"></font><p align=\"CENTER\"><font face=\"Arial\" size=\"1\"></font>","nolessone") .replace(" ",""); return Jsoup.parse(answer).text();//      answer    UI- } @Override protected void onPostExecute(String result) { super.onPostExecute(result); /*            */ request = "";//   String temp = result.toString();//   //  ,   timetable.txt,        try { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(openFileOutput("timetable.txt",MODE_PRIVATE))); writer.write(temp); writer.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } boolean start = false; for(String str:temp.split(": ")){ if(start) { //      newweek    request request += "newweek"+str.split("")[0] + "\n"; } start = true; } //      newday,     request = request.replace("","newday").replace("","newday") .replace("","newday").replace("","newday") .replace("","newday").replace("","newday"); /*    count = 0,       count = -1,    count = 1,    . */ Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); // ,      formating(format.format(dayformat)); if(offline && !temp.equals("")){ // ,      Toast.makeText(getApplicationContext(),"   !",Toast.LENGTH_LONG).show(); } //    ,    if(temp.equals("")){ Toast.makeText(getApplicationContext()," !",Toast.LENGTH_LONG).show(); } getSupportActionBar().setTitle(""); } } } 


Das Abrufen des Zeitplans erfolgt in der formating () -Methode. Durch Übermitteln eines Datums an die Eingabemethode erhalten wir einen Zeitplan für diesen Tag. So können wir einfach den Code für die Buttons "zurück" und "weiter" implementieren

Code der nächsten Schaltfläche:

 count++; Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); formating(format.format(dayformat));//  formating 

Mit dem Kalender erhalten wir das heutige Datum. Mit der add-Methode addieren wir die Anzahl der Tage zum heutigen Datum. Der Code für die Zurück-Taste ist ähnlich, nur die Zählung muss den Wert verringern.

Screenshot


Fazit


Natürlich können Sie am Design arbeiten, aber das ist ein anderes Thema. Ich wollte nur die grundlegenden Technologien teilen. In den Spoilern unten habe ich Screenshots mit einem verbesserten Design angehängt. Ich habe auch verschiedene Funktionen hinzugefügt, zum Beispiel: Einstellungen, die Möglichkeit, eine Lerngruppe auszuwählen, usw. Die Anwendung selbst kann etwas später eingesehen werden, sobald ich daran denke.

Anwendungs-Screenshot

Source: https://habr.com/ru/post/de483284/


All Articles