Monday, 10 February 2014

XML, XPath and Mapbasic: A dynamic Menu from configuration

If you have found this page, there is a good chance you have been searching for an example of how to use the XML library that is not well documented for use in MapBasic.

In this example I will show you how to read an XML file using Xpath and the XMLlib library in MapBasic. Currently the XMLLib does not have functionality to re-serialize an xml object so there is no ability to modify or create XML. There are work arounds for this which I could detail in a later post if requested, but if you have complex XML it would be advised that you create your own dll that allows you to serialize and de-serialize XML.

This example also shows a really useful way to drive menus in Mapbasic from configuration. I have personally used similar code to this to drive menus based on a user privilege and context.


The example code can be downloaded here XML Example.zip


'Note the order of the includes

Include "MAPBASIC.DEF"

Include "XMLTypes.def"
Include "XMLLib.def"

Declare Sub Main
Declare Function LoadConfig() as Logical

Declare Sub ShowMenu
Declare Sub AdminUser
Declare Sub GeneralUser
Declare Sub AboutExample
Declare Sub ExitExample

Define CONFIG_FILE "XMLexample.config.xml" 

Type MenuType  
   MenuName as String
   MenuItemName() as String
   MenuItemCallingSub() as String
End Type

Dim
Mnu as MenuType
'-------------------------------------------------------------------

Sub Main
OnError Goto
CatchExPrint Chr$(12)

   'This could be called at any time from anywhere but in this example
   'it initializes the menu at mbx start up

   If LoadConfig() Then
      Call ShowMenu  
   End If


Done:  
   Exit Sub

CatchEx:   Note Error$()
   Resume Done
End Sub
'-------------------------------------------------------------------
Function LoadConfig() as Logical
OnError Goto CatchEx
LoadConfig = false

   'First check and see if xml file exists
   If NOT FileExists(ApplicationDirectory$() & CONFIG_FILE) Then
      Note "Configuration file " & CONFIG_FILE & " is not in the " & ApplicationName$() & " directory!"
      Goto Done
   End If

   'Create an xml document
   Dim xmlDoc as MIXmlDocumentxmlDoc = MIXmlDocumentCreate()
   Dim parseErr, valid, resext as SmallInt

   Dim i as SmallInt
   Dim sXmlDocPath as String
   sXmlDocPath = ApplicationDirectory$() & CONFIG_FILE
   Print "Attempting to load - " & sXmlDocPath
   'Load config into xmlDoc

   i = MIXmlDocumentLoad(xmlDoc, sXmlDocPath, parseErr, valid, resext)
   Print "parseErr " & parseErr
   'Check load errors

   If parseErr > 0 then
      Note "Could not load " & ApplicationDirectory$() & CONFIG_FILE & Chr$(13) &
      "This could be because the file contains invalid XML" & Chr$(13) &
      "Please check " & CONFIG_FILE & " is valid and try again!"
      Goto Done
   End If

   Print "Successfully Loaded - " & sXmlDocPath

   'Select root node <Config>

   Dim xmlRootNode as MIXmlNodexmlRootNode =   MIXmlDocumentGetRootNode(xmlDoc)
   Print "Loading Menu Settings....."


   'XPath query to select menu config
   Dim strXPath as String
   strXPath = "/Config/Menus/Menu"

   Dim xmlMenusList as MIXmlNodeList
   xmlMenusList = MIXmlSelectNodes(xmlRootNode,strXPath)

   'Select fist menu node

   Dim xml_node as MIXmlNode
   xml_node = MIXmlGetNextNode(xmlMenusList)

   Dim strValue as String

   strValue = String$(500," ")
   Dim nLen As Integer

   nLen = 500
   'Get attribute value from Name attribute

   i = MIXmlNodeGetAttributeValue(xml_node, "Name", strValue, nLen)Mnu.MenuName = strValue
   Print "Menu Name - " & Mnu.MenuName

   Dim xmlMenuItemsList as MIXmlNodeList
   xmlMenuItemsList = MIXmlGetChildList(xml_node)
   xml_node = MIXmlGetNextNode(xmlMenuItemsList)

   Dim iMenuItemCount as Integer

   iMenuItemCount = 1
   Do While xml_node > 0
   'Get element/node value for iMenuItemCount menu item

      Redim Mnu.MenuItemName(iMenuItemCount)i = MIXmlNodeGetValue(xml_node, strValue, nLen)
      Mnu.MenuItemName(iMenuItemCount) = strValue

      'Get CallingSub attribute value for iMenuItemCount menu item

      Redim Mnu.MenuItemCallingSub(iMenuItemCount) i = MIXmlNodeGetAttributeValue(xml_node, "CallingSub", strValue, nLen)
      Mnu.MenuItemCallingSub(iMenuItemCount) = strValue

      Print "Menu Item " & iMenuItemCount & " - " & Mnu.MenuItemName(iMenuItemCount) & " Calling " & Mnu.MenuItemCallingSub(iMenuItemCount)

      'Get next note in the collection list

      xml_node = MIXmlGetNextNode(xmlMenuItemsList)
      iMenuItemCount = iMenuItemCount + 1
   Loop

   Print " "
   LoadConfig = true
