Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Java: XML-datan käsittely

Sivun loppuun

TheJapsu1 [30.04.2019 20:43:31]

#

Elikkäs tarkotuksena olis keksiä tapa saada javalla ilmatieteenlaitokselta tämänhetken uusin merenpinnan korkeus vaasan alueelta ja tallentaa se muuttujaan.
Tähän asti minulla on koodinpätkä jolla saan sivun html-koodin printattua konsoliin:

public static void main(String[] args) {

        try {

            URL url = new URL("http://opendata.fmi.fi/wfs/fin?service=WFS&version=2.0.0&request=GetFeature&storedquery_id=fmi::observations::mareograph::timevaluepair&fmisid=134223&");

            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));

            String line;
            while ((line = in.readLine()) != null) {

            	System.out.println(line);
            }
            in.close();

        }
        catch (MalformedURLException e) {
            System.out.println("Malformed URL: " + e.getMessage());
        }
        catch (IOException e) {
            System.out.println("I/O Error: " + e.getMessage());
        }

    }

Ongelma tosiaan on se, että tuloksena tulee sivun koko sisältö vaikka haluaisin vain uusimman korkeuden enkä yksinkertaisesti vain tiedä tai löydä tapaa hoitaa asia. Yksinkertaisemmin selitettynä saan tällä hetkellä outputtina tämän, kun haluaisin saada tämän.

Kiitän jo etukäteen kaikkia ketkä vaivautuvat auttamaan keskenkasvuista pojankloppia hädässä :)

dartvaneri [01.05.2019 13:16:53]

#

Ilmatieteenlaitokselta saa avointa dataa suoraan rajapinnan yli:
https://ilmatieteenlaitos.fi/avoin-data
Esimerkiksi merenpinnan korkeuden havainnot saa sieltä reaaliaika-havaintoina.
Jos on aikaa ja viitseliäisyyttä, hae tieto tuolta kautta. Parsimalla HTML-koodista tiedon, systeemi hajoaa suurella todennäköisyydellä kun ilmatieteenlaitos päivittää sivujaan.


EDIT. Näköjään haetkin datan rajapinnan kautta tulevasta XML-tiedostosta, etkä ilmatieteenlaitoksen sivuston HTML-tiedostosta.

Olisiko tästä apua:
https://stackoverflow.com/questions/14159086/how-to-get-values-of-all-elements-from-xml-string-in-java/14159232#14159232

Metabolix [01.05.2019 14:54:42]

#

Tuo tuloste on tosiaan XML-dataa eikä HTML-koodia.

Dataa voi käsitellä XPath-hauilla. Tässä on kokonaisuus, joka hoitaa homman. Aloittamiseen tarvitaan nähtävästi aika paljon koodia, mutta itse haku onnistuu tämän jälkeen yhdellä rivillä, joten samasta XML-dokumentista on helppo hakea montakin asiaa.

import java.io.*;
import java.net.*;
import java.util.*;
import javax.xml.xpath.*;
import javax.xml.namespace.*;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;

public class XMLQueryExample {
	public static class NamespaceResolver implements NamespaceContext {
		private Document document;
		public NamespaceResolver(Document doc) {
			document = doc;
		}
		public String getNamespaceURI(String prefix) {
			if (prefix.equals("")) {
				return document.lookupNamespaceURI(null);
			} else {
				return document.lookupNamespaceURI(prefix);
			}
		}
		public String getPrefix(String namespaceURI) {
			return document.lookupPrefix(namespaceURI);
		}
		public Iterator<String> getPrefixes(String namespaceURI) {
			return null;
		}
	}
	public static String downloadString(String url) throws Exception {
		StringBuilder sb = new StringBuilder();
		try (BufferedReader r = new BufferedReader(new InputStreamReader(new URL(url).openStream(), "UTF-8"))) {
			String line;
			while ((line = r.readLine()) != null) {
				sb.append(line + "\n");
			}
		}
		return sb.toString();
	}
	public static Document createDocumentFromString(String xml) throws Exception {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);
		return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
	}

	public static void main(String[] args) throws Exception {
		String url = "http://opendata.fmi.fi/wfs/fin?service=WFS&version=2.0.0&request=GetFeature&storedquery_id=fmi::observations::mareograph::timevaluepair&fmisid=134223&";

		// Ladataan dokumentti ja luodaan tarvittavat rakenteet.
		String xml = downloadString(url);
		Document document = createDocumentFromString(xml);
		XPath xpath = XPathFactory.newInstance().newXPath();
		xpath.setNamespaceContext(new NamespaceResolver(document));

		// Varsinainen haku.
		String time = xpath.evaluate("//wml2:MeasurementTimeseries[@gml:id='obs-obs-1-1-WATLEV']/wml2:point[last()]//wml2:time", document);
		String value = xpath.evaluate("//wml2:MeasurementTimeseries[@gml:id='obs-obs-1-1-WATLEV']/wml2:point[last()]//wml2:value", document);
		System.out.format("time = %s; value = %s\n", time, value);
	}
}

