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 }}
+
+
+