Python

How to Test an XML Command Table

Q How do I test a MicroStation Python XML command table?

Q Are there any diagnostic tools for an XML command table?

A I wrote a Command Table Tester to help diagnose problems with XML command tables.

Q Can I document an XML command table?

A I added a documentation method to the CommandTableHandler described below.

Introduction

MicroStation Python lets you write custom key-in commands for your application. Commands are defined in an XML command table. Some examples are provided with the MicroStation Python installation (not all delivered examples have a command table).

A command table provides your app with key-in commands, similar to MicroStation's. There are several benefits obtained by providing key-ins …

There's an example command table below. Similar tables are included with the Python project you can download. Things to note about the command table XML structure …

Root Element

Sub Table Elements

Nested Sub Table Elements

KeyinHandlers Element

Python Implementation

MicroStation Python provides a KeyInManager which you get by calling PythonKeyinManager.GetManager(). KeyInManager.LoadCommandTableFromXml(path_to_caller, xml_file_path) loads your XML command table.

Load Command Table

status = PythonKeyinManager.GetManager().LoadCommandTableFromXml (WString (callingFile), WString (xmlFilePath))

If successful, status == 0 and KeyInManager.LoadCommandTableFromXml() loads your commands into your application memory space. The key-in commands are associated with your command functions.

If unsuccessful, status is non-zero and MicroStation Python will complain. Depending on the problem, it may crash MicroStation.

Key-In Errors

Some errors are not found when your XML command table is loaded. It may pass the XML syntax check but fail later. For example, if you've defined a KeyinHandler in your table, but the function is mis-named or doesn't exist, you see a message when you attempt to key-in that command. For example, in response to key-in COMMANDTEST TEST1, defined in commands_AMBIGUOUSMATCH.xml, you see this …

NameError: name 'cmdTest3' is not defined

LoadCommandTableFromXml status codes

The KeyInManager.LoadCommandTableFromXml() return codes (status) are undocumented at the time of writing (early 2025). However, they were revealed in the MicroStation Programming Forum in response to a question. Thanks to Bentley Systems staffers Robert Hook and Leonard Jones for their help.

Here they are …

from enum import IntEnum
class CommandTableStatus(IntEnum):
    '''
    LoadCommandTableFromXml status codes are not documented.
    Codes shown here are unconfirmed by Bentley Systems.
    '''
    CT_SUCCESS                          = 0
    CT_ERROR                            = 32768
    CT_RESOURCENOTFOUND                 = 0x5000 + 1
    CT_BADRESOURCETYPE                  = 0x5000 + 2
    CT_BADRESOURCE                      = 0x5000 + 3
    CT_EXCEEDSMAXIMUMNESTLEVEL          = 0x5000 + 4
    CT_XMLMISSINGROOTTABLE              = 0x5000 + 0x20
    CT_XMLDUPLICATEROOTTABLE            = 0x5000 + 0x21
    CT_XMLMISSINGCOMMANDWORD            = 0x5000 + 0x22
    CT_XMLMISSINGSUBTABLE               = 0x5000 + 0x23
    CT_XMLDUPLICATESUBTABLE             = 0x5000 + 0x24
    CT_XMLBADFEATUREASPECT              = 0x5000 + 0x25
    CT_XMLDUPLICATEKEYINHANDLERSNODE    = 0x5000 + 0x26
    CT_XMLMISSINGKEYINNODE              = 0x5000 + 0x27
    CT_XMLMISSINGFUNCTIONNODE           = 0x5000 + 0x28

    CT_NOCOMMANDMATCH                   = (-1)
    CT_AMBIGUOUSMATCH                   = (-2)

Some are self-explanatory, others more obscure. I wrote a class CommandTableHandler to wrap KeyInManager.LoadCommandTableFromXml() and explain the CommandTableStatus status code when things go wrong.

Usage

Use it like this in your own code …

handler = CommandTableHandler()
scriptPath = str(Path (os.path.dirname(__file__)) / 'CommandTableTester')
handler.LoadCommandTable(__file__, scriptPath)

If the load returns zero (meaning success) then your command table has been loaded into your app's runtime environment. A non-zero status results in a note in MicroStation's Message Center that provides a clue to a problem in your XML command table …

Diagnostic

Documentation

Method ComposeHtml() generates a brief HTML file. It mentions the command table name along with an explanation of the status returned by LoadCommandTable. It looks something like this (for a well-formed command file) …

App 'Command Table Tester'

Table Loaded Sucessfully

Key-InFunction
COMMANDTEST TEST1 cmdTest1
COMMANDTEST TEST2 ARG2cmdTest2Arg2

The intention of that documentation is simply to provide a record of your command table and the key-in commands that it defines.

Package

The code is packaged into example la_solutions_command_table_tester, which you can download below. The package includes several Python files and a handful of XML command tables: one good and several bad.

The example contains one good XML command table and several that have some kind of problem. When you run the code, you will see a message similar to that above for a bad table.

Download la_solutions_command_table_tester.zip

Unpack the ZIP file and copy the Python file into a folder that MicroStation knows about.

Python Manager

Use MicroStation's Python Manager to find and execute the script.

Questions

Post questions about MicroStation programming to the MicroStation Programming Forum.

Command Table Example

A command table looks something like this …

<?xml version="1.0" encoding="utf-8" ?>
<!-- This command table should pass all tests -->
<KeyinTree xmlns="http://www.bentley.com/schemas/1.0/MicroStation/AddIn/KeyinTree.xsd">
    <RootKeyinTable ID="root">
        <Keyword SubtableRef="CommandsTest1" CommandClass="MacroCommand" CommandWord="COMMANDTEST">
            <Options Required="true" />
        </Keyword>
    </RootKeyinTable>

   <SubKeyinTables>
        <KeyinTable ID="CommandsTest1">
            <Keyword CommandWord="TEST1" />
            <Keyword SubtableRef="SecondLevel" CommandWord="TEST2" />
        </KeyinTable>
    </SubKeyinTables>

    <SubKeyinTables>
        <KeyinTable ID="SecondLevel">
            <Keyword CommandWord="ARG2" />
        </KeyinTable>
    </SubKeyinTables>

    <KeyinHandlers>
        <KeyinHandler Keyin="COMMANDTEST TEST1 "               Function="cmdTest1" />
        <KeyinHandler Keyin="COMMANDTEST TEST2 ARG2"           Function="cmdTest2Arg2" />
    </KeyinHandlers>
</KeyinTree>