Refactoring Altova MapForce Functions

By | November 20, 2014

In this post I want to share a Python script I have created that refactors a user-defined function in an Altova MapForce mapping to a library (mapping) file while retaining all the instances of the function in the mapping and their connections to other parts.
The use case for this is when you have several mappings which make use of the same function(s) and would prefer to move them to a library of mappings, possibly to be shared among several developers.

Pre-Code Words

Before we begin, I want to say the following:

  • I am in no way associated with Altova, except for being a happy customer of theirs.
  • I have not received any information on the file-format used to save MapForce mappings.
    Altova being the Masters of XML, the file-format is of course XML so it is readable but I cannot claim to understand it completely.
  • You use this script at your own risk.
    It may possibly chew your mappings and fart afterwards. Make a backup and you will be safe.
  • Feel free to modify the script but please make any improvements available.
    The script is distributed under the GPL v3 license.
  • I have made limited tests with the script and Altova MapForce 2015.

Given my limited knowledge about Altova MapForce, I would accomplish what the script does by manually copying and pasting the function from the mapping to the library, then replace (and rewire) all the instances of the original function in the mapping with instances of the function from the library.
It is reasonable procedure the first few times, but I suspect that it will become tedious after a while and it is also slightly error-prone, given that you have to re-wire the function connections.


Enough chit-chat, here is the code:


import xml.etree.ElementTree as ET
import sys

# Example usage:
# ./ [function name] [current library name, usually 'user'] [destination library name]
#   [mapping file containing function] [library file to which function is to be moved]
# Copyright 2014 Ivan Krizsan. All rights reserved. Distributed under the GPL v3 license.
if len(sys.argv) != 6:
    print "Wrong number of arguments."
    print "Parameters:"
    print "   [function name]"
    print "   [current library name, usually 'user']"
    print "   [destination library name]"
    print "   [mapping file containing function]"
    print "   [library file to which function is to be moved]"

theFunctionName = sys.argv[1]
theCurrentLibraryName = sys.argv[2]
theDestinationLibraryName = sys.argv[3]
theMappingFile = sys.argv[4]
theLibraryFile = sys.argv[5]
theInputPath = ""
theOutputPath = ""

# Checks the value of the attribute with supplied name in the supplied element whether
# it is equal to the expected value.
# If attribute is not present on the element in question, false will be returned.
# @param inElement Element in which attribute is located.
# @param inAttributeName Name of attribute which value to check.
# @param inExpectedAttributeValue Expected value of the attribute to check.
# @return True if the value of the attribute is equal to the expected value, false otherwise.
def checkAttributeValueInElement(inElement, inAttributeName, inExpectedAttributeValue):
    assert (inExpectedAttributeValue is not None)
    theElementAttributes = inElement.attrib
    theAttributeValue = None
    if inAttributeName in theElementAttributes:
        theAttributeValue = theElementAttributes[inAttributeName]
    theAttributeValueMatchFlag = (inExpectedAttributeValue == theAttributeValue)
    return theAttributeValueMatchFlag

# Checks whether the supplied element has an attribute with the supplied name.
# @param inElement Element to check if contains the attribute or not.
# @param inAttributeName Name of attribute which to check for.
# @return True if element has attribute, false otherwise.
def hasAttribute(inElement, inAttributeName):
    theElementAttributes = inElement.attrib
    theHasAttributeFlag = False
    if inAttributeName in theElementAttributes:
        theHasAttributeFlag = True
    return theHasAttributeFlag

# Read mapping in which function to move is located.
theMappingXmlTree = ET.parse(theInputPath + theMappingFile)
theMappingRoot = theMappingXmlTree.getroot()

theFunctionDefinitionComponent = None
theFunctionReferenceList = []

# Search through all the <component> elements in the mapping for the definition of the function
# and for references to the function.
# Looks for a <component> element that has a name attribute with the value equal to the name of the function that
# is to be refactored and a library attribute with the value equal to the name of the library the function currently
# is contained in.
# The function definition has an inline attribute (with any value), while the references do not.
for theComponentElement in theMappingRoot.iter('component'):
    # Check the name of the function.
    if checkAttributeValueInElement(theComponentElement, 'name', theFunctionName):
        # Check the name of the current library.
        if checkAttributeValueInElement(theComponentElement, 'library', theCurrentLibraryName):
            # Is it the function definition?
            if hasAttribute(theComponentElement, 'inline'):
                print "Found function definition"
                theFunctionDefinitionComponent = theComponentElement
                print "Found function reference"

assert(theFunctionDefinitionComponent is not None)

# Remove the user defined function from the mapping in where it was originally defined

# Change all references of the function to refer to the library to which the function is moved.
for theFunctionReference in theFunctionReferenceList:
    theFunctionReference.set('library', theDestinationLibraryName)

# Read library to which function to move is to be moved.
theLibraryXmlTree = ET.parse(theInputPath + theLibraryFile)
theLibraryRoot = theLibraryXmlTree.getroot()

# Find the next free <component> uid in the library.
theHighestComponentUid = None
for theComponentElement in theLibraryRoot.iter('component'):
    if 'uid' in theComponentElement.attrib:
        theComponentUid = int(theComponentElement.attrib['uid'])
        if theHighestComponentUid is None:
            theHighestComponentUid = theComponentUid
        elif theComponentUid > theHighestComponentUid:
            theHighestComponentUid = theComponentUid
theHighestComponentUid += 1

# Change the library name in the definition of the function that is refactored.
theFunctionDefinitionComponent.set('library', theDestinationLibraryName)
# Modify the uid attribute of the refactored function and all contained <component> elements.
for theComponentElement in theFunctionDefinitionComponent.iter('component'):
    if 'uid' in theComponentElement.attrib:
        theComponentElement.set('uid', str(theHighestComponentUid))
        theHighestComponentUid += 1

# Append the refactored function to the library.

# Write the modified files.
theMappingXmlTree.write(theOutputPath + theMappingFile)
theLibraryXmlTree.write(theOutputPath + theLibraryFile)

After having saved the script, you may need to perform a “cmod +x [script filename]” in order to make it executable.

As stated in the initial comment, the script is to be executed with the following parameters:

  • Function name.
    This is the name of the user-defined MapForce function that you want to move to your library file. The name of the user-defined function will not change as part of the refactoring.
  • Current library name.
    The name of the library in which the function is located prior to the mapping.
    This name can be found in the MapForce Library pane and is the name of the parent-node of the function. Usually, the name of the current library is “user” for user-defined functions.
  • Destination library name.
    The name of the library to which the function will be moved.
    The library may contain other user-defined functions.
    If you want the function to remain in the same library as it was located prior to the refactoring, enter the same value as for the previous parameter.
  • Mapping file.
    The path to the mapping file containing the user-defined function to move.
    The function will be removed from this file and the mapping file will be updated by the script.
  • Library file.
    The path to the mapping file to which the user-defined function will be moved.
    The function, possibly with its library name updated, will be written to this file.

Recommended Approach

I recommend the following approach in MapForce when refactoring user-defined functions using the above script:

  • Open the mapping containing the function(s) that you want to refactor in MapForce.
  • Create a new mapping in MapForce and save it as your library file.
    If you already have a library file you want to use, skip this step.
  • Go back to the mapping containing the function, click the button Add/Remove Libraries… in the Library pane on the right in MapForce and add the library file of your choice, if it is not already added as a library.
  • Save the mapping containing the function.
  • Use the script above to refactor the function.
  • Allow MapForce to reload the modified files.
    If MapForce does not properly discover the modifications, you may need to close the files and open them again.

Leave a Reply

Your email address will not be published. Required fields are marked *