[WASAPI] fix stream types and frequencies enumeration
[xbmc.git] / xbmc / guilib / GUIIncludes.cpp
blobda8f3a33c943e974319d8f371c34c98a2010b72d
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 "GUIIncludes.h"
11 #include "GUIInfoManager.h"
12 #include "addons/Skin.h"
13 #include "guilib/GUIComponent.h"
14 #include "guilib/guiinfo/GUIInfoLabel.h"
15 #include "interfaces/info/SkinVariable.h"
16 #include "utils/StringUtils.h"
17 #include "utils/XBMCTinyXML.h"
18 #include "utils/XMLUtils.h"
19 #include "utils/log.h"
21 using namespace KODI::GUILIB;
23 CGUIIncludes::CGUIIncludes()
25 m_constantAttributes.insert("x");
26 m_constantAttributes.insert("y");
27 m_constantAttributes.insert("width");
28 m_constantAttributes.insert("height");
29 m_constantAttributes.insert("center");
30 m_constantAttributes.insert("max");
31 m_constantAttributes.insert("min");
32 m_constantAttributes.insert("w");
33 m_constantAttributes.insert("h");
34 m_constantAttributes.insert("time");
35 m_constantAttributes.insert("acceleration");
36 m_constantAttributes.insert("delay");
37 m_constantAttributes.insert("start");
38 m_constantAttributes.insert("end");
39 m_constantAttributes.insert("center");
40 m_constantAttributes.insert("border");
41 m_constantAttributes.insert("repeat");
43 m_constantNodes.insert("posx");
44 m_constantNodes.insert("posy");
45 m_constantNodes.insert("left");
46 m_constantNodes.insert("centerleft");
47 m_constantNodes.insert("right");
48 m_constantNodes.insert("centerright");
49 m_constantNodes.insert("top");
50 m_constantNodes.insert("centertop");
51 m_constantNodes.insert("bottom");
52 m_constantNodes.insert("centerbottom");
53 m_constantNodes.insert("width");
54 m_constantNodes.insert("height");
55 m_constantNodes.insert("offsetx");
56 m_constantNodes.insert("offsety");
57 m_constantNodes.insert("textoffsetx");
58 m_constantNodes.insert("textoffsety");
59 m_constantNodes.insert("textwidth");
60 m_constantNodes.insert("spinposx");
61 m_constantNodes.insert("spinposy");
62 m_constantNodes.insert("spinwidth");
63 m_constantNodes.insert("spinheight");
64 m_constantNodes.insert("radioposx");
65 m_constantNodes.insert("radioposy");
66 m_constantNodes.insert("radiowidth");
67 m_constantNodes.insert("radioheight");
68 m_constantNodes.insert("sliderwidth");
69 m_constantNodes.insert("sliderheight");
70 m_constantNodes.insert("itemgap");
71 m_constantNodes.insert("bordersize");
72 m_constantNodes.insert("timeperimage");
73 m_constantNodes.insert("fadetime");
74 m_constantNodes.insert("pauseatend");
75 m_constantNodes.insert("depth");
76 m_constantNodes.insert("movement");
77 m_constantNodes.insert("focusposition");
79 m_expressionAttributes.insert("condition");
81 m_expressionNodes.insert("visible");
82 m_expressionNodes.insert("enable");
83 m_expressionNodes.insert("usealttexture");
84 m_expressionNodes.insert("selected");
87 CGUIIncludes::~CGUIIncludes() = default;
89 void CGUIIncludes::Clear()
91 m_includes.clear();
92 m_defaults.clear();
93 m_constants.clear();
94 m_skinvariables.clear();
95 m_files.clear();
96 m_expressions.clear();
99 void CGUIIncludes::Load(const std::string &file)
101 if (!Load_Internal(file))
102 return;
103 FlattenExpressions();
104 FlattenSkinVariableConditions();
107 bool CGUIIncludes::Load_Internal(const std::string &file)
109 // check to see if we already have this loaded
110 if (HasLoaded(file))
111 return true;
113 CXBMCTinyXML doc;
114 if (!doc.LoadFile(file))
116 CLog::Log(LOGINFO, "Error loading include file {}: {} (row: {}, col: {})", file,
117 doc.ErrorDesc(), doc.ErrorRow(), doc.ErrorCol());
118 return false;
121 TiXmlElement *root = doc.RootElement();
122 if (!root || !StringUtils::EqualsNoCase(root->Value(), "includes"))
124 CLog::Log(LOGERROR, "Error loading include file {}: Root element <includes> required.", file);
125 return false;
128 // load components
129 LoadDefaults(root);
130 LoadConstants(root);
131 LoadExpressions(root);
132 LoadVariables(root);
133 LoadIncludes(root);
135 m_files.push_back(file);
137 return true;
140 void CGUIIncludes::LoadDefaults(const TiXmlElement *node)
142 if (!node)
143 return;
145 const TiXmlElement* child = node->FirstChildElement("default");
146 while (child)
148 const char *type = child->Attribute("type");
149 if (type && child->FirstChild())
150 m_defaults.insert(std::make_pair(type, *child));
152 child = child->NextSiblingElement("default");
156 void CGUIIncludes::LoadExpressions(const TiXmlElement *node)
158 if (!node)
159 return;
161 const TiXmlElement* child = node->FirstChildElement("expression");
162 while (child)
164 const char *tagName = child->Attribute("name");
165 if (tagName && child->FirstChild())
166 m_expressions.insert(std::make_pair(tagName, "[" + child->FirstChild()->ValueStr() + "]"));
168 child = child->NextSiblingElement("expression");
173 void CGUIIncludes::LoadConstants(const TiXmlElement *node)
175 if (!node)
176 return;
178 const TiXmlElement* child = node->FirstChildElement("constant");
179 while (child)
181 const char *tagName = child->Attribute("name");
182 if (tagName && child->FirstChild())
183 m_constants.insert(std::make_pair(tagName, child->FirstChild()->ValueStr()));
185 child = child->NextSiblingElement("constant");
189 void CGUIIncludes::LoadVariables(const TiXmlElement *node)
191 if (!node)
192 return;
194 const TiXmlElement* child = node->FirstChildElement("variable");
195 while (child)
197 const char *tagName = child->Attribute("name");
198 if (tagName && child->FirstChild())
199 m_skinvariables.insert(std::make_pair(tagName, *child));
201 child = child->NextSiblingElement("variable");
205 void CGUIIncludes::LoadIncludes(const TiXmlElement *node)
207 if (!node)
208 return;
210 const TiXmlElement* child = node->FirstChildElement("include");
211 while (child)
213 const char *tagName = child->Attribute("name");
214 if (tagName && child->FirstChild())
216 // we'll parse and store parameter list with defaults when include definition is first encountered
217 // if there's a <definition> tag only use its body as the actually included part
218 const TiXmlElement *definitionTag = child->FirstChildElement("definition");
219 const TiXmlElement *includeBody = definitionTag ? definitionTag : child;
221 // if there's a <param> tag there also must be a <definition> tag
222 Params defaultParams;
223 bool haveParamTags = GetParameters(child, "default", defaultParams);
224 if (haveParamTags && !definitionTag)
225 CLog::Log(LOGWARNING, "Skin has invalid include definition: {}", tagName);
226 else
227 m_includes.insert({ tagName, { *includeBody, std::move(defaultParams) } });
229 else if (child->Attribute("file"))
231 std::string file = g_SkinInfo->GetSkinPath(child->Attribute("file"));
232 const char *condition = child->Attribute("condition");
234 if (condition)
235 { // load include file if condition evals to true
236 if (CServiceBroker::GetGUI()->GetInfoManager().Register(condition)->Get(
237 INFO::DEFAULT_CONTEXT))
238 Load_Internal(file);
240 else
241 Load_Internal(file);
243 child = child->NextSiblingElement("include");
247 void CGUIIncludes::FlattenExpressions()
249 for (auto& expression : m_expressions)
251 std::vector<std::string> resolved = std::vector<std::string>();
252 resolved.push_back(expression.first);
253 FlattenExpression(expression.second, resolved);
257 void CGUIIncludes::FlattenExpression(std::string &expression, const std::vector<std::string> &resolved)
259 std::string original(expression);
260 GUIINFO::CGUIInfoLabel::ReplaceSpecialKeywordReferences(expression, "EXP", [&](const std::string &expressionName) -> std::string {
261 if (std::find(resolved.begin(), resolved.end(), expressionName) != resolved.end())
263 CLog::Log(LOGERROR, "Skin has a circular expression \"{}\": {}", resolved.back(), original);
264 return std::string();
266 auto it = m_expressions.find(expressionName);
267 if (it == m_expressions.end())
268 return std::string();
270 std::vector<std::string> rescopy = resolved;
271 rescopy.push_back(expressionName);
272 FlattenExpression(it->second, rescopy);
274 return it->second;
278 void CGUIIncludes::FlattenSkinVariableConditions()
280 for (auto& variable : m_skinvariables)
282 TiXmlElement* valueNode = variable.second.FirstChildElement("value");
283 while (valueNode)
285 const char *condition = valueNode->Attribute("condition");
286 if (condition)
287 valueNode->SetAttribute("condition", ResolveExpressions(condition));
289 valueNode = valueNode->NextSiblingElement("value");
294 bool CGUIIncludes::HasLoaded(const std::string &file) const
296 for (const auto& loadedFile : m_files)
298 if (loadedFile == file)
299 return true;
301 return false;
304 void CGUIIncludes::Resolve(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
306 if (!node)
307 return;
309 SetDefaults(node);
310 ResolveConstants(node);
311 ResolveExpressions(node);
312 ResolveIncludes(node, xmlIncludeConditions);
314 TiXmlElement *child = node->FirstChildElement();
315 while (child)
317 // recursive call
318 Resolve(child, xmlIncludeConditions);
319 child = child->NextSiblingElement();
323 void CGUIIncludes::SetDefaults(TiXmlElement *node)
325 if (node->ValueStr() != "control")
326 return;
328 std::string type = XMLUtils::GetAttribute(node, "type");
329 const auto it = m_defaults.find(type);
330 if (it != m_defaults.end())
332 // we don't insert <left> et. al. if <posx> or <posy> is specified
333 bool hasPosX(node->FirstChild("posx") != nullptr);
334 bool hasPosY(node->FirstChild("posy") != nullptr);
336 const TiXmlElement &element = (*it).second;
337 const TiXmlElement *tag = element.FirstChildElement();
338 while (tag)
340 std::string value = tag->ValueStr();
341 bool skip(false);
342 if (hasPosX && (value == "left" || value == "right" || value == "centerleft" || value == "centerright"))
343 skip = true;
344 if (hasPosY && (value == "top" || value == "bottom" || value == "centertop" || value == "centerbottom"))
345 skip = true;
346 // we insert at the end of block
347 if (!skip)
348 node->InsertEndChild(*tag);
349 tag = tag->NextSiblingElement();
354 void CGUIIncludes::ResolveConstants(TiXmlElement *node)
356 if (!node)
357 return;
359 TiXmlNode *child = node->FirstChild();
360 if (child && child->Type() == TiXmlNode::TINYXML_TEXT && m_constantNodes.count(node->ValueStr()))
362 child->SetValue(ResolveConstant(child->ValueStr()));
364 else
366 TiXmlAttribute *attribute = node->FirstAttribute();
367 while (attribute)
369 if (m_constantAttributes.count(attribute->Name()))
370 attribute->SetValue(ResolveConstant(attribute->ValueStr()));
372 attribute = attribute->Next();
377 void CGUIIncludes::ResolveExpressions(TiXmlElement *node)
379 if (!node)
380 return;
382 TiXmlNode *child = node->FirstChild();
383 if (child && child->Type() == TiXmlNode::TINYXML_TEXT && m_expressionNodes.count(node->ValueStr()))
385 child->SetValue(ResolveExpressions(child->ValueStr()));
387 else
389 TiXmlAttribute *attribute = node->FirstAttribute();
390 while (attribute)
392 if (m_expressionAttributes.count(attribute->Name()))
393 attribute->SetValue(ResolveExpressions(attribute->ValueStr()));
395 attribute = attribute->Next();
400 void CGUIIncludes::ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
402 if (!node)
403 return;
405 TiXmlElement *include = node->FirstChildElement("include");
406 while (include)
408 // file: load includes from specified XML file
409 const char *file = include->Attribute("file");
410 if (file)
411 Load(g_SkinInfo->GetSkinPath(file));
413 // condition: process include if condition evals to true
414 const char *condition = include->Attribute("condition");
415 if (condition)
417 INFO::InfoPtr conditionID =
418 CServiceBroker::GetGUI()->GetInfoManager().Register(ResolveExpressions(condition));
419 bool value = false;
421 if (conditionID)
423 value = conditionID->Get(INFO::DEFAULT_CONTEXT);
425 if (xmlIncludeConditions)
426 xmlIncludeConditions->insert(std::make_pair(conditionID, value));
429 if (!value)
431 include = include->NextSiblingElement("include");
432 continue;
436 Params params;
437 std::string tagName;
439 // determine which form of include call we have
440 const char *name = include->Attribute("content");
441 if (name)
443 // <include content="MyControl" />
444 // or
445 // <include content="MyControl">
446 // <param name="posx" value="225" />
447 // <param name="posy">150</param>
448 // ...
449 // </include>
450 tagName = name;
451 GetParameters(include, "value", params);
453 else
455 const TiXmlNode *child = include->FirstChild();
456 if (child && child->Type() == TiXmlNode::TINYXML_TEXT)
458 // <include>MyControl</include>
459 // old-style includes for backward compatibility
460 tagName = child->ValueStr();
464 // check, whether the include exists and therefore should be replaced by its definition
465 auto it = m_includes.find(tagName);
466 if (it != m_includes.end())
468 const TiXmlElement *includeDefinition = &it->second.first;
470 // combine passed include parameters with their default values into a single list (no overwrites)
471 const Params& defaultParams = it->second.second;
472 params.insert(defaultParams.begin(), defaultParams.end());
474 // process include definition
475 const TiXmlElement *includeDefinitionChild = includeDefinition->FirstChildElement();
476 while (includeDefinitionChild)
478 // insert before <include> element to keep order of occurrence in xml file
479 TiXmlElement *insertedNode = static_cast<TiXmlElement*>(node->InsertBeforeChild(include, *includeDefinitionChild));
481 // process nested
482 InsertNested(node, include, insertedNode);
484 // resolve parameters even if parameter list is empty (to remove param references)
485 ResolveParametersForNode(insertedNode, params);
487 includeDefinitionChild = includeDefinitionChild->NextSiblingElement();
490 // remove the include element itself
491 node->RemoveChild(include);
493 include = node->FirstChildElement("include");
495 else
496 { // invalid include
497 CLog::Log(LOGWARNING, "Skin has invalid include: {}", tagName);
498 include = include->NextSiblingElement("include");
503 void CGUIIncludes::InsertNested(TiXmlElement *controls, TiXmlElement *include, TiXmlElement *node)
505 TiXmlElement *target;
506 TiXmlElement *nested;
508 if (node->ValueStr() == "nested")
510 nested = node;
511 target = controls;
513 else
515 nested = node->FirstChildElement("nested");
516 target = node;
519 if (nested)
521 // copy all child elements except param elements
522 const TiXmlElement *child = include->FirstChildElement();
523 while (child)
525 if (child->ValueStr() != "param")
527 // insert before <nested> element to keep order of occurrence in xml file
528 target->InsertBeforeChild(nested, *child);
530 child = child->NextSiblingElement();
532 if (nested != node)
533 target->RemoveChild(nested);
538 bool CGUIIncludes::GetParameters(const TiXmlElement *include, const char *valueAttribute, Params& params)
540 bool foundAny = false;
542 // collect parameters from include tag
543 // <include content="MyControl">
544 // <param name="posx" value="225" /> <!-- comments and other tags are ignored here -->
545 // <param name="posy">150</param>
546 // ...
547 // </include>
549 if (include)
551 const TiXmlElement *param = include->FirstChildElement("param");
552 foundAny = param != NULL; // doesn't matter if param isn't entirely valid
553 while (param)
555 std::string paramName = XMLUtils::GetAttribute(param, "name");
556 if (!paramName.empty())
558 std::string paramValue;
560 // <param name="posx" value="120" />
561 const char *value = param->Attribute(valueAttribute); // try attribute first
562 if (value)
563 paramValue = value;
564 else
566 // <param name="posx">120</param>
567 const TiXmlNode *child = param->FirstChild();
568 if (child && child->Type() == TiXmlNode::TINYXML_TEXT)
569 paramValue = child->ValueStr(); // and then tag value
572 params.insert({ paramName, paramValue }); // no overwrites
574 param = param->NextSiblingElement("param");
578 return foundAny;
581 void CGUIIncludes::ResolveParametersForNode(TiXmlElement *node, const Params& params)
583 if (!node)
584 return;
585 std::string newValue;
586 // run through this element's attributes, resolving any parameters
587 TiXmlAttribute *attribute = node->FirstAttribute();
588 while (attribute)
590 ResolveParamsResult result = ResolveParameters(attribute->ValueStr(), newValue, params);
591 if (result == SINGLE_UNDEFINED_PARAM_RESOLVED && strcmp(node->Value(), "param") == 0 &&
592 strcmp(attribute->Name(), "value") == 0 && node->Parent() && strcmp(node->Parent()->Value(), "include") == 0)
594 // special case: passing <param name="someName" value="$PARAM[undefinedParam]" /> to the nested include
595 // this usually happens when trying to forward a missing parameter from the enclosing include to the nested include
596 // problem: since 'undefinedParam' is not defined, it expands to <param name="someName" value="" /> and overrides any default value set with <param name="someName" default="someValue" /> in the nested include
597 // to prevent this, we'll completely remove this parameter from the nested include call so that the default value can be correctly picked up later
598 node->Parent()->RemoveChild(node);
599 return;
601 else if (result != NO_PARAMS_FOUND)
602 attribute->SetValue(newValue);
603 attribute = attribute->Next();
605 // run through this element's value and children, resolving any parameters
606 TiXmlNode *child = node->FirstChild();
607 if (child)
609 if (child->Type() == TiXmlNode::TINYXML_TEXT)
611 ResolveParamsResult result = ResolveParameters(child->ValueStr(), newValue, params);
612 if (result == SINGLE_UNDEFINED_PARAM_RESOLVED && strcmp(node->Value(), "param") == 0 &&
613 node->Parent() && strcmp(node->Parent()->Value(), "include") == 0)
615 // special case: passing <param name="someName">$PARAM[undefinedParam]</param> to the nested include
616 // we'll remove the offending param tag same as above
617 node->Parent()->RemoveChild(node);
619 else if (result != NO_PARAMS_FOUND)
620 child->SetValue(newValue);
622 else if (child->Type() == TiXmlNode::TINYXML_ELEMENT ||
623 child->Type() == TiXmlNode::TINYXML_COMMENT)
627 // save next as current child might be removed from the tree
628 TiXmlElement* next = child->NextSiblingElement();
630 if (child->Type() == TiXmlNode::TINYXML_ELEMENT)
631 ResolveParametersForNode(static_cast<TiXmlElement*>(child), params);
633 child = next;
635 while (child);
640 class ParamReplacer
642 const std::map<std::string, std::string>& m_params;
643 // keep some stats so that we know exactly what's been resolved
644 int m_numTotalParams = 0;
645 int m_numUndefinedParams = 0;
647 public:
648 explicit ParamReplacer(const std::map<std::string, std::string>& params) : m_params(params) {}
649 int GetNumTotalParams() const { return m_numTotalParams; }
650 int GetNumDefinedParams() const { return m_numTotalParams - m_numUndefinedParams; }
651 int GetNumUndefinedParams() const { return m_numUndefinedParams; }
653 std::string operator()(const std::string &paramName)
655 m_numTotalParams++;
656 std::map<std::string, std::string>::const_iterator it = m_params.find(paramName);
657 if (it != m_params.end())
658 return it->second;
659 m_numUndefinedParams++;
660 return "";
664 CGUIIncludes::ResolveParamsResult CGUIIncludes::ResolveParameters(const std::string& strInput, std::string& strOutput, const Params& params)
666 ParamReplacer paramReplacer(params);
667 if (GUIINFO::CGUIInfoLabel::ReplaceSpecialKeywordReferences(strInput, "PARAM", std::ref(paramReplacer), strOutput))
668 // detect special input values of the form "$PARAM[undefinedParam]" (with no extra characters around)
669 return paramReplacer.GetNumUndefinedParams() == 1 && paramReplacer.GetNumTotalParams() == 1 && strOutput.empty() ? SINGLE_UNDEFINED_PARAM_RESOLVED : PARAMS_RESOLVED;
670 return NO_PARAMS_FOUND;
673 std::string CGUIIncludes::ResolveConstant(const std::string &constant) const
675 std::vector<std::string> values = StringUtils::Split(constant, ",");
676 for (auto& i : values)
678 std::map<std::string, std::string>::const_iterator it = m_constants.find(i);
679 if (it != m_constants.end())
680 i = it->second;
682 return StringUtils::Join(values, ",");
685 std::string CGUIIncludes::ResolveExpressions(const std::string &expression) const
687 std::string work(expression);
688 GUIINFO::CGUIInfoLabel::ReplaceSpecialKeywordReferences(work, "EXP", [&](const std::string &str) -> std::string {
689 std::map<std::string, std::string>::const_iterator it = m_expressions.find(str);
690 if (it != m_expressions.end())
691 return it->second;
692 return "";
695 return work;
698 const INFO::CSkinVariableString* CGUIIncludes::CreateSkinVariable(const std::string& name, int context)
700 std::map<std::string, TiXmlElement>::const_iterator it = m_skinvariables.find(name);
701 if (it != m_skinvariables.end())
702 return INFO::CSkinVariable::CreateFromXML(it->second, context);
703 return NULL;