Comment j'ai analysé le programme

Salut Habr!

Cher lecteur! Si vous êtes intéressé par l'analyse HTML et le développement Android, cet article est pour vous. J'espère que vous y trouverez beaucoup de choses intéressantes et utiles. J'y partage mon expérience dans ce domaine.

Description du problème


Un peu sur moi. Je suis un étudiant de troisième année de l'ITA SFU. Comme tous les élèves, je dois regarder le programme des cours tous les jours. Et j'ai besoin de connaître le calendrier non seulement le lendemain, mais aussi une ou deux semaines à l'avance.

Il semblerait, pourquoi ne pas simplement sauvegarder le programme et l'utiliser? Malheureusement, plusieurs raisons empêchent cela, à savoir:

  • L'horaire d'une semaine peut être très différent de celui d'une autre
  • L'horaire n'est pas constant et peut changer

Bien sûr, il existe un site avec un calendrier, mais ce n'est pas très pratique, car il affiche simplement un tableau brut avec un calendrier pour 20 semaines. L'étudiant doit retourner une grande page, à la recherche d'un horaire pour la journée souhaitée. De plus, en mode hors ligne, le programme devient indisponible.
J'ai décidé de faire une petite application qui pourrait analyser le site avec le calendrier de mon institut, et aurait l'ensemble de goodies suivant:

  • Affichage: numéro de la semaine en cours, date, jour de la semaine et horaire pour ce jour
  • La possibilité de faire défiler le calendrier avec les boutons "précédent" et "suivant"
  • S'il n'y a pas d'Internet, affichez la dernière version hors ligne téléchargée du programme

Procéder à l'exécution


Alors, retroussant mes manches, je me mis au travail. Vous devez commencer petit. À savoir, de la modification du fichier manifeste. Il convient de rappeler que notre application fonctionnera avec Internet et il est très important pour nous d'obtenir l'autorisation appropriée:

Fichier manifeste
Accédez à manifestes-> AndroidManifest.xml. Ajouter une autorisation. Le résultat est quelque chose comme ceci:

<?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> 

Passons maintenant à l'interface. Pour l'instant, concentrons-nous sur les fonctionnalités et ne pas abuser des widgets. Par conséquent, je n'ai placé que quatre widgets: titre, zone de texte et boutons: d'avant en arrière.

Balisage d'activité
 <?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> 

Commençons maintenant l'analyse. C'est là que le merveilleux analyseur open source Jsoup nous aide. J'ai immédiatement rejeté l'option en utilisant WebView, car j'ai trouvé cette méthode extrêmement gênante. De plus, je ne voulais pas vraiment utiliser un widget supplémentaire, sans lequel vous pouvez facilement le faire.

Connexion Jsoup
Ajoutez la dépendance à build.gradle:
 implementation 'org.jsoup:jsoup:1.11.1' 


N'oubliez pas que travailler avec le Web pour Android est une tâche difficile. Pour empêcher l'application de se bloquer, vous devez travailler avec le Web situé à l'extérieur du flux d'interface utilisateur. Par conséquent, nous utiliserons la classe AsyncTask. Nous y mettrons les fonctionnalités de base, puis transférerons simplement les données vers le flux d'interface utilisateur.

Pour ceux qui ne connaissent pas AsyncTask, je veux dire que cette classe devrait être située à l'intérieur de la classe de votre activité. La classe elle-même est illustrée ci-dessous.

Code d'activité avec la classe AsyncTask
 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(); } } } } 


En conséquence, nous obtenons les données sous cette forme:

Horaire d'une semaine


Analysons les méthodes que nous avons utilisées:

Créer un élément de type Document

 Document document = null; 

Nous obtenons la page

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

Maintenant, nous obtenons le contenu de la balise body

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

Jsoup peut également récupérer le contenu d'autres balises principales. Par exemple, vous pouvez obtenir le titre de la page en utilisant la méthode title (), etc. Méthode Html () Renvoie du code html et text () est du texte brut sans balises html.

Après avoir reçu le code html, vous pouvez le convertir en texte brut, en supprimant toutes les balises. Cela peut être fait en utilisant parse (htmlcode) .text ():

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

Je voudrais partager quelques méthodes Jsoup plus utiles qui n'ont pas été utilisées:

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

L'image dans le spoiler ci-dessus est un exemple de programme d'une semaine. En fait, 20 de ces semaines nous seront retournées. Maintenant, notre tâche est de trouver aujourd'hui dans cet ensemble de données et de l'afficher.

Rappelant


Alors qu'avons-nous? Nous avons appris à convertir le code html d'une page en une chaîne qui peut être facilement analysée. Cela peut facilement être fait en utilisant les méthodes de chaîne .split () et .replace ().

En général, l'algorithme ressemblera à ceci.

D'abord, nous obtenons la date souhaitée d'Android. Ensuite, nous faisons deux cycles, l'un imbriqué dans l'autre. Le premier cycle se déroule chaque semaine, le second, qui est à l'intérieur, parcourt les jours de la semaine. Si la date du jour coïncide avec la date reçue d'Android, nous affichons le calendrier de cette journée dans la zone de texte. Cependant, chacun peut écrire cet algorithme à sa manière. J'ai joint ma version de sa mise en œuvre.

Code d'activité complet
 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(""); } } } 


la récupération de la planification s'effectue dans la méthode formating (). En soumettant une date à la méthode de saisie, nous obtiendrons un calendrier pour cette journée. Nous pouvons donc facilement implémenter le code des boutons précédent et suivant

Code du bouton suivant:

 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 

En utilisant le calendrier, nous obtenons la date d'aujourd'hui. En utilisant la méthode add, nous ajoutons le nombre de jours enregistrés en nombre à la date d'aujourd'hui. Le code du bouton de retour sera similaire, seul le décompte devra diminuer la valeur.

Capture d'écran


Conclusion


Bien sûr, vous pouvez travailler sur la conception, mais c'est un autre sujet. Je voulais juste partager les technologies de base. Dans les spoilers ci-dessous, j'ai joint des captures d'écran avec un design amélioré. J'ai également ajouté plusieurs fonctions, par exemple: les paramètres, la possibilité de sélectionner un groupe d'étude, etc. L'application elle-même peut être consultée un peu plus tard, dès que j'y pense.

Capture d'écran de l'application

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


All Articles