| #!/usr/bin/env python3 | |
| # Script to read a GDS file, modify the given string, and rewrite the GDS file. | |
| # The string may be a substring; the GDS file will be parsed completely for | |
| # library name, structure name, instance name, and other strings, and the | |
| # replacement made everywhere it occurs, finding the bounds of the entire | |
| # string around the search text, and adjusting the record bounds accordingly. | |
| import os | |
| import re | |
| import sys | |
| def usage(): | |
| print('change_gds_string.py <old_string> <new_string> [...] <path_to_gds_in> [<path_to_gds_out>]') | |
| if __name__ == '__main__': | |
| debug = 0 | |
| verbatim = False | |
| if len(sys.argv) == 1: | |
| print("No options given to change_gds_string.py.") | |
| usage() | |
| sys.exit(0) | |
| optionlist = [] | |
| arguments = [] | |
| for option in sys.argv[1:]: | |
| if option.find('-', 0) == 0: | |
| optionlist.append(option) | |
| else: | |
| arguments.append(option) | |
| if len(arguments) < 3: | |
| print("Wrong number of arguments given to change_gds_string.py.") | |
| usage() | |
| sys.exit(0) | |
| for option in optionlist: | |
| opval = option.split('=') | |
| if opval[0] == '-debug': | |
| if len(opval) == 2: | |
| debug = int(opval[1]) | |
| else: | |
| debug = 1 | |
| elif opval[0] == '-verbatim': | |
| verbatim = True | |
| # If next-to-last argument is a valid path, then the last argument should | |
| # be the path to GDS out. Otherwise, overwrite the source file. | |
| if os.path.isfile(arguments[-2]): | |
| dest = arguments[-1] | |
| source = arguments[-2] | |
| oldstrings = arguments[0:-2:2] | |
| newstrings = arguments[1:-2:2] | |
| else: | |
| dest = arguments[-1] | |
| source = arguments[-1] | |
| oldstrings = arguments[0:-1:2] | |
| newstrings = arguments[1:-1:2] | |
| if len(oldstrings) != len(newstrings): | |
| print('Error: List of strings and replacements is not in pairs.') | |
| sys.exit(1) | |
| sourcedir = os.path.split(source)[0] | |
| gdsinfile = os.path.split(source)[1] | |
| destdir = os.path.split(dest)[0] | |
| gdsoutfile = os.path.split(dest)[1] | |
| with open(source, 'rb') as ifile: | |
| gdsdata = ifile.read() | |
| # To be done: Allow the user to select a specific record type or types | |
| # in which to restrict the string substitution. If no restrictions are | |
| # specified, then substitue in library name, structure name, and strings. | |
| recordtypes = ['libname', 'strname', 'sname', 'string'] | |
| recordfilter = [2, 6, 18, 25] | |
| bsearchlist = list(bytes(item, 'ascii') for item in oldstrings) | |
| breplist = list(bytes(item, 'ascii') for item in newstrings) | |
| if debug > 1: | |
| print('Search list = ' + str(bsearchlist)) | |
| print('Replace list = ' + str(breplist)) | |
| datalen = len(gdsdata) | |
| if debug > 0: | |
| print('Original data length = ' + str(datalen)) | |
| dataptr = 0 | |
| while dataptr < datalen: | |
| # Read stream records up to any string, then search for search text. | |
| bheader = gdsdata[dataptr:dataptr + 2] | |
| reclen = int.from_bytes(bheader, 'big') | |
| newlen = reclen | |
| if newlen == 0: | |
| print('Error: found zero-length record at position ' + str(dataptr)) | |
| break | |
| rectype = gdsdata[dataptr + 2] | |
| datatype = gdsdata[dataptr + 3] | |
| if rectype in recordfilter: | |
| # Datatype 6 is STRING | |
| if datatype == 6: | |
| bstring = gdsdata[dataptr + 4: dataptr + reclen] | |
| if debug > 1: | |
| idx = recordfilter.index(rectype) | |
| print(recordtypes[idx] + ' string = ' + str(bstring)) | |
| for bsearch,brep in zip(bsearchlist, breplist): | |
| # Verbatim option: search string must match GDS string exactly | |
| if verbatim: | |
| blen = reclen - 4 | |
| if bstring[-1] == 0: | |
| blen = blen - 1 | |
| if len(bsearch) != blen: | |
| continue | |
| repstring = re.sub(bsearch, brep, bstring) | |
| if repstring != bstring: | |
| before = gdsdata[0:dataptr] | |
| after = gdsdata[dataptr + reclen:] | |
| newlen = len(repstring) + 4 | |
| # Record sizes must be even | |
| if newlen % 2 != 0: | |
| # Was original string padded with null byte? If so, | |
| # remove the null byte and reduce newlen. Otherwise, | |
| # add a null byte and increase newlen. | |
| if bstring[-1] == 0: | |
| repstring = repstring[0:-1] | |
| newlen -= 1 | |
| else: | |
| repstring += b'\x00' | |
| newlen += 1 | |
| bnewlen = newlen.to_bytes(2, byteorder='big') | |
| brectype = rectype.to_bytes(1, byteorder='big') | |
| bdatatype = datatype.to_bytes(1, byteorder='big') | |
| # Assemble the new record | |
| newrecord = bnewlen + brectype + bdatatype + repstring | |
| # Reassemble the GDS data around the new record | |
| gdsdata = before + newrecord[0:newlen] + after | |
| # Adjust the data end location | |
| datalen += (newlen - reclen) | |
| if debug > 0: | |
| print('Replaced ' + str(bstring) + ' with ' + str(repstring)) | |
| else: | |
| if debug > 1: | |
| idx = recordfilter.index(rectype) | |
| print(recordtypes[idx] + ' record = ' + str(datatype) + ' is not a string') | |
| # Advance the pointer past the data | |
| dataptr += newlen | |
| with open(dest, 'wb') as ofile: | |
| ofile.write(gdsdata) | |
| exit(0) |