B4J Library [B4X] Xml2Map - Simple way to parse XML documents

Status
Not open for further replies.
Nobody likes to parse XML.

Parsing JSON is simple and fun. Parsing XML is tedious and boring.

That is the reason behind the Xml2Map class. It internally parses the XML document and returns a Map with the parsed data. It is similar to parsing JSON.
Tip: You can use this tool to help you with parsing JSON: https://b4x.com:51041/json/index.html

So instead of the code explained in the old tutorial: https://www.b4x.com/android/forum/threads/xml-parsing-with-the-xmlsax-library.6866/#content

We can achieve the same thing with this code:
B4X:
Sub Process_Globals
   Private ParsedData As Map
End Sub

Sub Globals
   Private ListView1 As ListView
End Sub

Sub Activity_Create(FirstTime As Boolean)
   If FirstTime Then
     Dim xm As Xml2Map
     xm.Initialize
     xm.StripNamespaces = True '<--- new in v1.01
     ParsedData = xm.Parse(File.ReadString(File.DirAssets, "rss.xml"))
   End If
   Activity.LoadLayout("1")
   ListView1.SingleLineLayout.ItemHeight = 60dip
   Dim rss As Map = ParsedData.Get("rss")
   Dim channel As Map = rss.Get("channel")
   Dim items As List = channel.Get("item")
   For Each item As Map In items
     Dim title As String = item.Get("title")
     Dim link As String = item.Get("link")
     ListView1.AddSingleLine2(title, link)
   Next
End Sub

Sub ListView1_ItemClick (Position As Int, Value As Object)
   Dim pi As PhoneIntents
   StartActivity(pi.OpenBrowser(Value))
End Sub

You can use the JSON library to convert the Map to a json string, this is useful for understanding how to access the data:
B4X:
Dim jg As JSONGenerator
jg.Initialize(ParsedData)
Log(jg.ToPrettyString(4))

