Commit 0baf0d63 authored by Evan W. Patton's avatar Evan W. Patton Committed by Jeffrey Schiller

Implement dictionary-based JSON and XML methods in Web component

Change-Id: Ie8ff8124d159d03d9e099e15f4c0a61a0355e893
parent f1e1863f
......@@ -1586,6 +1586,12 @@ public final class YoungAndroidFormUpgrader {
// timeout behavior.
srcCompVersion = 6;
}
if (srcCompVersion < 7) {
// The JsonTextDecodeWithDictionaries was added to parse JSON using dictionaries.
// The XMLTextDecodeAsDictionary was added to provide a more robust representation
// of XML using dictionaries.
srcCompVersion = 7;
}
return srcCompVersion;
}
......
......@@ -2628,7 +2628,10 @@ Blockly.Versioning.AllUpgradeMaps =
5: "noUpgrade",
// AI2: Added property Timeout and event TimedOut
6: "noUpgrade"
6: "noUpgrade",
// AI2: Added methods JsonTextDecodeWithDictionaries and XMLTextDecodeAsDictionary
7: "noUpgrade"
}, // End Web upgraders
......
......@@ -490,6 +490,7 @@ public class YaVersion {
// - WEBVIEWER_COMPONENT_VERSION was incremented to 8
// For YOUNG_ANDROID_VERSION 197:
// - BLOCKS_LANGUAGE_VERSION was incremented to 28
// - WEB_COMPONENT_VERSION was incremented to 7
public static final int YOUNG_ANDROID_VERSION = 197;
......@@ -1264,7 +1265,10 @@ public class YaVersion {
// For WEB_COMPONENT_VERSION 6:
// - The Timeout property was added.
// - The TimedOut event was added for timed out web requests.
public static final int WEB_COMPONENT_VERSION = 6;
// For WEB_COMPONENT_VERSION 7:
// - The JsonTextDecodeWithDictionaries method was added
// - The XMLTextDecodeAsDictionary method was added.
public static final int WEB_COMPONENT_VERSION = 7;
// For WEBVIEWER_COMPONENT_VERSION 2:
// - The CanGoForward and CanGoBack methods were added
......
......@@ -139,6 +139,7 @@ public final class ErrorMessages {
public static final int ERROR_WEB_UNABLE_TO_DELETE = 1114;
public static final int ERROR_WEB_XML_TEXT_DECODE_FAILED = 1115;
public static final int ERROR_WEB_REQUEST_TIMED_OUT = 1117; //Continuing from number after contact picker
public static final int ERROR_WEB_JSON_TEXT_ENCODE_FAILED = 1118;
// Contact picker (and PhoneNumberPicker) errors
public static final int ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER = 1107;
public static final int ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING = 1108;
......@@ -508,6 +509,8 @@ public final class ErrorMessages {
"Unable to delete a resource with the specified URL: %s");
errorMessages.put(ERROR_WEB_REQUEST_TIMED_OUT,
"Took longer then timeout period to receive data from the URL: %s");
errorMessages.put(ERROR_WEB_JSON_TEXT_ENCODE_FAILED,
"Unable to encode as JSON the object %s");
// Contact picker (and PhoneNumberPicker) errors
errorMessages.put(ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER,
"The software used in this app cannot extract contacts from this type of phone.");
......
......@@ -459,5 +459,20 @@ public class JsonUtil {
files[i].delete();
}
}
/**
* Encodes the given JSON object to a JSON string
*
* @param jsonObject the JSON object to encode
* @return the encoded string
* @throws IllegalArgumentException if the JSON object can't be encoded
*/
public static String encodeJsonObject(Object jsonObject) throws IllegalArgumentException {
try {
return getJsonRepresentation(jsonObject);
} catch (JSONException e) {
throw new IllegalArgumentException("jsonObject is not a legal JSON object");
}
}
}
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2019 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.runtime.util;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
@SuppressWarnings("unchecked")
public class XmlParser extends DefaultHandler {
private static final String CONTENT_TAG = "$content";
private YailDictionary root = null;
private YailDictionary currentElement = null;
private Deque<YailDictionary> stack = new LinkedList<>();
@Override
public void startElement(String uri, String localName, String qname, Attributes attributes) {
YailDictionary el = new YailDictionary();
el.put("$tag", qname);
el.put("$namespaceUri", uri);
el.put("$localName", localName.isEmpty() ? qname : localName);
if (qname.contains(":")) {
String[] parts = qname.split(":");
el.put("$namespace", parts[0]);
} else {
el.put("$namespace", "");
}
YailDictionary attrs = new YailDictionary();
for (int i = 0; i < attributes.getLength(); i++) {
attrs.put(attributes.getQName(i), attributes.getValue(i));
}
el.put("$attributes", attrs);
el.put(CONTENT_TAG, new ArrayList<>());
if (currentElement != null) {
((List<Object>) currentElement.get(CONTENT_TAG)).add(el);
if (!currentElement.containsKey(qname)) {
currentElement.put(qname, new ArrayList<>());
}
((List<Object>) currentElement.get(qname)).add(el);
stack.push(currentElement);
} else {
root = el;
}
currentElement = el;
}
@Override
public void characters(char[] ch, int start, int length) {
List<Object> items = (List<Object>) currentElement.get(CONTENT_TAG);
if (items instanceof ArrayList) {
String content = new String(ch, start, length);
content = content.trim();
if (!content.isEmpty()) {
items.add(content);
}
}
}
@Override
public void endElement(String uri, String localName, String qname) {
for (Entry<Object, Object> e : currentElement.entrySet()) {
if (e.getValue() instanceof ArrayList) {
e.setValue(YailList.makeList((List<?>) e.getValue()));
}
}
if (!stack.isEmpty()) {
currentElement = stack.pop();
}
}
public YailDictionary getRoot() {
return root;
}
}
......@@ -539,14 +539,27 @@ set the following properties:
<dd>Decodes the given HTML text value.
<p>HTML Character Entities such as <code class="highlighter-rouge">&amp;amp;</code>, <code class="highlighter-rouge">&amp;lt;</code>, <code class="highlighter-rouge">&amp;gt;</code>, <code class="highlighter-rouge">&amp;apos;</code>, and <code class="highlighter-rouge">&amp;quot;</code> are
changed to &amp;, &lt;, &gt;, ‘, and “.
changed to <code class="highlighter-rouge">&amp;</code>, <code class="highlighter-rouge">&lt;</code>, <code class="highlighter-rouge">&gt;</code>, <code class="highlighter-rouge">'</code>, and <code class="highlighter-rouge">"</code>.
Entities such as <code class="highlighter-rouge">&amp;#xhhhh;</code>, and <code class="highlighter-rouge">&amp;#nnnn;</code> are changed to the appropriate characters.</p>
</dd>
<dt id="Web.JsonObjectEncode" class="method returns text"><i></i> JsonObjectEncode(<em class="any">jsonObject</em>)</dt>
<dd>Returns the value of a built-in type (i.e., boolean, number, text, list, dictionary)
in its JavaScript Object Notation representation. If the value cannot be
represented as JSON, the Screen’s ErrorOccurred event will be run, if any,
and the Web component will return the empty string.</dd>
<dt id="Web.JsonTextDecode" class="method returns any"><i></i> JsonTextDecode(<em class="text">jsonText</em>)</dt>
<dd>Decodes the given JSON encoded value to produce a corresponding AppInventor value.
A JSON list <code class="highlighter-rouge">[x, y, z]</code> decodes to a list <code class="highlighter-rouge">(x y z)</code>, A JSON object with key A and value B,
(denoted as <code class="highlighter-rouge">{A:B}</code>) decodes to a list <code class="highlighter-rouge">((A B))</code>, that is, a list containing the two-element
list <code class="highlighter-rouge">(A B)</code>.</dd>
list <code class="highlighter-rouge">(A B)</code>.
<p>Use the method <a href="#Web.JsonTextDecodeWithDictionaries">JsonTextDecodeWithDictionaries</a> if you
would prefer to get back dictionary objects rather than lists-of-lists in the result.</p>
</dd>
<dt id="Web.JsonTextDecodeWithDictionaries" class="method returns any"><i></i> JsonTextDecodeWithDictionaries(<em class="text">jsonText</em>)</dt>
<dd>Decodes the given JSON encoded value to produce a corresponding App Inventor value.
A JSON list [x, y, z] decodes to a list (x y z). A JSON Object with name A and value B,
denoted as {a: b} decodes to a dictionary with the key a and value b.</dd>
<dt id="Web.PostFile" class="method"><i></i> PostFile(<em class="text">path</em>)</dt>
<dd>Performs an HTTP POST request using the Url property and data from the specified file.
......@@ -618,19 +631,54 @@ set the following properties:
a list that contains a pair of tag and string. More generally, if obj1, obj2, …
are tag-delimited XML strings, then <code class="highlighter-rouge">&lt;tag&gt;obj1 obj2 ...&lt;/tag&gt;</code> decodes to a list
that contains a pair whose first element is tag and whose second element is the
list of the decoded obj’s, ordered alphabetically by tags. Examples:
<code class="highlighter-rouge">&lt;foo&gt;123&lt;/foo&gt;</code> decodes to a one-item list containing the pair (foo, 123)
<code class="highlighter-rouge">&lt;foo&gt;1 2 3&lt;/foo&gt;</code> decodes to a one-item list containing the pair (foo,”1 2 3”)
<code class="highlighter-rouge">&lt;a&gt;&lt;foo&gt;1 2 3&lt;/foo&gt;&lt;bar&gt;456&lt;/bar&gt;&lt;/a&gt;</code> decodes to a list containing the pair
(a,X) where X is a 2-item list that contains the pair (bar,123) and the pair (foo,”1 2 3”).
If the sequence of obj’s mixes tag-delimited and non-tag-delimited
items, then the non-tag-delimited items are pulled out of the sequence and wrapped
with a “content” tag. For example, decoding <code class="highlighter-rouge">&lt;a&gt;&lt;bar&gt;456&lt;/bar&gt;many&lt;foo&gt;1 2 3&lt;/foo&gt;apples&lt;/a&gt;</code>
list of the decoded obj’s, ordered alphabetically by tags.
<p>Examples:</p>
<ul>
<li><code class="highlighter-rouge">&lt;foo&gt;&lt;123/foo&gt;</code> decodes to a one-item list containing the pair <code class="highlighter-rouge">(foo 123)</code></li>
<li><code class="highlighter-rouge">&lt;foo&gt;1 2 3&lt;/foo&gt;</code> decodes to a one-item list containing the pair <code class="highlighter-rouge">(foo "1 2 3")</code></li>
<li><code class="highlighter-rouge">&lt;a&gt;&lt;foo&gt;1 2 3&lt;/foo&gt;&lt;bar&gt;456&lt;/bar&gt;&lt;/a&gt;</code> decodes to a list containing the pair <code class="highlighter-rouge">(a X)</code>
where X is a 2-item list that contains the pair <code class="highlighter-rouge">(bar 123)</code> and the pair <code class="highlighter-rouge">(foo "1 2 3")</code>.</li>
</ul>
<p>If the sequence of obj’s mixes tag-delimited and non-tag-delimited items, then the
non-tag-delimited items are pulled out of the sequence and wrapped with a “content” tag.
For example, decoding <code class="highlighter-rouge">&lt;a&gt;&lt;bar&gt;456&lt;/bar&gt;many&lt;foo&gt;1 2 3&lt;/foo&gt;apples&lt;a&gt;&lt;/code&gt;</code>
is similar to above, except that the list X is a 3-item list that contains the additional pair
whose first item is the string “content”, and whose second item is the list (many, apples).
This method signals an error and returns the empty list if the result is not well-formed XML.
<p>See <a href="../other/xml.html">“Working with XML and Web Services”</a> for more details.</p>
This method signals an error and returns the empty list if the result is not well-formed XML.</p>
</dd>
<dt id="Web.XMLTextDecodeAsDictionary" class="method returns any"><i></i> XMLTextDecodeAsDictionary(<em class="text">XmlText</em>)</dt>
<dd>Decodes the given XML string to produce a dictionary structure. The dictionary includes the
special keys <code class="highlighter-rouge">$tag</code>, <code class="highlighter-rouge">$localName</code>, <code class="highlighter-rouge">$namespace</code>, <code class="highlighter-rouge">$namespaceUri</code>, <code class="highlighter-rouge">$attributes</code>, and <code class="highlighter-rouge">$content</code>,
as well as a key for each unique tag for every node, which points to a list of elements of
the same structure as described here.
<p>The <code class="highlighter-rouge">$tag</code> key is the full tag name, e.g., foo:bar. The <code class="highlighter-rouge">$localName</code> is the local portion of
the name (everything after the colon <code class="highlighter-rouge">:</code> character). If a namespace is given (everything before
the colon <code class="highlighter-rouge">:</code> character), it is provided in <code class="highlighter-rouge">$namespace</code> and the corresponding URI is given
in <code class="highlighter-rouge">$namespaceUri</code>. The attributes are stored in a dictionary in <code class="highlighter-rouge">$attributes</code> and the
child nodes are given as a list under <code class="highlighter-rouge">$content</code>.</p>
<p><strong>More Information on Special Keys</strong></p>
<p>Consider the following XML document:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;ex:Book</span> <span class="na">xmlns:ex=</span><span class="s">"http://example.com/"</span><span class="nt">&gt;</span>
<span class="nt">&lt;ex:title</span> <span class="na">xml:lang=</span><span class="s">"en"</span><span class="nt">&gt;</span>On the Origin of Species<span class="nt">&lt;/ex:title&gt;</span>
<span class="nt">&lt;ex:author&gt;</span>Charles Darwin<span class="nt">&lt;/ex:author&gt;</span>
<span class="nt">&lt;/ex:Book&gt;</span>
</code></pre></div> </div>
<p>When parsed, the <code class="highlighter-rouge">$tag</code> key will be <code class="highlighter-rouge">"ex:Book"</code>, the <code class="highlighter-rouge">$localName</code> key will be <code class="highlighter-rouge">"Book"</code>, the
<code class="highlighter-rouge">$namespace</code> key will be <code class="highlighter-rouge">"ex"</code>, <code class="highlighter-rouge">$namespaceUri</code> will be <code class="highlighter-rouge">"http://example.com/"</code>, the
<code class="highlighter-rouge">$attributes</code> key will be a dictionary <code class="highlighter-rouge">{}</code> (xmlns is removed for the namespace), and the
<code class="highlighter-rouge">$content</code> will be a list of two items representing the decoded <code class="highlighter-rouge">&lt;ex:title&gt;</code> and <code class="highlighter-rouge">&lt;ex:author&gt;</code>
elements. The first item, which corresponds to the <code class="highlighter-rouge">&lt;ex:title&gt;</code> element, will have an
<code class="highlighter-rouge">$attributes</code> key containing the dictionary <code class="highlighter-rouge">{"xml:lang": "en"}</code>. For each <code class="highlighter-rouge">name=value</code>
attribute on an element, a key-value pair mapping <code class="highlighter-rouge">name</code> to <code class="highlighter-rouge">value</code> will exist in the
<code class="highlighter-rouge">$attributes</code> dictionary. In addition to these special keys, there will also be <code class="highlighter-rouge">"ex:title"</code>
and <code class="highlighter-rouge">"ex:author"</code> to allow lookups faster than having to traverse the <code class="highlighter-rouge">$content</code> list.</p>
</dd>
</dl>
......
......@@ -470,15 +470,29 @@ Non-visible component that provides functions for HTTP GET, POST, PUT, and DELET
: Decodes the given HTML text value.
HTML Character Entities such as `&amp;`, `&lt;`, `&gt;`, `&apos;`, and `&quot;` are
changed to &, <, >, ', and ".
changed to `&`, `<`, `>`, `'`, and `"`.
Entities such as `&#xhhhh;`, and `&#nnnn;` are changed to the appropriate characters.
{:id="Web.JsonObjectEncode" class="method returns text"} <i/> JsonObjectEncode(*jsonObject*{:.any})
: Returns the value of a built-in type (i.e., boolean, number, text, list, dictionary)
in its JavaScript Object Notation representation. If the value cannot be
represented as JSON, the Screen's ErrorOccurred event will be run, if any,
and the Web component will return the empty string.
{:id="Web.JsonTextDecode" class="method returns any"} <i/> JsonTextDecode(*jsonText*{:.text})
: Decodes the given JSON encoded value to produce a corresponding AppInventor value.
A JSON list `[x, y, z]` decodes to a list `(x y z)`, A JSON object with key A and value B,
(denoted as `{A:B}`) decodes to a list `((A B))`, that is, a list containing the two-element
list `(A B)`.
Use the method [JsonTextDecodeWithDictionaries](#Web.JsonTextDecodeWithDictionaries) if you
would prefer to get back dictionary objects rather than lists-of-lists in the result.
{:id="Web.JsonTextDecodeWithDictionaries" class="method returns any"} <i/> JsonTextDecodeWithDictionaries(*jsonText*{:.text})
: Decodes the given JSON encoded value to produce a corresponding App Inventor value.
A JSON list [x, y, z] decodes to a list (x y z). A JSON Object with name A and value B,
denoted as \{a: b\} decodes to a dictionary with the key a and value b.
{:id="Web.PostFile" class="method"} <i/> PostFile(*path*{:.text})
: Performs an HTTP POST request using the Url property and data from the specified file.
......@@ -552,16 +566,50 @@ Non-visible component that provides functions for HTTP GET, POST, PUT, and DELET
a list that contains a pair of tag and string. More generally, if obj1, obj2, ...
are tag-delimited XML strings, then `<tag>obj1 obj2 ...</tag>` decodes to a list
that contains a pair whose first element is tag and whose second element is the
list of the decoded obj's, ordered alphabetically by tags. Examples:
`<foo>123</foo>` decodes to a one-item list containing the pair (foo, 123)
`<foo>1 2 3</foo>` decodes to a one-item list containing the pair (foo,"1 2 3")
`<a><foo>1 2 3</foo><bar>456</bar></a>` decodes to a list containing the pair
(a,X) where X is a 2-item list that contains the pair (bar,123) and the pair (foo,"1 2 3").
If the sequence of obj's mixes tag-delimited and non-tag-delimited
items, then the non-tag-delimited items are pulled out of the sequence and wrapped
with a "content" tag. For example, decoding `<a><bar>456</bar>many<foo>1 2 3</foo>apples</a>`
list of the decoded obj's, ordered alphabetically by tags.
Examples:
* `<foo><123/foo>` decodes to a one-item list containing the pair `(foo 123)`
* `<foo>1 2 3</foo>` decodes to a one-item list containing the pair `(foo "1 2 3")`
* `<a><foo>1 2 3</foo><bar>456</bar></a>` decodes to a list containing the pair `(a X)`
where X is a 2-item list that contains the pair `(bar 123)` and the pair `(foo "1 2 3")`.
If the sequence of obj's mixes tag-delimited and non-tag-delimited items, then the
non-tag-delimited items are pulled out of the sequence and wrapped with a "content" tag.
For example, decoding `<a><bar>456</bar>many<foo>1 2 3</foo>apples<a></code>`
is similar to above, except that the list X is a 3-item list that contains the additional pair
whose first item is the string "content", and whose second item is the list (many, apples).
This method signals an error and returns the empty list if the result is not well-formed XML.
See ["Working with XML and Web Services"](../other/xml.html) for more details.
{:id="Web.XMLTextDecodeAsDictionary" class="method returns any"} <i/> XMLTextDecodeAsDictionary(*XmlText*{:.text})
: Decodes the given XML string to produce a dictionary structure. The dictionary includes the
special keys `$tag`, `$localName`, `$namespace`, `$namespaceUri`, `$attributes`, and `$content`,
as well as a key for each unique tag for every node, which points to a list of elements of
the same structure as described here.
The `$tag` key is the full tag name, e.g., foo:bar. The `$localName` is the local portion of
the name (everything after the colon `:` character). If a namespace is given (everything before
the colon `:` character), it is provided in `$namespace` and the corresponding URI is given
in `$namespaceUri`. The attributes are stored in a dictionary in `$attributes` and the
child nodes are given as a list under `$content`.
**More Information on Special Keys**
Consider the following XML document:
```xml
<ex:Book xmlns:ex="http://example.com/">
<ex:title xml:lang="en">On the Origin of Species</ex:title>
<ex:author>Charles Darwin</ex:author>
</ex:Book>
```
When parsed, the `$tag` key will be `"ex:Book"`, the `$localName` key will be `"Book"`, the
`$namespace` key will be `"ex"`, `$namespaceUri` will be `"http://example.com/"`, the
`$attributes` key will be a dictionary `{}` (xmlns is removed for the namespace), and the
`$content` will be a list of two items representing the decoded `<ex:title>` and `<ex:author>`
elements. The first item, which corresponds to the `<ex:title>` element, will have an
`$attributes` key containing the dictionary `{"xml:lang": "en"}`. For each `name=value`
attribute on an element, a key-value pair mapping `name` to `value` will exist in the
`$attributes` dictionary. In addition to these special keys, there will also be `"ex:title"`
and `"ex:author"` to allow lookups faster than having to traverse the `$content` list.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment