8

I have hundreds of QGIS projects that have been built over years and many different QGIS versions. Now I need to move them to another server and I want to be able to edit the project files to show the new file paths.

My issue is that I cannot unzip the qgz files because my PC Administrator has not given me access to an unzipping program. Normally, if the file I want to unzip is one recognised by WinZip, then I can right click and choose Extract. But right clicking a qgz file does not give me the Extract option.

My research indicates that earlier qgs files are easy to access, but my issue is about project files created by the 3.0 series of QGIS and the most recent project file version and my IT admin's distrust of my intentions.

How else can I open the project files to access the file paths in the qgs part?

Johanna
  • 1,215
  • 14
  • 26
  • So, yes, I have access to an unzipping program - but it only recognises .zip file. I cannot use it to unzip any other zipped format. – Johanna Aug 12 '19 at 01:54
  • 2
    Try [shift] [right-click] and "open with...". Or manually - rename .qgz to .zip, extract zip, change paths, re-zip, rename .zip to .qgz. If you have python skills, you could automate this with os.walk (os.path.walk in python 2x) and zipfile. – user2856 Aug 12 '19 at 02:00
  • Thank you. The [shift][right-click] doesn't do anything for me, but the renaming does. – Johanna Aug 12 '19 at 02:05

1 Answers1

14

To fix this manually, rename .qgz to .zip, extract zip, change paths, re-zip, rename .zip to .qgz.

Alternatively, I've hacked together a very basic and very minimally tested python script to do this (Python 3 only unless you modify it):

from fnmatch import filter
from io import BytesIO
from os import (walk, path, rename)
from zipfile import ZipFile, ZIP_DEFLATED


def main(in_dir, old_path, new_path, backup_suffix=None):
    for (dirpath, dirnames, filenames) in walk(in_dir):
        qgzs = filter(filenames, '*.qgz')
        for qgz in qgzs:
            qgz_path = path.join(dirpath, qgz)
            mem_qgz = BytesIO()
            with ZipFile(qgz_path, 'r') as in_qgz, ZipFile(mem_qgz, 'w', compression=ZIP_DEFLATED) as tmp_qgz:
                for f in in_qgz.infolist():
                    data = in_qgz.read(f.filename).decode('UTF-8')
                    if f.filename.endswith('.qgs'):
                        data = data.replace('source="{}'.format(old_path), 'source="{}'.format(new_path))
                    tmp_qgz.writestr(f.filename, data)

            if backup_suffix is not None:
                backup_suffix = backup_suffix if backup_suffix.startswith('.') else '.' + backup_suffix
                try:
                    rename(qgz_path, qgz_path + backup_suffix)
                except FileExistsError as err:
                    print('Unable to backup file, skipping {} ({})'.format(qgz_path, err))
                    continue

            with open(qgz_path, 'wb') as out_qgz:
                out_qgz.write(mem_qgz.getvalue())


if __name__ == '__main__':
    backup_suffix = '.orig'  # Don't overwrite orig .qgz (just in case...), append ".orig"
    in_dir = r'D:\Temp'

    old_path = 'D:\\'  # 2 trailing backslashes on Windows, 1 forward for Unix like paths
    new_path = r'C:\\'

    main(in_dir, old_path, new_path, backup_suffix)

Note: the path changes are a kludge and rely on a simple search and replace, but the zipped .qgs file is actually XML and really should be parsed as such.

user2856
  • 65,736
  • 6
  • 115
  • 196
  • 1
    I edited your answer to include the manual method you mentioned in the comments above. Thank you, that's an excellent tip. I had no idea you could do that. – csk Aug 12 '19 at 16:26
  • A heads-up that, at least on QGIS 3.30 when save path is relative and a project home is defined in the project, source path are stored with / not , and may have several ../.. to traverse up the tree from the project home path. So this is a great script to use, but browse a few source paths in some of your .qgs files to figure out what to use for the old and new path encoding in your situation. – Houska Mar 22 '23 at 13:25