chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
 
namespace OpenTap.Package
{
    internal static class ExpansionHelper
    {
        private static TraceSource log = Log.CreateSource("XML Expander");
        /// <summary>
        /// Replace all the occurrences of the token '$(token)' with 'value' in the element
        /// </summary>
        /// <param name="element"></param>
        /// <param name="token"></param>
        /// <param name="value"></param>
        internal static void ReplaceToken(XElement element, string token, string value)
        {
            var textNodes = element.Nodes().OfType<XText>().ToArray();
 
            foreach (var textNode in textNodes)
            {
                var curr = textNode.Value;
                var newValue = textNode.Value.Replace($"$({token})", value)
                    .Replace($"$!({token})", value);
 
                if (curr != newValue)
                {
                    log.Debug($"Expanded '{curr}' -> '{newValue}'");
                    textNode.Value = newValue;
                }
            }
 
            foreach (var attribute in element.Attributes().ToArray())
            {
                var curr = attribute.Value;
                var newValue = attribute.Value.Replace($"$({token})", value)
                    .Replace($"$!({token})", value);
 
                if (curr != newValue)
                {
                    log.Debug($"Expanded '{curr}' -> '{newValue}'");
                    attribute.Value = newValue;
                }
            }
        }
    }
 
    internal class ElementExpander
    {
        private static TraceSource log = Log.CreateSource($"Variable Expander");
 
        private List<IElementExpander> ElementExpanders = new List<IElementExpander>();
        internal void AddProvider(IElementExpander prov)
        {
            ElementExpanders.Add(prov);
            isOrdered = false;
        }
 
        Type[] getDependents(IElementExpander e) => e.GetType().GetCustomAttributes<DependsOnAttribute>().Select(d => d.Dependent).ToArray();
        private bool isOrdered;
        void order()
        {
            var orderedExpanders = new List<IElementExpander>();
            var expanders = ElementExpanders.ToList();
            // Track progress in order to detect cycles;
            // this iterative approach is guaranteed to progress at least 1 item per iteration, but only if there are no circular dependencies.
            var progress = -1;
            while (expanders.Any())
            {
                if (orderedExpanders.Count == progress)
                {
                    var issue = string.Join(", ", expanders.Select(e => e.GetType().Name));
                    throw new Exception(
                        $"Cycle detected while resolving expander order: [{issue}]. Aborting Package XML preprocessing.");
                }
                progress = orderedExpanders.Count;
 
                foreach (var exp in expanders.ToArray())
                {
                    var dependents = getDependents(exp);
                    var satisfied = orderedExpanders.Select(o => o.GetType());
 
                    if (dependents.All(d => satisfied.Contains(d)))
                    {
                        orderedExpanders.Add(exp);
                        expanders.Remove(exp);
                    }
                }
            }
 
            ElementExpanders = orderedExpanders;
            isOrdered = true;
        }
 
        internal void ExpandElement(XElement element)
        {
            if (!isOrdered) order();
 
            foreach (var provider in ElementExpanders)
            {
                provider.Expand(element);
            }
        }
    }
}