Cómo analicé el horario

Hola habr

Estimado lector! Si está interesado en el análisis html y el desarrollo de Android, este artículo es para usted. Espero que encuentres muchas cosas interesantes y útiles. En él quiero compartir mi experiencia en este campo.

Descripción del problema


Un poco sobre mi. Soy un estudiante de tercer año de ITA SFU. Como todos los estudiantes, necesito mirar el horario de clases todos los días. Y necesito saber el horario no solo al día siguiente, sino también con una o dos semanas de anticipación.

Parecería, ¿por qué no solo guardar el horario y usarlo? Desafortunadamente, hay una serie de razones que evitan esto, a saber:

  • El horario de una semana puede ser muy diferente del horario de otra.
  • El horario no es constante y puede cambiar

Por supuesto, hay un sitio con un horario, pero no es muy conveniente, ya que solo muestra una tabla en bruto con un horario durante 20 semanas. El estudiante tiene que pasar una página grande, buscando un horario para el día deseado. Además, en el modo fuera de línea, la programación deja de estar disponible.
Decidí hacer una pequeña aplicación que pudiera analizar el sitio con el horario de mi instituto y que tuviera el siguiente conjunto de cosas:

  • Pantalla: número de semana actual, fecha, día de la semana y horario para ese día
  • La capacidad de desplazarse por la programación con los botones "atrás" y "siguiente"
  • Si no hay Internet, muestre la última versión descargada de la programación fuera de línea

Proceder a la ejecución


Entonces, arremangándome las mangas, me puse a trabajar. Necesitas empezar de a poco. Es decir, desde la edición del archivo de manifiesto. Vale la pena recordar que nuestra aplicación funcionará con Internet y es muy importante para nosotros obtener el permiso apropiado:

Archivo de manifiesto
Vaya a manifiestos-> AndroidManifest.xml. Agregar permiso El resultado es algo como esto:

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

Ahora pasemos a la interfaz. Por ahora, centrémonos en la funcionalidad y no en abusar de los widgets. Por lo tanto, coloqué solo cuatro widgets: Título, cuadro de texto y botones: adelante y atrás.

Marcado de actividad
 <?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> 

Ahora comencemos a analizar. Aquí es donde nos ayuda el maravilloso analizador de código abierto Jsoup. Inmediatamente descarté la opción usando WebView, ya que este método me pareció extremadamente inconveniente. Además, realmente no quería usar un widget adicional, sin el cual puedes hacerlo fácilmente.

Conexión Jsoup
Agregue la dependencia a build.gradle:
 implementation 'org.jsoup:jsoup:1.11.1' 


No olvides que trabajar con web para Android es una tarea difícil. Para evitar que la aplicación se cuelgue, debe trabajar con la web ubicada fuera de la transmisión de la interfaz de usuario. Por lo tanto, utilizaremos la clase AsyncTask. Pondremos la funcionalidad básica en él y luego simplemente transferiremos los datos a la secuencia de la interfaz de usuario.

Para aquellos que no están familiarizados con AsyncTask, quiero decir que esta clase debe ubicarse dentro de la clase de su actividad. La clase en sí se muestra a continuación.

Código de actividad con clase 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(); } } } } 


Como resultado, obtenemos los datos de esta forma:

Calendario de una semana


Analicemos los métodos que utilizamos:

Crear un elemento de tipo Documento

 Document document = null; 

Obtenemos la página

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

Ahora obtenemos el contenido de la etiqueta del cuerpo

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

Jsoup también puede recuperar el contenido de otras etiquetas principales. Por ejemplo, puede obtener el título de la página utilizando el método title (), etc. Método html () Devuelve el código html y text () es texto sin etiquetas html.

Una vez recibido el código html, puede convertirlo a texto sin formato, eliminando todas las etiquetas. Esto se puede hacer usando parse (htmlcode) .text ():

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

Me gustaría compartir algunos métodos Jsoup más útiles que no se utilizaron:

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

La imagen en el spoiler anterior es un ejemplo de un calendario de una semana. De hecho, se nos devolverán 20 de esas semanas. Ahora nuestra tarea es encontrar hoy en este conjunto de datos y mostrarlo.

Trayendo a la mente


Entonces, ¿qué tenemos? Aprendimos a convertir el código html de una página en una cadena que se puede analizar fácilmente. Esto se puede hacer fácilmente usando los métodos de cadena .split () y .replace ().

En general, el algoritmo se verá así.

Primero obtenemos la fecha deseada de Android. Luego hacemos dos ciclos, uno anidado en el otro. El primer ciclo recorre las semanas, el segundo, que está dentro de él, recorre los días de la semana. Si la fecha del día coincide con la fecha recibida de Android, mostramos la programación de este día en el cuadro de texto. Sin embargo, todos pueden escribir este algoritmo a su manera. Adjunto mi versión de su implementación.

Código de actividad completo
 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 búsqueda de la programación se produce en el método formating (). Al enviar una fecha al método de entrada, obtendremos un cronograma para ese día. Entonces podemos implementar fácilmente el código para los botones "atrás" y "siguiente"

Código del siguiente botón:

 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 

Usando Calendario obtenemos la fecha de hoy. Usando el método de agregar, agregamos el número de días registrados en conteo a la fecha de hoy. El código para el botón Atrás será similar, solo el conteo deberá disminuir el valor.

Captura de pantalla


Conclusión


Por supuesto, puedes trabajar en el diseño, pero ese es otro tema. Solo quería compartir las tecnologías básicas. En los spoilers a continuación, adjunté capturas de pantalla con un diseño mejorado. También agregué varias funciones, por ejemplo: configuraciones, la capacidad de seleccionar un grupo de estudio, etc. La aplicación en sí se puede ver un poco más tarde, tan pronto como lo recuerde.

Captura de pantalla de la aplicación

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


All Articles