Initial commit of public repository open_pdks.
diff --git a/common/change_gds_string.py b/common/change_gds_string.py
new file mode 100755
index 0000000..b8a0321
--- /dev/null
+++ b/common/change_gds_string.py
@@ -0,0 +1,127 @@
+#!/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)