Compare commits

...

37 Commits

Author SHA1 Message Date
Sean Leary
47fb49b6a8 Update for release 20230227 2023-02-27 07:21:11 -06:00
Sean Leary
0df034c9fd Update for release 20230227 2023-02-27 07:20:10 -06:00
Sean Leary
f0a05e6911 Update README.md 2023-02-27 07:17:51 -06:00
Sean Leary
1275f6809d Merge pull request #723 from TamasPergerDWP/master
JSONML should be protected from stack overflow exceptions caused by recursion, resolving #722
2023-02-17 13:47:36 -06:00
Tamas Perger
9234eab00a refactor: make JSONMLParserConfiguration all-args constructor private, enforcing the builder pattern. 2023-02-13 01:09:29 +00:00
Tamas Perger
72f4c3e646 refactor: rename XMLtoJSONMLParserConfiguration to JSONMLParserConfiguration 2023-02-12 01:32:34 +00:00
Tamas Perger
df2d6f8363 fix: introduce optional XMLtoJSONMLParserConfiguration parameter for JSONML.toJSONArray(...) functions, to facilitate max nesting depth override. 2023-02-11 01:52:13 +00:00
Tamas Perger
a6e412bded fix: limit the nesting depth in JSONML
Limit the XML nesting depth for CVE-2022-45688 when using the JsonML transform.
2023-02-10 01:46:44 +00:00
Tamas Perger
2391d248cc fix: amend XMLParserConfiguration.clone() to include the new maxNestingDepth param.
Amend Javadoc for XML and XMLParserConfiguration classes.
2023-02-10 01:45:34 +00:00
Sean Leary
401495ae86 Merge pull request #720 from cleydyr/issue-708
Limit the XML nesting depth for CVE-2022-45688
2023-02-05 19:30:04 -06:00
Cleydyr de Albuquerque
448e204186 docs: remove wrong description of parse method 2023-02-02 20:16:16 +01:00
Cleydyr de Albuquerque
eb56704e68 fix: set default maximum nesting depth as 512 2023-02-02 18:15:03 +01:00
Cleydyr de Albuquerque
651511f500 tests: add new test to verify that an XML having the permitted nesting depth can be converted 2023-02-01 20:22:47 +01:00
Cleydyr de Albuquerque
a14cb12c85 refactor: keep consistence with other tests and tidy up constant 2023-02-01 20:22:32 +01:00
Cleydyr de Albuquerque
f566a1d9ee fix: limit the nesting depth 2023-02-01 16:26:58 +01:00
Sean Leary
5920eca2d7 Merge pull request #711 from 6d64/revert-pull-707-interviewbit-spam
Revert pull 707 - interviewbit spam
2022-11-30 20:20:39 -06:00
6d64
3b097d051a Revert pull 707 - interviewbit spam
Reverted commit that was added by a bot adding interviewbit spam to
repos on github
2022-12-01 03:21:26 +11:00
Sean Leary
4e630e58a4 Merge pull request #707 from ASAlishaa/patch-1
Added new resource to the repos
2022-11-17 19:14:11 -06:00
ASAlisha
b732188e4e Added new resource to this repos.
Added resource in the correct format.
2022-11-15 16:31:05 +05:30
ASAlisha
5369442671 Added new resource to the repos
Added new useful JSON resource.
2022-11-14 03:26:18 +05:30
Sean Leary
bb1138762a Merge pull request #703 from TomerPacific/feature/update-release-for-JSONMap-Change
Update Releases.md for JSONObject(Map): Throws NPE if key is null
2022-11-05 17:39:13 -05:00
Sean Leary
6a732ec99d Merge pull request #704 from niranjanib/fix-javadoc-not-visible-in-website
move javadoc comments above the interface definition to make it visible
2022-11-05 17:38:15 -05:00
Niranjani
c798c76ddd move javadoc comments above the interface definition to make it visible
Fix #670
2022-10-30 22:10:38 +05:30
unknown
23d5e52a53 feature/update-release-for-JSONMap-Change adding breaking change for JSONMap to corresponding release 2022-10-28 08:45:54 +03:00
Sean Leary
98df35449a Merge pull request #696 from bmk15897/fix-flaky-test
Update JSONPointerTest for NonDex compatibility
2022-10-15 08:24:01 -05:00
Sean Leary
1be6ee31a7 Merge pull request #694 from DeaneOC/Pretty-Print-XML-Functionality
Pretty print XML
2022-10-13 21:46:26 -05:00
Bharati Kulkarni
a2d3d3c9b5 Fix Flaky Test 2022-10-11 14:33:43 -05:00
Dean
bf9219386a Merge branch 'master' of https://github.com/stleary/JSON-java into Pretty-Print-XML-Functionality 2022-10-10 11:12:49 +01:00
Dean
85495facbd Corrected test 2022-10-10 11:12:35 +01:00
Dean
7aba3ac941 System line seperator now being used in JUnit test 2022-10-10 11:09:42 +01:00
Dean
9cb8e153bf Added JavaDocs 2022-10-07 17:57:07 +01:00
Dean
80c1479ad8 Merge branch 'master' of https://github.com/stleary/JSON-java into Pretty-Print-XML-Functionality 2022-10-07 17:56:54 +01:00
Dean
a2c0562e04 Removed unused import 2022-10-07 15:04:09 +01:00
Dean
153972afdf Adding resources 2022-10-07 10:35:14 +01:00
Dean
4a8ff28fd8 Reduced Test code length by using resources 2022-10-07 10:35:06 +01:00
Dean
fa457a4113 Test cases for XML toString indentation 2022-10-06 12:01:26 +01:00
Dean
b7f708b222 Altered XML toString to allow indentation param 2022-10-06 12:01:13 +01:00
16 changed files with 2337 additions and 116 deletions

View File

@@ -8,7 +8,7 @@ JSON in Java [package org.json]
[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json) [![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json)
**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20220924/json-20220924.jar)** **[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20230227/json-20230227.jar)**
# Overview # Overview

View File

@@ -5,6 +5,8 @@ and artifactId "json". For example:
[https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav](https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav) [https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav](https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav)
~~~ ~~~
20230227 Fix for CVE-2022-45688 and recent commits
20220924 New License - public domain, and some minor updates 20220924 New License - public domain, and some minor updates
20220320 Wrap StackOverflow with JSONException 20220320 Wrap StackOverflow with JSONException
@@ -20,6 +22,7 @@ and artifactId "json". For example:
20190722 Recent commits 20190722 Recent commits
20180813 POM change to include Automatic-Module-Name (#431) 20180813 POM change to include Automatic-Module-Name (#431)
JSONObject(Map) now throws an exception if any of a map keys are null (#405)
20180130 Recent commits 20180130 Recent commits

View File

@@ -3,7 +3,7 @@
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>20220924</version> <version>20230227</version>
<packaging>bundle</packaging> <packaging>bundle</packaging>
<name>JSON in Java</name> <name>JSON in Java</name>

View File

@@ -27,7 +27,32 @@ public class JSONML {
XMLTokener x, XMLTokener x,
boolean arrayForm, boolean arrayForm,
JSONArray ja, JSONArray ja,
boolean keepStrings boolean keepStrings,
int currentNestingDepth
) throws JSONException {
return parse(x,arrayForm, ja,
keepStrings ? JSONMLParserConfiguration.KEEP_STRINGS : JSONMLParserConfiguration.ORIGINAL,
currentNestingDepth);
}
/**
* Parse XML values and store them in a JSONArray.
* @param x The XMLTokener containing the source string.
* @param arrayForm true if array form, false if object form.
* @param ja The JSONArray that is containing the current tag or null
* if we are at the outermost level.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means Don't type-convert text nodes and attribute values.
* @return A JSONArray if the value is the outermost tag, otherwise null.
* @throws JSONException if a parsing error occurs
*/
private static Object parse(
XMLTokener x,
boolean arrayForm,
JSONArray ja,
JSONMLParserConfiguration config,
int currentNestingDepth
) throws JSONException { ) throws JSONException {
String attribute; String attribute;
char c; char c;
@@ -152,7 +177,7 @@ public class JSONML {
if (!(token instanceof String)) { if (!(token instanceof String)) {
throw x.syntaxError("Missing value"); throw x.syntaxError("Missing value");
} }
newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token)); newjo.accumulate(attribute, config.isKeepStrings() ? ((String)token) :XML.stringToValue((String)token));
token = null; token = null;
} else { } else {
newjo.accumulate(attribute, ""); newjo.accumulate(attribute, "");
@@ -181,7 +206,12 @@ public class JSONML {
if (token != XML.GT) { if (token != XML.GT) {
throw x.syntaxError("Misshaped tag"); throw x.syntaxError("Misshaped tag");
} }
closeTag = (String)parse(x, arrayForm, newja, keepStrings);
if (currentNestingDepth == config.getMaxNestingDepth()) {
throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
}
closeTag = (String)parse(x, arrayForm, newja, config, currentNestingDepth + 1);
if (closeTag != null) { if (closeTag != null) {
if (!closeTag.equals(tagName)) { if (!closeTag.equals(tagName)) {
throw x.syntaxError("Mismatched '" + tagName + throw x.syntaxError("Mismatched '" + tagName +
@@ -203,7 +233,7 @@ public class JSONML {
} else { } else {
if (ja != null) { if (ja != null) {
ja.put(token instanceof String ja.put(token instanceof String
? keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token) ? (config.isKeepStrings() ? XML.unescape((String)token) : XML.stringToValue((String)token))
: token); : token);
} }
} }
@@ -224,7 +254,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(String string) throws JSONException { public static JSONArray toJSONArray(String string) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, false); return (JSONArray)parse(new XMLTokener(string), true, null, JSONMLParserConfiguration.ORIGINAL, 0);
} }
@@ -235,8 +265,8 @@ public class JSONML {
* attributes, then the second element will be JSONObject containing the * attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and * name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child tags. * JSONArrays will represent the child tags.
* As opposed to toJSONArray this method does not attempt to convert * As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type * any text node or attribute value to any type
* but just leaves it as a string. * but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored. * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param string The source string. * @param string The source string.
@@ -246,7 +276,32 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException { public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings); return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONArray using the JsonML transform. Each XML tag is represented as
* a JSONArray in which the first element is the tag name. If the tag has
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child tags.
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param string The source string.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(String string, JSONMLParserConfiguration config) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, config, 0);
} }
@@ -257,8 +312,32 @@ public class JSONML {
* attributes, then the second element will be JSONObject containing the * attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and * name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child content and tags. * JSONArrays will represent the child content and tags.
* As opposed to toJSONArray this method does not attempt to convert * As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type * any text node or attribute value to any type
* but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param x An XMLTokener.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(XMLTokener x, JSONMLParserConfiguration config) throws JSONException {
return (JSONArray)parse(x, true, null, config, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONArray using the JsonML transform. Each XML tag is represented as
* a JSONArray in which the first element is the tag name. If the tag has
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child content and tags.
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string. * but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored. * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param x An XMLTokener. * @param x An XMLTokener.
@@ -268,7 +347,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException { public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException {
return (JSONArray)parse(x, true, null, keepStrings); return (JSONArray)parse(x, true, null, keepStrings, 0);
} }
@@ -285,7 +364,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray * @throws JSONException Thrown on error converting to a JSONArray
*/ */
public static JSONArray toJSONArray(XMLTokener x) throws JSONException { public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
return (JSONArray)parse(x, true, null, false); return (JSONArray)parse(x, true, null, false, 0);
} }
@@ -303,10 +382,10 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(String string) throws JSONException { public static JSONObject toJSONObject(String string) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, false); return (JSONObject)parse(new XMLTokener(string), false, null, false, 0);
} }
/** /**
* Convert a well-formed (but not necessarily valid) XML string into a * Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as * JSONObject using the JsonML transform. Each XML tag is represented as
@@ -323,10 +402,32 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings); return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings, 0);
} }
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param string The XML source text.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(String string, JSONMLParserConfiguration config) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, config, 0);
}
/** /**
* Convert a well-formed (but not necessarily valid) XML string into a * Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as * JSONObject using the JsonML transform. Each XML tag is represented as
@@ -341,7 +442,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(XMLTokener x) throws JSONException { public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
return (JSONObject)parse(x, false, null, false); return (JSONObject)parse(x, false, null, false, 0);
} }
@@ -361,7 +462,29 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject * @throws JSONException Thrown on error converting to a JSONObject
*/ */
public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException { public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException {
return (JSONObject)parse(x, false, null, keepStrings); return (JSONObject)parse(x, false, null, keepStrings, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param x An XMLTokener of the XML source text.
* @param config The parser configuration:
* JSONMLParserConfiguration.ORIGINAL is the default behaviour;
* JSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(XMLTokener x, JSONMLParserConfiguration config) throws JSONException {
return (JSONObject)parse(x, false, null, config, 0);
} }
@@ -442,6 +565,7 @@ public class JSONML {
return sb.toString(); return sb.toString();
} }
/** /**
* Reverse the JSONML transformation, making an XML text from a JSONObject. * Reverse the JSONML transformation, making an XML text from a JSONObject.
* The JSONObject must contain a "tagName" property. If it has children, * The JSONObject must contain a "tagName" property. If it has children,

View File

@@ -0,0 +1,128 @@
package org.json;
/*
Public Domain.
*/
/**
* Configuration object for the XML to JSONML parser. The configuration is immutable.
*/
@SuppressWarnings({""})
public class JSONMLParserConfiguration {
/**
* Used to indicate there's no defined limit to the maximum nesting depth when parsing a XML
* document to JSONML.
*/
public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
/**
* The default maximum nesting depth when parsing a XML document to JSONML.
*/
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
/** Original Configuration of the XML to JSONML Parser. */
public static final JSONMLParserConfiguration ORIGINAL
= new JSONMLParserConfiguration();
/** Original configuration of the XML to JSONML Parser except that values are kept as strings. */
public static final JSONMLParserConfiguration KEEP_STRINGS
= new JSONMLParserConfiguration().withKeepStrings(true);
/**
* When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*/
private boolean keepStrings;
/**
* The maximum nesting depth when parsing a XML document to JSONML.
*/
private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
/**
* Default parser configuration. Does not keep strings (tries to implicitly convert values).
*/
public JSONMLParserConfiguration() {
this.keepStrings = false;
}
/**
* Configure the parser string processing and use the default CDATA Tag Name as "content".
* @param keepStrings <code>true</code> to parse all values as string.
* <code>false</code> to try and convert XML string values into a JSON value.
* @param maxNestingDepth <code>int</code> to limit the nesting depth
*/
private JSONMLParserConfiguration(final boolean keepStrings, final int maxNestingDepth) {
this.keepStrings = keepStrings;
this.maxNestingDepth = maxNestingDepth;
}
/**
* Provides a new instance of the same configuration.
*/
@Override
protected JSONMLParserConfiguration clone() {
// future modifications to this method should always ensure a "deep"
// clone in the case of collections. i.e. if a Map is added as a configuration
// item, a new map instance should be created and if possible each value in the
// map should be cloned as well. If the values of the map are known to also
// be immutable, then a shallow clone of the map is acceptable.
return new JSONMLParserConfiguration(
this.keepStrings,
this.maxNestingDepth
);
}
/**
* When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*
* @return The <code>keepStrings</code> configuration value.
*/
public boolean isKeepStrings() {
return this.keepStrings;
}
/**
* When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*
* @param newVal
* new value to use for the <code>keepStrings</code> configuration option.
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public JSONMLParserConfiguration withKeepStrings(final boolean newVal) {
JSONMLParserConfiguration newConfig = this.clone();
newConfig.keepStrings = newVal;
return newConfig;
}
/**
* The maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSONML.
* @return the maximum nesting depth set for this configuration
*/
public int getMaxNestingDepth() {
return maxNestingDepth;
}
/**
* Defines the maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSONML. The default max nesting depth is 512, which means the parser
* will throw a JsonException if the maximum depth is reached.
* Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
* which means the parses will go as deep as the maximum call stack size allows.
* @param maxNestingDepth the maximum nesting depth allowed to the XML parser
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public JSONMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
JSONMLParserConfiguration newConfig = this.clone();
if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
newConfig.maxNestingDepth = maxNestingDepth;
} else {
newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
}
return newConfig;
}
}

View File

@@ -11,13 +11,13 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Documented
@Retention(RUNTIME)
@Target({METHOD})
/** /**
* Use this annotation on a getter method to override the Bean name * Use this annotation on a getter method to override the Bean name
* parser for Bean -&gt; JSONObject mapping. If this annotation is * parser for Bean -&gt; JSONObject mapping. If this annotation is
* present at any level in the class hierarchy, then the method will * present at any level in the class hierarchy, then the method will
* not be serialized from the bean into the JSONObject. * not be serialized from the bean into the JSONObject.
*/ */
@Documented
@Retention(RUNTIME)
@Target({METHOD})
public @interface JSONPropertyIgnore { } public @interface JSONPropertyIgnore { }

View File

@@ -11,14 +11,14 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Documented
@Retention(RUNTIME)
@Target({METHOD})
/** /**
* Use this annotation on a getter method to override the Bean name * Use this annotation on a getter method to override the Bean name
* parser for Bean -&gt; JSONObject mapping. A value set to empty string <code>""</code> * parser for Bean -&gt; JSONObject mapping. A value set to empty string <code>""</code>
* will have the Bean parser fall back to the default field name processing. * will have the Bean parser fall back to the default field name processing.
*/ */
@Documented
@Retention(RUNTIME)
@Target({METHOD})
public @interface JSONPropertyName { public @interface JSONPropertyName {
/** /**
* @return The name of the property as to be used in the JSON Object. * @return The name of the property as to be used in the JSON Object.

View File

@@ -98,7 +98,7 @@ public class XML {
/** /**
* Replace special characters with XML escapes: * Replace special characters with XML escapes:
* *
* <pre>{@code * <pre>{@code
* &amp; (ampersand) is replaced by &amp;amp; * &amp; (ampersand) is replaced by &amp;amp;
* &lt; (less than) is replaced by &amp;lt; * &lt; (less than) is replaced by &amp;lt;
* &gt; (greater than) is replaced by &amp;gt; * &gt; (greater than) is replaced by &amp;gt;
@@ -229,10 +229,14 @@ public class XML {
* The JSONObject that will include the new material. * The JSONObject that will include the new material.
* @param name * @param name
* The tag name. * The tag name.
* @param config
* The XML parser configuration.
* @param currentNestingDepth
* The current nesting depth.
* @return true if the close tag is processed. * @return true if the close tag is processed.
* @throws JSONException * @throws JSONException Thrown if any parsing error occurs.
*/ */
private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config) private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth)
throws JSONException { throws JSONException {
char c; char c;
int i; int i;
@@ -402,7 +406,11 @@ public class XML {
} else if (token == LT) { } else if (token == LT) {
// Nested element // Nested element
if (parse(x, jsonObject, tagName, config)) { if (currentNestingDepth == config.getMaxNestingDepth()) {
throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
}
if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) {
if (config.getForceList().contains(tagName)) { if (config.getForceList().contains(tagName)) {
// Force the value to be an array // Force the value to be an array
if (jsonObject.length() == 0) { if (jsonObject.length() == 0) {
@@ -423,7 +431,7 @@ public class XML {
context.accumulate(tagName, jsonObject); context.accumulate(tagName, jsonObject);
} }
} }
return false; return false;
} }
} }
@@ -487,7 +495,7 @@ public class XML {
} }
return string; return string;
} }
/** /**
* direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support. * direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
*/ */
@@ -534,7 +542,7 @@ public class XML {
// integer representation. // integer representation.
// This will narrow any values to the smallest reasonable Object representation // This will narrow any values to the smallest reasonable Object representation
// (Integer, Long, or BigInteger) // (Integer, Long, or BigInteger)
// BigInteger down conversion: We use a similar bitLength compare as // BigInteger down conversion: We use a similar bitLength compare as
// BigInteger#intValueExact uses. Increases GC, but objects hold // BigInteger#intValueExact uses. Increases GC, but objects hold
// only what they need. i.e. Less runtime overhead if the value is // only what they need. i.e. Less runtime overhead if the value is
@@ -550,7 +558,7 @@ public class XML {
} }
throw new NumberFormatException("val ["+val+"] is not a valid number."); throw new NumberFormatException("val ["+val+"] is not a valid number.");
} }
/** /**
* direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support. * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
*/ */
@@ -568,7 +576,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to * name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar * distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a * elements are represented as JSONArrays. Content text may be placed in a
* "content" member. Comments, prologs, DTDs, and <pre>{@code * "content" member. Comments, prologs, DTDs, and <pre>{@code
* &lt;[ [ ]]>}</pre> * &lt;[ [ ]]>}</pre>
* are ignored. * are ignored.
* *
@@ -589,7 +597,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to * name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar * distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a * elements are represented as JSONArrays. Content text may be placed in a
* "content" member. Comments, prologs, DTDs, and <pre>{@code * "content" member. Comments, prologs, DTDs, and <pre>{@code
* &lt;[ [ ]]>}</pre> * &lt;[ [ ]]>}</pre>
* are ignored. * are ignored.
* *
@@ -655,7 +663,7 @@ public class XML {
while (x.more()) { while (x.more()) {
x.skipPast("<"); x.skipPast("<");
if(x.more()) { if(x.more()) {
parse(x, jo, null, config); parse(x, jo, null, config, 0);
} }
} }
return jo; return jo;
@@ -669,7 +677,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to * name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar * distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a * elements are represented as JSONArrays. Content text may be placed in a
* "content" member. Comments, prologs, DTDs, and <pre>{@code * "content" member. Comments, prologs, DTDs, and <pre>{@code
* &lt;[ [ ]]>}</pre> * &lt;[ [ ]]>}</pre>
* are ignored. * are ignored.
* *
@@ -695,7 +703,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to * name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar * distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a * elements are represented as JSONArrays. Content text may be placed in a
* "content" member. Comments, prologs, DTDs, and <pre>{@code * "content" member. Comments, prologs, DTDs, and <pre>{@code
* &lt;[ [ ]]>}</pre> * &lt;[ [ ]]>}</pre>
* are ignored. * are ignored.
* *
@@ -752,6 +760,28 @@ public class XML {
*/ */
public static String toString(final Object object, final String tagName, final XMLParserConfiguration config) public static String toString(final Object object, final String tagName, final XMLParserConfiguration config)
throws JSONException { throws JSONException {
return toString(object, tagName, config, 0, 0);
}
/**
* Convert a JSONObject into a well-formed, element-normal XML string,
* either pretty print or single-lined depending on indent factor.
*
* @param object
* A JSONObject.
* @param tagName
* The optional name of the enclosing tag.
* @param config
* Configuration that can control output to XML.
* @param indentFactor
* The number of spaces to add to each level of indentation.
* @param indent
* The current ident level in spaces.
* @return
* @throws JSONException
*/
private static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor, int indent)
throws JSONException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
JSONArray ja; JSONArray ja;
JSONObject jo; JSONObject jo;
@@ -761,9 +791,14 @@ public class XML {
// Emit <tagName> // Emit <tagName>
if (tagName != null) { if (tagName != null) {
sb.append(indent(indent));
sb.append('<'); sb.append('<');
sb.append(tagName); sb.append(tagName);
sb.append('>'); sb.append('>');
if(indentFactor > 0){
sb.append("\n");
indent += indentFactor;
}
} }
// Loop thru the keys. // Loop thru the keys.
@@ -806,31 +841,39 @@ public class XML {
sb.append('<'); sb.append('<');
sb.append(key); sb.append(key);
sb.append('>'); sb.append('>');
sb.append(toString(val, null, config)); sb.append(toString(val, null, config, indentFactor, indent));
sb.append("</"); sb.append("</");
sb.append(key); sb.append(key);
sb.append('>'); sb.append('>');
} else { } else {
sb.append(toString(val, key, config)); sb.append(toString(val, key, config, indentFactor, indent));
} }
} }
} else if ("".equals(value)) { } else if ("".equals(value)) {
sb.append(indent(indent));
sb.append('<'); sb.append('<');
sb.append(key); sb.append(key);
sb.append("/>"); sb.append("/>");
if(indentFactor > 0){
sb.append("\n");
}
// Emit a new tag <k> // Emit a new tag <k>
} else { } else {
sb.append(toString(value, key, config)); sb.append(toString(value, key, config, indentFactor, indent));
} }
} }
if (tagName != null) { if (tagName != null) {
// Emit the </tagName> close tag // Emit the </tagName> close tag
sb.append(indent(indent - indentFactor));
sb.append("</"); sb.append("</");
sb.append(tagName); sb.append(tagName);
sb.append('>'); sb.append('>');
if(indentFactor > 0){
sb.append("\n");
}
} }
return sb.toString(); return sb.toString();
@@ -849,15 +892,85 @@ public class XML {
// XML does not have good support for arrays. If an array // XML does not have good support for arrays. If an array
// appears in a place where XML is lacking, synthesize an // appears in a place where XML is lacking, synthesize an
// <array> element. // <array> element.
sb.append(toString(val, tagName == null ? "array" : tagName, config)); sb.append(toString(val, tagName == null ? "array" : tagName, config, indentFactor, indent));
} }
return sb.toString(); return sb.toString();
} }
string = (object == null) ? "null" : escape(object.toString());
return (tagName == null) ? "\"" + string + "\""
: (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName
+ ">" + string + "</" + tagName + ">";
string = (object == null) ? "null" : escape(object.toString());
if(tagName == null){
return indent(indent) + "\"" + string + "\"" + ((indentFactor > 0) ? "\n" : "");
} else if(string.length() == 0){
return indent(indent) + "<" + tagName + "/>" + ((indentFactor > 0) ? "\n" : "");
} else {
return indent(indent) + "<" + tagName
+ ">" + string + "</" + tagName + ">" + ((indentFactor > 0) ? "\n" : "");
}
}
/**
* Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
*
* @param object
* A JSONObject.
* @param indentFactor
* The number of spaces to add to each level of indentation.
* @return A string.
* @throws JSONException Thrown if there is an error parsing the string
*/
public static String toString(Object object, int indentFactor){
return toString(object, null, XMLParserConfiguration.ORIGINAL, indentFactor);
}
/**
* Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
*
* @param object
* A JSONObject.
* @param tagName
* The optional name of the enclosing tag.
* @param indentFactor
* The number of spaces to add to each level of indentation.
* @return A string.
* @throws JSONException Thrown if there is an error parsing the string
*/
public static String toString(final Object object, final String tagName, int indentFactor) {
return toString(object, tagName, XMLParserConfiguration.ORIGINAL, indentFactor);
}
/**
* Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
*
* @param object
* A JSONObject.
* @param tagName
* The optional name of the enclosing tag.
* @param config
* Configuration that can control output to XML.
* @param indentFactor
* The number of spaces to add to each level of indentation.
* @return A string.
* @throws JSONException Thrown if there is an error parsing the string
*/
public static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor)
throws JSONException {
return toString(object, tagName, config, indentFactor, 0);
}
/**
* Return a String consisting of a number of space characters specified by indent
*
* @param indent
* The number of spaces to be appended to the String.
* @return
*/
private static final String indent(int indent) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indent; i++) {
sb.append(' ');
}
return sb.toString();
} }
} }

View File

@@ -16,6 +16,17 @@ import java.util.Set;
*/ */
@SuppressWarnings({""}) @SuppressWarnings({""})
public class XMLParserConfiguration { public class XMLParserConfiguration {
/**
* Used to indicate there's no defined limit to the maximum nesting depth when parsing a XML
* document to JSON.
*/
public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
/**
* The default maximum nesting depth when parsing a XML document to JSON.
*/
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
/** Original Configuration of the XML Parser. */ /** Original Configuration of the XML Parser. */
public static final XMLParserConfiguration ORIGINAL public static final XMLParserConfiguration ORIGINAL
= new XMLParserConfiguration(); = new XMLParserConfiguration();
@@ -28,14 +39,14 @@ public class XMLParserConfiguration {
* they should try to be guessed into JSON values (numeric, boolean, string) * they should try to be guessed into JSON values (numeric, boolean, string)
*/ */
private boolean keepStrings; private boolean keepStrings;
/** /**
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
* processing. * processing.
*/ */
private String cDataTagName; private String cDataTagName;
/** /**
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true" * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(<code>false</code>), or they should be converted to * should be kept as attribute(<code>false</code>), or they should be converted to
@@ -54,6 +65,11 @@ public class XMLParserConfiguration {
*/ */
private Set<String> forceList; private Set<String> forceList;
/**
* The maximum nesting depth when parsing a XML document to JSON.
*/
private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
/** /**
* Default parser configuration. Does not keep strings (tries to implicitly convert * Default parser configuration. Does not keep strings (tries to implicitly convert
* values), and the CDATA Tag Name is "content". * values), and the CDATA Tag Name is "content".
@@ -140,15 +156,18 @@ public class XMLParserConfiguration {
* <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}. * <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
* @param xsiTypeMap <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute * @param xsiTypeMap <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string * xsi:type="integer" as integer, xsi:type="string" as string
* @param forceList <code>new HashSet<String>()</code> to parse the provided tags' values as arrays * @param forceList <code>new HashSet<String>()</code> to parse the provided tags' values as arrays
* @param maxNestingDepth <code>int</code> to limit the nesting depth
*/ */
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList ) { final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList,
final int maxNestingDepth) {
this.keepStrings = keepStrings; this.keepStrings = keepStrings;
this.cDataTagName = cDataTagName; this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull; this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap); this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
this.forceList = Collections.unmodifiableSet(forceList); this.forceList = Collections.unmodifiableSet(forceList);
this.maxNestingDepth = maxNestingDepth;
} }
/** /**
@@ -166,14 +185,15 @@ public class XMLParserConfiguration {
this.cDataTagName, this.cDataTagName,
this.convertNilAttributeToNull, this.convertNilAttributeToNull,
this.xsiTypeMap, this.xsiTypeMap,
this.forceList this.forceList,
this.maxNestingDepth
); );
} }
/** /**
* When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if * When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string) * they should try to be guessed into JSON values (numeric, boolean, string)
* *
* @return The <code>keepStrings</code> configuration value. * @return The <code>keepStrings</code> configuration value.
*/ */
public boolean isKeepStrings() { public boolean isKeepStrings() {
@@ -183,10 +203,10 @@ public class XMLParserConfiguration {
/** /**
* When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if * When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string) * they should try to be guessed into JSON values (numeric, boolean, string)
* *
* @param newVal * @param newVal
* new value to use for the <code>keepStrings</code> configuration option. * new value to use for the <code>keepStrings</code> configuration option.
* *
* @return The existing configuration will not be modified. A new configuration is returned. * @return The existing configuration will not be modified. A new configuration is returned.
*/ */
public XMLParserConfiguration withKeepStrings(final boolean newVal) { public XMLParserConfiguration withKeepStrings(final boolean newVal) {
@@ -199,7 +219,7 @@ public class XMLParserConfiguration {
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
* processing. * processing.
* *
* @return The <code>cDataTagName</code> configuration value. * @return The <code>cDataTagName</code> configuration value.
*/ */
public String getcDataTagName() { public String getcDataTagName() {
@@ -210,10 +230,10 @@ public class XMLParserConfiguration {
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
* processing. * processing.
* *
* @param newVal * @param newVal
* new value to use for the <code>cDataTagName</code> configuration option. * new value to use for the <code>cDataTagName</code> configuration option.
* *
* @return The existing configuration will not be modified. A new configuration is returned. * @return The existing configuration will not be modified. A new configuration is returned.
*/ */
public XMLParserConfiguration withcDataTagName(final String newVal) { public XMLParserConfiguration withcDataTagName(final String newVal) {
@@ -226,7 +246,7 @@ public class XMLParserConfiguration {
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true" * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(<code>false</code>), or they should be converted to * should be kept as attribute(<code>false</code>), or they should be converted to
* <code>null</code>(<code>true</code>) * <code>null</code>(<code>true</code>)
* *
* @return The <code>convertNilAttributeToNull</code> configuration value. * @return The <code>convertNilAttributeToNull</code> configuration value.
*/ */
public boolean isConvertNilAttributeToNull() { public boolean isConvertNilAttributeToNull() {
@@ -237,10 +257,10 @@ public class XMLParserConfiguration {
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true" * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(<code>false</code>), or they should be converted to * should be kept as attribute(<code>false</code>), or they should be converted to
* <code>null</code>(<code>true</code>) * <code>null</code>(<code>true</code>)
* *
* @param newVal * @param newVal
* new value to use for the <code>convertNilAttributeToNull</code> configuration option. * new value to use for the <code>convertNilAttributeToNull</code> configuration option.
* *
* @return The existing configuration will not be modified. A new configuration is returned. * @return The existing configuration will not be modified. A new configuration is returned.
*/ */
public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) { public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) {
@@ -278,7 +298,7 @@ public class XMLParserConfiguration {
/** /**
* When parsing the XML into JSON, specifies that tags that will be converted to arrays * When parsing the XML into JSON, specifies that tags that will be converted to arrays
* in this configuration {@code Set<String>} to parse the provided tags' values as arrays * in this configuration {@code Set<String>} to parse the provided tags' values as arrays
* @return <code>forceList</code> unmodifiable configuration set. * @return <code>forceList</code> unmodifiable configuration set.
*/ */
public Set<String> getForceList() { public Set<String> getForceList() {
@@ -287,8 +307,8 @@ public class XMLParserConfiguration {
/** /**
* When parsing the XML into JSON, specifies that tags that will be converted to arrays * When parsing the XML into JSON, specifies that tags that will be converted to arrays
* in this configuration {@code Set<String>} to parse the provided tags' values as arrays * in this configuration {@code Set<String>} to parse the provided tags' values as arrays
* @param forceList {@code new HashSet<String>()} to parse the provided tags' values as arrays * @param forceList {@code new HashSet<String>()} to parse the provided tags' values as arrays
* @return The existing configuration will not be modified. A new configuration is returned. * @return The existing configuration will not be modified. A new configuration is returned.
*/ */
public XMLParserConfiguration withForceList(final Set<String> forceList) { public XMLParserConfiguration withForceList(final Set<String> forceList) {
@@ -297,4 +317,34 @@ public class XMLParserConfiguration {
newConfig.forceList = Collections.unmodifiableSet(cloneForceList); newConfig.forceList = Collections.unmodifiableSet(cloneForceList);
return newConfig; return newConfig;
} }
/**
* The maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSON.
* @return the maximum nesting depth set for this configuration
*/
public int getMaxNestingDepth() {
return maxNestingDepth;
}
/**
* Defines the maximum nesting depth that the parser will descend before throwing an exception
* when parsing the XML into JSON. The default max nesting depth is 512, which means the parser
* will throw a JsonException if the maximum depth is reached.
* Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
* which means the parses will go as deep as the maximum call stack size allows.
* @param maxNestingDepth the maximum nesting depth allowed to the XML parser
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
XMLParserConfiguration newConfig = this.clone();
if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
newConfig.maxNestingDepth = maxNestingDepth;
} else {
newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
}
return newConfig;
}
} }

View File

@@ -6,7 +6,6 @@ Public Domain.
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;

View File

@@ -11,19 +11,19 @@ import org.junit.Test;
/** /**
* Tests for org.json.JSONML.java * Tests for org.json.JSONML.java
* *
* Certain inputs are expected to result in exceptions. These tests are * Certain inputs are expected to result in exceptions. These tests are
* executed first. JSONML provides an API to: * executed first. JSONML provides an API to:
* Convert an XML string into a JSONArray or a JSONObject. * Convert an XML string into a JSONArray or a JSONObject.
* Convert a JSONArray or JSONObject into an XML string. * Convert a JSONArray or JSONObject into an XML string.
* Both fromstring and tostring operations operations should be symmetrical * Both fromstring and tostring operations operations should be symmetrical
* within the limits of JSONML. * within the limits of JSONML.
* It should be possible to perform the following operations, which should * It should be possible to perform the following operations, which should
* result in the original string being recovered, within the limits of the * result in the original string being recovered, within the limits of the
* underlying classes: * underlying classes:
* Convert a string -> JSONArray -> string -> JSONObject -> string * Convert a string -> JSONArray -> string -> JSONObject -> string
* Convert a string -> JSONObject -> string -> JSONArray -> string * Convert a string -> JSONObject -> string -> JSONArray -> string
* *
*/ */
public class JSONMLTest { public class JSONMLTest {
@@ -56,7 +56,7 @@ public class JSONMLTest {
/** /**
* Attempts to call JSONML.toString() with a null JSONArray. * Attempts to call JSONML.toString() with a null JSONArray.
* Expects a NullPointerException. * Expects a NullPointerException.
*/ */
@Test(expected=NullPointerException.class) @Test(expected=NullPointerException.class)
public void nullJSONXMLException() { public void nullJSONXMLException() {
@@ -69,7 +69,7 @@ public class JSONMLTest {
/** /**
* Attempts to call JSONML.toString() with a null JSONArray. * Attempts to call JSONML.toString() with a null JSONArray.
* Expects a JSONException. * Expects a JSONException.
*/ */
@Test @Test
public void emptyJSONXMLException() { public void emptyJSONXMLException() {
@@ -125,7 +125,7 @@ public class JSONMLTest {
"[\"addresses\","+ "[\"addresses\","+
"{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+ "{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
"\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+ "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
// this array has no name // this array has no name
"["+ "["+
"[\"name\"],"+ "[\"name\"],"+
"[\"nocontent\"],"+ "[\"nocontent\"],"+
@@ -180,7 +180,7 @@ public class JSONMLTest {
} }
/** /**
* Attempts to transform a malformed XML document * Attempts to transform a malformed XML document
* (element tag has a frontslash) to a JSONArray.\ * (element tag has a frontslash) to a JSONArray.\
* Expects a JSONException * Expects a JSONException
*/ */
@@ -191,7 +191,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because the 'name' element * In this case, the XML is invalid because the 'name' element
* contains an invalid frontslash. * contains an invalid frontslash.
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ " xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -216,7 +216,7 @@ public class JSONMLTest {
*/ */
@Test @Test
public void invalidBangInTagException() { public void invalidBangInTagException() {
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ " xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -246,7 +246,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element * In this case, the XML is invalid because an element
* starts with '!' and has no closing tag * starts with '!' and has no closing tag
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ " xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -276,7 +276,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element * In this case, the XML is invalid because an element
* has no closing '>'. * has no closing '>'.
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ " xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -306,7 +306,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element * In this case, the XML is invalid because an element
* has no name after the closing tag '</'. * has no name after the closing tag '</'.
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ " xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -336,7 +336,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element * In this case, the XML is invalid because an element
* has '>' after the closing tag '</' and name. * has '>' after the closing tag '</' and name.
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xsi:noNamespaceSchemaLocation=\"test.xsd\">\n"+ " xsi:noNamespaceSchemaLocation=\"test.xsd\">\n"+
@@ -364,9 +364,9 @@ public class JSONMLTest {
/** /**
* xmlStr contains XML text which is transformed into a JSONArray. * xmlStr contains XML text which is transformed into a JSONArray.
* In this case, the XML is invalid because an element * In this case, the XML is invalid because an element
* does not have a complete CDATA string. * does not have a complete CDATA string.
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ " xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -388,7 +388,7 @@ public class JSONMLTest {
/** /**
* Convert an XML document into a JSONArray, then use JSONML.toString() * Convert an XML document into a JSONArray, then use JSONML.toString()
* to convert it into a string. This string is then converted back into * to convert it into a string. This string is then converted back into
* a JSONArray. Both JSONArrays are compared against a control to * a JSONArray. Both JSONArrays are compared against a control to
* confirm the contents. * confirm the contents.
*/ */
@Test @Test
@@ -405,7 +405,7 @@ public class JSONMLTest {
* which is used to create a final JSONArray, which is also compared * which is used to create a final JSONArray, which is also compared
* against the expected JSONArray. * against the expected JSONArray.
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
"xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ "xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -414,7 +414,7 @@ public class JSONMLTest {
"<nocontent/>>\n"+ "<nocontent/>>\n"+
"</address>\n"+ "</address>\n"+
"</addresses>"; "</addresses>";
String expectedStr = String expectedStr =
"[\"addresses\","+ "[\"addresses\","+
"{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+ "{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
"\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+ "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
@@ -434,12 +434,12 @@ public class JSONMLTest {
} }
/** /**
* Convert an XML document into a JSONObject. Use JSONML.toString() to * Convert an XML document into a JSONObject. Use JSONML.toString() to
* convert it back into a string, and then re-convert it into a JSONObject. * convert it back into a string, and then re-convert it into a JSONObject.
* Both JSONObjects are compared against a control JSONObject to confirm * Both JSONObjects are compared against a control JSONObject to confirm
* the contents. * the contents.
* <p> * <p>
* Next convert the XML document into a JSONArray. Use JSONML.toString() to * Next convert the XML document into a JSONArray. Use JSONML.toString() to
* convert it back into a string, and then re-convert it into a JSONArray. * convert it back into a string, and then re-convert it into a JSONArray.
* Both JSONArrays are compared against a control JSONArray to confirm * Both JSONArrays are compared against a control JSONArray to confirm
* the contents. * the contents.
@@ -452,23 +452,23 @@ public class JSONMLTest {
/** /**
* xmlStr contains XML text which is transformed into a JSONObject, * xmlStr contains XML text which is transformed into a JSONObject,
* restored to XML, transformed into a JSONArray, and then restored * restored to XML, transformed into a JSONArray, and then restored
* to XML again. Both JSONObject and JSONArray should contain the same * to XML again. Both JSONObject and JSONArray should contain the same
* information and should produce the same XML, allowing for non-ordered * information and should produce the same XML, allowing for non-ordered
* attributes. * attributes.
* *
* Transformation to JSONObject: * Transformation to JSONObject:
* The elementName is stored as a string where key="tagName" * The elementName is stored as a string where key="tagName"
* Attributes are simply stored as key/value pairs * Attributes are simply stored as key/value pairs
* If the element has either content or child elements, they are stored * If the element has either content or child elements, they are stored
* in a jsonArray with key="childNodes". * in a jsonArray with key="childNodes".
* *
* Transformation to JSONArray: * Transformation to JSONArray:
* 1st entry = elementname * 1st entry = elementname
* 2nd entry = attributes object (if present) * 2nd entry = attributes object (if present)
* 3rd entry = content (if present) * 3rd entry = content (if present)
* 4th entry = child element JSONArrays (if present) * 4th entry = child element JSONArrays (if present)
*/ */
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+ "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
"xsi:noNamespaceSchemaLocation='test.xsd'>\n"+ "xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
@@ -585,7 +585,7 @@ public class JSONMLTest {
"\"tagName\":\"addresses\""+ "\"tagName\":\"addresses\""+
"}"; "}";
String expectedJSONArrayStr = String expectedJSONArrayStr =
"["+ "["+
"\"addresses\","+ "\"addresses\","+
"{"+ "{"+
@@ -645,12 +645,12 @@ public class JSONMLTest {
JSONObject finalJsonObject = JSONML.toJSONObject(jsonObjectXmlToStr); JSONObject finalJsonObject = JSONML.toJSONObject(jsonObjectXmlToStr);
Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject);
// create a JSON array from the original string and make sure it // create a JSON array from the original string and make sure it
// looks as expected // looks as expected
JSONArray jsonArray = JSONML.toJSONArray(xmlStr); JSONArray jsonArray = JSONML.toJSONArray(xmlStr);
JSONArray expectedJsonArray = new JSONArray(expectedJSONArrayStr); JSONArray expectedJsonArray = new JSONArray(expectedJSONArrayStr);
Util.compareActualVsExpectedJsonArrays(jsonArray,expectedJsonArray); Util.compareActualVsExpectedJsonArrays(jsonArray,expectedJsonArray);
// restore the XML, then make another JSONArray and make sure it // restore the XML, then make another JSONArray and make sure it
// looks as expected // looks as expected
String jsonArrayXmlToStr = JSONML.toString(jsonArray); String jsonArrayXmlToStr = JSONML.toString(jsonArray);
@@ -668,14 +668,14 @@ public class JSONMLTest {
* Convert an XML document which contains embedded comments into * Convert an XML document which contains embedded comments into
* a JSONArray. Use JSONML.toString() to turn it into a string, then * a JSONArray. Use JSONML.toString() to turn it into a string, then
* reconvert it into a JSONArray. Compare both JSONArrays to a control * reconvert it into a JSONArray. Compare both JSONArrays to a control
* JSONArray to confirm the contents. * JSONArray to confirm the contents.
* <p> * <p>
* This test shows how XML comments are handled. * This test shows how XML comments are handled.
*/ */
@Test @Test
public void commentsInXML() { public void commentsInXML() {
String xmlStr = String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<!-- this is a comment -->\n"+ "<!-- this is a comment -->\n"+
"<addresses>\n"+ "<addresses>\n"+
@@ -734,7 +734,7 @@ public class JSONMLTest {
final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",\"1\"],[\"id\",\"00\"],[\"id\",\"0\"],[\"item\",{\"id\":\"01\"}],[\"title\",\"True\"]]"; final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",\"1\"],[\"id\",\"00\"],[\"id\",\"0\"],[\"item\",{\"id\":\"01\"}],[\"title\",\"True\"]]";
final JSONArray json = JSONML.toJSONArray(originalXml,true); final JSONArray json = JSONML.toJSONArray(originalXml,true);
assertEquals(expectedJsonString, json.toString()); assertEquals(expectedJsonString, json.toString());
final String reverseXml = JSONML.toString(json); final String reverseXml = JSONML.toString(json);
assertEquals(originalXml, reverseXml); assertEquals(originalXml, reverseXml);
} }
@@ -749,7 +749,7 @@ public class JSONMLTest {
final String revertedXml = JSONML.toString(jsonArray); final String revertedXml = JSONML.toString(jsonArray);
assertEquals(revertedXml, originalXml); assertEquals(revertedXml, originalXml);
} }
/** /**
* JSON string cannot be reverted to original xml. See test result in * JSON string cannot be reverted to original xml. See test result in
* comment below. * comment below.
@@ -770,7 +770,7 @@ public class JSONMLTest {
// 1. Our XML parser does not handle generic HTML entities, only valid XML entities. Hence &nbsp; // 1. Our XML parser does not handle generic HTML entities, only valid XML entities. Hence &nbsp;
// or other HTML specific entities would fail on reversability // or other HTML specific entities would fail on reversability
// 2. Our JSON implementation for storing the XML attributes uses the standard unordered map. // 2. Our JSON implementation for storing the XML attributes uses the standard unordered map.
// This means that <tag attr1="v1" attr2="v2" /> can not be reversed reliably. // This means that <tag attr1="v1" attr2="v2" /> can not be reversed reliably.
// //
// /** // /**
// * Test texts taken from jsonml.org. Currently our implementation FAILS this conversion but shouldn't. // * Test texts taken from jsonml.org. Currently our implementation FAILS this conversion but shouldn't.
@@ -783,13 +783,13 @@ public class JSONMLTest {
// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"\u00A0\",[\"span\",{ \"style\" : \"background-color:maroon\" },\"\u00A9\"],\"\u00A0\"]]]"; // final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"\u00A0\",[\"span\",{ \"style\" : \"background-color:maroon\" },\"\u00A9\"],\"\u00A0\"]]]";
// final JSONArray json = JSONML.toJSONArray(originalXml,true); // final JSONArray json = JSONML.toJSONArray(originalXml,true);
// final String actualJsonString = json.toString(); // final String actualJsonString = json.toString();
// //
// final String reverseXml = JSONML.toString(json); // final String reverseXml = JSONML.toString(json);
// assertNotEquals(originalXml, reverseXml); // assertNotEquals(originalXml, reverseXml);
// //
// assertNotEquals(expectedJsonString, actualJsonString); // assertNotEquals(expectedJsonString, actualJsonString);
// } // }
// //
// /** // /**
// * Test texts taken from jsonml.org but modified to have XML entities only. // * Test texts taken from jsonml.org but modified to have XML entities only.
// */ // */
@@ -799,15 +799,15 @@ public class JSONMLTest {
// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"&\",[\"span\",{ \"style\" : \"background-color:maroon\" },\">\"],\"<\"]]]"; // final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"&\",[\"span\",{ \"style\" : \"background-color:maroon\" },\">\"],\"<\"]]]";
// final JSONArray jsonML = JSONML.toJSONArray(originalXml,true); // final JSONArray jsonML = JSONML.toJSONArray(originalXml,true);
// final String actualJsonString = jsonML.toString(); // final String actualJsonString = jsonML.toString();
// //
// final String reverseXml = JSONML.toString(jsonML); // final String reverseXml = JSONML.toString(jsonML);
// // currently not equal because the hashing of the attribute objects makes the attribute // // currently not equal because the hashing of the attribute objects makes the attribute
// // order not happen the same way twice // // order not happen the same way twice
// assertEquals(originalXml, reverseXml); // assertEquals(originalXml, reverseXml);
// //
// assertEquals(expectedJsonString, actualJsonString); // assertEquals(expectedJsonString, actualJsonString);
// } // }
@Test (timeout = 6000) @Test (timeout = 6000)
public void testIssue484InfinteLoop1() { public void testIssue484InfinteLoop1() {
try { try {
@@ -819,11 +819,11 @@ public class JSONMLTest {
ex.getMessage()); ex.getMessage());
} }
} }
@Test (timeout = 6000) @Test (timeout = 6000)
public void testIssue484InfinteLoop2() { public void testIssue484InfinteLoop2() {
try { try {
String input = "??*\n" + String input = "??*\n" +
"??|?CglR??`??>?w??PIlr??D?$?-?o??O?*??{OD?Y??`2a????NM?bq?:O?>S$ ?J?B.gUK?m\b??zE???!v]???????c??????h???s???g???`?qbi??:Zl?)?}1^??k?0??:$V?$?Ovs(}J??????2;gQ????Tg?K?`?h%c?hmGA?<!C*?9?~?t?)??,zA???S}?Q??.q?j????]"; "??|?CglR??`??>?w??PIlr??D?$?-?o??O?*??{OD?Y??`2a????NM?bq?:O?>S$ ?J?B.gUK?m\b??zE???!v]???????c??????h???s???g???`?qbi??:Zl?)?}1^??k?0??:$V?$?Ovs(}J??????2;gQ????Tg?K?`?h%c?hmGA?<!C*?9?~?t?)??,zA???S}?Q??.q?j????]";
JSONML.toJSONObject(input); JSONML.toJSONObject(input);
fail("Exception expected for invalid JSON."); fail("Exception expected for invalid JSON.");
@@ -833,4 +833,128 @@ public class JSONMLTest {
ex.getMessage()); ex.getMessage());
} }
} }
@Test
public void testToJSONArrayMaxNestingDepthOf42IsRespected() {
final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "<a>");
final int maxNestingDepth = 42;
try {
JSONML.toJSONArray(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONArrayMaxNestingDepthIsRespectedWithValidXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 1;
try {
JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONArrayMaxNestingDepthWithValidFittingXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 3;
try {
JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
} catch (JSONException e) {
e.printStackTrace();
fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
"parameter of the JSONMLParserConfiguration used");
}
}
@Test
public void testToJSONObjectMaxNestingDepthOf42IsRespected() {
final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "<a>");
final int maxNestingDepth = 42;
try {
JSONML.toJSONObject(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONObjectMaxNestingDepthIsRespectedWithValidXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 1;
try {
JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testToJSONObjectMaxNestingDepthWithValidFittingXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 3;
try {
JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
} catch (JSONException e) {
e.printStackTrace();
fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
"parameter of the JSONMLParserConfiguration used");
}
}
} }

View File

@@ -72,8 +72,10 @@ public class JSONPointerTest {
@Test @Test
public void queryByEmptyKeySubObject() { public void queryByEmptyKeySubObject() {
assertEquals( "{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" + JSONObject json = new JSONObject("{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" +
" other value\"}", query("/obj/").toString()); " other value\"}");
JSONObject obj = (JSONObject) query("/obj/");
assertTrue(json.similar(obj));
} }
@Test @Test

View File

@@ -1051,6 +1051,29 @@ public class XMLConfigurationTest {
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
} }
@Test
public void testMaxNestingDepthIsSet() {
XMLParserConfiguration xmlParserConfiguration = XMLParserConfiguration.ORIGINAL;
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(42);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 42);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(0);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 0);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(-31415926);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(Integer.MIN_VALUE);
assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
}
/** /**
* Convenience method, given an input string and expected result, * Convenience method, given an input string and expected result,

View File

@@ -21,13 +21,7 @@ import java.io.StringReader;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.json.JSONArray; import org.json.*;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.json.XML;
import org.json.XMLParserConfiguration;
import org.json.XMLXsiTypeConverter;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
@@ -1049,4 +1043,270 @@ public class XMLTest {
fail("Expected to be unable to modify the config"); fail("Expected to be unable to modify the config");
} catch (Exception ignored) { } } catch (Exception ignored) { }
} }
@Test
public void testIndentComplicatedJsonObject(){
String str = "{\n" +
" \"success\": true,\n" +
" \"error\": null,\n" +
" \"response\": [\n" +
" {\n" +
" \"timestamp\": 1664917200,\n" +
" \"dateTimeISO\": \"2022-10-05T00:00:00+03:00\",\n" +
" \"loc\": {\n" +
" \"lat\": 39.91987,\n" +
" \"long\": 32.85427\n" +
" },\n" +
" \"place\": {\n" +
" \"name\": \"ankara\",\n" +
" \"state\": \"an\",\n" +
" \"country\": \"tr\"\n" +
" },\n" +
" \"profile\": {\n" +
" \"tz\": \"Europe/Istanbul\"\n" +
" },\n" +
" \"sun\": {\n" +
" \"rise\": 1664941721,\n" +
" \"riseISO\": \"2022-10-05T06:48:41+03:00\",\n" +
" \"set\": 1664983521,\n" +
" \"setISO\": \"2022-10-05T18:25:21+03:00\",\n" +
" \"transit\": 1664962621,\n" +
" \"transitISO\": \"2022-10-05T12:37:01+03:00\",\n" +
" \"midnightSun\": false,\n" +
" \"polarNight\": false,\n" +
" \"twilight\": {\n" +
" \"civilBegin\": 1664940106,\n" +
" \"civilBeginISO\": \"2022-10-05T06:21:46+03:00\",\n" +
" \"civilEnd\": 1664985136,\n" +
" \"civilEndISO\": \"2022-10-05T18:52:16+03:00\",\n" +
" \"nauticalBegin\": 1664938227,\n" +
" \"nauticalBeginISO\": \"2022-10-05T05:50:27+03:00\",\n" +
" \"nauticalEnd\": 1664987015,\n" +
" \"nauticalEndISO\": \"2022-10-05T19:23:35+03:00\",\n" +
" \"astronomicalBegin\": 1664936337,\n" +
" \"astronomicalBeginISO\": \"2022-10-05T05:18:57+03:00\",\n" +
" \"astronomicalEnd\": 1664988905,\n" +
" \"astronomicalEndISO\": \"2022-10-05T19:55:05+03:00\"\n" +
" }\n" +
" },\n" +
" \"moon\": {\n" +
" \"rise\": 1664976480,\n" +
" \"riseISO\": \"2022-10-05T16:28:00+03:00\",\n" +
" \"set\": 1664921520,\n" +
" \"setISO\": \"2022-10-05T01:12:00+03:00\",\n" +
" \"transit\": 1664994240,\n" +
" \"transitISO\": \"2022-10-05T21:24:00+03:00\",\n" +
" \"underfoot\": 1664949360,\n" +
" \"underfootISO\": \"2022-10-05T08:56:00+03:00\",\n" +
" \"phase\": {\n" +
" \"phase\": 0.3186,\n" +
" \"name\": \"waxing gibbous\",\n" +
" \"illum\": 71,\n" +
" \"age\": 9.41,\n" +
" \"angle\": 0.55\n" +
" }\n" +
" }\n" +
" }\n" +
" ]\n" +
"}" ;
JSONObject jsonObject = new JSONObject(str);
String actualIndentedXmlString = XML.toString(jsonObject, 1);
String expected = "<success>true</success>\n" +
"<response>\n" +
" <dateTimeISO>2022-10-05T00:00:00+03:00</dateTimeISO>\n" +
" <loc>\n" +
" <lat>39.91987</lat>\n" +
" <long>32.85427</long>\n" +
" </loc>\n" +
" <moon>\n" +
" <phase>\n" +
" <phase>0.3186</phase>\n" +
" <name>waxing gibbous</name>\n" +
" <angle>0.55</angle>\n" +
" <illum>71</illum>\n" +
" <age>9.41</age>\n" +
" </phase>\n" +
" <setISO>2022-10-05T01:12:00+03:00</setISO>\n" +
" <underfoot>1664949360</underfoot>\n" +
" <set>1664921520</set>\n" +
" <transit>1664994240</transit>\n" +
" <transitISO>2022-10-05T21:24:00+03:00</transitISO>\n" +
" <riseISO>2022-10-05T16:28:00+03:00</riseISO>\n" +
" <rise>1664976480</rise>\n" +
" <underfootISO>2022-10-05T08:56:00+03:00</underfootISO>\n" +
" </moon>\n" +
" <profile>\n" +
" <tz>Europe/Istanbul</tz>\n" +
" </profile>\n" +
" <place>\n" +
" <country>tr</country>\n" +
" <name>ankara</name>\n" +
" <state>an</state>\n" +
" </place>\n" +
" <sun>\n" +
" <setISO>2022-10-05T18:25:21+03:00</setISO>\n" +
" <midnightSun>false</midnightSun>\n" +
" <set>1664983521</set>\n" +
" <transit>1664962621</transit>\n" +
" <polarNight>false</polarNight>\n" +
" <transitISO>2022-10-05T12:37:01+03:00</transitISO>\n" +
" <riseISO>2022-10-05T06:48:41+03:00</riseISO>\n" +
" <rise>1664941721</rise>\n" +
" <twilight>\n" +
" <civilEnd>1664985136</civilEnd>\n" +
" <astronomicalBegin>1664936337</astronomicalBegin>\n" +
" <astronomicalEnd>1664988905</astronomicalEnd>\n" +
" <astronomicalBeginISO>2022-10-05T05:18:57+03:00</astronomicalBeginISO>\n" +
" <civilBegin>1664940106</civilBegin>\n" +
" <nauticalEndISO>2022-10-05T19:23:35+03:00</nauticalEndISO>\n" +
" <astronomicalEndISO>2022-10-05T19:55:05+03:00</astronomicalEndISO>\n" +
" <nauticalBegin>1664938227</nauticalBegin>\n" +
" <nauticalEnd>1664987015</nauticalEnd>\n" +
" <nauticalBeginISO>2022-10-05T05:50:27+03:00</nauticalBeginISO>\n" +
" <civilBeginISO>2022-10-05T06:21:46+03:00</civilBeginISO>\n" +
" <civilEndISO>2022-10-05T18:52:16+03:00</civilEndISO>\n" +
" </twilight>\n" +
" </sun>\n" +
" <timestamp>1664917200</timestamp>\n" +
"</response>\n" +
"<error>null</error>\n";
assertEquals(actualIndentedXmlString, expected);
}
@Test
public void testIndentSimpleJsonObject(){
String str = "{ \"employee\": { \n" +
" \"name\": \"sonoo\", \n" +
" \"salary\": 56000, \n" +
" \"married\": true \n" +
" }}";
JSONObject jsonObject = new JSONObject(str);
String actual = XML.toString(jsonObject, "Test", 2);
String expected = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
assertEquals(actual, expected);
}
@Test
public void testIndentSimpleJsonArray(){
String str = "[ \n" +
" {\"name\":\"Ram\", \"email\":\"Ram@gmail.com\"}, \n" +
" {\"name\":\"Bob\", \"email\":\"bob32@gmail.com\"} \n" +
"] ";
JSONArray jsonObject = new JSONArray(str);
String actual = XML.toString(jsonObject, 2);
String expected = "<array>\n" +
" <name>Ram</name>\n" +
" <email>Ram@gmail.com</email>\n" +
"</array>\n" +
"<array>\n" +
" <name>Bob</name>\n" +
" <email>bob32@gmail.com</email>\n" +
"</array>\n";
assertEquals(actual, expected);
}
@Test
public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){
try {
InputStream jsonStream = null;
try {
jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.json");
final JSONObject object = new JSONObject(new JSONTokener(jsonStream));
String actualString = XML.toString(object, null, XMLParserConfiguration.KEEP_STRINGS,2);
InputStream xmlStream = null;
try {
xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.xml");
int bufferSize = 1024;
char[] buffer = new char[bufferSize];
StringBuilder expected = new StringBuilder();
Reader in = new InputStreamReader(xmlStream, "UTF-8");
for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) {
expected.append(buffer, 0, numRead);
}
assertEquals(expected.toString(), actualString.replaceAll("\\n|\\r\\n", System.getProperty("line.separator")));
} finally {
if (xmlStream != null) {
xmlStream.close();
}
}
} finally {
if (jsonStream != null) {
jsonStream.close();
}
}
} catch (IOException e) {
fail("file writer error: " +e.getMessage());
}
}
@Test
public void testMaxNestingDepthOf42IsRespected() {
final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "<a>");
final int maxNestingDepth = 42;
try {
XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testMaxNestingDepthIsRespectedWithValidXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 1;
try {
XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
fail("Expecting a JSONException");
} catch (JSONException e) {
assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
}
}
@Test
public void testMaxNestingDepthWithValidFittingXML() {
final String perfectlyFineXML = "<Test>\n" +
" <employee>\n" +
" <name>sonoo</name>\n" +
" <salary>56000</salary>\n" +
" <married>true</married>\n" +
" </employee>\n" +
"</Test>\n";
final int maxNestingDepth = 3;
try {
XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
} catch (JSONException e) {
e.printStackTrace();
fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
"parameter of the XMLParserConfiguration used");
}
}
} }

View File

@@ -0,0 +1,704 @@
{
"success": true,
"error": null,
"response": [
{
"loc": {
"long": 31.25,
"lat": 30.063
},
"interval": "day",
"place": {
"name": "cairo",
"state": "qh",
"country": "eg"
},
"periods": [
{
"timestamp": 1665032400,
"validTime": "2022-10-06T07:00:00+02:00",
"dateTimeISO": "2022-10-06T07:00:00+02:00",
"maxTempC": 32,
"maxTempF": 90,
"minTempC": 19,
"minTempF": 66,
"avgTempC": 25,
"avgTempF": 78,
"tempC": null,
"tempF": null,
"maxFeelslikeC": 32,
"maxFeelslikeF": 89,
"minFeelslikeC": 21,
"minFeelslikeF": 70,
"avgFeelslikeC": 26,
"avgFeelslikeF": 80,
"feelslikeC": 21,
"feelslikeF": 70,
"maxDewpointC": 17,
"maxDewpointF": 63,
"minDewpointC": 11,
"minDewpointF": 52,
"avgDewpointC": 14,
"avgDewpointF": 58,
"dewpointC": 17,
"dewpointF": 63,
"maxHumidity": 77,
"minHumidity": 29,
"humidity": 77,
"pop": 0,
"precipMM": 0,
"precipIN": 0,
"iceaccum": null,
"iceaccumMM": null,
"iceaccumIN": null,
"snowCM": 0,
"snowIN": 0,
"pressureMB": 1015,
"pressureIN": 29.97,
"windDir": "N",
"windDirDEG": 353,
"windSpeedKTS": 5,
"windSpeedKPH": 9,
"windSpeedMPH": 6,
"windGustKTS": 21,
"windGustKPH": 40,
"windGustMPH": 25,
"windDirMax": "NNW",
"windDirMaxDEG": 342,
"windSpeedMaxKTS": 9,
"windSpeedMaxKPH": 16,
"windSpeedMaxMPH": 10,
"windDirMin": "N",
"windDirMinDEG": 353,
"windSpeedMinKTS": 1,
"windSpeedMinKPH": 2,
"windSpeedMinMPH": 1,
"windDir80m": "N",
"windDir80mDEG": 11,
"windSpeed80mKTS": 12,
"windSpeed80mKPH": 22,
"windSpeed80mMPH": 13,
"windGust80mKTS": 22,
"windGust80mKPH": 41,
"windGust80mMPH": 25,
"windDirMax80m": "NNW",
"windDirMax80mDEG": 343,
"windSpeedMax80mKTS": 22,
"windSpeedMax80mKPH": 41,
"windSpeedMax80mMPH": 25,
"windDirMin80m": "E",
"windDirMin80mDEG": 95,
"windSpeedMin80mKTS": 8,
"windSpeedMin80mKPH": 15,
"windSpeedMin80mMPH": 10,
"sky": 22,
"cloudsCoded": "FW",
"weather": "Mostly Sunny",
"weatherCoded": [],
"weatherPrimary": "Mostly Sunny",
"weatherPrimaryCoded": "::FW",
"icon": "fair.png",
"visibilityKM": 24.135,
"visibilityMI": 15,
"uvi": 6,
"solradWM2": 5608,
"solradMinWM2": 0,
"solradMaxWM2": 778,
"isDay": true,
"maxCoverage": "",
"sunrise": 1665028274,
"sunset": 1665070502,
"sunriseISO": "2022-10-06T05:51:14+02:00",
"sunsetISO": "2022-10-06T17:35:02+02:00"
},
{
"timestamp": 1665118800,
"validTime": "2022-10-07T07:00:00+02:00",
"dateTimeISO": "2022-10-07T07:00:00+02:00",
"maxTempC": 30,
"maxTempF": 86,
"minTempC": 19,
"minTempF": 66,
"avgTempC": 24,
"avgTempF": 76,
"tempC": null,
"tempF": null,
"maxFeelslikeC": 29,
"maxFeelslikeF": 85,
"minFeelslikeC": 19,
"minFeelslikeF": 67,
"avgFeelslikeC": 24,
"avgFeelslikeF": 76,
"feelslikeC": 19,
"feelslikeF": 67,
"maxDewpointC": 15,
"maxDewpointF": 60,
"minDewpointC": 10,
"minDewpointF": 50,
"avgDewpointC": 12,
"avgDewpointF": 54,
"dewpointC": 15,
"dewpointF": 60,
"maxHumidity": 77,
"minHumidity": 30,
"humidity": 77,
"pop": 0,
"precipMM": 0,
"precipIN": 0,
"iceaccum": null,
"iceaccumMM": null,
"iceaccumIN": null,
"snowCM": 0,
"snowIN": 0,
"pressureMB": 1014,
"pressureIN": 29.95,
"windDir": "NW",
"windDirDEG": 325,
"windSpeedKTS": 1,
"windSpeedKPH": 2,
"windSpeedMPH": 1,
"windGustKTS": 16,
"windGustKPH": 29,
"windGustMPH": 18,
"windDirMax": "WNW",
"windDirMaxDEG": 298,
"windSpeedMaxKTS": 7,
"windSpeedMaxKPH": 13,
"windSpeedMaxMPH": 8,
"windDirMin": "NW",
"windDirMinDEG": 325,
"windSpeedMinKTS": 1,
"windSpeedMinKPH": 2,
"windSpeedMinMPH": 1,
"windDir80m": "NNW",
"windDir80mDEG": 347,
"windSpeed80mKTS": 6,
"windSpeed80mKPH": 10,
"windSpeed80mMPH": 6,
"windGust80mKTS": 20,
"windGust80mKPH": 37,
"windGust80mMPH": 23,
"windDirMax80m": "NW",
"windDirMax80mDEG": 316,
"windSpeedMax80mKTS": 20,
"windSpeedMax80mKPH": 37,
"windSpeedMax80mMPH": 23,
"windDirMin80m": "NNW",
"windDirMin80mDEG": 347,
"windSpeedMin80mKTS": 6,
"windSpeedMin80mKPH": 10,
"windSpeedMin80mMPH": 6,
"sky": 30,
"cloudsCoded": "FW",
"weather": "Mostly Sunny",
"weatherCoded": [],
"weatherPrimary": "Mostly Sunny",
"weatherPrimaryCoded": "::FW",
"icon": "fair.png",
"visibilityKM": 24.135,
"visibilityMI": 15,
"uvi": 6,
"solradWM2": 5486,
"solradMinWM2": 0,
"solradMaxWM2": 742,
"isDay": true,
"maxCoverage": "",
"sunrise": 1665114710,
"sunset": 1665156831,
"sunriseISO": "2022-10-07T05:51:50+02:00",
"sunsetISO": "2022-10-07T17:33:51+02:00"
},
{
"timestamp": 1665205200,
"validTime": "2022-10-08T07:00:00+02:00",
"dateTimeISO": "2022-10-08T07:00:00+02:00",
"maxTempC": 30,
"maxTempF": 87,
"minTempC": 19,
"minTempF": 66,
"avgTempC": 25,
"avgTempF": 76,
"tempC": null,
"tempF": null,
"maxFeelslikeC": 30,
"maxFeelslikeF": 86,
"minFeelslikeC": 19,
"minFeelslikeF": 67,
"avgFeelslikeC": 25,
"avgFeelslikeF": 76,
"feelslikeC": 19,
"feelslikeF": 67,
"maxDewpointC": 15,
"maxDewpointF": 59,
"minDewpointC": 11,
"minDewpointF": 52,
"avgDewpointC": 13,
"avgDewpointF": 56,
"dewpointC": 15,
"dewpointF": 59,
"maxHumidity": 76,
"minHumidity": 32,
"humidity": 76,
"pop": 0,
"precipMM": 0,
"precipIN": 0,
"iceaccum": null,
"iceaccumMM": null,
"iceaccumIN": null,
"snowCM": 0,
"snowIN": 0,
"pressureMB": 1014,
"pressureIN": 29.94,
"windDir": "NNE",
"windDirDEG": 21,
"windSpeedKTS": 1,
"windSpeedKPH": 2,
"windSpeedMPH": 1,
"windGustKTS": 17,
"windGustKPH": 32,
"windGustMPH": 20,
"windDirMax": "WNW",
"windDirMaxDEG": 301,
"windSpeedMaxKTS": 7,
"windSpeedMaxKPH": 13,
"windSpeedMaxMPH": 8,
"windDirMin": "NNE",
"windDirMinDEG": 21,
"windSpeedMinKTS": 1,
"windSpeedMinKPH": 2,
"windSpeedMinMPH": 1,
"windDir80m": "NW",
"windDir80mDEG": 309,
"windSpeed80mKTS": 5,
"windSpeed80mKPH": 9,
"windSpeed80mMPH": 5,
"windGust80mKTS": 17,
"windGust80mKPH": 31,
"windGust80mMPH": 19,
"windDirMax80m": "NW",
"windDirMax80mDEG": 322,
"windSpeedMax80mKTS": 17,
"windSpeedMax80mKPH": 31,
"windSpeedMax80mMPH": 19,
"windDirMin80m": "NW",
"windDirMin80mDEG": 309,
"windSpeedMin80mKTS": 5,
"windSpeedMin80mKPH": 9,
"windSpeedMin80mMPH": 5,
"sky": 47,
"cloudsCoded": "SC",
"weather": "Partly Cloudy",
"weatherCoded": [],
"weatherPrimary": "Partly Cloudy",
"weatherPrimaryCoded": "::SC",
"icon": "pcloudy.png",
"visibilityKM": 24.135,
"visibilityMI": 15,
"uvi": 7,
"solradWM2": 4785,
"solradMinWM2": 0,
"solradMaxWM2": 682,
"isDay": true,
"maxCoverage": "",
"sunrise": 1665201146,
"sunset": 1665243161,
"sunriseISO": "2022-10-08T05:52:26+02:00",
"sunsetISO": "2022-10-08T17:32:41+02:00"
},
{
"timestamp": 1665291600,
"validTime": "2022-10-09T07:00:00+02:00",
"dateTimeISO": "2022-10-09T07:00:00+02:00",
"maxTempC": 31,
"maxTempF": 87,
"minTempC": 19,
"minTempF": 67,
"avgTempC": 25,
"avgTempF": 77,
"tempC": null,
"tempF": null,
"maxFeelslikeC": 30,
"maxFeelslikeF": 86,
"minFeelslikeC": 20,
"minFeelslikeF": 67,
"avgFeelslikeC": 25,
"avgFeelslikeF": 77,
"feelslikeC": 20,
"feelslikeF": 67,
"maxDewpointC": 17,
"maxDewpointF": 63,
"minDewpointC": 11,
"minDewpointF": 52,
"avgDewpointC": 14,
"avgDewpointF": 57,
"dewpointC": 17,
"dewpointF": 63,
"maxHumidity": 86,
"minHumidity": 31,
"humidity": 86,
"pop": 0,
"precipMM": 0,
"precipIN": 0,
"iceaccum": null,
"iceaccumMM": null,
"iceaccumIN": null,
"snowCM": 0,
"snowIN": 0,
"pressureMB": 1016,
"pressureIN": 29.99,
"windDir": "N",
"windDirDEG": 356,
"windSpeedKTS": 2,
"windSpeedKPH": 4,
"windSpeedMPH": 2,
"windGustKTS": 19,
"windGustKPH": 36,
"windGustMPH": 22,
"windDirMax": "NNW",
"windDirMaxDEG": 343,
"windSpeedMaxKTS": 8,
"windSpeedMaxKPH": 14,
"windSpeedMaxMPH": 9,
"windDirMin": "N",
"windDirMinDEG": 356,
"windSpeedMinKTS": 2,
"windSpeedMinKPH": 4,
"windSpeedMinMPH": 2,
"windDir80m": "NW",
"windDir80mDEG": 316,
"windSpeed80mKTS": 5,
"windSpeed80mKPH": 9,
"windSpeed80mMPH": 6,
"windGust80mKTS": 20,
"windGust80mKPH": 36,
"windGust80mMPH": 23,
"windDirMax80m": "N",
"windDirMax80mDEG": 354,
"windSpeedMax80mKTS": 20,
"windSpeedMax80mKPH": 36,
"windSpeedMax80mMPH": 23,
"windDirMin80m": "NW",
"windDirMin80mDEG": 316,
"windSpeedMin80mKTS": 5,
"windSpeedMin80mKPH": 9,
"windSpeedMin80mMPH": 6,
"sky": 47,
"cloudsCoded": "SC",
"weather": "Partly Cloudy",
"weatherCoded": [],
"weatherPrimary": "Partly Cloudy",
"weatherPrimaryCoded": "::SC",
"icon": "pcloudy.png",
"visibilityKM": 24.135,
"visibilityMI": 15,
"uvi": 7,
"solradWM2": 4768,
"solradMinWM2": 0,
"solradMaxWM2": 726,
"isDay": true,
"maxCoverage": "",
"sunrise": 1665287583,
"sunset": 1665329491,
"sunriseISO": "2022-10-09T05:53:03+02:00",
"sunsetISO": "2022-10-09T17:31:31+02:00"
},
{
"timestamp": 1665378000,
"validTime": "2022-10-10T07:00:00+02:00",
"dateTimeISO": "2022-10-10T07:00:00+02:00",
"maxTempC": 31,
"maxTempF": 87,
"minTempC": 21,
"minTempF": 70,
"avgTempC": 26,
"avgTempF": 78,
"tempC": null,
"tempF": null,
"maxFeelslikeC": 30,
"maxFeelslikeF": 86,
"minFeelslikeC": 21,
"minFeelslikeF": 69,
"avgFeelslikeC": 25,
"avgFeelslikeF": 78,
"feelslikeC": 21,
"feelslikeF": 69,
"maxDewpointC": 16,
"maxDewpointF": 61,
"minDewpointC": 13,
"minDewpointF": 55,
"avgDewpointC": 14,
"avgDewpointF": 58,
"dewpointC": 16,
"dewpointF": 61,
"maxHumidity": 75,
"minHumidity": 35,
"humidity": 75,
"pop": 0,
"precipMM": 0,
"precipIN": 0,
"iceaccum": null,
"iceaccumMM": null,
"iceaccumIN": null,
"snowCM": 0,
"snowIN": 0,
"pressureMB": 1017,
"pressureIN": 30.03,
"windDir": "N",
"windDirDEG": 358,
"windSpeedKTS": 2,
"windSpeedKPH": 4,
"windSpeedMPH": 2,
"windGustKTS": 16,
"windGustKPH": 30,
"windGustMPH": 19,
"windDirMax": "N",
"windDirMaxDEG": 10,
"windSpeedMaxKTS": 8,
"windSpeedMaxKPH": 15,
"windSpeedMaxMPH": 9,
"windDirMin": "N",
"windDirMinDEG": 358,
"windSpeedMinKTS": 2,
"windSpeedMinKPH": 4,
"windSpeedMinMPH": 2,
"windDir80m": "N",
"windDir80mDEG": 8,
"windSpeed80mKTS": 7,
"windSpeed80mKPH": 13,
"windSpeed80mMPH": 8,
"windGust80mKTS": 19,
"windGust80mKPH": 36,
"windGust80mMPH": 22,
"windDirMax80m": "N",
"windDirMax80mDEG": 10,
"windSpeedMax80mKTS": 19,
"windSpeedMax80mKPH": 36,
"windSpeedMax80mMPH": 22,
"windDirMin80m": "E",
"windDirMin80mDEG": 91,
"windSpeedMin80mKTS": 7,
"windSpeedMin80mKPH": 13,
"windSpeedMin80mMPH": 8,
"sky": 64,
"cloudsCoded": "SC",
"weather": "Partly Cloudy",
"weatherCoded": [],
"weatherPrimary": "Partly Cloudy",
"weatherPrimaryCoded": "::SC",
"icon": "pcloudy.png",
"visibilityKM": 24.135,
"visibilityMI": 15,
"uvi": 6,
"solradWM2": 4494,
"solradMinWM2": 0,
"solradMaxWM2": 597,
"isDay": true,
"maxCoverage": "",
"sunrise": 1665374020,
"sunset": 1665415821,
"sunriseISO": "2022-10-10T05:53:40+02:00",
"sunsetISO": "2022-10-10T17:30:21+02:00"
},
{
"timestamp": 1665464400,
"validTime": "2022-10-11T07:00:00+02:00",
"dateTimeISO": "2022-10-11T07:00:00+02:00",
"maxTempC": 31,
"maxTempF": 87,
"minTempC": 21,
"minTempF": 70,
"avgTempC": 26,
"avgTempF": 78,
"tempC": null,
"tempF": null,
"maxFeelslikeC": 31,
"maxFeelslikeF": 87,
"minFeelslikeC": 22,
"minFeelslikeF": 72,
"avgFeelslikeC": 26,
"avgFeelslikeF": 79,
"feelslikeC": 22,
"feelslikeF": 72,
"maxDewpointC": 17,
"maxDewpointF": 62,
"minDewpointC": 11,
"minDewpointF": 51,
"avgDewpointC": 13,
"avgDewpointF": 55,
"dewpointC": 17,
"dewpointF": 62,
"maxHumidity": 71,
"minHumidity": 30,
"humidity": 71,
"pop": 0,
"precipMM": 0,
"precipIN": 0,
"iceaccum": null,
"iceaccumMM": null,
"iceaccumIN": null,
"snowCM": 0,
"snowIN": 0,
"pressureMB": 1015,
"pressureIN": 29.98,
"windDir": "NNE",
"windDirDEG": 13,
"windSpeedKTS": 8,
"windSpeedKPH": 15,
"windSpeedMPH": 9,
"windGustKTS": 15,
"windGustKPH": 28,
"windGustMPH": 17,
"windDirMax": "NNE",
"windDirMaxDEG": 28,
"windSpeedMaxKTS": 15,
"windSpeedMaxKPH": 28,
"windSpeedMaxMPH": 18,
"windDirMin": "NNE",
"windDirMinDEG": 14,
"windSpeedMinKTS": 7,
"windSpeedMinKPH": 14,
"windSpeedMinMPH": 8,
"windDir80m": "NNE",
"windDir80mDEG": 16,
"windSpeed80mKTS": 10,
"windSpeed80mKPH": 19,
"windSpeed80mMPH": 12,
"windGust80mKTS": 17,
"windGust80mKPH": 31,
"windGust80mMPH": 19,
"windDirMax80m": "NNE",
"windDirMax80mDEG": 28,
"windSpeedMax80mKTS": 17,
"windSpeedMax80mKPH": 31,
"windSpeedMax80mMPH": 19,
"windDirMin80m": "NNE",
"windDirMin80mDEG": 13,
"windSpeedMin80mKTS": 9,
"windSpeedMin80mKPH": 18,
"windSpeedMin80mMPH": 11,
"sky": 0,
"cloudsCoded": "CL",
"weather": "Sunny",
"weatherCoded": [],
"weatherPrimary": "Sunny",
"weatherPrimaryCoded": "::CL",
"icon": "sunny.png",
"visibilityKM": 24.135,
"visibilityMI": 15,
"uvi": null,
"solradWM2": 5450,
"solradMinWM2": 0,
"solradMaxWM2": 758,
"isDay": true,
"maxCoverage": "",
"sunrise": 1665460458,
"sunset": 1665502153,
"sunriseISO": "2022-10-11T05:54:18+02:00",
"sunsetISO": "2022-10-11T17:29:13+02:00"
},
{
"timestamp": 1665550800,
"validTime": "2022-10-12T07:00:00+02:00",
"dateTimeISO": "2022-10-12T07:00:00+02:00",
"maxTempC": 31,
"maxTempF": 88,
"minTempC": 21,
"minTempF": 69,
"avgTempC": 26,
"avgTempF": 79,
"tempC": null,
"tempF": null,
"maxFeelslikeC": 31,
"maxFeelslikeF": 88,
"minFeelslikeC": 22,
"minFeelslikeF": 72,
"avgFeelslikeC": 26,
"avgFeelslikeF": 80,
"feelslikeC": 22,
"feelslikeF": 72,
"maxDewpointC": 16,
"maxDewpointF": 60,
"minDewpointC": 11,
"minDewpointF": 51,
"avgDewpointC": 13,
"avgDewpointF": 55,
"dewpointC": 16,
"dewpointF": 60,
"maxHumidity": 68,
"minHumidity": 29,
"humidity": 68,
"pop": 0,
"precipMM": 0,
"precipIN": 0,
"iceaccum": null,
"iceaccumMM": null,
"iceaccumIN": null,
"snowCM": 0,
"snowIN": 0,
"pressureMB": 1014,
"pressureIN": 29.95,
"windDir": "NNE",
"windDirDEG": 12,
"windSpeedKTS": 8,
"windSpeedKPH": 15,
"windSpeedMPH": 9,
"windGustKTS": 15,
"windGustKPH": 28,
"windGustMPH": 17,
"windDirMax": "E",
"windDirMaxDEG": 96,
"windSpeedMaxKTS": 14,
"windSpeedMaxKPH": 26,
"windSpeedMaxMPH": 16,
"windDirMin": "NNE",
"windDirMinDEG": 12,
"windSpeedMinKTS": 7,
"windSpeedMinKPH": 13,
"windSpeedMinMPH": 8,
"windDir80m": "NNE",
"windDir80mDEG": 15,
"windSpeed80mKTS": 10,
"windSpeed80mKPH": 19,
"windSpeed80mMPH": 12,
"windGust80mKTS": 18,
"windGust80mKPH": 33,
"windGust80mMPH": 21,
"windDirMax80m": "E",
"windDirMax80mDEG": 96,
"windSpeedMax80mKTS": 18,
"windSpeedMax80mKPH": 33,
"windSpeedMax80mMPH": 21,
"windDirMin80m": "NNE",
"windDirMin80mDEG": 15,
"windSpeedMin80mKTS": 10,
"windSpeedMin80mKPH": 18,
"windSpeedMin80mMPH": 11,
"sky": 27,
"cloudsCoded": "FW",
"weather": "Mostly Sunny",
"weatherCoded": [],
"weatherPrimary": "Mostly Sunny",
"weatherPrimaryCoded": "::FW",
"icon": "fair.png",
"visibilityKM": 24.135,
"visibilityMI": 15,
"uvi": null,
"solradWM2": 4740,
"solradMinWM2": 0,
"solradMaxWM2": 743,
"isDay": true,
"maxCoverage": "",
"sunrise": 1665546895,
"sunset": 1665588484,
"sunriseISO": "2022-10-12T05:54:55+02:00",
"sunsetISO": "2022-10-12T17:28:04+02:00"
}
],
"profile": {
"tz": "Africa/Cairo",
"elevM": 23,
"elevFT": 75
}
}
]
}

View File

@@ -0,0 +1,691 @@
<success>true</success>
<response>
<loc>
<long>31.25</long>
<lat>30.063</lat>
</loc>
<profile>
<elevM>23</elevM>
<tz>Africa/Cairo</tz>
<elevFT>75</elevFT>
</profile>
<periods>
<dateTimeISO>2022-10-06T07:00:00+02:00</dateTimeISO>
<windDirMin80m>E</windDirMin80m>
<windDirMin80mDEG>95</windDirMin80mDEG>
<feelslikeC>21</feelslikeC>
<visibilityMI>15</visibilityMI>
<windSpeedMaxMPH>10</windSpeedMaxMPH>
<windDirDEG>353</windDirDEG>
<windDir>N</windDir>
<sunriseISO>2022-10-06T05:51:14+02:00</sunriseISO>
<iceaccumMM>null</iceaccumMM>
<windSpeedMaxKTS>9</windSpeedMaxKTS>
<iceaccumIN>null</iceaccumIN>
<minTempF>66</minTempF>
<snowIN>0</snowIN>
<weather>Mostly Sunny</weather>
<sunsetISO>2022-10-06T17:35:02+02:00</sunsetISO>
<maxFeelslikeC>32</maxFeelslikeC>
<humidity>77</humidity>
<windDir80m>N</windDir80m>
<maxFeelslikeF>89</maxFeelslikeF>
<precipMM>0</precipMM>
<sky>22</sky>
<windGust80mMPH>25</windGust80mMPH>
<windSpeedMax80mMPH>25</windSpeedMax80mMPH>
<weatherPrimary>Mostly Sunny</weatherPrimary>
<windGust80mKPH>41</windGust80mKPH>
<avgDewpointF>58</avgDewpointF>
<windSpeedMax80mKPH>41</windSpeedMax80mKPH>
<windGust80mKTS>22</windGust80mKTS>
<avgDewpointC>14</avgDewpointC>
<precipIN>0</precipIN>
<windSpeedMax80mKTS>22</windSpeedMax80mKTS>
<windDirMinDEG>353</windDirMinDEG>
<windSpeedMaxKPH>16</windSpeedMaxKPH>
<windSpeedMin80mKTS>8</windSpeedMin80mKTS>
<feelslikeF>70</feelslikeF>
<validTime>2022-10-06T07:00:00+02:00</validTime>
<windSpeedMin80mMPH>10</windSpeedMin80mMPH>
<solradMaxWM2>778</solradMaxWM2>
<avgTempC>25</avgTempC>
<windSpeedMin80mKPH>15</windSpeedMin80mKPH>
<weatherPrimaryCoded>::FW</weatherPrimaryCoded>
<sunrise>1665028274</sunrise>
<avgTempF>78</avgTempF>
<windDirMin>N</windDirMin>
<maxCoverage/>
<icon>fair.png</icon>
<minFeelslikeC>21</minFeelslikeC>
<dewpointC>17</dewpointC>
<cloudsCoded>FW</cloudsCoded>
<minFeelslikeF>70</minFeelslikeF>
<minHumidity>29</minHumidity>
<dewpointF>63</dewpointF>
<windSpeed80mKTS>12</windSpeed80mKTS>
<pop>0</pop>
<snowCM>0</snowCM>
<windDirMax>NNW</windDirMax>
<windSpeed80mMPH>13</windSpeed80mMPH>
<windSpeed80mKPH>22</windSpeed80mKPH>
<windDir80mDEG>11</windDir80mDEG>
<maxTempC>32</maxTempC>
<pressureMB>1015</pressureMB>
<visibilityKM>24.135</visibilityKM>
<timestamp>1665032400</timestamp>
<maxTempF>90</maxTempF>
<tempF>null</tempF>
<minDewpointC>11</minDewpointC>
<solradMinWM2>0</solradMinWM2>
<windSpeedMinKTS>1</windSpeedMinKTS>
<windDirMax80mDEG>343</windDirMax80mDEG>
<windGustKTS>21</windGustKTS>
<windSpeedMinKPH>2</windSpeedMinKPH>
<maxDewpointF>63</maxDewpointF>
<windSpeedMinMPH>1</windSpeedMinMPH>
<avgFeelslikeC>26</avgFeelslikeC>
<uvi>6</uvi>
<windDirMax80m>NNW</windDirMax80m>
<maxDewpointC>17</maxDewpointC>
<pressureIN>29.97</pressureIN>
<avgFeelslikeF>80</avgFeelslikeF>
<iceaccum>null</iceaccum>
<isDay>true</isDay>
<minTempC>19</minTempC>
<minDewpointF>52</minDewpointF>
<windSpeedKTS>5</windSpeedKTS>
<sunset>1665070502</sunset>
<solradWM2>5608</solradWM2>
<windSpeedKPH>9</windSpeedKPH>
<windGustMPH>25</windGustMPH>
<maxHumidity>77</maxHumidity>
<windSpeedMPH>6</windSpeedMPH>
<windGustKPH>40</windGustKPH>
<windDirMaxDEG>342</windDirMaxDEG>
<tempC>null</tempC>
</periods>
<periods>
<dateTimeISO>2022-10-07T07:00:00+02:00</dateTimeISO>
<windDirMin80m>NNW</windDirMin80m>
<windDirMin80mDEG>347</windDirMin80mDEG>
<feelslikeC>19</feelslikeC>
<visibilityMI>15</visibilityMI>
<windSpeedMaxMPH>8</windSpeedMaxMPH>
<windDirDEG>325</windDirDEG>
<windDir>NW</windDir>
<sunriseISO>2022-10-07T05:51:50+02:00</sunriseISO>
<iceaccumMM>null</iceaccumMM>
<windSpeedMaxKTS>7</windSpeedMaxKTS>
<iceaccumIN>null</iceaccumIN>
<minTempF>66</minTempF>
<snowIN>0</snowIN>
<weather>Mostly Sunny</weather>
<sunsetISO>2022-10-07T17:33:51+02:00</sunsetISO>
<maxFeelslikeC>29</maxFeelslikeC>
<humidity>77</humidity>
<windDir80m>NNW</windDir80m>
<maxFeelslikeF>85</maxFeelslikeF>
<precipMM>0</precipMM>
<sky>30</sky>
<windGust80mMPH>23</windGust80mMPH>
<windSpeedMax80mMPH>23</windSpeedMax80mMPH>
<weatherPrimary>Mostly Sunny</weatherPrimary>
<windGust80mKPH>37</windGust80mKPH>
<avgDewpointF>54</avgDewpointF>
<windSpeedMax80mKPH>37</windSpeedMax80mKPH>
<windGust80mKTS>20</windGust80mKTS>
<avgDewpointC>12</avgDewpointC>
<precipIN>0</precipIN>
<windSpeedMax80mKTS>20</windSpeedMax80mKTS>
<windDirMinDEG>325</windDirMinDEG>
<windSpeedMaxKPH>13</windSpeedMaxKPH>
<windSpeedMin80mKTS>6</windSpeedMin80mKTS>
<feelslikeF>67</feelslikeF>
<validTime>2022-10-07T07:00:00+02:00</validTime>
<windSpeedMin80mMPH>6</windSpeedMin80mMPH>
<solradMaxWM2>742</solradMaxWM2>
<avgTempC>24</avgTempC>
<windSpeedMin80mKPH>10</windSpeedMin80mKPH>
<weatherPrimaryCoded>::FW</weatherPrimaryCoded>
<sunrise>1665114710</sunrise>
<avgTempF>76</avgTempF>
<windDirMin>NW</windDirMin>
<maxCoverage/>
<icon>fair.png</icon>
<minFeelslikeC>19</minFeelslikeC>
<dewpointC>15</dewpointC>
<cloudsCoded>FW</cloudsCoded>
<minFeelslikeF>67</minFeelslikeF>
<minHumidity>30</minHumidity>
<dewpointF>60</dewpointF>
<windSpeed80mKTS>6</windSpeed80mKTS>
<pop>0</pop>
<snowCM>0</snowCM>
<windDirMax>WNW</windDirMax>
<windSpeed80mMPH>6</windSpeed80mMPH>
<windSpeed80mKPH>10</windSpeed80mKPH>
<windDir80mDEG>347</windDir80mDEG>
<maxTempC>30</maxTempC>
<pressureMB>1014</pressureMB>
<visibilityKM>24.135</visibilityKM>
<timestamp>1665118800</timestamp>
<maxTempF>86</maxTempF>
<tempF>null</tempF>
<minDewpointC>10</minDewpointC>
<solradMinWM2>0</solradMinWM2>
<windSpeedMinKTS>1</windSpeedMinKTS>
<windDirMax80mDEG>316</windDirMax80mDEG>
<windGustKTS>16</windGustKTS>
<windSpeedMinKPH>2</windSpeedMinKPH>
<maxDewpointF>60</maxDewpointF>
<windSpeedMinMPH>1</windSpeedMinMPH>
<avgFeelslikeC>24</avgFeelslikeC>
<uvi>6</uvi>
<windDirMax80m>NW</windDirMax80m>
<maxDewpointC>15</maxDewpointC>
<pressureIN>29.95</pressureIN>
<avgFeelslikeF>76</avgFeelslikeF>
<iceaccum>null</iceaccum>
<isDay>true</isDay>
<minTempC>19</minTempC>
<minDewpointF>50</minDewpointF>
<windSpeedKTS>1</windSpeedKTS>
<sunset>1665156831</sunset>
<solradWM2>5486</solradWM2>
<windSpeedKPH>2</windSpeedKPH>
<windGustMPH>18</windGustMPH>
<maxHumidity>77</maxHumidity>
<windSpeedMPH>1</windSpeedMPH>
<windGustKPH>29</windGustKPH>
<windDirMaxDEG>298</windDirMaxDEG>
<tempC>null</tempC>
</periods>
<periods>
<dateTimeISO>2022-10-08T07:00:00+02:00</dateTimeISO>
<windDirMin80m>NW</windDirMin80m>
<windDirMin80mDEG>309</windDirMin80mDEG>
<feelslikeC>19</feelslikeC>
<visibilityMI>15</visibilityMI>
<windSpeedMaxMPH>8</windSpeedMaxMPH>
<windDirDEG>21</windDirDEG>
<windDir>NNE</windDir>
<sunriseISO>2022-10-08T05:52:26+02:00</sunriseISO>
<iceaccumMM>null</iceaccumMM>
<windSpeedMaxKTS>7</windSpeedMaxKTS>
<iceaccumIN>null</iceaccumIN>
<minTempF>66</minTempF>
<snowIN>0</snowIN>
<weather>Partly Cloudy</weather>
<sunsetISO>2022-10-08T17:32:41+02:00</sunsetISO>
<maxFeelslikeC>30</maxFeelslikeC>
<humidity>76</humidity>
<windDir80m>NW</windDir80m>
<maxFeelslikeF>86</maxFeelslikeF>
<precipMM>0</precipMM>
<sky>47</sky>
<windGust80mMPH>19</windGust80mMPH>
<windSpeedMax80mMPH>19</windSpeedMax80mMPH>
<weatherPrimary>Partly Cloudy</weatherPrimary>
<windGust80mKPH>31</windGust80mKPH>
<avgDewpointF>56</avgDewpointF>
<windSpeedMax80mKPH>31</windSpeedMax80mKPH>
<windGust80mKTS>17</windGust80mKTS>
<avgDewpointC>13</avgDewpointC>
<precipIN>0</precipIN>
<windSpeedMax80mKTS>17</windSpeedMax80mKTS>
<windDirMinDEG>21</windDirMinDEG>
<windSpeedMaxKPH>13</windSpeedMaxKPH>
<windSpeedMin80mKTS>5</windSpeedMin80mKTS>
<feelslikeF>67</feelslikeF>
<validTime>2022-10-08T07:00:00+02:00</validTime>
<windSpeedMin80mMPH>5</windSpeedMin80mMPH>
<solradMaxWM2>682</solradMaxWM2>
<avgTempC>25</avgTempC>
<windSpeedMin80mKPH>9</windSpeedMin80mKPH>
<weatherPrimaryCoded>::SC</weatherPrimaryCoded>
<sunrise>1665201146</sunrise>
<avgTempF>76</avgTempF>
<windDirMin>NNE</windDirMin>
<maxCoverage/>
<icon>pcloudy.png</icon>
<minFeelslikeC>19</minFeelslikeC>
<dewpointC>15</dewpointC>
<cloudsCoded>SC</cloudsCoded>
<minFeelslikeF>67</minFeelslikeF>
<minHumidity>32</minHumidity>
<dewpointF>59</dewpointF>
<windSpeed80mKTS>5</windSpeed80mKTS>
<pop>0</pop>
<snowCM>0</snowCM>
<windDirMax>WNW</windDirMax>
<windSpeed80mMPH>5</windSpeed80mMPH>
<windSpeed80mKPH>9</windSpeed80mKPH>
<windDir80mDEG>309</windDir80mDEG>
<maxTempC>30</maxTempC>
<pressureMB>1014</pressureMB>
<visibilityKM>24.135</visibilityKM>
<timestamp>1665205200</timestamp>
<maxTempF>87</maxTempF>
<tempF>null</tempF>
<minDewpointC>11</minDewpointC>
<solradMinWM2>0</solradMinWM2>
<windSpeedMinKTS>1</windSpeedMinKTS>
<windDirMax80mDEG>322</windDirMax80mDEG>
<windGustKTS>17</windGustKTS>
<windSpeedMinKPH>2</windSpeedMinKPH>
<maxDewpointF>59</maxDewpointF>
<windSpeedMinMPH>1</windSpeedMinMPH>
<avgFeelslikeC>25</avgFeelslikeC>
<uvi>7</uvi>
<windDirMax80m>NW</windDirMax80m>
<maxDewpointC>15</maxDewpointC>
<pressureIN>29.94</pressureIN>
<avgFeelslikeF>76</avgFeelslikeF>
<iceaccum>null</iceaccum>
<isDay>true</isDay>
<minTempC>19</minTempC>
<minDewpointF>52</minDewpointF>
<windSpeedKTS>1</windSpeedKTS>
<sunset>1665243161</sunset>
<solradWM2>4785</solradWM2>
<windSpeedKPH>2</windSpeedKPH>
<windGustMPH>20</windGustMPH>
<maxHumidity>76</maxHumidity>
<windSpeedMPH>1</windSpeedMPH>
<windGustKPH>32</windGustKPH>
<windDirMaxDEG>301</windDirMaxDEG>
<tempC>null</tempC>
</periods>
<periods>
<dateTimeISO>2022-10-09T07:00:00+02:00</dateTimeISO>
<windDirMin80m>NW</windDirMin80m>
<windDirMin80mDEG>316</windDirMin80mDEG>
<feelslikeC>20</feelslikeC>
<visibilityMI>15</visibilityMI>
<windSpeedMaxMPH>9</windSpeedMaxMPH>
<windDirDEG>356</windDirDEG>
<windDir>N</windDir>
<sunriseISO>2022-10-09T05:53:03+02:00</sunriseISO>
<iceaccumMM>null</iceaccumMM>
<windSpeedMaxKTS>8</windSpeedMaxKTS>
<iceaccumIN>null</iceaccumIN>
<minTempF>67</minTempF>
<snowIN>0</snowIN>
<weather>Partly Cloudy</weather>
<sunsetISO>2022-10-09T17:31:31+02:00</sunsetISO>
<maxFeelslikeC>30</maxFeelslikeC>
<humidity>86</humidity>
<windDir80m>NW</windDir80m>
<maxFeelslikeF>86</maxFeelslikeF>
<precipMM>0</precipMM>
<sky>47</sky>
<windGust80mMPH>23</windGust80mMPH>
<windSpeedMax80mMPH>23</windSpeedMax80mMPH>
<weatherPrimary>Partly Cloudy</weatherPrimary>
<windGust80mKPH>36</windGust80mKPH>
<avgDewpointF>57</avgDewpointF>
<windSpeedMax80mKPH>36</windSpeedMax80mKPH>
<windGust80mKTS>20</windGust80mKTS>
<avgDewpointC>14</avgDewpointC>
<precipIN>0</precipIN>
<windSpeedMax80mKTS>20</windSpeedMax80mKTS>
<windDirMinDEG>356</windDirMinDEG>
<windSpeedMaxKPH>14</windSpeedMaxKPH>
<windSpeedMin80mKTS>5</windSpeedMin80mKTS>
<feelslikeF>67</feelslikeF>
<validTime>2022-10-09T07:00:00+02:00</validTime>
<windSpeedMin80mMPH>6</windSpeedMin80mMPH>
<solradMaxWM2>726</solradMaxWM2>
<avgTempC>25</avgTempC>
<windSpeedMin80mKPH>9</windSpeedMin80mKPH>
<weatherPrimaryCoded>::SC</weatherPrimaryCoded>
<sunrise>1665287583</sunrise>
<avgTempF>77</avgTempF>
<windDirMin>N</windDirMin>
<maxCoverage/>
<icon>pcloudy.png</icon>
<minFeelslikeC>20</minFeelslikeC>
<dewpointC>17</dewpointC>
<cloudsCoded>SC</cloudsCoded>
<minFeelslikeF>67</minFeelslikeF>
<minHumidity>31</minHumidity>
<dewpointF>63</dewpointF>
<windSpeed80mKTS>5</windSpeed80mKTS>
<pop>0</pop>
<snowCM>0</snowCM>
<windDirMax>NNW</windDirMax>
<windSpeed80mMPH>6</windSpeed80mMPH>
<windSpeed80mKPH>9</windSpeed80mKPH>
<windDir80mDEG>316</windDir80mDEG>
<maxTempC>31</maxTempC>
<pressureMB>1016</pressureMB>
<visibilityKM>24.135</visibilityKM>
<timestamp>1665291600</timestamp>
<maxTempF>87</maxTempF>
<tempF>null</tempF>
<minDewpointC>11</minDewpointC>
<solradMinWM2>0</solradMinWM2>
<windSpeedMinKTS>2</windSpeedMinKTS>
<windDirMax80mDEG>354</windDirMax80mDEG>
<windGustKTS>19</windGustKTS>
<windSpeedMinKPH>4</windSpeedMinKPH>
<maxDewpointF>63</maxDewpointF>
<windSpeedMinMPH>2</windSpeedMinMPH>
<avgFeelslikeC>25</avgFeelslikeC>
<uvi>7</uvi>
<windDirMax80m>N</windDirMax80m>
<maxDewpointC>17</maxDewpointC>
<pressureIN>29.99</pressureIN>
<avgFeelslikeF>77</avgFeelslikeF>
<iceaccum>null</iceaccum>
<isDay>true</isDay>
<minTempC>19</minTempC>
<minDewpointF>52</minDewpointF>
<windSpeedKTS>2</windSpeedKTS>
<sunset>1665329491</sunset>
<solradWM2>4768</solradWM2>
<windSpeedKPH>4</windSpeedKPH>
<windGustMPH>22</windGustMPH>
<maxHumidity>86</maxHumidity>
<windSpeedMPH>2</windSpeedMPH>
<windGustKPH>36</windGustKPH>
<windDirMaxDEG>343</windDirMaxDEG>
<tempC>null</tempC>
</periods>
<periods>
<dateTimeISO>2022-10-10T07:00:00+02:00</dateTimeISO>
<windDirMin80m>E</windDirMin80m>
<windDirMin80mDEG>91</windDirMin80mDEG>
<feelslikeC>21</feelslikeC>
<visibilityMI>15</visibilityMI>
<windSpeedMaxMPH>9</windSpeedMaxMPH>
<windDirDEG>358</windDirDEG>
<windDir>N</windDir>
<sunriseISO>2022-10-10T05:53:40+02:00</sunriseISO>
<iceaccumMM>null</iceaccumMM>
<windSpeedMaxKTS>8</windSpeedMaxKTS>
<iceaccumIN>null</iceaccumIN>
<minTempF>70</minTempF>
<snowIN>0</snowIN>
<weather>Partly Cloudy</weather>
<sunsetISO>2022-10-10T17:30:21+02:00</sunsetISO>
<maxFeelslikeC>30</maxFeelslikeC>
<humidity>75</humidity>
<windDir80m>N</windDir80m>
<maxFeelslikeF>86</maxFeelslikeF>
<precipMM>0</precipMM>
<sky>64</sky>
<windGust80mMPH>22</windGust80mMPH>
<windSpeedMax80mMPH>22</windSpeedMax80mMPH>
<weatherPrimary>Partly Cloudy</weatherPrimary>
<windGust80mKPH>36</windGust80mKPH>
<avgDewpointF>58</avgDewpointF>
<windSpeedMax80mKPH>36</windSpeedMax80mKPH>
<windGust80mKTS>19</windGust80mKTS>
<avgDewpointC>14</avgDewpointC>
<precipIN>0</precipIN>
<windSpeedMax80mKTS>19</windSpeedMax80mKTS>
<windDirMinDEG>358</windDirMinDEG>
<windSpeedMaxKPH>15</windSpeedMaxKPH>
<windSpeedMin80mKTS>7</windSpeedMin80mKTS>
<feelslikeF>69</feelslikeF>
<validTime>2022-10-10T07:00:00+02:00</validTime>
<windSpeedMin80mMPH>8</windSpeedMin80mMPH>
<solradMaxWM2>597</solradMaxWM2>
<avgTempC>26</avgTempC>
<windSpeedMin80mKPH>13</windSpeedMin80mKPH>
<weatherPrimaryCoded>::SC</weatherPrimaryCoded>
<sunrise>1665374020</sunrise>
<avgTempF>78</avgTempF>
<windDirMin>N</windDirMin>
<maxCoverage/>
<icon>pcloudy.png</icon>
<minFeelslikeC>21</minFeelslikeC>
<dewpointC>16</dewpointC>
<cloudsCoded>SC</cloudsCoded>
<minFeelslikeF>69</minFeelslikeF>
<minHumidity>35</minHumidity>
<dewpointF>61</dewpointF>
<windSpeed80mKTS>7</windSpeed80mKTS>
<pop>0</pop>
<snowCM>0</snowCM>
<windDirMax>N</windDirMax>
<windSpeed80mMPH>8</windSpeed80mMPH>
<windSpeed80mKPH>13</windSpeed80mKPH>
<windDir80mDEG>8</windDir80mDEG>
<maxTempC>31</maxTempC>
<pressureMB>1017</pressureMB>
<visibilityKM>24.135</visibilityKM>
<timestamp>1665378000</timestamp>
<maxTempF>87</maxTempF>
<tempF>null</tempF>
<minDewpointC>13</minDewpointC>
<solradMinWM2>0</solradMinWM2>
<windSpeedMinKTS>2</windSpeedMinKTS>
<windDirMax80mDEG>10</windDirMax80mDEG>
<windGustKTS>16</windGustKTS>
<windSpeedMinKPH>4</windSpeedMinKPH>
<maxDewpointF>61</maxDewpointF>
<windSpeedMinMPH>2</windSpeedMinMPH>
<avgFeelslikeC>25</avgFeelslikeC>
<uvi>6</uvi>
<windDirMax80m>N</windDirMax80m>
<maxDewpointC>16</maxDewpointC>
<pressureIN>30.03</pressureIN>
<avgFeelslikeF>78</avgFeelslikeF>
<iceaccum>null</iceaccum>
<isDay>true</isDay>
<minTempC>21</minTempC>
<minDewpointF>55</minDewpointF>
<windSpeedKTS>2</windSpeedKTS>
<sunset>1665415821</sunset>
<solradWM2>4494</solradWM2>
<windSpeedKPH>4</windSpeedKPH>
<windGustMPH>19</windGustMPH>
<maxHumidity>75</maxHumidity>
<windSpeedMPH>2</windSpeedMPH>
<windGustKPH>30</windGustKPH>
<windDirMaxDEG>10</windDirMaxDEG>
<tempC>null</tempC>
</periods>
<periods>
<dateTimeISO>2022-10-11T07:00:00+02:00</dateTimeISO>
<windDirMin80m>NNE</windDirMin80m>
<windDirMin80mDEG>13</windDirMin80mDEG>
<feelslikeC>22</feelslikeC>
<visibilityMI>15</visibilityMI>
<windSpeedMaxMPH>18</windSpeedMaxMPH>
<windDirDEG>13</windDirDEG>
<windDir>NNE</windDir>
<sunriseISO>2022-10-11T05:54:18+02:00</sunriseISO>
<iceaccumMM>null</iceaccumMM>
<windSpeedMaxKTS>15</windSpeedMaxKTS>
<iceaccumIN>null</iceaccumIN>
<minTempF>70</minTempF>
<snowIN>0</snowIN>
<weather>Sunny</weather>
<sunsetISO>2022-10-11T17:29:13+02:00</sunsetISO>
<maxFeelslikeC>31</maxFeelslikeC>
<humidity>71</humidity>
<windDir80m>NNE</windDir80m>
<maxFeelslikeF>87</maxFeelslikeF>
<precipMM>0</precipMM>
<sky>0</sky>
<windGust80mMPH>19</windGust80mMPH>
<windSpeedMax80mMPH>19</windSpeedMax80mMPH>
<weatherPrimary>Sunny</weatherPrimary>
<windGust80mKPH>31</windGust80mKPH>
<avgDewpointF>55</avgDewpointF>
<windSpeedMax80mKPH>31</windSpeedMax80mKPH>
<windGust80mKTS>17</windGust80mKTS>
<avgDewpointC>13</avgDewpointC>
<precipIN>0</precipIN>
<windSpeedMax80mKTS>17</windSpeedMax80mKTS>
<windDirMinDEG>14</windDirMinDEG>
<windSpeedMaxKPH>28</windSpeedMaxKPH>
<windSpeedMin80mKTS>9</windSpeedMin80mKTS>
<feelslikeF>72</feelslikeF>
<validTime>2022-10-11T07:00:00+02:00</validTime>
<windSpeedMin80mMPH>11</windSpeedMin80mMPH>
<solradMaxWM2>758</solradMaxWM2>
<avgTempC>26</avgTempC>
<windSpeedMin80mKPH>18</windSpeedMin80mKPH>
<weatherPrimaryCoded>::CL</weatherPrimaryCoded>
<sunrise>1665460458</sunrise>
<avgTempF>78</avgTempF>
<windDirMin>NNE</windDirMin>
<maxCoverage/>
<icon>sunny.png</icon>
<minFeelslikeC>22</minFeelslikeC>
<dewpointC>17</dewpointC>
<cloudsCoded>CL</cloudsCoded>
<minFeelslikeF>72</minFeelslikeF>
<minHumidity>30</minHumidity>
<dewpointF>62</dewpointF>
<windSpeed80mKTS>10</windSpeed80mKTS>
<pop>0</pop>
<snowCM>0</snowCM>
<windDirMax>NNE</windDirMax>
<windSpeed80mMPH>12</windSpeed80mMPH>
<windSpeed80mKPH>19</windSpeed80mKPH>
<windDir80mDEG>16</windDir80mDEG>
<maxTempC>31</maxTempC>
<pressureMB>1015</pressureMB>
<visibilityKM>24.135</visibilityKM>
<timestamp>1665464400</timestamp>
<maxTempF>87</maxTempF>
<tempF>null</tempF>
<minDewpointC>11</minDewpointC>
<solradMinWM2>0</solradMinWM2>
<windSpeedMinKTS>7</windSpeedMinKTS>
<windDirMax80mDEG>28</windDirMax80mDEG>
<windGustKTS>15</windGustKTS>
<windSpeedMinKPH>14</windSpeedMinKPH>
<maxDewpointF>62</maxDewpointF>
<windSpeedMinMPH>8</windSpeedMinMPH>
<avgFeelslikeC>26</avgFeelslikeC>
<uvi>null</uvi>
<windDirMax80m>NNE</windDirMax80m>
<maxDewpointC>17</maxDewpointC>
<pressureIN>29.98</pressureIN>
<avgFeelslikeF>79</avgFeelslikeF>
<iceaccum>null</iceaccum>
<isDay>true</isDay>
<minTempC>21</minTempC>
<minDewpointF>51</minDewpointF>
<windSpeedKTS>8</windSpeedKTS>
<sunset>1665502153</sunset>
<solradWM2>5450</solradWM2>
<windSpeedKPH>15</windSpeedKPH>
<windGustMPH>17</windGustMPH>
<maxHumidity>71</maxHumidity>
<windSpeedMPH>9</windSpeedMPH>
<windGustKPH>28</windGustKPH>
<windDirMaxDEG>28</windDirMaxDEG>
<tempC>null</tempC>
</periods>
<periods>
<dateTimeISO>2022-10-12T07:00:00+02:00</dateTimeISO>
<windDirMin80m>NNE</windDirMin80m>
<windDirMin80mDEG>15</windDirMin80mDEG>
<feelslikeC>22</feelslikeC>
<visibilityMI>15</visibilityMI>
<windSpeedMaxMPH>16</windSpeedMaxMPH>
<windDirDEG>12</windDirDEG>
<windDir>NNE</windDir>
<sunriseISO>2022-10-12T05:54:55+02:00</sunriseISO>
<iceaccumMM>null</iceaccumMM>
<windSpeedMaxKTS>14</windSpeedMaxKTS>
<iceaccumIN>null</iceaccumIN>
<minTempF>69</minTempF>
<snowIN>0</snowIN>
<weather>Mostly Sunny</weather>
<sunsetISO>2022-10-12T17:28:04+02:00</sunsetISO>
<maxFeelslikeC>31</maxFeelslikeC>
<humidity>68</humidity>
<windDir80m>NNE</windDir80m>
<maxFeelslikeF>88</maxFeelslikeF>
<precipMM>0</precipMM>
<sky>27</sky>
<windGust80mMPH>21</windGust80mMPH>
<windSpeedMax80mMPH>21</windSpeedMax80mMPH>
<weatherPrimary>Mostly Sunny</weatherPrimary>
<windGust80mKPH>33</windGust80mKPH>
<avgDewpointF>55</avgDewpointF>
<windSpeedMax80mKPH>33</windSpeedMax80mKPH>
<windGust80mKTS>18</windGust80mKTS>
<avgDewpointC>13</avgDewpointC>
<precipIN>0</precipIN>
<windSpeedMax80mKTS>18</windSpeedMax80mKTS>
<windDirMinDEG>12</windDirMinDEG>
<windSpeedMaxKPH>26</windSpeedMaxKPH>
<windSpeedMin80mKTS>10</windSpeedMin80mKTS>
<feelslikeF>72</feelslikeF>
<validTime>2022-10-12T07:00:00+02:00</validTime>
<windSpeedMin80mMPH>11</windSpeedMin80mMPH>
<solradMaxWM2>743</solradMaxWM2>
<avgTempC>26</avgTempC>
<windSpeedMin80mKPH>18</windSpeedMin80mKPH>
<weatherPrimaryCoded>::FW</weatherPrimaryCoded>
<sunrise>1665546895</sunrise>
<avgTempF>79</avgTempF>
<windDirMin>NNE</windDirMin>
<maxCoverage/>
<icon>fair.png</icon>
<minFeelslikeC>22</minFeelslikeC>
<dewpointC>16</dewpointC>
<cloudsCoded>FW</cloudsCoded>
<minFeelslikeF>72</minFeelslikeF>
<minHumidity>29</minHumidity>
<dewpointF>60</dewpointF>
<windSpeed80mKTS>10</windSpeed80mKTS>
<pop>0</pop>
<snowCM>0</snowCM>
<windDirMax>E</windDirMax>
<windSpeed80mMPH>12</windSpeed80mMPH>
<windSpeed80mKPH>19</windSpeed80mKPH>
<windDir80mDEG>15</windDir80mDEG>
<maxTempC>31</maxTempC>
<pressureMB>1014</pressureMB>
<visibilityKM>24.135</visibilityKM>
<timestamp>1665550800</timestamp>
<maxTempF>88</maxTempF>
<tempF>null</tempF>
<minDewpointC>11</minDewpointC>
<solradMinWM2>0</solradMinWM2>
<windSpeedMinKTS>7</windSpeedMinKTS>
<windDirMax80mDEG>96</windDirMax80mDEG>
<windGustKTS>15</windGustKTS>
<windSpeedMinKPH>13</windSpeedMinKPH>
<maxDewpointF>60</maxDewpointF>
<windSpeedMinMPH>8</windSpeedMinMPH>
<avgFeelslikeC>26</avgFeelslikeC>
<uvi>null</uvi>
<windDirMax80m>E</windDirMax80m>
<maxDewpointC>16</maxDewpointC>
<pressureIN>29.95</pressureIN>
<avgFeelslikeF>80</avgFeelslikeF>
<iceaccum>null</iceaccum>
<isDay>true</isDay>
<minTempC>21</minTempC>
<minDewpointF>51</minDewpointF>
<windSpeedKTS>8</windSpeedKTS>
<sunset>1665588484</sunset>
<solradWM2>4740</solradWM2>
<windSpeedKPH>15</windSpeedKPH>
<windGustMPH>17</windGustMPH>
<maxHumidity>68</maxHumidity>
<windSpeedMPH>9</windSpeedMPH>
<windGustKPH>28</windGustKPH>
<windDirMaxDEG>96</windDirMaxDEG>
<tempC>null</tempC>
</periods>
<interval>day</interval>
<place>
<country>eg</country>
<name>cairo</name>
<state>qh</state>
</place>
</response>
<error>null</error>