def processAlgorithm( self, # pylint:disable=too-many-locals,too-many-statements,too-many-branches parameters, context, feedback): input_file = self.parameterAsString(parameters, self.INPUT, context) output_file = self.parameterAsFileOutput(parameters, self.OUTPUT, context) fields = QgsFields() fields.append(QgsField('name', QVariant.String, '', 60)) fields.append(QgsField('warning', QVariant.String, '', 250)) sink, dest = self.parameterAsSink(parameters, self.REPORT, context, fields) style = QgsStyle() style.createMemoryDatabase() results = {} symbol_names = set() def make_name_unique(original_name): """ Ensures that the symbol name is unique (in a case-insensitive way) """ counter = 0 candidate = original_name while candidate.lower() in symbol_names: # make name unique if counter == 0: candidate += '_1' else: candidate = candidate[:candidate.rfind('_') + 1] + str(counter) counter += 1 symbol_names.add(candidate.lower()) return candidate symbols_to_extract = [ Extractor.FILL_SYMBOLS, Extractor.LINE_SYMBOLS, Extractor.MARKER_SYMBOLS, Extractor.COLOR_RAMPS, Extractor.LINE_PATCHES, Extractor.AREA_PATCHES ] if Qgis.QGIS_VERSION_INT >= 30900: symbols_to_extract.extend( (Extractor.TEXT_SYMBOLS, Extractor.LABELS, Extractor.MAPLEX_LABELS)) type_percent = 100.0 / len(symbols_to_extract) results[self.LABEL_SETTINGS_COUNT] = 0 results[self.UNREADABLE_LABEL_SETTINGS] = 0 for type_index, symbol_type in enumerate(symbols_to_extract): feedback.pushInfo('Importing {} from {}'.format( symbol_type, input_file)) try: raw_symbols = Extractor.extract_styles(input_file, symbol_type) except MissingBinaryException: raise QgsProcessingException( # pylint: disable=raise-missing-from 'The MDB tools "mdb-export" utility is required to convert .style databases. Please setup a path to the MDB tools utility in the SLYR options panel.' ) feedback.pushInfo('Found {} symbols of type "{}"\n\n'.format( len(raw_symbols), symbol_type)) if feedback.isCanceled(): break unreadable = 0 for index, raw_symbol in enumerate(raw_symbols): feedback.setProgress(index / len(raw_symbols) * type_percent + type_percent * type_index) if feedback.isCanceled(): break name = raw_symbol[Extractor.NAME] tags = raw_symbol[Extractor.TAGS].split(';') feedback.pushInfo('{}/{}: {}'.format(index + 1, len(raw_symbols), name)) unique_name = make_name_unique(name) if name != unique_name: feedback.pushInfo( 'Corrected to unique name of {}'.format(unique_name)) handle = BytesIO(raw_symbol[Extractor.BLOB]) stream = Stream(handle) f = QgsFeature() try: symbol = stream.read_object() except UnreadableSymbolException as e: feedback.reportError( 'Error reading symbol {}: {}'.format(name, e), False) unreadable += 1 if sink: f.setAttributes( [name, 'Error reading symbol: {}'.format(e)]) sink.addFeature(f) continue except NotImplementedException as e: feedback.reportError( 'Parsing {} is not supported: {}'.format(name, e), False) unreadable += 1 if sink: f.setAttributes( [name, 'Parsing not supported: {}'.format(e)]) sink.addFeature(f) continue except UnsupportedVersionException as e: feedback.reportError( 'Cannot read {} version: {}'.format(name, e), False) unreadable += 1 if sink: f.setAttributes( [name, 'Version not supported: {}'.format(e)]) sink.addFeature(f) continue except UnknownClsidException as e: feedback.reportError(str(e), False) unreadable += 1 if sink: f.setAttributes([name, 'Unknown object: {}'.format(e)]) sink.addFeature(f) continue except UnreadablePictureException as e: feedback.reportError(str(e), False) unreadable += 1 if sink: f.setAttributes( [name, 'Unreadable picture: {}'.format(e)]) sink.addFeature(f) continue def unsupported_object_callback(msg, level=Context.WARNING): if level == Context.WARNING: feedback.reportError('Warning: {}'.format(msg), False) elif level == Context.CRITICAL: feedback.reportError(msg, False) if sink: feat = QgsFeature() feat.setAttributes([name, msg]) # pylint: disable=cell-var-from-loop sink.addFeature(feat) context = Context() context.symbol_name = unique_name context.style_folder, _ = os.path.split(output_file) context.unsupported_object_callback = unsupported_object_callback if symbol_type in (Extractor.AREA_PATCHES, Extractor.LINE_PATCHES): feedback.reportError( '{}: Legend patch conversion requires the licensed version of SLYR' .format(name), False) unreadable += 1 if sink: f.setAttributes( [name, 'Unreadable legend patch: {}'.format(name)]) sink.addFeature(f) continue try: qgis_symbol = SymbolConverter.Symbol_to_QgsSymbol( symbol, context) except NotImplementedException as e: feedback.reportError(str(e), False) unreadable += 1 if sink: f.setAttributes([name, str(e)]) sink.addFeature(f) continue except UnreadablePictureException as e: feedback.reportError(str(e), False) unreadable += 1 if sink: f.setAttributes( [name, 'Unreadable picture: {}'.format(e)]) sink.addFeature(f) continue if isinstance(qgis_symbol, QgsSymbol): style.addSymbol(unique_name, qgis_symbol, True) elif isinstance(qgis_symbol, QgsColorRamp): style.addColorRamp(unique_name, qgis_symbol, True) elif isinstance(qgis_symbol, QgsTextFormat): if Qgis.QGIS_VERSION_INT >= 30900: style.addTextFormat(unique_name, qgis_symbol, True) elif isinstance(qgis_symbol, QgsPalLayerSettings): if Qgis.QGIS_VERSION_INT >= 30900: style.addLabelSettings(unique_name, qgis_symbol, True) elif Qgis.QGIS_VERSION_INT >= 31300: if isinstance(qgis_symbol, QgsLegendPatchShape): style.addLegendPatchShape(unique_name, qgis_symbol, True) if tags: if isinstance(qgis_symbol, QgsSymbol): assert style.tagSymbol(QgsStyle.SymbolEntity, unique_name, tags) elif isinstance(qgis_symbol, QgsColorRamp): assert style.tagSymbol(QgsStyle.ColorrampEntity, unique_name, tags) elif isinstance(qgis_symbol, QgsTextFormat) and hasattr( QgsStyle, 'TextFormatEntity'): assert style.tagSymbol(QgsStyle.TextFormatEntity, unique_name, tags) elif isinstance(qgis_symbol, QgsPalLayerSettings) and hasattr( QgsStyle, 'LabelSettingsEntity'): assert style.tagSymbol(QgsStyle.LabelSettingsEntity, unique_name, tags) elif Qgis.QGIS_VERSION_INT >= 31300: if isinstance(qgis_symbol, QgsLegendPatchShape): assert style.tagSymbol( QgsStyle.LegendPatchShapeEntity, unique_name, tags) if symbol_type == Extractor.FILL_SYMBOLS: results[self.FILL_SYMBOL_COUNT] = len(raw_symbols) results[self.UNREADABLE_FILL_SYMBOLS] = unreadable elif symbol_type == Extractor.LINE_SYMBOLS: results[self.LINE_SYMBOL_COUNT] = len(raw_symbols) results[self.UNREADABLE_LINE_SYMBOLS] = unreadable elif symbol_type == Extractor.MARKER_SYMBOLS: results[self.MARKER_SYMBOL_COUNT] = len(raw_symbols) results[self.UNREADABLE_MARKER_SYMBOLS] = unreadable elif symbol_type == Extractor.COLOR_RAMPS: results[self.COLOR_RAMP_COUNT] = len(raw_symbols) results[self.UNREADABLE_COLOR_RAMPS] = unreadable elif symbol_type == Extractor.TEXT_SYMBOLS: results[self.TEXT_FORMAT_COUNT] = len(raw_symbols) results[self.UNREADABLE_TEXT_FORMATS] = unreadable elif symbol_type in (Extractor.MAPLEX_LABELS, Extractor.LABELS): results[self.LABEL_SETTINGS_COUNT] += len(raw_symbols) results[self.UNREADABLE_LABEL_SETTINGS] += unreadable elif symbol_type == Extractor.LINE_PATCHES: results[self.LINE_PATCH_COUNT] = len(raw_symbols) results[self.UNREADABLE_LINE_PATCHES] = unreadable elif symbol_type == Extractor.AREA_PATCHES: results[self.AREA_PATCH_COUNT] = len(raw_symbols) results[self.UNREADABLE_AREA_PATCHES] = unreadable style.exportXml(output_file) results[self.OUTPUT] = output_file results[self.REPORT] = dest return results
def open_style(input_file): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """ Opens a .style file """ if input_file.lower().endswith( '.style') and not Extractor.is_mdb_tools_binary_available(): message_bar = iface.messageBar() widget = message_bar.createMessage('SLYR', "MDB Tools utility not found") settings_button = QPushButton("Configure…", pressed=partial( BrowserUtils.open_settings, widget)) widget.layout().addWidget(settings_button) message_bar.pushWidget(widget, Qgis.Critical) return True style = QgsStyle() style.createMemoryDatabase() symbol_names = set() def make_name_unique(original_name): """ Ensures that the symbol name is unique (in a case-insensitive way) """ counter = 0 candidate = original_name while candidate.lower() in symbol_names: # make name unique if counter == 0: candidate += '_1' else: candidate = candidate[:candidate.rfind('_') + 1] + str(counter) counter += 1 symbol_names.add(candidate.lower()) return candidate feedback = QgsFeedback() progress_dialog = QProgressDialog("Loading style database…", "Abort", 0, 100, None) progress_dialog.setWindowTitle("Loading Style") def progress_changed(progress: float): """ Handles feedback to progress dialog bridge """ progress_dialog.setValue(int(progress)) iterations = 0 while QCoreApplication.hasPendingEvents() and iterations < 100: QCoreApplication.processEvents() iterations += 1 feedback.progressChanged.connect(progress_changed) def cancel(): """ Slot to cancel the import """ feedback.cancel() progress_dialog.canceled.connect(cancel) unreadable = [] warnings = set() errors = set() types_to_extract = [ Extractor.FILL_SYMBOLS, Extractor.LINE_SYMBOLS, Extractor.MARKER_SYMBOLS, Extractor.COLOR_RAMPS, Extractor.TEXT_SYMBOLS, Extractor.LABELS, Extractor.MAPLEX_LABELS, Extractor.AREA_PATCHES, Extractor.LINE_PATCHES ] type_percent = 100 / len(types_to_extract) for type_index, symbol_type in enumerate(types_to_extract): try: raw_symbols = Extractor.extract_styles(input_file, symbol_type) except MissingBinaryException: BrowserUtils.show_warning( 'MDB Tools utility not found', 'Convert style', 'The MDB tools "mdb-export" utility is required to convert .style databases. Please setup a path to the MDB tools utility in the SLYR options panel.', level=Qgis.Critical) progress_dialog.deleteLater() return True if feedback.isCanceled(): break for index, raw_symbol in enumerate(raw_symbols): feedback.setProgress( int(index / len(raw_symbols) * type_percent + type_percent * type_index)) if feedback.isCanceled(): break name = raw_symbol[Extractor.NAME] tags = raw_symbol[Extractor.TAGS].split(';') unique_name = make_name_unique(name) handle = BytesIO(raw_symbol[Extractor.BLOB]) stream = Stream(handle) stream.allow_shortcuts = False try: symbol = stream.read_object() except UnreadableSymbolException as e: e = 'Unreadable object: {}'.format(e) unreadable.append('<b>{}</b>: {}'.format( html.escape(name), html.escape(str(e)))) continue except NotImplementedException as e: unreadable.append('<b>{}</b>: {}'.format( html.escape(name), html.escape(str(e)))) continue except UnsupportedVersionException as e: e = 'Unsupported version: {}'.format(e) unreadable.append('<b>{}</b>: {}'.format( html.escape(name), html.escape(str(e)))) continue except UnknownClsidException as e: unreadable.append('<b>{}</b>: {}'.format( html.escape(name), html.escape(str(e)))) continue except UnreadablePictureException as e: unreadable.append('<b>{}</b>: {}'.format( html.escape(name), html.escape(str(e)))) continue context = Context() context.symbol_name = unique_name def unsupported_object_callback(msg, level=Context.WARNING): if level == Context.WARNING: warnings.add('<b>{}</b>: {}'.format( html.escape(unique_name), # pylint: disable=cell-var-from-loop html.escape(msg))) elif level == Context.CRITICAL: errors.add('<b>{}</b>: {}'.format( html.escape(unique_name), # pylint: disable=cell-var-from-loop html.escape(msg))) context.unsupported_object_callback = unsupported_object_callback # context.style_folder, _ = os.path.split(output_file) if symbol_type in (Extractor.AREA_PATCHES, Extractor.LINE_PATCHES): unreadable.append( '<b>{}</b>: Legend patch conversion requires the licensed version of SLYR' .format(html.escape(name))) continue try: qgis_symbol = SymbolConverter.Symbol_to_QgsSymbol( symbol, context) except NotImplementedException as e: unreadable.append('<b>{}</b>: {}'.format( html.escape(name), html.escape(str(e)))) continue except UnreadablePictureException as e: unreadable.append('<b>{}</b>: {}'.format( html.escape(name), html.escape(str(e)))) continue if isinstance(qgis_symbol, QgsSymbol): # self.check_for_missing_fonts(qgis_symbol, feedback) style.addSymbol(unique_name, qgis_symbol, True) elif isinstance(qgis_symbol, QgsColorRamp): style.addColorRamp(unique_name, qgis_symbol, True) elif isinstance(qgis_symbol, QgsTextFormat): if Qgis.QGIS_VERSION_INT >= 30900: style.addTextFormat(unique_name, qgis_symbol, True) elif isinstance(qgis_symbol, QgsPalLayerSettings): if Qgis.QGIS_VERSION_INT >= 30900: style.addLabelSettings(unique_name, qgis_symbol, True) elif Qgis.QGIS_VERSION_INT >= 31300: if isinstance(qgis_symbol, QgsLegendPatchShape): style.addLegendPatchShape(unique_name, qgis_symbol, True) if tags: if isinstance(qgis_symbol, QgsSymbol): assert style.tagSymbol(QgsStyle.SymbolEntity, unique_name, tags) elif isinstance(qgis_symbol, QgsColorRamp): assert style.tagSymbol(QgsStyle.ColorrampEntity, unique_name, tags) elif isinstance(qgis_symbol, QgsTextFormat) and hasattr( QgsStyle, 'TextFormatEntity'): assert style.tagSymbol(QgsStyle.TextFormatEntity, unique_name, tags) elif isinstance(qgis_symbol, QgsPalLayerSettings) and hasattr( QgsStyle, 'LabelSettingsEntity'): assert style.tagSymbol(QgsStyle.LabelSettingsEntity, unique_name, tags) elif Qgis.QGIS_VERSION_INT >= 31300: if isinstance(qgis_symbol, QgsLegendPatchShape): assert style.tagSymbol( QgsStyle.LegendPatchShapeEntity, unique_name, tags) progress_dialog.deleteLater() if feedback.isCanceled(): return True if errors or unreadable or warnings: message = '' if unreadable: message = '<p>The following symbols could not be converted:</p>' message += '<ul>' for w in unreadable: message += '<li>{}</li>'.format(w.replace('\n', '<br>')) message += '</ul>' if errors: message += '<p>The following errors were generated while converting symbols:</p>' message += '<ul>' for w in errors: message += '<li>{}</li>'.format(w.replace('\n', '<br>')) message += '</ul>' if warnings: message += '<p>The following warnings were generated while converting symbols:</p>' message += '<ul>' for w in warnings: message += '<li>{}</li>'.format(w.replace('\n', '<br>')) message += '</ul>' BrowserUtils.show_warning( 'style could not be completely converted', 'Convert style', message, level=Qgis.Critical if (unreadable or errors) else Qgis.Warning) if Qgis.QGIS_VERSION_INT >= 30800: dlg = QgsStyleManagerDialog(style, readOnly=True) dlg.setFavoritesGroupVisible(False) dlg.setSmartGroupsVisible(False) fi = QFileInfo(input_file) dlg.setBaseStyleName(fi.baseName()) else: dlg = QgsStyleManagerDialog(style) dlg.setWindowTitle(fi.baseName()) dlg.exec_() return True