Merge pull request #26293 from the-black-eagle/mka_read_more_tags
[xbmc.git] / xbmc / addons / addoninfo / AddonInfoBuilder.cpp
blob19cc351b6d924bf164b17ec5d362b74fce236b02
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "AddonInfoBuilder.h"
11 #include "CompileInfo.h"
12 #include "LangInfo.h"
13 #include "addons/Repository.h"
14 #include "addons/addoninfo/AddonInfo.h"
15 #include "addons/addoninfo/AddonType.h"
16 #include "filesystem/File.h"
17 #include "filesystem/SpecialProtocol.h"
18 #include "utils/JSONVariantParser.h"
19 #include "utils/JSONVariantWriter.h"
20 #include "utils/StringUtils.h"
21 #include "utils/URIUtils.h"
22 #include "utils/Variant.h"
23 #include "utils/XBMCTinyXML2.h"
24 #include "utils/log.h"
26 #include <algorithm>
27 #include <memory>
28 #include <regex>
30 namespace
32 // Note that all of these characters are url-safe
33 const std::string VALID_ADDON_IDENTIFIER_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_@!$";
36 namespace ADDON
39 CAddonInfoBuilderFromDB::CAddonInfoBuilderFromDB() : m_addonInfo(std::make_shared<CAddonInfo>())
43 void CAddonInfoBuilderFromDB::SetId(std::string id)
45 m_addonInfo->m_id = std::move(id);
48 void CAddonInfoBuilderFromDB::SetName(std::string name)
50 m_addonInfo->m_name = std::move(name);
53 void CAddonInfoBuilderFromDB::SetLicense(std::string license)
55 m_addonInfo->m_license = std::move(license);
58 void CAddonInfoBuilderFromDB::SetSummary(std::string summary)
60 m_addonInfo->m_summary.insert(std::pair<std::string, std::string>("unk", std::move(summary)));
63 void CAddonInfoBuilderFromDB::SetDescription(std::string description)
65 m_addonInfo->m_description.insert(
66 std::pair<std::string, std::string>("unk", std::move(description)));
69 void CAddonInfoBuilderFromDB::SetDisclaimer(std::string disclaimer)
71 m_addonInfo->m_disclaimer.insert(
72 std::pair<std::string, std::string>("unk", std::move(disclaimer)));
75 void CAddonInfoBuilderFromDB::SetAuthor(std::string author)
77 m_addonInfo->m_author = std::move(author);
80 void CAddonInfoBuilderFromDB::SetSource(std::string source)
82 m_addonInfo->m_source = std::move(source);
85 void CAddonInfoBuilderFromDB::SetWebsite(std::string website)
87 m_addonInfo->m_website = std::move(website);
90 void CAddonInfoBuilderFromDB::SetForum(std::string forum)
92 m_addonInfo->m_forum = std::move(forum);
95 void CAddonInfoBuilderFromDB::SetEMail(std::string email)
97 m_addonInfo->m_email = std::move(email);
100 void CAddonInfoBuilderFromDB::SetIcon(std::string icon)
102 m_addonInfo->m_icon = std::move(icon);
105 void CAddonInfoBuilderFromDB::SetArt(const std::string& type, std::string value)
107 m_addonInfo->m_art[type] = std::move(value);
110 void CAddonInfoBuilderFromDB::SetArt(std::map<std::string, std::string> art)
112 m_addonInfo->m_art = std::move(art);
115 void CAddonInfoBuilderFromDB::SetScreenshots(std::vector<std::string> screenshots)
117 m_addonInfo->m_screenshots = std::move(screenshots);
120 void CAddonInfoBuilderFromDB::SetChangelog(std::string changelog)
122 m_addonInfo->m_changelog.insert(std::pair<std::string, std::string>("unk", std::move(changelog)));
125 void CAddonInfoBuilderFromDB::SetLifecycleState(AddonLifecycleState state, std::string description)
127 m_addonInfo->m_lifecycleState = state;
128 m_addonInfo->m_lifecycleStateDescription.emplace("unk", std::move(description));
131 void CAddonInfoBuilderFromDB::SetPath(std::string path)
133 m_addonInfo->m_path = std::move(path);
136 void CAddonInfoBuilderFromDB::SetLibName(std::string libname)
138 m_addonInfo->m_libname = std::move(libname);
141 void CAddonInfoBuilderFromDB::SetVersion(CAddonVersion version)
143 m_addonInfo->m_version = std::move(version);
146 void CAddonInfoBuilderFromDB::SetDependencies(std::vector<DependencyInfo> dependencies)
148 m_addonInfo->m_dependencies = std::move(dependencies);
151 void CAddonInfoBuilderFromDB::SetExtrainfo(InfoMap extrainfo)
153 m_addonInfo->m_extrainfo = std::move(extrainfo);
156 void CAddonInfoBuilderFromDB::SetInstallDate(const CDateTime& installDate)
158 m_addonInfo->m_installDate = installDate;
161 void CAddonInfoBuilderFromDB::SetLastUpdated(const CDateTime& lastUpdated)
163 m_addonInfo->m_lastUpdated = lastUpdated;
166 void CAddonInfoBuilderFromDB::SetLastUsed(const CDateTime& lastUsed)
168 m_addonInfo->m_lastUsed = lastUsed;
171 void CAddonInfoBuilderFromDB::SetOrigin(std::string origin)
173 m_addonInfo->m_origin = std::move(origin);
176 void CAddonInfoBuilderFromDB::SetPackageSize(uint64_t size)
178 m_addonInfo->m_packageSize = size;
181 void CAddonInfoBuilderFromDB::SetExtensions(CAddonType addonType)
183 if (!addonType.GetValue("provides").empty())
184 addonType.SetProvides(addonType.GetValue("provides").asString());
186 m_addonInfo->m_types.push_back(std::move(addonType));
187 m_addonInfo->m_mainType = addonType.m_type;
190 AddonInfoPtr CAddonInfoBuilder::Generate(const std::string& id, AddonType type)
192 // Check addon identifier for forbidden characters
193 // The identifier is used e.g. in URLs so we shouldn't allow just
194 // any character to go through.
195 if (id.empty() || id.find_first_not_of(VALID_ADDON_IDENTIFIER_CHARACTERS) != std::string::npos)
197 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: identifier '{}' is invalid", __FUNCTION__, id);
198 return nullptr;
201 AddonInfoPtr addon = std::make_shared<CAddonInfo>();
202 addon->m_id = id;
203 addon->m_mainType = type;
204 return addon;
207 AddonInfoPtr CAddonInfoBuilder::Generate(const std::string& addonPath, bool platformCheck /*= true*/)
209 auto addonRealPath = CSpecialProtocol::TranslatePath(addonPath);
211 CXBMCTinyXML2 xmlDoc;
212 if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(addonRealPath, "addon.xml")))
214 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: Unable to load '{}', Line {}\n{}", __FUNCTION__,
215 URIUtils::AddFileToFolder(addonRealPath, "addon.xml"), xmlDoc.ErrorLineNum(),
216 xmlDoc.ErrorStr());
217 return nullptr;
220 AddonInfoPtr addon = std::make_shared<CAddonInfo>();
221 if (!ParseXML(addon, xmlDoc.RootElement(), addonRealPath))
222 return nullptr;
224 if (!platformCheck || PlatformSupportsAddon(addon))
225 return addon;
227 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: No platform for add-on {} (supported platforms: {})",
228 __FUNCTION__, addon->ID(), StringUtils::Join(addon->m_platforms, ", "));
230 return nullptr;
233 AddonInfoPtr CAddonInfoBuilder::Generate(const tinyxml2::XMLElement* baseElement,
234 const RepositoryDirInfo& repo,
235 bool platformCheck /*= true*/)
237 AddonInfoPtr addon = std::make_shared<CAddonInfo>();
238 if (!ParseXML(addon, baseElement, repo.datadir, repo))
239 return nullptr;
241 if (!platformCheck || PlatformSupportsAddon(addon))
242 return addon;
244 return nullptr;
247 void CAddonInfoBuilder::SetInstallData(const AddonInfoPtr& addon, const CDateTime& installDate, const CDateTime& lastUpdated,
248 const CDateTime& lastUsed, const std::string& origin)
250 if (!addon)
251 return;
253 addon->m_installDate = installDate;
254 addon->m_lastUpdated = lastUpdated;
255 addon->m_lastUsed = lastUsed;
256 addon->m_origin = origin;
259 bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon,
260 const tinyxml2::XMLElement* element,
261 const std::string& addonPath)
263 return ParseXML(addon, element, addonPath, {});
266 bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon,
267 const tinyxml2::XMLElement* element,
268 const std::string& addonPath,
269 const RepositoryDirInfo& repo)
272 * Following values currently not set from creator:
273 * - CDateTime installDate;
274 * - CDateTime lastUpdated;
275 * - CDateTime lastUsed;
276 * - std::string origin;
279 if (!StringUtils::EqualsNoCase(element->Value(), "addon"))
281 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file from '{}' doesn't contain <addon>", __FUNCTION__, addonPath);
282 return false;
286 * The function variable "repo" is only used when reading data stored on the internet.
287 * A boolean value is then set here for easier identification.
289 const bool isRepoXMLContent = !repo.datadir.empty();
292 * Parse addon.xml:
293 * <addon id="???"
294 * name="???"
295 * version="???"
296 * provider-name="???">
298 addon->m_id = StringUtils::CreateFromCString(element->Attribute("id"));
299 addon->m_name = StringUtils::CreateFromCString(element->Attribute("name"));
300 addon->m_author = StringUtils::CreateFromCString(element->Attribute("provider-name"));
302 const std::string version = StringUtils::CreateFromCString(element->Attribute("version"));
303 addon->m_version = CAddonVersion(version);
305 if (addon->m_id.empty() || addon->m_version.empty())
307 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesn't contain required values on <addon ... > id='{}', version='{}'",
308 __FUNCTION__,
309 addonPath,
310 addon->m_id.empty() ? "missing" : addon->m_id,
311 addon->m_version.empty() ? "missing" : addon->m_version.asString());
312 return false;
315 // Check addon identifier for forbidden characters
316 // The identifier is used e.g. in URLs so we shouldn't allow just
317 // any character to go through.
318 if (addon->m_id.find_first_not_of(VALID_ADDON_IDENTIFIER_CHARACTERS) != std::string::npos)
320 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: identifier {} is invalid", __FUNCTION__, addon->m_id);
321 return false;
325 * Parse addon.xml:
326 * <backwards-compatibility abi="???"/>
328 const auto* backwards = element->FirstChildElement("backwards-compatibility");
329 if (backwards)
331 const std::string minVersion = StringUtils::CreateFromCString(backwards->Attribute("abi"));
332 addon->m_minversion = CAddonVersion(minVersion);
336 * Parse addon.xml:
337 * <requires>
338 * <import addon="???" minversion="???" version="???" optional="???"/>
339 * </requires>
341 const auto* _requires = element->FirstChildElement("requires");
342 if (_requires)
344 for (const auto* child = _requires->FirstChildElement("import"); child != nullptr;
345 child = child->NextSiblingElement("import"))
347 if (child->Attribute("addon"))
349 const std::string minVersion =
350 StringUtils::CreateFromCString(child->Attribute("minversion"));
351 const std::string version = StringUtils::CreateFromCString(child->Attribute("version"));
353 bool optional = false;
354 child->QueryBoolAttribute("optional", &optional);
356 addon->m_dependencies.emplace_back(child->Attribute("addon"), CAddonVersion(minVersion),
357 CAddonVersion(version), optional);
362 std::string assetBasePath;
363 if (!isRepoXMLContent && !addonPath.empty())
365 // Default for add-on information not loaded from repository
366 assetBasePath = addonPath;
367 addon->m_path = addonPath;
369 else
371 assetBasePath = URIUtils::AddFileToFolder(repo.artdir, addon->m_id);
372 addon->m_path = URIUtils::AddFileToFolder(repo.datadir, addon->m_id, StringUtils::Format("{}-{}.zip", addon->m_id, addon->m_version.asString()));
375 addon->m_profilePath = StringUtils::Format("special://profile/addon_data/{}/", addon->m_id);
378 * Parse addon.xml:
379 * <extension>
380 * ...
381 * </extension>
383 for (const auto* child = element->FirstChildElement("extension"); child != nullptr;
384 child = child->NextSiblingElement("extension"))
386 const std::string point = StringUtils::CreateFromCString(child->Attribute("point"));
388 if (point == "kodi.addon.metadata" || point == "xbmc.addon.metadata")
391 * Parse addon.xml "<path">...</path>" (special related to repository path),
392 * do first and if present override the default. Also set assetBasePath to
393 * find screenshots and icons.
395 element = child->FirstChildElement("path");
396 if (element && element->GetText() != nullptr && !repo.datadir.empty())
398 addon->m_path = URIUtils::AddFileToFolder(repo.datadir, element->GetText());
399 assetBasePath = URIUtils::GetDirectory(URIUtils::AddFileToFolder(repo.artdir, element->GetText()));
403 * Parse addon.xml "<summary lang="..">...</summary>"
405 GetTextList(child, "summary", addon->m_summary);
408 * Parse addon.xml "<description lang="..">...</description>"
410 GetTextList(child, "description", addon->m_description);
413 * Parse addon.xml "<disclaimer lang="..">...</disclaimer>"
415 GetTextList(child, "disclaimer", addon->m_disclaimer);
418 * Parse addon.xml "<assets>...</assets>"
420 const auto* element = child->FirstChildElement("assets");
421 if (element)
423 for (const auto* elementsAssets = element->FirstChildElement(); elementsAssets != nullptr;
424 elementsAssets = elementsAssets->NextSiblingElement())
426 std::string value = elementsAssets->Value();
427 if (value == "icon")
429 if (elementsAssets->GetText() != nullptr)
430 addon->m_icon = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText());
432 else if (value == "screenshot")
434 if (elementsAssets->GetText() != nullptr)
435 addon->m_screenshots.emplace_back(URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()));
437 else if (value == "fanart")
439 if (elementsAssets->GetText() != nullptr)
440 addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText());
442 else if (value == "banner")
444 if (elementsAssets->GetText() != nullptr)
445 addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText());
447 else if (value == "clearlogo")
449 if (elementsAssets->GetText() != nullptr)
450 addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText());
452 else if (value == "thumb")
454 if (elementsAssets->GetText() != nullptr)
455 addon->m_art[value] =
456 URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText());
461 /* Parse addon.xml "<platform">...</platform>" */
462 element = child->FirstChildElement("platform");
463 if (element && element->GetText() != nullptr)
465 auto platforms = StringUtils::Split(element->GetText(),
466 {" ", "\t", "\n", "\r"});
467 platforms.erase(std::remove_if(platforms.begin(), platforms.end(),
468 [](const std::string& platform) { return platform.empty(); }),
469 platforms.cend());
470 addon->m_platforms = platforms;
473 /* Parse addon.xml "<license">...</license>" */
474 element = child->FirstChildElement("license");
475 if (element && element->GetText() != nullptr)
476 addon->m_license = element->GetText();
478 /* Parse addon.xml "<source">...</source>" */
479 element = child->FirstChildElement("source");
480 if (element && element->GetText() != nullptr)
481 addon->m_source = element->GetText();
483 /* Parse addon.xml "<email">...</email>" */
484 element = child->FirstChildElement("email");
485 if (element && element->GetText() != nullptr)
486 addon->m_email = element->GetText();
488 /* Parse addon.xml "<website">...</website>" */
489 element = child->FirstChildElement("website");
490 if (element && element->GetText() != nullptr)
491 addon->m_website = element->GetText();
493 /* Parse addon.xml "<forum">...</forum>" */
494 element = child->FirstChildElement("forum");
495 if (element && element->GetText() != nullptr)
496 addon->m_forum = element->GetText();
498 /* Parse addon.xml "<broken">...</broken>"
499 * NOTE: Replaced with <lifecyclestate>, available for backward compatibility */
500 element = child->FirstChildElement("broken");
501 if (element && element->GetText() != nullptr)
503 addon->m_lifecycleState = AddonLifecycleState::BROKEN;
504 addon->m_lifecycleStateDescription.emplace(KODI_ADDON_DEFAULT_LANGUAGE_CODE,
505 element->GetText());
508 /* Parse addon.xml "<lifecyclestate">...</lifecyclestate>" */
509 element = child->FirstChildElement("lifecyclestate");
510 if (element && element->GetText() != nullptr)
512 const char* lang = element->Attribute("type");
513 if (lang)
515 if (strcmp(lang, "broken") == 0)
516 addon->m_lifecycleState = AddonLifecycleState::BROKEN;
517 else if (strcmp(lang, "deprecated") == 0)
518 addon->m_lifecycleState = AddonLifecycleState::DEPRECATED;
519 else
520 addon->m_lifecycleState = AddonLifecycleState::NORMAL;
522 GetTextList(child, "lifecyclestate", addon->m_lifecycleStateDescription);
526 /* Parse addon.xml "<language">...</language>" */
527 element = child->FirstChildElement("language");
528 if (element && element->GetText() != nullptr)
529 addon->AddExtraInfo("language", element->GetText());
531 /* Parse addon.xml "<reuselanguageinvoker">...</reuselanguageinvoker>" */
532 element = child->FirstChildElement("reuselanguageinvoker");
533 if (element && element->GetText() != nullptr)
534 addon->AddExtraInfo("reuselanguageinvoker", element->GetText());
536 /* Parse addon.xml "<size">...</size>" */
537 element = child->FirstChildElement("size");
538 if (element && element->GetText() != nullptr)
539 addon->m_packageSize = StringUtils::ToUint64(element->GetText(), 0);
541 /* Parse addon.xml "<news lang="..">...</news>"
543 * In the event that the changelog (news) in addon.xml is empty, check
544 * whether it is an installed addon and read a changelog.txt as a
545 * replacement, if available. */
546 GetTextList(child, "news", addon->m_changelog);
547 if (addon->m_changelog.empty() && !isRepoXMLContent && !addonPath.empty())
549 using XFILE::CFile;
551 const std::string changelog = URIUtils::AddFileToFolder(addonPath, "changelog.txt");
552 if (CFile::Exists(changelog))
554 CFile file;
555 std::vector<uint8_t> buf;
556 if (file.LoadFile(changelog, buf) > 0)
557 addon->m_changelog[KODI_ADDON_DEFAULT_LANGUAGE_CODE].assign(
558 reinterpret_cast<char*>(buf.data()), buf.size());
562 else
564 AddonType type = CAddonInfo::TranslateType(point);
565 if (type == AddonType::UNKNOWN || type >= AddonType::MAX_TYPES)
567 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesn't contain a valid add-on type name ({})", __FUNCTION__, addon->m_path, point);
568 return false;
571 CAddonType addonType(type);
572 if (ParseXMLTypes(addonType, addon, child))
573 addon->m_types.emplace_back(std::move(addonType));
578 * If nothing is defined in addon.xml set addon as unknown to have minimum one
579 * instance type present.
581 if (addon->m_types.empty())
583 CAddonType addonType(AddonType::UNKNOWN);
584 addon->m_types.emplace_back(std::move(addonType));
587 addon->m_mainType = addon->m_types[0].Type();
588 addon->m_libname = addon->m_types[0].m_libname;
589 if (!addon->m_types[0].GetValue("provides").empty())
590 addon->AddExtraInfo("provides", addon->m_types[0].GetValue("provides").asString());
592 // Ensure binary types have a valid library for the platform
593 if (addon->m_mainType == AddonType::VISUALIZATION ||
594 addon->m_mainType == AddonType::SCREENSAVER || addon->m_mainType == AddonType::PVRDLL ||
595 addon->m_mainType == AddonType::AUDIOENCODER ||
596 addon->m_mainType == AddonType::AUDIODECODER || addon->m_mainType == AddonType::VFS ||
597 addon->m_mainType == AddonType::IMAGEDECODER || addon->m_mainType == AddonType::INPUTSTREAM ||
598 addon->m_mainType == AddonType::PERIPHERALDLL || addon->m_mainType == AddonType::GAMEDLL)
600 if (addon->m_libname.empty())
602 // Prevent log file entry if data is from repository, there normal on
603 // addons for other OS's
604 if (!isRepoXMLContent)
605 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: addon.xml from '{}' for binary type '{}' doesn't contain library and addon becomes ignored",
606 __FUNCTION__, addon->ID(), CAddonInfo::TranslateType(addon->m_mainType));
607 return false;
611 if (!isRepoXMLContent)
613 using XFILE::CFile;
614 if (CFile::Exists(URIUtils::AddFileToFolder(addonPath, "resources", "settings.xml")))
615 addon->m_supportsAddonSettings = true;
616 if (CFile::Exists(URIUtils::AddFileToFolder(addonPath, "resources", "instance-settings.xml")))
617 addon->m_supportsInstanceSettings = true;
620 addon->m_addonInstanceSupportType = CAddonInfo::InstanceSupportType(addon->m_mainType);
622 return true;
625 bool CAddonInfoBuilder::ParseXMLTypes(CAddonType& addonType,
626 const AddonInfoPtr& info,
627 const tinyxml2::XMLElement* child)
629 if (child)
631 addonType.m_path = info->Path();
633 // Get add-on library file name (if present)
634 const char* library = child->Attribute("library");
635 if (library == nullptr)
636 library = GetPlatformLibraryName(child);
637 if (library != nullptr)
639 addonType.m_libname = library;
643 // linux is different and has the version number after the suffix
644 static const std::regex libRegex("^.*" +
645 CCompileInfo::CCompileInfo::GetSharedLibrarySuffix() +
646 "\\.?[0-9]*\\.?[0-9]*\\.?[0-9]*$");
647 if (std::regex_match(library, libRegex))
649 info->SetBinary(true);
650 CLog::Log(LOGDEBUG, "CAddonInfoBuilder::{}: Binary addon found: {}", __func__,
651 info->ID());
654 catch (const std::regex_error& e)
656 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: Regex error caught: {}", __func__,
657 e.what());
661 if (!ParseXMLExtension(addonType, child))
663 CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: addon.xml file doesn't contain a valid add-on extensions ({})", __FUNCTION__, info->ID());
664 return false;
666 if (!addonType.GetValue("provides").empty())
667 addonType.SetProvides(addonType.GetValue("provides").asString());
668 return true;
670 return false;
673 bool CAddonInfoBuilder::ParseXMLExtension(CAddonExtensions& addonExt,
674 const tinyxml2::XMLElement* element)
676 addonExt.m_point = StringUtils::CreateFromCString(element->Attribute("point"));
678 EXT_VALUE extension;
679 const auto* attribute = element->FirstAttribute();
680 while (attribute)
682 std::string name = attribute->Name();
683 if (name != "point")
685 const std::string value = StringUtils::CreateFromCString(attribute->Value());
686 if (!value.empty())
688 name = "@" + name;
689 extension.emplace_back(std::make_pair(name, SExtValue(value)));
692 attribute = attribute->Next();
694 if (!extension.empty())
695 addonExt.m_values.emplace_back(std::pair<std::string, EXT_VALUE>("", std::move(extension)));
697 const auto* childElement = element->FirstChildElement();
698 while (childElement)
700 const std::string id = StringUtils::CreateFromCString(childElement->Value());
701 if (!id.empty())
703 EXT_VALUE childExtension;
704 const auto* attribute = childElement->FirstAttribute();
705 while (attribute)
707 std::string name = attribute->Name();
708 if (name != "point")
710 const std::string value = StringUtils::CreateFromCString(attribute->Value());
711 if (!value.empty())
713 name = id + "@" + name;
714 childExtension.emplace_back(std::make_pair(name, SExtValue(value)));
717 attribute = attribute->Next();
720 const std::string childElementText = StringUtils::CreateFromCString(childElement->GetText());
722 if (!childElementText.empty())
724 childExtension.emplace_back(std::make_pair(id, SExtValue(childElementText)));
727 if (!childExtension.empty())
728 addonExt.m_values.emplace_back(std::make_pair(id, std::move(childExtension)));
730 if (childElementText.empty())
732 const tinyxml2::XMLElement* childSubElement = childElement->FirstChildElement();
733 if (childSubElement)
735 CAddonExtensions subElement;
736 if (ParseXMLExtension(subElement, childElement))
737 addonExt.m_children.emplace_back(std::make_pair(id, std::move(subElement)));
741 childElement = childElement->NextSiblingElement();
744 return true;
747 bool CAddonInfoBuilder::GetTextList(const tinyxml2::XMLElement* element,
748 const std::string& tag,
749 std::unordered_map<std::string, std::string>& translatedValues)
751 if (!element)
752 return false;
754 translatedValues.clear();
756 for (const auto* child = element->FirstChildElement(tag.c_str()); child != nullptr;
757 child = child->NextSiblingElement(tag.c_str()))
759 const char* lang = child->Attribute("lang");
760 const char* text = child->GetText();
761 if (lang != nullptr)
763 if (strcmp(lang, "no") == 0)
764 translatedValues.insert(std::make_pair("nb_NO", text != nullptr ? text : ""));
765 else
766 translatedValues.insert(std::make_pair(lang, text != nullptr ? text : ""));
768 else
769 translatedValues.insert(
770 std::make_pair(KODI_ADDON_DEFAULT_LANGUAGE_CODE, text != nullptr ? text : ""));
773 return !translatedValues.empty();
776 const char* CAddonInfoBuilder::GetPlatformLibraryName(const tinyxml2::XMLElement* element)
778 const char* libraryName = nullptr;
779 #if defined(TARGET_ANDROID)
780 libraryName = element->Attribute("library_android");
781 #elif defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
782 #if defined(TARGET_FREEBSD)
783 libraryName = element->Attribute("library_freebsd");
784 if (libraryName == nullptr)
785 #endif
786 libraryName = element->Attribute("library_linux");
787 #elif defined(TARGET_WINDOWS_DESKTOP)
788 libraryName = element->Attribute("library_windx");
789 if (libraryName == nullptr)
790 libraryName = element->Attribute("library_windows");
791 #elif defined(TARGET_WINDOWS_STORE)
792 libraryName = element->Attribute("library_windowsstore");
793 #elif defined(TARGET_DARWIN)
794 #if defined(TARGET_DARWIN_EMBEDDED)
795 libraryName = element->Attribute("library_darwin_embedded");
796 #else
797 libraryName = element->Attribute("library_osx");
798 #endif
799 #endif
801 return libraryName;
804 bool CAddonInfoBuilder::PlatformSupportsAddon(const AddonInfoPtr& addon)
806 if (addon->m_platforms.empty())
807 return true;
809 std::vector<std::string> supportedPlatforms = {
810 "all",
811 #if defined(TARGET_ANDROID)
812 "android",
813 #if defined(__ARM_ARCH_7A__)
814 "android-armv7",
815 #elif defined(__aarch64__)
816 "android-aarch64",
817 #elif defined(__i686__)
818 "android-i686",
819 #elif defined(__x86_64__)
820 "android-x86_64",
821 #else
822 #warning no architecture dependant platform tag
823 #endif
824 #elif defined(TARGET_FREEBSD)
825 "freebsd",
826 #elif defined(TARGET_LINUX)
827 "linux",
828 #if defined(__ARM_ARCH_7A__)
829 "linux-armv7",
830 #elif defined(__aarch64__)
831 "linux-aarch64",
832 #elif defined(__i686__)
833 "linux-i686",
834 #elif defined(__x86_64__)
835 "linux-x86_64",
836 #else
837 #warning no architecture dependant platform tag
838 #endif
839 #elif defined(TARGET_WINDOWS_DESKTOP)
840 "windx",
841 "windows",
842 #if defined(_M_IX86)
843 "windows-i686",
844 #elif defined(_M_AMD64)
845 "windows-x86_64",
846 #else
847 #error no architecture dependant platform tag
848 #endif
849 #elif defined(TARGET_WINDOWS_STORE)
850 "windowsstore",
851 #elif defined(TARGET_DARWIN_EMBEDDED)
852 "darwin_embedded",
853 #if defined(TARGET_DARWIN_IOS)
854 "ios",
855 #if defined(__aarch64__)
856 "ios-aarch64",
857 #else
858 #warning no architecture dependant platform tag
859 #endif
860 #elif defined(TARGET_DARWIN_TVOS)
861 "tvos",
862 "tvos-aarch64",
863 #endif
864 #elif defined(TARGET_DARWIN_OSX)
865 "osx",
866 #if defined(__x86_64__)
867 "osx64",
868 "osx-x86_64",
869 #elif defined(__aarch64__)
870 "osxarm64",
871 "osx-arm64",
872 #else
873 #warning no architecture dependant platform tag
874 #endif
875 #endif
878 return std::find_first_of(addon->m_platforms.begin(), addon->m_platforms.end(),
879 supportedPlatforms.begin(), supportedPlatforms.end()) != addon->m_platforms.end();