_Pete_ [03.05.2019 16:06:59]

#

Jos tarvii Javalla oikeasti lukea netistä ja parsia html niin tämä kirjasto on erittäin hyvä:

https://jsoup.org/

TheJapsu1 [04.05.2019 13:52:17]

#

Metabolix kirjoitti:

(01.05.2019 14:54:42): Tuo tuloste on tosiaan XML-dataa eikä HTML...

Kiitoksia tästä, kokeilin koodinpätkääsi Eclipsessä ja vaikutti ihan hyvin toimivan ja tein muutaman muokkauksen saadakseni palautettua "Value":n arvon toiseen classiin "Testclass.result();" kyselyllä. Lakkasi toimimasta yllätys yllätys, palauttaa joka kerta "null". Kuten todennäköisesti huomaa en ole ikinä ennen javaa kunnolla kirjoittanut.

Tässä siis muokattu koodi:

static String value;

public static String result(){
    try {
        String url = "http://opendata.fmi.fi/wfs/fin?service=WFS&version=2.0.0&request=GetFeature&storedquery_id=fmi::observations::mareograph::timevaluepair&fmisid=134223&";

        // Ladataan dokumentti ja luodaan tarvittavat rakenteet.
        String xml = downloadString(url);
        Document document = createDocumentFromString(xml);
        XPath xpath = XPathFactory.newInstance().newXPath();
        xpath.setNamespaceContext(new NamespaceResolver(document));

        // Varsinainen haku.
        String time = xpath.evaluate("//wml2:MeasurementTimeseries[@gml:id='obs-obs-1-1-WATLEV']/wml2:point[last()]//wml2:time", document);
        value = xpath.evaluate("//wml2:MeasurementTimeseries[@gml:id='obs-obs-1-1-WATLEV']/wml2:point[last()]//wml2:value", document);
        System.out.format("time = %s; value = %s\n", time, value);
    } catch (XPathException e) {
        e.printStackTrace();

    } catch (Exception e) {
        e.printStackTrace();

    }
    return "V: " + value;
}

Lisäys:

TheJapsu1 kirjoitti:

(04.05.2019 13:52:17): ”– –” Kiitoksia tästä, kokeilin koodin­pät­kääsi...

Kokeilin vielä muokkaamaani koodia eclipsessä ja vaikuttaisi siellä toimivan aivan mainiosti, mutta jostain syystä android studiossa ei. Eclipsessä palauttaa oikean arvon, android studiossa "null".

Metabolix [04.05.2019 14:05:35]

#

Oletko katsonut, tuleeko Android Studiossa jokin noista poikkeuksista, joita aktiivisesti otat kiinni catch-lohkoilla mutta et juurikaan käsittele sen enempää? Jos virheiden lukeminen muuten tuottaa vaikeuksia, voisit muokata koodia niin, että virhe palautetaan näkyvässä muodossa:

public static String result() {
    try {
        // ...
        String value = xpath.evaluate(ja_niin_edelleen);
        return "V: " + value;
    } catch (Exception e) {
        return "FAIL: " + e.toString();
    }
}

Ei kannata ottaa kiinni erikseen eri tyyppisiä poikkeuksia, jos ei tee niillä mitään järkevää. Debuggausta varten tuo yksi catch-lohko riittää aivan hyvin.

Sivuhuomiona mainittakoon, että muuttujan on järkevää olla metodin sisällä, kun et käytä sitä muualla.

