Skip to content

Instantly share code, notes, and snippets.

@kzaremski
Last active April 26, 2023 14:49
Show Gist options
  • Save kzaremski/4d7c02d7fecb0212c0373abc13cb2a47 to your computer and use it in GitHub Desktop.
Save kzaremski/4d7c02d7fecb0212c0373abc13cb2a47 to your computer and use it in GitHub Desktop.
OneStream XF Business Rule Source Code Extractor
#!/usr/bin/python
#Python 3.7 <=
# Comments and improvements welcome on GitHub! <https://gist.github.com/kzaremski/4d7c02d7fecb0212c0373abc13cb2a47>
'''
Copyright 2022 Konstantin Zaremski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
OneStream XF Business Rule Source Code Extractor
Konstantin Zaremski - 26 December 2021
The purpose of this software is to extract OneStream business rule source
code from OneStreamXF xml files exported from the platform.
Usage:
The script accepts two arguments: the input path followed by the output path.
-- If the input path is a directory, all files within that directory will be
parsed.
-- By default the input and output are the present working directory.
Usage Examples (unix/linux style paths, windows works too):
$ python business-rule-code-extractor.py ~/input ~/Documents/businessrules
Takes files from ~/input and outputs to ~/Documents/businessrules
$ python business-rule-code-extractor.py . ~/Documents/businessrules
Takes files from the current working directory and outputs to
~/Documents/businessrules
$ python business-rule-code-extractor.py ~/input
Takes files from ~/input and outputs to the current working directory
$ python business-rule-code-extractor.py ~/input.xml ~/Documents
Takes the single ~/input.xml file as an input and outputs all business
rules to ~/Documents/input.businessRuleType.businessRuleName.vb
'''
# Import dependencies
import os
from xml.etree import ElementTree
import sys
# Main program loop
def main(inputpath, outputpath):
# If the output path does not exist, exit and do nothing
if not os.path.exists(inputpath):
print(f'The input path {inputpath} does not exist.')
return False
# If the input path resolves to a file and not a directory, then the input is a single file
inputIsSingleFile = os.path.isfile(inputpath)
# Use OS listdir to get a list of folder icons
try:
# Only get a list of files within './input'
inputItems = [f for f in os.listdir(inputpath) if os.path.isfile(os.path.join(inputpath, f))] if not inputIsSingleFile else [inputpath]
# Iterate through the input items and remove all items that do not end in .xml or .XML
validInputItems = []
for item in inputItems:
if item.lower().split('.').pop() == 'xml' and item[0] != '.': validInputItems.append(item)
inputItems = validInputItems
# If there are no leftover XML files, we have nothing to do
if len(inputItems) == 0 and not inputIsSingleFile:
print('There are no valid input XML files in the input directory, exiting.')
return False
elif inputIsSingleFile:
print('The input file is not an XML but was explicitly provied so continuing.')
inputItems = [inputpath]
print(f'Loaded {str(len(inputItems))} input file{"s" if len(inputItems) > 1 else ""} for processing.')
except Exception as e:
print(e)
# If the directory does not exist we have nothing to do
print(f'The input path {inputpath} does not exist.')
return False
# If the output path does not exist, exit and do nothing
if not os.path.exists(outputpath):
print(f'The output path {outputpath} does not exist.')
return False
# If the output path is a file and not a folder
elif os.path.isfile(outputpath):
print(f'The output path {outputpath} is a file. It needs to be a directory to accomodate multiple business rules.')
return False
# Extractor method
def extractFromFile(filepath):
try:
# Find all business rules according to the known OneStreamXF file structure
inputXML = ElementTree.parse(filepath)
businessRuleIterator = inputXML.getroot().iter('businessRule')
# Convert iterator to standard array
businessRules = []
for businessRule in businessRuleIterator:
businessRules.append(businessRule)
except Exception as e:
print(f'There was an issue parsing the business rules from {filepath}, perhaps the file is malformed?')
return False
# If there are no business rules in this file, we have nothing to do
if len(businessRules) == 0:
print(f'No business rules detected in {filepath}')
return False
# Business rule parsing function
def parseBusinessRule(businessRule):
# Business rule contents object
businessRuleContents = {
'type': businessRule.attrib['businessRuleType'],
'name': businessRule.attrib['name'],
'encrypted': str(businessRule.find('isEncrypted').text),
'sourceCode': str(businessRule.find('sourceCode').text)
}
# Write the sourcecode to a file
# Create a filename based on the location of the business rule, and if it is encrypted
filename = f'{filepath.split("/").pop().split(".").pop(-2)}.{businessRuleContents["type"]}.{businessRuleContents["name"]}.{"encrypted" if businessRuleContents["encrypted"] == "true" else "vb"}'
# Output file object
outputFile = open(os.path.join(outputpath, filename), 'w')
# Write lines
outputFile.writelines(businessRuleContents['sourceCode'])
# Close file
outputFile.close()
print(f' Finished extracting {businessRuleContents["type"]}.{businessRuleContents["name"]}{" (encrypted)" if businessRuleContents["encrypted"] == "true" else ""} from {filepath}')
# Iterate through the business rules
for businessRule in businessRules:
try:
parseBusinessRule(businessRule)
except Exception as e:
print(str(e))
print(f'Finished extracting rules from {filepath}')
# Parse each remaining item as XML and search for the sorucecode tag within each business rule
for inputFileName in inputItems:
extractFromFile(os.path.join(inputpath, inputFileName) if not inputIsSingleFile else inputFileName)
print('* * * * * FINISHED * * * * *')
return True
# Entry point
if __name__ == '__main__':
runtimeArguments = sys.argv.pop(0)
inputpath = sys.argv[0] if (len(sys.argv) > 0) else '.'
outputpath = sys.argv[1] if (len(sys.argv) > 1) else '.'
main(inputpath, outputpath)
@Krakor92
Copy link

Hi, I don't know if it's my version of python that is causing this (v3.10.6) but when I run the script, it says local variable 'inputIsSingleFile' referenced before assignment and the process stops with an exception thrown. Simply adding inputIsSingleFile = False at the beginning of the main func did the trick

@kzaremski
Copy link
Author

Hi, I don't know if it's my version of python that is causing this (v3.10.6) but when I run the script, it says local variable 'inputIsSingleFile' referenced before assignment and the process stops with an exception thrown. Simply adding inputIsSingleFile = False at the beginning of the main func did the trick

Hi. Yeah you're right.

I wrote this towards the end of last year and I must have been testing with a single file only then.

I have changed line 62 from if os.path.isfile(inputpath): inputIsSingleFile = True to inputIsSingleFile = os.path.isfile(inputpath). Looking back it wasn't good practice anyways to define a boolean variable using an if statement when I could just set it to the condition itself anyways; I don't know why I made that decision, but this change rectifies the problem.

Thanks for catching this and letting me know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment