diff --git a/nextcloudappstore/api/v1/release/__init__.py b/nextcloudappstore/api/v1/release/__init__.py index 98aa35ebd49..4d652f84cde 100644 --- a/nextcloudappstore/api/v1/release/__init__.py +++ b/nextcloudappstore/api/v1/release/__init__.py @@ -12,6 +12,8 @@ def __init__(self) -> None: self.info_schema = read_relative_file(__file__, 'info.xsd') self.info_xslt = read_relative_file(__file__, 'info.xslt') self.pre_info_xslt = read_relative_file(__file__, 'pre-info.xslt') + self.changes_schema = read_relative_file(__file__, 'changes.xsd') + self.pre_changes_xslt = read_relative_file(__file__, 'pre-changes.xslt') self.db_schema = read_relative_file(__file__, 'database.xsd') self.pre_db_xslt = read_relative_file(__file__, 'pre-database.xslt') self.languages = settings.LANGUAGES diff --git a/nextcloudappstore/api/v1/release/changes.xsd b/nextcloudappstore/api/v1/release/changes.xsd new file mode 100644 index 00000000000..7b2cf9f92c2 --- /dev/null +++ b/nextcloudappstore/api/v1/release/changes.xsd @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nextcloudappstore/api/v1/release/parser.py b/nextcloudappstore/api/v1/release/parser.py index 41704e99611..44a18835bc0 100644 --- a/nextcloudappstore/api/v1/release/parser.py +++ b/nextcloudappstore/api/v1/release/parser.py @@ -208,16 +208,16 @@ def create_safe_xml_parser() -> lxml.etree.XMLParser: remove_blank_text=True, dtd_validation=False # type: ignore ) # type: ignore - -def parse_app_metadata(xml: str, schema: str, pre_xslt: str, - xslt: str) -> Dict: +def parse_app_xml_file(xml: str, schema: str, pre_xslt: str, + xslt: str, filename: str) -> Dict: """ Parses, validates and maps the xml onto a dict - :argument xml the info.xml string to parse + :argument xml the xml string to parse :argument schema the schema xml as string :argument pre_xslt xslt which is run before validation to ensure that everything is in the correct order and that unknown elements are excluded :argument xslt the xslt to transform it to a matching structure + :argument filename the file that is parsed essentially for error output :raises InvalidAppMetadataXmlException if the schema does not validate :return the parsed xml as dict """ @@ -225,7 +225,7 @@ def parse_app_metadata(xml: str, schema: str, pre_xslt: str, try: doc = lxml.etree.fromstring(bytes(xml, encoding='utf-8'), parser) except lxml.etree.XMLSyntaxError as e: - msg = 'info.xml contains malformed xml: %s' % e + msg = '%s contains malformed xml: %s' % (filename, e) raise XMLSyntaxError(msg) for _ in doc.iter(lxml.etree.Entity): # type: ignore raise InvalidAppMetadataXmlException('Must not contain entities') @@ -236,15 +236,36 @@ def parse_app_metadata(xml: str, schema: str, pre_xslt: str, try: schema.assertValid(pre_transformed_doc) # type: ignore except lxml.etree.DocumentInvalid as e: - msg = 'info.xml did not validate: %s' % e + msg = '%s did not validate: %s' % (filename, e) raise InvalidAppMetadataXmlException(msg) transform = lxml.etree.XSLT(lxml.etree.XML(xslt)) # type: ignore transformed_doc = transform(pre_transformed_doc) # type: ignore mapped = element_to_dict(transformed_doc.getroot()) # type: ignore + return mapped + +def parse_app_metadata(xml: str, schema: str, pre_xslt: str, + xslt: str) -> Dict: + """ + Parses, validates and maps the xml onto a dict + :argument xml the info.xml string to parse + :argument schema the schema xml as string + :argument pre_xslt xslt which is run before validation to ensure that + everything is in the correct order and that unknown elements are excluded + :argument xslt the xslt to transform it to a matching structure + :raises InvalidAppMetadataXmlException if the schema does not validate + :return the parsed xml as dict + """ + mapped = parse_app_xml_file(xml, schema, pre_xslt, xslt, "info.xml") validate_english_present(mapped) fix_partial_translations(mapped) return mapped +def parse_app_whats_new(xml: str, schema: str, pre_xslt: str, + xslt: str) -> Dict: + mapped = parse_app_xml_file(xml, schema, pre_xslt, xslt, "changes.xml") + validate_english_present(mapped) + fix_partial_translations(mapped) + return mapped def validate_database(xml: str, schema: str, pre_xslt: str) -> None: """ diff --git a/nextcloudappstore/api/v1/release/pre-changes.xslt b/nextcloudappstore/api/v1/release/pre-changes.xslt new file mode 100644 index 00000000000..509b982e773 --- /dev/null +++ b/nextcloudappstore/api/v1/release/pre-changes.xslt @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nextcloudappstore/api/v1/release/provider.py b/nextcloudappstore/api/v1/release/provider.py index 702843252c4..a9b238f312d 100644 --- a/nextcloudappstore/api/v1/release/provider.py +++ b/nextcloudappstore/api/v1/release/provider.py @@ -6,8 +6,8 @@ from nextcloudappstore.api.v1.release.downloader import \ AppReleaseDownloader from nextcloudappstore.api.v1.release.parser import \ - GunZipAppMetadataExtractor, parse_app_metadata, parse_changelog, \ - validate_database + GunZipAppMetadataExtractor, parse_app_metadata, parse_app_whats_new, \ + parse_changelog, validate_database class InvalidAppDirectoryException(ValidationError): @@ -35,6 +35,9 @@ def get_release_info(self, url: str, is_nightly: bool = False) -> Release: info = parse_app_metadata(meta.info_xml, self.config.info_schema, self.config.pre_info_xslt, self.config.info_xslt) + if meta.changes_xml: + whats_new = parse_app_whats_new(meta.changes_xml, self.config.changes_schema, + self.config.pre_changes_xslt) if meta.database_xml: validate_database(meta.database_xml, self.config.db_schema, self.config.pre_db_xslt) diff --git a/nextcloudappstore/scaffolding/app-templates/12/app/appinfo/changes.xml b/nextcloudappstore/scaffolding/app-templates/12/app/appinfo/changes.xml new file mode 100644 index 00000000000..19c2c3d9260 --- /dev/null +++ b/nextcloudappstore/scaffolding/app-templates/12/app/appinfo/changes.xml @@ -0,0 +1,12 @@ + + + + + + First {{ app.name }} release! + + + requires {{ app.nextcloud_version }} + + +