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.
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 …
http://www.bentley.com/schemas/1.0/MicroStation/AddIn/KeyinTree.xsd
RootKeyinTable
whose ID
is root
RootKeyinTable
references one or more SubKeyinTables
RootKeyinTable
provides a single CommandWord
(e.g. COMMANDTEST
)
CommandWord
is the first word of a user key-in (e.g. COMMANDTEST
)
SubKeyinTables
element contains one or more KeyinTable
s
KeyinTable
has an ID
(e.g. CommandsTest1) that is unique within the key-in tree
KeyinTable
appends a CommandWord
(e.g. TEST1
) to the key-in tree.
This provides a key-in COMMANDTEST TEST1
KeyinTable
may introduce a named SubtableRef
(e.g. SecondLevel)
SubtableRef
may introduce a KeyinTable
having a unique ID
KeyinTable
adds a command word. For example, COMMANDTEST TEST2 ARG2
KeyinHandlers
list that links your key-in commands with your Python functions
KeyinHandler
links a Keyin
with a Function
.
For example, key-in COMMANDTEST TEST2 ARG2
is associated with function cmdTest2Arg2
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.
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.
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
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.
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 …
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) …
Table Loaded Sucessfully
Key-In | Function |
---|---|
COMMANDTEST TEST1 | cmdTest1 |
COMMANDTEST TEST2 ARG2 | cmdTest2Arg2 |
The intention of that documentation is simply to provide a record of your command table and the key-in commands that it defines.
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.
Unpack the ZIP file and copy the Python file into a folder that MicroStation knows about.
Use MicroStation's Python Manager to find and execute the script.
Post questions about MicroStation programming to the MicroStation Programming Forum.
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>