AppstreamCollection.fromXml constructor
AppstreamCollection.fromXml(
- String xml
Decodes an Appstream collection in XML format.
Implementation
factory AppstreamCollection.fromXml(String xml) {
var document = XmlDocument.parse(xml);
var root = document.getElement('components');
if (root == null) {
throw FormatException("XML document doesn't contain components tag");
}
var version = root.getAttribute('version');
if (version == null) {
throw FormatException('Missing AppStream version');
}
var origin = root.getAttribute('origin');
if (origin == null) {
throw FormatException('Missing repository origin');
}
var architecture = root.getAttribute('architecture');
var components = <AppstreamComponent>[];
for (var component in root.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'component')) {
var typeName = component.getAttribute('type');
if (typeName == null) {
throw FormatException('Missing component type');
}
var type = _parseComponentType(typeName);
var id = component.getElement('id');
if (id == null) {
throw FormatException('Missing component ID');
}
var package = component.getElement('pkgname');
if (package == null) {
throw FormatException('Missing component package');
}
var name = _getXmlTranslatedString(component, 'name');
var summary = _getXmlTranslatedString(component, 'summary');
var description = _getXmlTranslatedString(component, 'description');
var developerName = _getXmlTranslatedString(component, 'developer_name');
var projectLicense = component.getElement('project_license')?.text;
var projectGroup = component.getElement('project_group')?.text;
var elements = component.children.whereType<XmlElement>();
var icons = <AppstreamIcon>[];
for (var icon in elements.where((e) => e.name.local == 'icon')) {
var type = icon.getAttribute('type');
if (type == null) {
throw FormatException('Missing icon type');
}
var w = icon.getAttribute('width');
var width = w != null ? int.parse(w) : null;
var h = icon.getAttribute('height');
var height = h != null ? int.parse(h) : null;
switch (type) {
case 'stock':
icons.add(AppstreamStockIcon(icon.text));
break;
case 'cached':
icons.add(
AppstreamCachedIcon(icon.text, width: width, height: height));
break;
case 'local':
icons.add(
AppstreamLocalIcon(icon.text, width: width, height: height));
break;
case 'remote':
icons.add(
AppstreamRemoteIcon(icon.text, width: width, height: height));
break;
}
}
var urls = <AppstreamUrl>[];
for (var url in elements.where((e) => e.name.local == 'url')) {
var typeName = url.getAttribute('type');
if (typeName == null) {
throw FormatException('Missing Url type');
}
urls.add(AppstreamUrl(url.text, type: _parseUrlType(typeName)));
}
var launchables = <AppstreamLaunchable>[];
for (var launchable
in elements.where((e) => e.name.local == 'launchable')) {
switch (launchable.getAttribute('type')) {
case 'desktop-id':
launchables.add(AppstreamLaunchableDesktopId(launchable.text));
break;
case 'service':
launchables.add(AppstreamLaunchableService(launchable.text));
break;
case 'cockpit-manifest':
launchables
.add(AppstreamLaunchableCockpitManifest(launchable.text));
break;
case 'url':
launchables.add(AppstreamLaunchableUrl(launchable.text));
break;
}
}
var categories = <String>[];
var categoriesElement = component.getElement('categories');
if (categoriesElement != null) {
categories = categoriesElement.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'category')
.map((e) => e.text)
.toList();
}
var keywords = <String, List<String>>{};
for (var keywordsElement
in elements.where((e) => e.name.local == 'keywords')) {
var lang = keywordsElement.getAttribute('xml:lang') ?? 'C';
keywords[lang] = keywordsElement.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'keyword')
.map((e) => e.text)
.toList();
}
var screenshots = <AppstreamScreenshot>[];
for (var screenshot
in elements.where((e) => e.name.local == 'screenshot')) {
var isDefault = screenshot.getAttribute('type') == 'default';
var caption = _getXmlTranslatedString(screenshot, 'caption');
var images = <AppstreamImage>[];
for (var imageElement in screenshot.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'image')) {
var typeName = imageElement.getAttribute('type');
if (typeName == null) {
throw FormatException('Missing image type');
}
var type = {
'source': AppstreamImageType.source,
'thumbnail': AppstreamImageType.thumbnail
}[typeName];
if (type == null) {
throw FormatException('Unknown image type');
}
var w = imageElement.getAttribute('width');
var width = w != null ? int.parse(w) : null;
var h = imageElement.getAttribute('height');
var height = h != null ? int.parse(h) : null;
var lang = imageElement.getAttribute('xml:lang');
images.add(AppstreamImage(
type: type,
url: imageElement.text,
width: width,
height: height,
lang: lang));
}
screenshots.add(AppstreamScreenshot(
images: images, caption: caption, isDefault: isDefault));
}
var compulsoryForDesktops = elements
.where((e) => e.name.local == 'compulsory_for_desktop')
.map((e) => e.text)
.toList();
var releases = <AppstreamRelease>[];
var releasesElement = component.getElement('releases');
if (releasesElement != null) {
for (var release in releasesElement.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'release')) {
var version = release.getAttribute('version');
DateTime? date;
var dateAttribute = release.getAttribute('date');
var unixTimestamp = release.getAttribute('timestamp');
if (unixTimestamp != null) {
date = DateTime.fromMillisecondsSinceEpoch(
int.parse(unixTimestamp) * 1000,
isUtc: true);
} else if (dateAttribute != null) {
date = DateTime.parse(dateAttribute);
}
AppstreamReleaseType? type;
var typeName = release.getAttribute('type');
if (typeName != null) {
type = _parseReleaseType(typeName);
}
AppstreamReleaseUrgency? urgency;
var urgencyName = release.getAttribute('urgency');
if (urgencyName != null) {
urgency = _parseReleaseUrgency(urgencyName);
}
var description = _getXmlTranslatedString(release, 'description');
var urlElement = release.getElement('url');
var url = urlElement?.text;
var issues = <AppstreamIssue>[];
var issuesElement = release.getElement('issues');
if (issuesElement != null) {
for (var issue in issuesElement.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'issue')) {
AppstreamIssueType? type;
var typeName = issue.getAttribute('type');
if (typeName != null) {
type = _parseIssueType(typeName);
}
var url = issue.getAttribute('url');
issues.add(AppstreamIssue(issue.text,
type: type ?? AppstreamIssueType.generic, url: url));
}
}
releases.add(AppstreamRelease(
version: version,
date: date,
type: type ?? AppstreamReleaseType.stable,
urgency: urgency ?? AppstreamReleaseUrgency.medium,
description: description,
url: url,
issues: issues));
}
}
var provides = <AppstreamProvides>[];
var providesElement = component.getElement('provides');
if (providesElement != null) {
for (var element in providesElement.children.whereType<XmlElement>()) {
switch (element.name.local) {
case 'mediatype':
provides.add(AppstreamProvidesMediatype(element.text));
break;
case 'library':
provides.add(AppstreamProvidesLibrary(element.text));
break;
case 'binary':
provides.add(AppstreamProvidesBinary(element.text));
break;
case 'font':
provides.add(AppstreamProvidesFont(element.text));
break;
case 'modalias':
provides.add(AppstreamProvidesModalias(element.text));
break;
case 'firmware':
var typeName = element.getAttribute('type');
if (typeName == null) {
throw FormatException('Missing firmware type');
}
var type = {
'runtime': AppstreamFirmwareType.runtime,
'flashed': AppstreamFirmwareType.flashed
}[typeName];
if (type == null) {
throw FormatException('Unknown firmware type $typeName');
}
provides.add(AppstreamProvidesFirmware(type, element.text));
break;
case 'python2':
provides.add(AppstreamProvidesPython2(element.text));
break;
case 'python3':
provides.add(AppstreamProvidesPython3(element.text));
break;
case 'dbus':
var type = element.getAttribute('type');
if (type == null) {
throw FormatException('Missing DBus bus type');
}
provides.add(
AppstreamProvidesDBus(_parseDBusType(type), element.text));
break;
case 'id':
provides.add(AppstreamProvidesId(element.text));
break;
}
}
}
var languages = <AppstreamLanguage>[];
var languagesElement = component.getElement('languages');
if (languagesElement != null) {
for (var language in languagesElement.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'lang')) {
var percentage = language.getAttribute('percentage');
languages.add(AppstreamLanguage(language.text,
percentage: percentage != null ? int.parse(percentage) : null));
}
}
var contentRatings = <String, Map<String, AppstreamContentRating>>{};
for (var contentRating
in elements.where((e) => e.name.local == 'content_rating')) {
var type = contentRating.getAttribute('type');
if (type == null) {
throw FormatException('Missing content rating type');
}
var ratings = <String, AppstreamContentRating>{};
for (var contentAttribute in contentRating.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'content_attribute')) {
var id = contentAttribute.getAttribute('id');
if (id == null) {
throw FormatException('Missing content attribute id');
}
ratings[id] = _parseContentRating(contentAttribute.text);
}
contentRatings[type] = ratings;
}
components.add(AppstreamComponent(
id: id.text,
type: type,
package: package.text,
name: name,
summary: summary,
description: description,
developerName: developerName,
projectLicense: projectLicense,
projectGroup: projectGroup,
icons: icons,
urls: urls,
launchables: launchables,
categories: categories,
keywords: keywords,
screenshots: screenshots,
compulsoryForDesktops: compulsoryForDesktops,
releases: releases,
provides: provides,
languages: languages,
contentRatings: contentRatings));
}
return AppstreamCollection(
version: version,
origin: origin,
architecture: architecture,
components: components);
}