-
-
Save kzaremski/4d7c02d7fecb0212c0373abc13cb2a47 to your computer and use it in GitHub Desktop.
#!/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) |
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 addinginputIsSingleFile = 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!
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 addinginputIsSingleFile = False
at the beginning of the main func did the trick