kiwi.xml_description
1# Copyright (c) 2015 SUSE Linux GmbH. All rights reserved. 2# 3# This file is part of kiwi. 4# 5# kiwi is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# kiwi is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with kiwi. If not, see <http://www.gnu.org/licenses/> 17# 18import importlib 19from typing import ( 20 Dict, Any 21) 22import os 23import logging 24from xml.dom import minidom 25from lxml import etree 26 27# project 28from kiwi.utils.temporary import Temporary 29from kiwi.markup import Markup 30from kiwi.defaults import Defaults 31from kiwi import xml_parse 32from kiwi.command import Command 33 34from kiwi.exceptions import ( 35 KiwiCommandError, 36 KiwiSchemaImportError, 37 KiwiValidationError, 38 KiwiDescriptionInvalid, 39 KiwiDataStructureError, 40 KiwiExtensionError, 41 KiwiCommandNotFound 42) 43 44log = logging.getLogger('kiwi') 45 46 47class XMLDescription: 48 """ 49 **Implements data management for the image description** 50 51 Supported description markup languages are XML, YAML, JSON and INI. 52 The provided input file is converted into XML, transformed to the 53 current RelaxNG schema via XSLT and validated against this result. 54 55 * XSLT Style Sheet processing to apply on this version of kiwi 56 * Schema Validation based on RelaxNG schema 57 * Loading XML data into internal data structures 58 59 Attributes 60 61 :param str description: path to description file 62 :param str derived_from: path to base description file 63 """ 64 def __init__( 65 self, description: str = '', derived_from: str = None 66 ): 67 log.info(f'Loading XML description: {description}') 68 self.markup = Markup.new(description) 69 self.description = self.markup.get_xml_description() 70 self.derived_from = derived_from 71 self.description_origin = description 72 self.extension_data: Dict = {} 73 74 def load(self) -> Any: 75 """ 76 Read XML description, validate it against the schema 77 and the schematron rules and pass it to the 78 autogenerated(generateDS) parser. 79 80 :return: instance of XML toplevel domain (image) 81 82 :rtype: object 83 """ 84 isoschematron = None 85 schematron = None 86 try: 87 isoschematron = importlib.import_module( 88 Defaults.get_schematron_module_name() 89 ) 90 except Exception as error: 91 log.warning(f"schematron validation skipped: {error}") 92 try: 93 schema_doc = etree.parse(Defaults.get_schema_file()) 94 relaxng = etree.RelaxNG(schema_doc) 95 if isoschematron: 96 schematron = isoschematron.Schematron( 97 schema_doc, store_report=True 98 ) 99 except Exception as issue: 100 raise KiwiSchemaImportError(issue) 101 try: 102 description = etree.parse(self.description) 103 validation_rng = relaxng.validate(description) 104 if schematron: 105 validation_schematron = schematron.validate(description) 106 except Exception as issue: 107 raise KiwiValidationError(issue) 108 if not validation_rng: 109 XMLDescription._get_relaxng_validation_details( 110 Defaults.get_schema_file(), 111 self.description, 112 relaxng.error_log 113 ) 114 if schematron and not validation_schematron: 115 XMLDescription._get_schematron_validation_details( 116 schematron.validation_report 117 ) 118 if not validation_rng or (schematron and not validation_schematron): 119 log.debug(open(self.description).read()) 120 raise KiwiDescriptionInvalid( 121 'Failed to validate schema and/or schematron rules. ' 122 'Use --debug for more details' 123 ) 124 125 parse_result = self._parse() 126 127 if parse_result.get_extension(): 128 extension_namespace_map = \ 129 description.getroot().xpath('extension')[0].nsmap 130 131 for namespace_name in extension_namespace_map: 132 extensions_for_namespace = description.getroot().xpath( 133 'extension/{namespace}:*'.format(namespace=namespace_name), 134 namespaces=extension_namespace_map 135 ) 136 if extensions_for_namespace: 137 # one toplevel entry point per extension via xmlns 138 if len(extensions_for_namespace) > 1: 139 raise KiwiExtensionError( 140 'Multiple toplevel sections for "{0}" found'.format( 141 namespace_name 142 ) 143 ) 144 145 # store extension xml data parse tree for this namespace 146 self.extension_data[namespace_name] = \ 147 etree.ElementTree(extensions_for_namespace[0]) 148 149 # validate extension xml data 150 try: 151 xml_catalog = Command.run( 152 [ 153 'xmlcatalog', '/etc/xml/catalog', 154 extension_namespace_map[namespace_name] 155 ] 156 ) 157 extension_schema = xml_catalog.output.rstrip().replace( 158 'file://', '' 159 ) 160 extension_relaxng = etree.RelaxNG( 161 etree.parse(extension_schema) 162 ) 163 except Exception as issue: 164 raise KiwiExtensionError( 165 'Extension schema error: {0}'.format(issue) 166 ) 167 validation_result = extension_relaxng.validate( 168 self.extension_data[namespace_name] 169 ) 170 if not validation_result: 171 xml_data_unformatted = etree.tostring( 172 self.extension_data[namespace_name], 173 encoding='utf-8' 174 ) 175 xml_data_domtree = minidom.parseString( 176 xml_data_unformatted 177 ) 178 extension_file = Temporary().new_file() 179 with open(extension_file.name, 'w') as xml_data: 180 xml_data.write(xml_data_domtree.toprettyxml()) 181 XMLDescription._get_relaxng_validation_details( 182 extension_schema, 183 extension_file.name, 184 extension_relaxng.error_log 185 ) 186 raise KiwiExtensionError( 187 'Schema validation for extension XML data failed' 188 ) 189 190 return parse_result 191 192 def get_extension_xml_data(self, namespace_name: str) -> Any: 193 """ 194 Return the xml etree parse result for the specified extension namespace 195 196 :param str namespace_name: name of the extension namespace 197 198 :return: result of etree.parse 199 200 :rtype: object 201 """ 202 return self.extension_data.get(namespace_name) 203 204 @staticmethod 205 def _get_relaxng_validation_details( 206 schema_file, description_file, error_log 207 ): 208 """ 209 Run jing program to validate description against the schema 210 211 Jing provides detailed error information in case of a schema 212 validation failure. If jing is not present the standard 213 error_log as provided from the raw XML libraries is used 214 """ 215 try: 216 Command.run( 217 ['jing', schema_file, description_file] 218 ) 219 except KiwiCommandError as issue: 220 log.info('RelaxNG validation failed. See jing report:') 221 log.info('--> {0}'.format(issue)) 222 except KiwiCommandNotFound as issue: 223 log.warning(issue) 224 log.warning( 225 'For detailed schema validation report, install: jing' 226 ) 227 log.info('Showing only raw library error log:') 228 log.info('--> {0}'.format(error_log)) 229 230 @staticmethod 231 def _get_schematron_validation_details(validation_report): 232 """ 233 Extract error message form the schematron validation report 234 235 :param etree validation_report: the schematron validation report 236 """ 237 nspaces = validation_report.getroot().nsmap 238 log.info('Schematron validation failed:') 239 for msg in validation_report.xpath( 240 '//svrl:failed-assert/svrl:text', namespaces=nspaces 241 ): 242 log.info('--> %s', msg.text) 243 244 def _parse(self): 245 try: 246 parse = xml_parse.parse( 247 self.description, True 248 ) 249 parse.description_dir = self.description_origin and os.path.dirname( 250 self.description_origin 251 ) 252 parse.derived_description_dir = self.derived_from 253 return parse 254 except Exception as issue: 255 raise KiwiDataStructureError(issue)
log =
<Logger kiwi (DEBUG)>
class
XMLDescription:
48class XMLDescription: 49 """ 50 **Implements data management for the image description** 51 52 Supported description markup languages are XML, YAML, JSON and INI. 53 The provided input file is converted into XML, transformed to the 54 current RelaxNG schema via XSLT and validated against this result. 55 56 * XSLT Style Sheet processing to apply on this version of kiwi 57 * Schema Validation based on RelaxNG schema 58 * Loading XML data into internal data structures 59 60 Attributes 61 62 :param str description: path to description file 63 :param str derived_from: path to base description file 64 """ 65 def __init__( 66 self, description: str = '', derived_from: str = None 67 ): 68 log.info(f'Loading XML description: {description}') 69 self.markup = Markup.new(description) 70 self.description = self.markup.get_xml_description() 71 self.derived_from = derived_from 72 self.description_origin = description 73 self.extension_data: Dict = {} 74 75 def load(self) -> Any: 76 """ 77 Read XML description, validate it against the schema 78 and the schematron rules and pass it to the 79 autogenerated(generateDS) parser. 80 81 :return: instance of XML toplevel domain (image) 82 83 :rtype: object 84 """ 85 isoschematron = None 86 schematron = None 87 try: 88 isoschematron = importlib.import_module( 89 Defaults.get_schematron_module_name() 90 ) 91 except Exception as error: 92 log.warning(f"schematron validation skipped: {error}") 93 try: 94 schema_doc = etree.parse(Defaults.get_schema_file()) 95 relaxng = etree.RelaxNG(schema_doc) 96 if isoschematron: 97 schematron = isoschematron.Schematron( 98 schema_doc, store_report=True 99 ) 100 except Exception as issue: 101 raise KiwiSchemaImportError(issue) 102 try: 103 description = etree.parse(self.description) 104 validation_rng = relaxng.validate(description) 105 if schematron: 106 validation_schematron = schematron.validate(description) 107 except Exception as issue: 108 raise KiwiValidationError(issue) 109 if not validation_rng: 110 XMLDescription._get_relaxng_validation_details( 111 Defaults.get_schema_file(), 112 self.description, 113 relaxng.error_log 114 ) 115 if schematron and not validation_schematron: 116 XMLDescription._get_schematron_validation_details( 117 schematron.validation_report 118 ) 119 if not validation_rng or (schematron and not validation_schematron): 120 log.debug(open(self.description).read()) 121 raise KiwiDescriptionInvalid( 122 'Failed to validate schema and/or schematron rules. ' 123 'Use --debug for more details' 124 ) 125 126 parse_result = self._parse() 127 128 if parse_result.get_extension(): 129 extension_namespace_map = \ 130 description.getroot().xpath('extension')[0].nsmap 131 132 for namespace_name in extension_namespace_map: 133 extensions_for_namespace = description.getroot().xpath( 134 'extension/{namespace}:*'.format(namespace=namespace_name), 135 namespaces=extension_namespace_map 136 ) 137 if extensions_for_namespace: 138 # one toplevel entry point per extension via xmlns 139 if len(extensions_for_namespace) > 1: 140 raise KiwiExtensionError( 141 'Multiple toplevel sections for "{0}" found'.format( 142 namespace_name 143 ) 144 ) 145 146 # store extension xml data parse tree for this namespace 147 self.extension_data[namespace_name] = \ 148 etree.ElementTree(extensions_for_namespace[0]) 149 150 # validate extension xml data 151 try: 152 xml_catalog = Command.run( 153 [ 154 'xmlcatalog', '/etc/xml/catalog', 155 extension_namespace_map[namespace_name] 156 ] 157 ) 158 extension_schema = xml_catalog.output.rstrip().replace( 159 'file://', '' 160 ) 161 extension_relaxng = etree.RelaxNG( 162 etree.parse(extension_schema) 163 ) 164 except Exception as issue: 165 raise KiwiExtensionError( 166 'Extension schema error: {0}'.format(issue) 167 ) 168 validation_result = extension_relaxng.validate( 169 self.extension_data[namespace_name] 170 ) 171 if not validation_result: 172 xml_data_unformatted = etree.tostring( 173 self.extension_data[namespace_name], 174 encoding='utf-8' 175 ) 176 xml_data_domtree = minidom.parseString( 177 xml_data_unformatted 178 ) 179 extension_file = Temporary().new_file() 180 with open(extension_file.name, 'w') as xml_data: 181 xml_data.write(xml_data_domtree.toprettyxml()) 182 XMLDescription._get_relaxng_validation_details( 183 extension_schema, 184 extension_file.name, 185 extension_relaxng.error_log 186 ) 187 raise KiwiExtensionError( 188 'Schema validation for extension XML data failed' 189 ) 190 191 return parse_result 192 193 def get_extension_xml_data(self, namespace_name: str) -> Any: 194 """ 195 Return the xml etree parse result for the specified extension namespace 196 197 :param str namespace_name: name of the extension namespace 198 199 :return: result of etree.parse 200 201 :rtype: object 202 """ 203 return self.extension_data.get(namespace_name) 204 205 @staticmethod 206 def _get_relaxng_validation_details( 207 schema_file, description_file, error_log 208 ): 209 """ 210 Run jing program to validate description against the schema 211 212 Jing provides detailed error information in case of a schema 213 validation failure. If jing is not present the standard 214 error_log as provided from the raw XML libraries is used 215 """ 216 try: 217 Command.run( 218 ['jing', schema_file, description_file] 219 ) 220 except KiwiCommandError as issue: 221 log.info('RelaxNG validation failed. See jing report:') 222 log.info('--> {0}'.format(issue)) 223 except KiwiCommandNotFound as issue: 224 log.warning(issue) 225 log.warning( 226 'For detailed schema validation report, install: jing' 227 ) 228 log.info('Showing only raw library error log:') 229 log.info('--> {0}'.format(error_log)) 230 231 @staticmethod 232 def _get_schematron_validation_details(validation_report): 233 """ 234 Extract error message form the schematron validation report 235 236 :param etree validation_report: the schematron validation report 237 """ 238 nspaces = validation_report.getroot().nsmap 239 log.info('Schematron validation failed:') 240 for msg in validation_report.xpath( 241 '//svrl:failed-assert/svrl:text', namespaces=nspaces 242 ): 243 log.info('--> %s', msg.text) 244 245 def _parse(self): 246 try: 247 parse = xml_parse.parse( 248 self.description, True 249 ) 250 parse.description_dir = self.description_origin and os.path.dirname( 251 self.description_origin 252 ) 253 parse.derived_description_dir = self.derived_from 254 return parse 255 except Exception as issue: 256 raise KiwiDataStructureError(issue)
Implements data management for the image description
Supported description markup languages are XML, YAML, JSON and INI. The provided input file is converted into XML, transformed to the current RelaxNG schema via XSLT and validated against this result.
- XSLT Style Sheet processing to apply on this version of kiwi
- Schema Validation based on RelaxNG schema
- Loading XML data into internal data structures
Attributes
Parameters
- str description: path to description file
- str derived_from: path to base description file
XMLDescription(description: str = '', derived_from: str = None)
65 def __init__( 66 self, description: str = '', derived_from: str = None 67 ): 68 log.info(f'Loading XML description: {description}') 69 self.markup = Markup.new(description) 70 self.description = self.markup.get_xml_description() 71 self.derived_from = derived_from 72 self.description_origin = description 73 self.extension_data: Dict = {}
def
load(self) -> Any:
75 def load(self) -> Any: 76 """ 77 Read XML description, validate it against the schema 78 and the schematron rules and pass it to the 79 autogenerated(generateDS) parser. 80 81 :return: instance of XML toplevel domain (image) 82 83 :rtype: object 84 """ 85 isoschematron = None 86 schematron = None 87 try: 88 isoschematron = importlib.import_module( 89 Defaults.get_schematron_module_name() 90 ) 91 except Exception as error: 92 log.warning(f"schematron validation skipped: {error}") 93 try: 94 schema_doc = etree.parse(Defaults.get_schema_file()) 95 relaxng = etree.RelaxNG(schema_doc) 96 if isoschematron: 97 schematron = isoschematron.Schematron( 98 schema_doc, store_report=True 99 ) 100 except Exception as issue: 101 raise KiwiSchemaImportError(issue) 102 try: 103 description = etree.parse(self.description) 104 validation_rng = relaxng.validate(description) 105 if schematron: 106 validation_schematron = schematron.validate(description) 107 except Exception as issue: 108 raise KiwiValidationError(issue) 109 if not validation_rng: 110 XMLDescription._get_relaxng_validation_details( 111 Defaults.get_schema_file(), 112 self.description, 113 relaxng.error_log 114 ) 115 if schematron and not validation_schematron: 116 XMLDescription._get_schematron_validation_details( 117 schematron.validation_report 118 ) 119 if not validation_rng or (schematron and not validation_schematron): 120 log.debug(open(self.description).read()) 121 raise KiwiDescriptionInvalid( 122 'Failed to validate schema and/or schematron rules. ' 123 'Use --debug for more details' 124 ) 125 126 parse_result = self._parse() 127 128 if parse_result.get_extension(): 129 extension_namespace_map = \ 130 description.getroot().xpath('extension')[0].nsmap 131 132 for namespace_name in extension_namespace_map: 133 extensions_for_namespace = description.getroot().xpath( 134 'extension/{namespace}:*'.format(namespace=namespace_name), 135 namespaces=extension_namespace_map 136 ) 137 if extensions_for_namespace: 138 # one toplevel entry point per extension via xmlns 139 if len(extensions_for_namespace) > 1: 140 raise KiwiExtensionError( 141 'Multiple toplevel sections for "{0}" found'.format( 142 namespace_name 143 ) 144 ) 145 146 # store extension xml data parse tree for this namespace 147 self.extension_data[namespace_name] = \ 148 etree.ElementTree(extensions_for_namespace[0]) 149 150 # validate extension xml data 151 try: 152 xml_catalog = Command.run( 153 [ 154 'xmlcatalog', '/etc/xml/catalog', 155 extension_namespace_map[namespace_name] 156 ] 157 ) 158 extension_schema = xml_catalog.output.rstrip().replace( 159 'file://', '' 160 ) 161 extension_relaxng = etree.RelaxNG( 162 etree.parse(extension_schema) 163 ) 164 except Exception as issue: 165 raise KiwiExtensionError( 166 'Extension schema error: {0}'.format(issue) 167 ) 168 validation_result = extension_relaxng.validate( 169 self.extension_data[namespace_name] 170 ) 171 if not validation_result: 172 xml_data_unformatted = etree.tostring( 173 self.extension_data[namespace_name], 174 encoding='utf-8' 175 ) 176 xml_data_domtree = minidom.parseString( 177 xml_data_unformatted 178 ) 179 extension_file = Temporary().new_file() 180 with open(extension_file.name, 'w') as xml_data: 181 xml_data.write(xml_data_domtree.toprettyxml()) 182 XMLDescription._get_relaxng_validation_details( 183 extension_schema, 184 extension_file.name, 185 extension_relaxng.error_log 186 ) 187 raise KiwiExtensionError( 188 'Schema validation for extension XML data failed' 189 ) 190 191 return parse_result
Read XML description, validate it against the schema and the schematron rules and pass it to the autogenerated(generateDS) parser.
Returns
instance of XML toplevel domain (image)
def
get_extension_xml_data(self, namespace_name: str) -> Any:
193 def get_extension_xml_data(self, namespace_name: str) -> Any: 194 """ 195 Return the xml etree parse result for the specified extension namespace 196 197 :param str namespace_name: name of the extension namespace 198 199 :return: result of etree.parse 200 201 :rtype: object 202 """ 203 return self.extension_data.get(namespace_name)
Return the xml etree parse result for the specified extension namespace
Parameters
- str namespace_name: name of the extension namespace
Returns
result of etree.parse