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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
//            Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using OpenTap.Cli;
 
namespace OpenTap.Package
{
    /// <summary>
    /// TapSerializerPlugin for <see cref="PackageDef"/>
    /// </summary>
    internal class PackageDefinitionSerializerPlugin : TapSerializerPlugin
    {
        /// <summary>
        /// Called as part for the deserialization chain. Returns false if it cannot serialize the XML element.  
        /// </summary>
        public override bool Deserialize(XElement node, ITypeData t, Action<object> setter)
        {
            if((node.Name.LocalName == "Package" && t.IsA(typeof(PackageDef))) || 
               (node.Name.LocalName == nameof(PackageIdentifier) && t.IsA(typeof(PackageIdentifier))))
            {
                var pkg = new PackageDef();
                foreach (XAttribute attr in node.Attributes())
                {
                    switch(attr.Name.LocalName)
                    {
                        case "Version":
                            pkg.RawVersion = attr.Value;
                            if (SemanticVersion.TryParse(attr.Value, out var semver))
                                pkg.Version = semver;
                            break;
                        case "Date":
                            if (DateTime.TryParse(attr.Value, out DateTime date))
                                pkg.Date = date;
                            else
                                pkg.Date = DateTime.MinValue;
                            break;
                        case "Architecture":
                            pkg.Architecture = (CpuArchitecture)Enum.Parse(typeof(CpuArchitecture), attr.Value);
                            break;
                        default:
                            var prop = pkg.GetType().GetProperty(attr.Name.LocalName);
                            if (prop != null)
                                prop.SetValue(pkg, attr.Value);
                            break;
                    }
                }
                foreach (var elm in node.Elements())
                {
                    switch (elm.Name.LocalName)
                    {
                        case "Description":
                            pkg.Description = Regex.Match(elm.ToString().Replace("\r", ""), "^<Description.*?>((?:.|\\n)+)</Description>", RegexOptions.Multiline).Groups[1].Value.Trim();
                            break;
                        case "PackageRepositoryUrl":
#pragma warning disable 618
                            pkg.Location = elm.Value;
#pragma warning restore 618
                            break;
                        case "Validation":
                            foreach (var e in elm.Elements())
                            {
                                Validation marker = null;
                                if (e.Name.LocalName == "FileExists")
                                {
                                    marker = new FileExists()
                                    {
                                        Path = e.Attribute("Path").Value
                                    };
                                }
                                if (marker != null)
                                {
                                    if (pkg.Validation == null)
                                        pkg.Validation = new();
                                    pkg.Validation.Add(marker);
                                }
 
                            }
 
                            break;
                        default:
                            var prop = pkg.GetType().GetProperty(elm.Name.LocalName);
                            if (prop != null)
                                Serializer.Deserialize(elm, o => prop.SetValue(pkg,o), prop.PropertyType);
                            else // If the property does not exist on the packagedef type, it is MetaData
                                pkg.MetaData.Add(elm.Name.LocalName, elm.Value);
                            break;
                    }
                }
 
                if (t.IsA(typeof(PackageIdentifier)))
                    setter.Invoke(new PackageIdentifier(pkg));
                else
                    setter.Invoke(pkg);
 
                // If the Version XML attribute is missing, default to same behavior as if Version="" was specified. We depend on packages having a version.
                if (pkg.Version is null && string.IsNullOrEmpty(pkg.RawVersion)) {
                    pkg.RawVersion = "";
                    if (SemanticVersion.TryParse("0.0.0", out var semver))
                        pkg.Version = semver;
                }
 
                return true;
            }
            return false;
        }
 