TheJapsu1 [04.05.2019 14:34:26]

#

Metabolix kirjoitti:

(04.05.2019 14:05:35): Oletko katsonut, tuleeko Android Studiossa...

Kiitoksia taas kerran! Virhe tosiaan vaikuttaisi olevan "android.os.NetworkOnMainThreadException". Pikainen googletus selittää että: "This exception is thrown when an application attempts to perform a networking operation on its main thread. Run your code in AsyncTask". Eli mikä käytännössä on ongelma?

Grez [04.05.2019 14:39:16]

#

Käytännössä ongelmana on, että yrität käyttää verkkoresurssia pääsäikeessä.

Koska verkkopyynnöissä kestää aina hetken aikaa, ei niitä tulee ajaa pääsäikeessä, koska silloin koko appi jumittaa sen ajan kun vastausta verkosta odotetaan.

TheJapsu1 [04.05.2019 14:45:21]

#

Grez kirjoitti:

Käytännössä ongelmana on, että yrität käyttää verkkoresurssia pääsäikeessä.

Koska verkkopyynnöissä kestää aina hetken aikaa, ei niitä tulee ajaa pääsäikeessä, koska silloin koko appi jumittaa sen ajan kun vastausta verkosta odotetaan.

Käytännössä, mikä olisi helpoin tapa korjata ongelma? Riittääkö jos lisään muutaman sekunnin delayta verkkopyynnän jälkeen?

Grez [04.05.2019 15:09:11]

#

Ei se auta mitään (jos se jotain vaikuttaisi niin pahentaisi vaan ongelmaa sillä kahdella sekunnilla). Ratkaisu on ajaa verkkopyynnön tekevä koodi eri säikeessä. "Aja koodisi AsyncTaskissa", niinkuin olit jo itsekin saanut googletettua. Toki mikä tahansa muukin eri säikeessä ajava ratkaisu poistaa ongelman.

TheJapsu1 [04.05.2019 15:18:02]

#

Grez kirjoitti:

(04.05.2019 15:09:11): Ei se auta mitään (jos se jotain vaikut­tai­si...

Mikä kohta tarkalleen minun pitäisi ajaa AsyncTaskissa, ja mikä olisi helpoin tapa tehdä se? Olen tosiaan edelleen aivan aloittelija ja opettelen tässä itsekkin samalla vasta.

_Pete_ [05.05.2019 09:18:38]

#

Onko tämän siis tarkoitus oikeasti toimia androidissa vai miksi ajelet sitä android studiossa?

The Alchemist [05.05.2019 17:46:31]

#

TheJapsu1 kirjoitti:

kohta tarkalleen minun pitäisi ajaa AsyncTaskissa, ja mikä olisi helpoin tapa tehdä se? Olen tosiaan edelleen aivan aloittelija ja opettelen tässä itsekkin samalla vasta.

Ymmärrätkö, mikä osa koodistasi suorittaa verkkopyynnön? Sulle on sanottu jo kaksi kertaa, että suorita verkkopyynnöt erillisessä säikeessä. Järkevintä on koodata totta kai sellainen ratkaisu, että saat kokonaisen parsitun XML-datan säikeen lopputuloksena. (Tässä tapauksessa siis, koska XML:n määrä on pieni, eikä sitä tarvitse ns. striimata.)

Metabolix [05.05.2019 18:18:42]

#

Googletuksen perusteella olettaisin, että kun olet jo tehnyt kuitenkin erillisen luokan hausta, voisit käyttää kyseistä luokkaa erillisessä säikeessä näin:

new AsyncTask<Void, Void, String>() {
	@Override
	protected String doInBackground(Void... params) {
		// Tämä metodi on erillisessä säikeessä.
		// Tässä tehdään verkkohaku ja palautetaan sen tulos pääsäikeelle.
		return VaasanMerenpinnanKorkeus.result();
	}
	@Override
	protected void onPostExecute(String value) {
		// Tämä metodi on pääsäikeessä.
		// Tässä voi laittaa ruudulle tuloksen, jonka edeltävä metodi palauttaa.
		laitaTulosRuudulle(value);
	}
}.execute();

Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta