-
-
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 assignmentand the process stops with an exception thrown. Simply addinginputIsSingleFile = Falseat 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 assignmentand the process stops with an exception thrown. Simply addinginputIsSingleFile = Falseat the beginning of the main func did the trick