blob: accbc5055860663ae25527a6f29045306d8dd646 [file] [log] [blame]
#!/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 sys
def usage():
print('change_gds_string.py <old_string> <new_string> <path_to_gds_in> [<path_to_gds_out>]')
if __name__ == '__main__':
debug = 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 or len(arguments) > 4:
print("Wrong number of arguments given to change_gds_string.py.")
usage()
sys.exit(0)
if '-debug' in optionlist:
debug = True
oldstring = arguments[0]
newstring = arguments[1]
source = arguments[2]
# If only three arguments are provided, then overwrite the source file.
if len(arguments) == 4:
dest = arguments[3]
else:
dest = arguments[2]
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]
bsearch = bytes(oldstring, 'ascii')
brep = bytes(newstring, 'ascii')
datalen = len(gdsdata)
if debug:
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:
if debug:
print('Record type = ' + str(rectype) + ' data type = ' + str(datatype) + ' length = ' + str(reclen))
bstring = gdsdata[dataptr + 4: dataptr + reclen]
repstring = bstring.replace(bsearch, brep)
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:
print('Replaced ' + str(bstring) + ' with ' + str(repstring))
# Advance the pointer past the data
dataptr += newlen
with open(dest, 'wb') as ofile:
ofile.write(gdsdata)
exit(0)