/** * Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, * or String, or the JSONObject.NULL object. * * @throws JSONException If syntax error. * @return An object. */ public Object nextValue() throws JSONException { char c = nextClean(); String s; switch (c) { case '"': case '\'': return nextString(c); case '{': back(); return new JSONObject(this); case '[': case '(': back(); return new JSONArray(this); } /* * Handle unquoted text. This could be the values true, false, or null, * or it can be a number. An implementation (such as this one) is * allowed to also accept non-standard forms. * * Accumulate characters until we reach the end of the text or a * formatting character. */ StringBuffer sb = new StringBuffer(); while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { sb.append(c); c = next(); } back(); s = sb.toString().trim(); if (s.equals("")) { throw syntaxError("Missing value"); } return JSONObject.stringToValue(s); }
/** * This method has been deprecated in favor of the {@link JSONObject.stringToValue(String)} * method. Use it instead. * * @deprecated Use {@link JSONObject#stringToValue(String)} instead. * @param string * @return JSON value of this string or the string */ public static Object stringToValue(String string) { return JSONObject.stringToValue(string); }
/** * Scan the content following the named tag, attaching it to the context. * * @param x The XMLTokener containing the source string. * @param context The JSONObject that will include the new material. * @param name The tag name. * @return true if the close tag is processed. * @throws JSONException */ private static boolean parse(XMLTokener x, JSONObject context, String name) throws JSONException { char c; int i; JSONObject jsonobject = null; String string; String tagName; Object token; // Test for and skip past these forms: // <!-- ... --> // <! ... > // <![ ... ]]> // <? ... ?> // Report errors for these forms: // <> // <= // << token = x.nextToken(); // <! if (token == BANG) { c = x.next(); if (c == '-') { if (x.next() == '-') { x.skipPast("-->"); return false; } x.back(); } else if (c == '[') { token = x.nextToken(); if ("CDATA".equals(token)) { if (x.next() == '[') { string = x.nextCDATA(); if (string.length() > 0) { context.accumulate("content", string); } return false; } } throw x.syntaxError("Expected 'CDATA['"); } i = 1; do { token = x.nextMeta(); if (token == null) { throw x.syntaxError("Missing '>' after '<!'."); } else if (token == LT) { i += 1; } else if (token == GT) { i -= 1; } } while (i > 0); return false; } else if (token == QUEST) { // <? x.skipPast("?>"); return false; } else if (token == SLASH) { // Close tag </ token = x.nextToken(); if (name == null) { throw x.syntaxError("Mismatched close tag " + token); } if (!token.equals(name)) { throw x.syntaxError("Mismatched " + name + " and " + token); } if (x.nextToken() != GT) { throw x.syntaxError("Misshaped close tag"); } return true; } else if (token instanceof Character) { throw x.syntaxError("Misshaped tag"); // Open tag < } else { tagName = (String) token; token = null; jsonobject = new JSONObject(); for (; ; ) { if (token == null) { token = x.nextToken(); } // attribute = value if (token instanceof String) { string = (String) token; token = x.nextToken(); if (token == EQ) { token = x.nextToken(); if (!(token instanceof String)) { throw x.syntaxError("Missing value"); } jsonobject.accumulate(string, JSONObject.stringToValue((String) token)); token = null; } else { jsonobject.accumulate(string, ""); } } else if (token == SLASH) { // Empty tag <.../> if (x.nextToken() != GT) { throw x.syntaxError("Misshaped tag"); } if (jsonobject.length() > 0) { context.accumulate(tagName, jsonobject); } else { context.accumulate(tagName, ""); } return false; } else if (token == GT) { // Content, between <...> and </...> for (; ; ) { token = x.nextContent(); if (token == null) { if (tagName != null) { throw x.syntaxError("Unclosed tag " + tagName); } return false; } else if (token instanceof String) { string = (String) token; if (string.length() > 0) { jsonobject.accumulate("content", JSONObject.stringToValue(string)); } } else if (token == LT) { // Nested element if (parse(x, jsonobject, tagName)) { if (jsonobject.length() == 0) { context.accumulate(tagName, ""); } else if (jsonobject.length() == 1 && jsonobject.opt("content") != null) { context.accumulate(tagName, jsonobject.opt("content")); } else { context.accumulate(tagName, jsonobject); } return false; } } } } else { throw x.syntaxError("Misshaped tag"); } } } }
/** * Gets the JSON-Representation of all open-data-fields for one specific open-test-uuid * * @param openTestUUID * @return the json-string * <p>columns in csv ("open data") 1:open_uuid, 2:open_test_uuid, 3:time, 4:cat_technology, * 5:network_type, 6:lat, 7:long, 8:loc_src, 9:zip_code, 10:download_kbit, 11:upload_kbit, * 12:ping_ms, 13:signal_strength, 14:server_name, 15:test_duration, 16:num_threads, * 17:platform, 18:model, 19:client_version, 20:network_mcc_mnc, 21:network_name, * 22:sim_mcc_mnc, 23:connection, 24:asn, 25:ip_anonym, 26:ndt_download_kbit, * 27:ndt_upload_kbit, 28:implausible, 29:lte_rsrp * <p>Columns in test table uid (internal) uuid (private) client_id client_version client_name * client_language (private) token (private, obsolete) server_id port use_ssl * time * speed_upload speed_download ping_shortest encryption * client_public_ip (private) plattform * os_version (internal) api_level (internal) device model product phone_type (internal) * data_state (internal) network_country (internal) network_operator network_operator_name * network_sim_country (internal) network_sim_operator network_sim_operator_name wifi_ssid * (private) wifi_bssid (private) wifi_network_id (private) duration num_threads status * timezone (private) bytes_download bytes_upload nsec_download nsec_upload server_ip * client_software_version geo_lat geo_long network_type location signal_strength * software_revision client_test_counter nat_type client_previous_test_status public_ip_asn * speed_upload_log speed_download_log total_bytes_download total_bytes_upload wifi_link_speed * public_ip_rdns public_ip_as_name test_slot provider_id network_is_roaming (internal) * ping_shortest_log run_ndt (internal) num_threads_requested client_public_ip_anonymized * zip_code geo_provider geo_accuracy deleted (internal) comment (internal) open_uuid * client_time (internal) zip_code_geo (internal) mobile_provider_id roaming_type * open_test_uuid country_asn country_location test_if_bytes_download test_if_bytes_upload * implausible testdl_if_bytes_download testdl_if_bytes_upload testul_if_bytes_download * testul_if_bytes_upload country_geoip location_max_distance location_max_distance_gps * network_group_name network_group_type time_dl_ns time_ul_ns num_threads_ul lte_rsrp * lte_rsrq mobile_network_id mobile_sim_id dist_prev speed_prev tag ping_median * ping_median_log client_ip_local_type (private) * <p>private: visible to user only (not public) internal: not visible (neither user nor * public) */ private String getSingleOpenTest(String openTestUUID) { final String sql = "SELECT t.uid as test_uid, " + " ('O' || t.open_test_uuid) open_test_uuid," + // csv open_test_uuid, UUID prefixed with 'O' " to_char(t.time AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') \"time\"," + " t.time full_time," + // csv: 3:time server time-stamp of start of measurement " t.client_time client_time," + // (internal) client time-stamp of start of measure " t.network_group_name cat_technology," + // csv 4:cat_technology " t.network_group_type network_type," + // csv 5:network_type " t.geo_lat lat," + // csv 6:lat " t.geo_long long," + // csv 7:long " t.geo_provider loc_src," + // csv 8:loc_src android: 'gps'/'network'; browser/iOS: '' (empty string) " t.geo_accuracy loc_accuracy, " + // accuracy of geo location in m /* //csv 6:lat " (CASE WHEN (t.geo_accuracy < ?) AND (t.geo_provider != 'manual') AND (t.geo_provider != 'geocoder') THEN" + " t.geo_lat" + " WHEN (t.geo_accuracy < ?) THEN" + " ROUND(t.geo_lat*1111)/1111" + // approx 100m " ELSE null" + " END) lat," + // csv 7:long " (CASE WHEN (t.geo_accuracy < ?) AND (t.geo_provider != 'manual') AND (t.geo_provider != 'geocoder') THEN" + " t.geo_long" + " WHEN (t.geo_accuracy < ?) THEN" + " ROUND(t.geo_long*741)/741 " + //approx 100m " ELSE null" + " END) long," + // csv 8:loc_src android: 'gps'/'network'; browser/iOS: '' (empty string) " (CASE WHEN ((t.geo_provider = 'manual') OR (t.geo_provider = 'geocoder')) THEN" + " 'rastered'" + //make raster transparent " ELSE t.geo_provider" + " END) loc_src," + // accuracy of geo location in m " (CASE WHEN (t.geo_accuracy < ?) AND (t.geo_provider != 'manual') AND (t.geo_provider != 'geocoder') " + " THEN t.geo_accuracy " + " WHEN (t.geo_accuracy < 100) AND ((t.geo_provider = 'manual') OR (t.geo_provider = 'geocoder')) THEN 100" + // limit accuracy to 100m " WHEN (t.geo_accuracy < ?) THEN t.geo_accuracy" + " ELSE null END) loc_accuracy, " + */ // csv 9:zip-code - defined as integer in data base, only meaningful for measurements in // Austria " (CASE WHEN (t.zip_code < 1000 OR t.zip_code > 9999) THEN null ELSE t.zip_code END) zip_code," + // " zip_code_geo," + //(internal) zip-code, integer derived from geo_location, Austria // only " t.speed_download download_kbit," + // csv 10:download_kbit " t.speed_upload upload_kbit," + // csv 11: upload_kbit " t.wifi_link_speed," + // nominal speed of wifi-link in mbit/s , Android-only " (t.ping_median::float / 1000000) ping_ms," + // median ping-time in ms (stored in ns in data base) " signal_strength," + // csv 13:signal_strength RSSI, mainly GSM/UMTS and Wifi, Android only, in dBm " lte_rsrp," + // csv 29: signal_strength RSRP, Android only, in dBm " lte_rsrq," + // signal quality RSRQ, Android only, in dB " ts.name server_name," + // csv 14:server_name, name of the test server used for download/upload (not // applicable for JStest) " implausible, " + // csv 28:implausible, measurement not shown in map nor used in statistics, normally // not visible " public_ip_as_name, " + // name of AS (not number) " duration test_duration," + // csv 15:test_duration, nominal duration of downlink and uplink throughput tests in // seconds " num_threads_requested," + // number of threads requested by control-server " num_threads," + // csv 16:num_threads, number of threads used in downlink throughput test (uplink may // differ) " num_threads_ul," + // number of threads used in uplink test " (CASE WHEN publish_public_data THEN COALESCE(t.plattform, t.client_name) ELSE '' END) platform," + // csv 17:platform; currently used: 'CLI'/'Android'/Applet/iOS/[from client_name: // RMBTws, RMBTjs](null); (null) is used for RMBTjs //AKOS: IC " (CASE WHEN publish_public_data THEN COALESCE(adm.fullname, t.model) ELSE '' END) model," + // csv 18:model, translated t.model (model_native) to human readable form //AKOS: IC " (CASE WHEN publish_public_data THEN t.model ELSE '' END) model_native," + // device used for test; Android API 'model'; iOS:'product'; Browser: Browser-name (zB // Firefox) //AKOS: IC " t.product product," + // product used for test; Android APO 'product'; iOS: '' (not used); Browser: same as // for model (browser name) " t.client_software_version client_version," + // csv 19:client_version, SW-version of client software (not RMBT-client-version), eg. // '1.3' " t.network_operator network_mcc_mnc," + // csv 20:network_mcc_mnc, mobile country and network code of current network (Android // only), string, eg "232-12' " network_country," + // (internal) Android-only, country code derived by client from mobile network code // " network_is_roaming," + //(internal) roaming-status of mobile network, boolean or // (null); Android-only (obsolete) " roaming_type," + // roaming-status of mobile network, integer: 0:not // roaming,1:national,2:international,(null):unknown (eg. iOS) " t.network_operator_name network_name," + // csv 21:network_name, name of current mobile network as displayed on device (eg: // '3likeHome') " t.network_sim_operator sim_mcc_mnc," + // csv 22:sim_mcc_mnc, home network of SIM (initial 5 digits from IMSI), eg '232-01' " t.network_sim_country sim_country," + // (internal) Android-only, country derived by client from SIM (country of home // network) " COALESCE(mprov.name,msim.shortname,msim.name,prov.name) provider_name," + // pre-defined list of providers (based on provider_id) //TODO replace provider " t.nat_type \"connection\"," + // csv 23:connection, translation-mechanism in NAT, eg. nat_local_to_public_ipv4 " t.public_ip_asn asn," + // csv 24:asn, AS (autonomous system) number, number of public IP network " (CASE WHEN publish_public_data THEN t.client_public_ip_anonymized ELSE '' END) ip_anonym," + // csv 25:ip_anonym, anonymized IP of client (IPv4: 8 bits removed, IPv6: 72 bits // removed) " (ndt.s2cspd*1000)::int ndt_download_kbit," + // csv 26:ndt_download_kbit, result of NDT downlink throughput test kbit/s " (ndt.c2sspd*1000)::int ndt_upload_kbit," + // csv 27 ndt_uoload_kbit, result of NDT uplink throughput test in kbit/s " country_geoip," + // country-code derived from public IP-address, eg. 'AT' " country_location," + // country-code derived from geo_location, eg. 'DE' " country_asn," + // country_code derived from AS, eg. 'EU' " data->>'region' region," + // si - region from geo location " data->>'municipality' municipality," + // si - municipality from geo location " data->>'settlement' settlement," + // si - settlement from geo location " data->>'whitespace' whitespot," + // si - whitespace from geo location " data->>'cell_id' cell_id," + // si - cell_id " data->>'cell_name' cell_name," + // si - cell_name " data->>'cell_id_multiple' cell_id_multiple," + // si - cell_id_multiple " bytes_download," + // number of bytes downloaded during test (download and upload) (obsolete) " bytes_upload," + // number of bytes uploaded during test (download and upload) (obsolete) " test_if_bytes_download," + // downloaded bytes on interface during total test (inc. training, ping, without NDT) // (obsolete) " test_if_bytes_upload," + // uploaded bytes on interface during total test (inc. training, ping, without NDT) // (obsolete) " testdl_if_bytes_download," + // downloaded bytes on interface during download-test (without training-seq) " testdl_if_bytes_upload," + // uploaded bytes on interface during download-test (without training-seq) " testul_if_bytes_download," + // downloaded bytes on interface during upload-test (without training-seq) " testul_if_bytes_upload," + // downloaded bytes on interface during upload-test (without training-seq) " (t.nsec_download::float / 1000000) duration_download_ms," + // duration of download-test in ms " (t.nsec_upload::float / 1000000) duration_upload_ms," + // duration of upload-test in ms " (t.time_dl_ns::float / 1000000) time_dl_ms," + // relative start time of download-test in ms (ignoring training-phase) " (t.time_ul_ns::float / 1000000) time_ul_ms," + // relative start time of download-test in ms (ignoring training-phase) // " phone_type" + //(internal) radio type of phone: 0 no mobile radio, 1 GSM (incl. // UMTS,LTE) 2 CDMA (obsolete) " t.publish_public_data, " + " ((EXTRACT (EPOCH FROM (t.timestamp - t.time))) * 1000) speed_test_duration " + " FROM test t" + " LEFT JOIN device_map adm ON adm.codename=t.model" + " LEFT JOIN test_server ts ON ts.uid=t.server_id" + " LEFT JOIN test_ndt ndt ON t.uid=ndt.test_id" + " LEFT JOIN provider prov ON t.provider_id=prov.uid" + " LEFT JOIN provider mprov ON t.mobile_provider_id=mprov.uid" + " LEFT JOIN mccmnc2name msim ON t.mobile_sim_id=msim.uid" + " WHERE " + " t.deleted = false " + " AND t.status = 'FINISHED' " + " AND t.open_test_uuid = ? "; // System.out.println(sql); final String[] columns; PreparedStatement ps = null; ResultSet rs = null; final JSONObject response = new JSONObject(); try { ps = conn.prepareStatement(sql); // insert filter for accuracy /* double accuracy = Double.parseDouble(getSetting("rmbt_geo_accuracy_detail_limit")); ps.setDouble(1, accuracy); ps.setDouble(2, accuracy); ps.setDouble(3, accuracy); ps.setDouble(4, accuracy); ps.setDouble(5, accuracy); ps.setDouble(6, accuracy); */ // openTestIDs are starting with "O" if (openTestUUID != null && openTestUUID.startsWith("O")) { openTestUUID = openTestUUID.substring(1); } ps.setObject(1, openTestUUID, Types.OTHER); if (!ps.execute()) return null; rs = ps.getResultSet(); if (rs.next()) { final boolean isPublishPublicData = rs.getBoolean("publish_public_data"); // fetch data for every field for (int i = 0; i < openDataFieldsFull.length; i++) { // convert data to correct json response final Object obj = rs.getObject(openDataFieldsFull[i]); if (openDataBooleanFields.contains(openDataFieldsFull[i])) { if (obj == null) { response.put(openDataFieldsFull[i], false); } else { response.put(openDataFieldsFull[i], obj); } } else if (obj == null) { response.put(openDataFieldsFull[i], JSONObject.NULL); } else if (openDataNumberFields.contains(openDataFieldsFull[i])) { final String tmp = obj.toString().trim(); if (tmp.isEmpty()) response.put(openDataFieldsFull[i], JSONObject.NULL); else response.put(openDataFieldsFull[i], JSONObject.stringToValue(tmp)); } else { final String tmp = obj.toString().trim(); if (tmp.isEmpty()) response.put(openDataFieldsFull[i], JSONObject.NULL); else response.put(openDataFieldsFull[i], tmp); } } /* obsolete (now in database) //special threatment for lat/lng: if too low accuracy -> do not send back to client double accuracy = rs.getDouble("loc_accuracy"); if (accuracy > Double.parseDouble(settings.getString("RMBT_GEO_ACCURACY_DETAIL_LIMIT"))) { response.put("loc_accuracy", JSONObject.NULL); response.put("long", JSONObject.NULL); response.put("lat", JSONObject.NULL); } try { // do not output invalid zip-codes, must be 4 digits int zip_code = rs.getInt("zip_code"); if (zip_code <= 999 || zip_code > 9999) response.put("zip_code", JSONObject.NULL); } catch (final SQLException e) { System.out.println("Error on zip_code: " + e.toString()); }; */ // classify download, upload, ping, signal response.put( "download_classification", Classification.classify( classification.THRESHOLD_DOWNLOAD, rs.getLong("download_kbit"))); response.put( "upload_classification", Classification.classify(classification.THRESHOLD_UPLOAD, rs.getLong("upload_kbit"))); response.put( "ping_classification", Classification.classify( classification.THRESHOLD_PING, rs.getLong("ping_ms") * 1000000)); // classify signal accordingly if ((rs.getString("signal_strength") != null || rs.getString("lte_rsrp") != null) && rs.getString("network_type") != null) { // signal available if (rs.getString("lte_rsrp") == null) { // use RSSI if (rs.getString("network_type").equals("WLAN")) { // RSSI for Wifi response.put( "signal_classification", Classification.classify( classification.THRESHOLD_SIGNAL_WIFI, rs.getLong("signal_strength"))); } else { // RSSI for Mobile response.put( "signal_classification", Classification.classify( classification.THRESHOLD_SIGNAL_MOBILE, rs.getLong("signal_strength"))); } } else // RSRP for LTE response.put( "signal_classification", Classification.classify( classification.THRESHOLD_SIGNAL_RSRP, rs.getLong("lte_rsrp"))); } else { // no signal available response.put("signal_classification", JSONObject.NULL); } // also load download/upload-speed-data, signal data and location data if possible JSONObject speedCurve = new JSONObject(); JSONArray downloadSpeeds = new JSONArray(); JSONArray uploadSpeeds = new JSONArray(); JSONArray locArray = new JSONArray(); JSONArray signalArray = new JSONArray(); // Load speed data from database SpeedGraph speedGraph = new SpeedGraph( rs.getLong("test_uid"), Math.max(rs.getInt("num_threads"), rs.getInt("num_threads_ul")), conn); for (SpeedGraph.SpeedGraphItem item : speedGraph.getUpload()) { JSONObject obj = new JSONObject(); obj.put("time_elapsed", item.getTimeElapsed()); obj.put("bytes_total", item.getBytesTotal()); uploadSpeeds.put(obj); } for (SpeedGraph.SpeedGraphItem item : speedGraph.getDownload()) { JSONObject obj = new JSONObject(); obj.put("time_elapsed", item.getTimeElapsed()); obj.put("bytes_total", item.getBytesTotal()); downloadSpeeds.put(obj); } // Load signal strength from database SignalGraph sigGraph = new SignalGraph(rs.getLong("test_uid"), rs.getTimestamp("client_time").getTime(), conn); for (SignalGraph.SignalGraphItem item : sigGraph.getSignalList()) { JSONObject json = new JSONObject(); json.put("time_elapsed", item.getTimeElapsed()); json.put("network_type", item.getNetworkType()); json.put("signal_strength", item.getSignalStrength()); json.put("lte_rsrp", item.getLteRsrp()); json.put("lte_rsrq", item.getLteRsrq()); json.put("cat_technology", item.getCatTechnology()); signalArray.put(json); } if (isPublishPublicData) { // Load gps coordinates from database LocationGraph locGraph = new LocationGraph( rs.getLong("test_uid"), rs.getTimestamp("client_time").getTime(), conn); double totalDistance = locGraph.getTotalDistance(); for (LocationGraph.LocationGraphItem item : locGraph.getLocations()) { JSONObject json = new JSONObject(); json.put("time_elapsed", item.getTimeElapsed()); json.put("lat", item.getLatitude()); json.put("long", item.getLongitude()); json.put( "loc_accuracy", (item.getAccuracy() > 0) ? item.getAccuracy() : JSONObject.NULL); locArray.put(json); } // add total distance during test - but only if within bounds if ((totalDistance > 0) && totalDistance <= Double.parseDouble(getSetting("rmbt_geo_distance_detail_limit"))) response.put("distance", totalDistance); else response.put("distance", JSONObject.NULL); } else { response.put("distance", JSONObject.NULL); } final TestStatDao testStatDao = new TestStatDao(conn); final TestStat ts = testStatDao.getById(rs.getLong("test_uid")); if (ts != null) { response.put("extended_test_stat", ts.toJsonObject()); } speedCurve.put("upload", uploadSpeeds); speedCurve.put("download", downloadSpeeds); speedCurve.put("signal", signalArray); speedCurve.put("location", locArray); response.put("speed_curve", speedCurve); } else { // invalid open_uuid setStatus(Status.CLIENT_ERROR_NOT_FOUND); response.put("error", "invalid open-uuid"); } } catch (final JSONException e) { Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, e); } catch (SQLException ex) { try { setStatus(Status.CLIENT_ERROR_NOT_FOUND); response.put("error", "invalid open-uuid"); } catch (JSONException ex1) { Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, ex1); } Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, ex); } finally { try { if (rs != null) rs.close(); if (ps != null) ps.close(); } catch (final SQLException e) { Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, e); } } return response.toString(); }
/** * Scan the content following the named tag, attaching it to the context. * * @param x The XMLTokener containing the source string. * @param context The JSONObject that will include the new material. * @param name The tag name. * @return true if the close tag is processed. * @throws JSONException */ private static boolean parse(XMLTokener x, JSONObject context, String name) throws JSONException { char c; int i; String n; JSONObject o = null; String s; Object t; // Test for and skip past these forms: // <!-- ... --> // <! ... > // <![ ... ]]> // <? ... ?> // Report errors for these forms: // <> // <= // << t = x.nextToken(); // <! if (t == BANG) { c = x.next(); if (c == '-') { if (x.next() == '-') { x.skipPast("-->"); return false; } x.back(); } else if (c == '[') { t = x.nextToken(); if (t.equals("CDATA")) { if (x.next() == '[') { s = x.nextCDATA(); if (s.length() > 0) { context.accumulate("content", s); } return false; } } throw x.syntaxError("Expected 'CDATA['"); } i = 1; do { t = x.nextMeta(); if (t == null) { throw x.syntaxError("Missing '>' after '<!'."); } else if (t == LT) { i += 1; } else if (t == GT) { i -= 1; } } while (i > 0); return false; } else if (t == QUEST) { // <? x.skipPast("?>"); return false; } else if (t == SLASH) { // Close tag </ t = x.nextToken(); if (name == null) { throw x.syntaxError("Mismatched close tag" + t); } if (!t.equals(name)) { throw x.syntaxError("Mismatched " + name + " and " + t); } if (x.nextToken() != GT) { throw x.syntaxError("Misshaped close tag"); } return true; } else if (t instanceof Character) { throw x.syntaxError("Misshaped tag"); // Open tag < } else { n = (String) t; t = null; o = new JSONObject(); for (; ; ) { if (t == null) { t = x.nextToken(); } // attribute = value if (t instanceof String) { s = (String) t; t = x.nextToken(); if (t == EQ) { t = x.nextToken(); if (!(t instanceof String)) { throw x.syntaxError("Missing value"); } o.accumulate(s, JSONObject.stringToValue((String) t)); t = null; } else { o.accumulate(s, ""); } // Empty tag <.../> } else if (t == SLASH) { if (x.nextToken() != GT) { throw x.syntaxError("Misshaped tag"); } if (o.length() > 0) { context.accumulate(n, o); } else { context.accumulate(n, ""); } return false; // Content, between <...> and </...> } else if (t == GT) { for (; ; ) { t = x.nextContent(); if (t == null) { if (n != null) { throw x.syntaxError("Unclosed tag " + n); } return false; } else if (t instanceof String) { s = (String) t; if (s.length() > 0) { o.accumulate("content", JSONObject.stringToValue(s)); } // Nested element } else if (t == LT) { if (parse(x, o, n)) { if (o.length() == 0) { context.accumulate(n, ""); } else if (o.length() == 1 && o.opt("content") != null) { context.accumulate(n, o.opt("content")); } else { context.accumulate(n, o); } return false; } } } } else { throw x.syntaxError("Misshaped tag"); } } } }