        /// <summary>
        /// Called as part for the serialization chain. Returns false if it cannot serialize the XML element.
        /// Disables the 'WritePackageDependencies' feature of the <see cref="TestPlanPackageDependencySerializer"/>
        /// further up the chain when it runs.
        /// </summary>
        public override bool Serialize(XElement node, object obj, ITypeData expectedType)
        {
            if (expectedType.IsA(typeof(PackageDef)) == false && expectedType.IsA(typeof(PackageIdentifier)) == false)
                return false;
            XNamespace ns = "http://opentap.io/schemas/package";
            node.Name = ns + (expectedType.IsA(typeof(PackageIdentifier)) ? nameof(PackageIdentifier) : "Package");
            node.SetAttributeValue("type", null);
            foreach (var prop in expectedType.GetMembers())
            {
                object val = prop.GetValue(obj);
                if (false == val is string && val is IEnumerable && (val as IEnumerable).GetEnumerator().MoveNext() == false)
                    continue;  // don't write empty enumerables
                var defaultAttr = prop.GetAttribute<DefaultValueAttribute>();
                if (defaultAttr != null && object.Equals(defaultAttr.Value, val))
                    continue;
                switch (prop.Name)
                {
                    case "RawVersion":
                        continue;
                    case "Date":
                        if(((DateTime)val) != DateTime.MinValue)
                            node.SetAttributeValue("Date", ((DateTime)val).ToString(CultureInfo.InvariantCulture));
                        break;
                    case "Description":
                        var mngr = new XmlNamespaceManager(new NameTable());
                        mngr.AddNamespace("", ns.NamespaceName); // or proper URL
                        var parserContext = new XmlParserContext(null, mngr, null, XmlSpace.None, null);
                        var txtReader = new XmlTextReader($"<Description>{val}</Description>", XmlNodeType.Element, parserContext);
                        var ele = XElement.Load(txtReader);
                        node.Add(ele);
                        break;
                    case nameof(PackageDef.MetaData):
                        var metadata = val as Dictionary<string, string>;
                        var packageProperties = typeof(PackageDef).GetProperties();
                        foreach (var key in metadata.Keys)
                        {
                            if (packageProperties.Any(p => p.Name == key))
                                throw new ExitCodeException((int)PackageExitCodes.PackageCreateError, $"PackageDef property '{key}' cannot be overridden by metadata.");
                            
                            var element = new XElement(key, metadata[key]);
                            SetAllNamespaces(element, ns);
                            node.Add(element);
                        }
                        break;
                    default:
                        var xmlAttr = prop.GetAttributes<XmlAttributeAttribute>().FirstOrDefault();
                        if (xmlAttr != null)
                        {
                            string name = prop.Name;
                            if (!String.IsNullOrWhiteSpace(xmlAttr.AttributeName))
                                name = xmlAttr.AttributeName;
                            node.SetAttributeValue(name, val);
                        }
                        else
                        {
                            var elm = new XElement(prop.Name);
                            if (obj != null)
                            {
                                Serializer.Serialize(elm, val, expectedType: prop.TypeDescriptor);
 
                                if (prop.Name == "Validation")
                                {
                                    if (val == null)
                                        continue;
 
                                    foreach (var elem in elm.Elements())
                                    {
                                        elem.Attribute("type").Remove();
                                    }
                                }
                            }
                            SetAllNamespaces(elm, ns);
                            node.Add(elm);
                        }
                        break;
                }
            }
            node.SetAttributeValue("xmlns", ns);
 
            // ask the TestPlanPackageDependency serializer (the one that writes the 
            // <Package.Dependencies> tag in the bottom of e.g. TestPlan files) to
            // not write the tag for this file.
            var depSerializer = Serializer.GetSerializer<TestPlanPackageDependencySerializer>();
            if (depSerializer != null)
                depSerializer.WritePackageDependencies = false;
 
            return true;
        }
 
        void SetAllNamespaces(XElement e, XNamespace ns)
        {
            e.Name = ns + e.Name.LocalName;
            foreach (var n in e.Elements())
                SetAllNamespaces(n, ns);
        }
    }
 
    /// <summary>
    /// TapSerializerPlugin for <see cref="PackageDependency"/>
    /// </summary>
    internal class PackageDependencySerializerPlugin : OpenTap.TapSerializerPlugin
    {
        /// <summary>
        /// Called as part for the deserialization chain. Returns false if it cannot serialize the XML element.  
        /// </summary>
        public override bool Deserialize(XElement node, ITypeData t, Action<object> setter)
        {
            if (t.IsA(typeof(PackageDependency)))
            {
                string name = null;
                string version = null;
                foreach (XAttribute attr in node.Attributes())
                {
                    switch (attr.Name.LocalName)
                    {
                        case "Version":
                            version = attr.Value;
                            break;
                        case "Package": // TAP 8.0 support
                        case "Name":
                            name = attr.Value;
                            break;
                        default:
                            Debug.Assert(false);
                            break;
                    }
                }
                setter.Invoke(new PackageDependency(name, ConvertVersion(version), version));
                return true;
            }
            return false;
        }
 
        private static VersionSpecifier ConvertVersion(string Version)
        {
            if (String.IsNullOrWhiteSpace(Version))
                return VersionSpecifier.Any;
            if (VersionSpecifier.TryParse(Version, out var semver))
            {
                return semver;
            }
            // For compatability (pre 9.0 packages may not have correctly formatted version numbers)
            var plugins = PluginManager.GetPlugins<IVersionTryConverter>().Concat(PluginManager.GetPlugins<IVersionConverter>());
 
            foreach (var plugin in plugins.OrderBy(p => p.GetDisplayAttribute().Order))
            {
                try
                {
                    object cvt = Activator.CreateInstance(plugin);
                    if (cvt is IVersionTryConverter vc2)
                    {
                        if (vc2.TryConvert(Version, out SemanticVersion sv))
                            return new VersionSpecifier(sv, VersionMatchBehavior.Compatible);
                    }
                    else if(cvt is IVersionConverter vc)
                    {
                        return new VersionSpecifier(vc.Convert(Version), VersionMatchBehavior.Compatible);
                    }
                }
                catch
                {
 
                }
            }
            return VersionSpecifier.Any;
        }
 
        /// <summary>
        /// Called as part for the serialization chain. Returns false if it cannot serialize the XML element.  
        /// </summary>
        public override bool Serialize(XElement node, object obj, ITypeData expectedType)
        {
            if (expectedType.IsA(typeof(PackageDependency)) == false)
                return false;
            node.Name = "Package"; 
            node.Name = "PackageDependency"; // TODO: remove when server is updated (this is only here for support of the TAP 8.x Repository server that does not yet have a parser that can handle the new name)
            node.SetAttributeValue("type", null);
            foreach (var prop in expectedType.GetMembers())
            {
                object val = prop.GetValue(obj);
                string name = prop.Name;
                if (val == null)
                    continue;
                if (name == "Name")
                    name = "Package"; // TODO: remove when server is updated (this is only here for support of the TAP 8.x Repository server that does not yet have a parser that can handle the new name)
                node.SetAttributeValue(name, val);
            }
            return true;
        }
    }
}