The result in this case will look like:
"rss": {
"Attributes": {
"version": "2.0"
},
"channel": {
"title": "Basic4ppc \/ Basic4android - Android programming",
"link": "http:\/\/www.b4x.com\/forum",
"description": "Basic4android - android programming and development",
"language": "en",
"lastBuildDate": "Sun, 12 Dec 2010 10:19:27 GMT",
"generator": "vBulletin",
"ttl": "60",
"image": {
"url": "http:\/\/www.b4x.com\/forum\/images\/misc\/rss.jpg",
"title": "Basic4ppc \/ Basic4android - Android programming",
"link": "http:\/\/www.b4x.com\/forum"
},
"item": [
{
"title": "Phone library was updated - V1.10",
"link": "http:\/\/www.b4x.com\/forum\/additional-libraries-official-updates\/6859-phone-library-updated-v1-10-a.html",
"pubDate": "Sun, 12 Dec 2010 09:27:38 GMT",
"description": "An Intent object was added. This allows creating custom intents for interacting with external applications and services.\n\nInstallation...",
"encoded": "<div>An Intent object was added...",
"category": {
"Attributes": {
"domain": "http:\/\/www.b4x.com\/forum\/additional-libraries-official-updates\/"
},
"Text": "Additional libraries and official updates"
},
"creator": "Erel",
"guid": {
"Attributes": {
"isPermaLink": "true"
},
"Text": "http:\/\/www.b4x.com\/forum\/additional-libraries-official-updates\/6859-phone-library-updated-v1-10-a.html"
}
MORE ITEMS HERE

Note that attributes are added under the Attributes key. In such cases the text will be available under the Text key.

This module is compatible with B4A, B4J and B4i.
It depends on XmlSax library (which is included in the IDE).

upload_2017-1-4_14-26-40.png


Edit (October 2017):

Common pitfall


Consider this xml:
B4X:
<root>
<book>
   <title>Book 1</title>
</book>
<book>
   <title>Book 2</title>
</book>
</root>

There could be any number of book elements.
You can parse it with:
B4X:
Dim root As Map = ParsedData.Get("root")
For Each book As Map In root.Get("book")
Dim title As String = book.Get("title")
Next
However this code will fail in two cases:
1. There is only one book in the xml so root.Get("book") will return a Map instead of a List.
2. There are no books at all so root.Get("book") will return Null.

To solve this issue you can use this sub:
B4X:
Sub GetElements (m As Map, key As String) As List
   Dim res As List
   If m.ContainsKey(key) = False Then
     res.Initialize
     Return res
   Else
     Dim value As Object = m.Get(key)
     If value Is List Then Return value
     res.Initialize
     res.Add(value)
     Return res
   End If
End Sub
It will return a list in all cases.
You can safely use it with:
B4X:
Dim root As Map = ParsedData.Get("root")
For Each book As Map In GetElements(root, "book"))
Dim title As String = book.Get("title")
Next


Map2Xml - New class!

Map2Xml converts the map created with Xml2Map to a Xml string. It uses XmlBuilder library and it is compatible with B4A, B4i and B4J.
It can be used to modify existing XML documents. You read the document with Xml2Map, make the changes in the returned map and write it back with Map2Xml.

It is an internal library now.

Updates:

- v1.01 - New StripNamespaces property. When set to true the namespaces from keys and attributes are stripped. It is recommend to set it true. The behavior regarding namespaces, between B4A, B4J and B4i is different when namespaces are kept.
 

Attachments

  • Xml2Map.b4xlib
    2.2 KB · Views: 201
Last edited:

tufanv

Expert
Licensed User
Longtime User
is it possible to use a textbox filled with xml response from an httpjob instead of :
ParsedData = xm.Parse(File.ReadString(File.DirAssets, "rss.xml"))

if yes , how can i do it ?
 

tufanv

Expert
Licensed User
Longtime User
Yes , figured it out , I was thinking i needed to download the xml as a file first but it is not needed.
 

Pedro Caldeira

Active Member
Licensed User
Longtime User
Hello,
I have tried your example, but I get the following error

Waiting for debugger to connect...
Program started.
Error occurred on line: 37 (Main)
java.lang.RuntimeException: Object should first be initialized (Map).
at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:32)
at anywheresoftware.b4a.objects.collections.Map.ContainsKey(Map.java:122)
at b4j.example.main._getelements(main.java:139)
at b4j.example.main._gomain(main.java:108)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:613)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:228)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:159)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:90)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:93)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:77)
at b4j.example.main.main(main.java:29)
 

Rusty

Well-Known Member
Licensed User
Longtime User
Can anyone help me with the following XML?
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ExerciseType language="en" name="Bicep-Curl (alternate)">
<Locale language="de" name="Bizeps-Curl (alternativ)"/>
<Locale language="it" name="Trazioni sui Bicipiti (alternativo)"/>
<Locale language="ja" name="上腕二頭筋カール (交互)"/>
<Muscle name="Bizeps" level="3"/>
<SportsEquipment name="Kurzhantel"/>
<Image author="Everkinetic" license="CC-BY-SA 3" path="Alternate-bicep-curl-1.png"/>
<Image author="Everkinetic" license="CC-BY-SA 3" path="Alternate-bicep-curl-2.png"/>
</ExerciseType>

I've tried modifying Erel's code but am lost...
B4X:
Sub Process_Globals
      Private ParsedData As Map
End Sub
Sub Globals
 Private lstWorkOuts As ListView
...
    If FirstTime Then
        Dim xm As XML2Map
        xm.Initialize
        ParsedData = xm.Parse(File.ReadString(File.Dirrootexternal & "/workout_images", "Alternatebicepcurl.xml" ))
    End If
    Activity.LoadLayout("WorkOuts")
    lstWorkOuts.SingleLineLayout.ItemHeight = 60dip
    Dim ExerciseType As Map = ParsedData.Get("ExerciseType")
    Dim channel As Map = ExerciseType.Get("SportsEquipment")
    Dim items As List = ExerciseType.Get("Locale")
    For Each item As Map In items
        Dim title As String = item.Get("title")
        Dim link As String = item.Get("link")
        lstWorkOuts.AddSingleLine2(title, link)
    Next
My head is spinning and I can't seem to make anything make sense...
Thanks in advance
Rusty
 
Status
Not open for further replies.
Top