Done:
   Exit Function

CatchEx:
   Note Error$()
   Resume Done
End Function
'-------------------------------------------------------------------
Sub ShowMenu
OnError Goto CatchEx

   Dim strCommand as String
   Dim i as Integer

   'Here I build a command string with values from the xml configuration

   strCommand = "Create Menu " & Chr$(34) & Mnu.MenuName & Chr$(34) & " As "

   For i = 1 to Ubound(Mnu.MenuItemName())
      If Mnu.MenuItemName(i) = "(-" Then

         'Create menu spacer line
         strCommand = strCommand & Chr$(34) & "(-" & Chr$(34)
      Else

         'Create Menu Item from configuration
         strCommand = strCommand & Chr$(34) & Mnu.MenuItemName(i) & Chr$(34) & " Calling " & Mnu.MenuItemCallingSub(i)
      End If

      If i + 1 <= Ubound( Mnu.MenuItemName()) Then
         'This just add the required comma unless it is the last item
         strCommand = strCommand & ", "
      End If
   Next


Print strCommand ' for debug
Run Command strCommand

Alter Menu Bar Add Mnu.MenuName

Done:
   Exit Sub
CatchEx:
   Note Error$()
   Resume Done

End Sub
'-------------------------------------------------------------------
Sub AdminUser
   Note "Hello Admin Configured User"
End Sub
'-------------------------------------------------------------------
Sub GeneralUser
   Note "Hello General Configured User"
End Sub
'-------------------------------------------------------------------
Sub AboutExample
   Note "XML Example with Dynamic Menus"
End Sub
'-------------------------------------------------------------------
Sub ExitExample
   End Program
End Sub


Here is the xml file that is used for configuration. If you want to remove a menu item eg: admin then just delete the menuitem line in the xml. Note: I did try to comment out using <!-- --> but the mapbasic xml implementation still read it?

<?xml version="1.0" encoding="utf-8"?>
<Config>
   <Menus>
      <Menu Name="XML Example">
         <MenuItem CallingSub="AdminUser">Hello - Admin</MenuItem>
         <MenuItem CallingSub="GeneralUser">Hello - General</MenuItem>
         <MenuItem CallingSub="">(-</MenuItem>
         <MenuItem CallingSub="AboutExample">About</MenuItem>
         <MenuItem CallingSub="">(-</MenuItem>
         <MenuItem CallingSub="ExitExample">Exit</MenuItem>
      </Menu>
   </Menus>
</Config>

5 comments:

  1. Here is an example of the the dynamic menu used by Brendan Stone http://blog.nadnerb.co.uk/?p=125

    This is great little mbx for getting basemaps or background imagery

    Cheers

    ReplyDelete
  2. Very good job . I was looking for this. Can you add some screenshots of result object please ?

    ReplyDelete
    Replies
    1. Hi Kurtarma, as posted above Brendan Stone posted a solution using this code, here http://blog.nadnerb.co.uk/?p=125
      Cheers

      Delete
  3. Good site. I'm trying to compile it, but i got an error: "Library GMLXlat.dll was not found. Unable to link to external library GMLXlat.dll."

    ReplyDelete
    Replies
    1. You can solve it easily, just download the file you need for example http://fix4dll.com/xinput1_3_dll and add it into your system 32 folder, try to it's very easy. Hope it will be useful for you. Good luck.

      Delete