Last active
April 26, 2023 14:49
-
-
Save kzaremski/4d7c02d7fecb0212c0373abc13cb2a47 to your computer and use it in GitHub Desktop.
OneStream XF Business Rule Source Code Extractor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
toinputIsSingleFile = 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!