[XamlBuild] Support classmodifier in xaml
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.XamlBuild / src / public / XamlBuild / XamlGenerator.cs
1 /*
2  * Copyright(c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 using System;
18 using System.CodeDom;
19 using System.CodeDom.Compiler;
20 using System.Collections.Generic;
21 using System.IO;
22 using System.Linq;
23 using System.Reflection;
24 using System.Xml;
25 using Microsoft.Build.Framework;
26 using Microsoft.Build.Utilities;
27 using Microsoft.CSharp;
28 using Mono.Cecil;
29 using Tizen.NUI.Binding;
30 using Tizen.NUI.Xaml;
31
32 namespace Tizen.NUI.Xaml.Build.Tasks
33 {
34     class XamlGenerator
35     {
36         internal XamlGenerator()
37         {
38         }
39
40         private class XmlnsInfo
41         {
42             public void Add(ModuleDefinition module, string nameSpace, int level)
43             {
44                 foreach (TypeDefinition type in module.Types)
45                 {
46                     if (type.Namespace == nameSpace
47                         &&
48                         type.IsPublic == true
49                         &&
50                         type.IsClass == true)
51                     {
52                         bool needUpdate = false;
53                         if (true == classNameToNameSpace.ContainsKey(type.Name))
54                         {
55                             NameSpaceInfo info = classNameToNameSpace[type.Name];
56
57                             if (level > info.level)
58                             {
59                                 needUpdate = true;
60                             }
61                         }
62                         else
63                         {
64                             needUpdate = true;
65                         }
66                         
67                         if (true == needUpdate)
68                         {
69                             classNameToNameSpace[type.Name] = new NameSpaceInfo(type.Namespace, level);
70                         }
71                     }
72                 }
73             }
74
75             public string GetNameSpace(string nameSpace)
76             {
77                 NameSpaceInfo ret;
78
79                 classNameToNameSpace.TryGetValue(nameSpace, out ret);
80
81                 return ret?.nameSpace;
82             }
83
84             private class NameSpaceInfo
85             {
86                 internal NameSpaceInfo(string nameSpace, int level)
87                 {
88                     this.nameSpace = nameSpace;
89                     this.level = level;
90                 }
91
92                 internal string nameSpace;
93                 internal int level;
94             }
95
96             private Dictionary<string, NameSpaceInfo> classNameToNameSpace = new Dictionary<string, NameSpaceInfo>();
97         }
98
99         static private Dictionary<string, XmlnsInfo> xmlnsNameToInfo = new Dictionary<string, XmlnsInfo>();
100
101         internal string ReferencePath
102         {
103             set
104             {
105                 if (!string.IsNullOrEmpty(value))
106                 {
107                     List<ModuleDefinition> assemblyList = new List<ModuleDefinition>();
108
109                     var paths = value.Replace("//", "/").Split(';');
110                     foreach (var p in paths)
111                     {
112                         ModuleDefinition module = ModuleDefinition.ReadModule(p);
113
114                         foreach (var attr in module.Assembly.CustomAttributes)
115                         {
116                             if (attr.AttributeType.FullName == "Tizen.NUI.XmlnsDefinitionAttribute")
117                             {
118                                 string xmlNamespace = attr.ConstructorArguments[0].Value as string;
119                                 string clrNamespace = attr.ConstructorArguments[1].Value as string;
120
121                                 int level = 0;
122                                 string assemblyName = module.Assembly.FullName;
123
124                                 if (true == attr.HasProperties)
125                                 {
126                                     foreach (var property in attr.Properties)
127                                     {
128                                         if ("Level" == property.Name)
129                                         {
130                                             level = int.Parse(property.Argument.Value.ToString());
131                                         }
132                                         if ("AssemblyName" == property.Name)
133                                         {
134                                             assemblyName = property.Argument.Value as string;
135                                         }
136                                     }
137                                 }
138
139                                 XmlnsInfo xmlsInfo = null;
140
141                                 if (xmlnsNameToInfo.ContainsKey(xmlNamespace))
142                                 {
143                                     xmlsInfo = xmlnsNameToInfo[xmlNamespace];
144                                 }
145                                 else
146                                 {
147                                     xmlsInfo = new XmlnsInfo();
148                                     xmlnsNameToInfo.Add(xmlNamespace, xmlsInfo);
149                                 }
150
151                                 xmlsInfo.Add(module, clrNamespace, level);
152                             }
153                         }
154                     }
155                 }
156             }
157         }
158
159         public XamlGenerator(
160             ITaskItem taskItem,
161             string language,
162             string assemblyName,
163             string outputFile,
164             string ReferencePath,
165             TaskLoggingHelper logger)
166             : this(
167                 taskItem.ItemSpec,
168                 language,
169                 taskItem.GetMetadata("ManifestResourceName"),
170                 taskItem.GetMetadata("TargetPath"),
171                 assemblyName,
172                 outputFile,
173                 logger)
174         {
175             this.ReferencePath = ReferencePath;
176         }
177
178         static int generatedTypesCount;
179         internal static CodeDomProvider Provider = new CSharpCodeProvider();
180
181         public string XamlFile { get; }
182         public string Language { get; }
183         public string ResourceId { get; }
184         public string TargetPath { get; }
185         public string AssemblyName { get; }
186         public string OutputFile { get; }
187         public TaskLoggingHelper Logger { get; }
188         public string RootClrNamespace { get; private set; }
189         public string RootType { get; private set; }
190         public bool AddXamlCompilationAttribute { get; set; }
191         public int XamlOptimization { get; set; }
192         bool GenerateDefaultCtor { get; set; }
193         bool HideFromIntellisense { get; set; }
194         bool XamlResourceIdOnly { get; set; }
195         internal IEnumerable<CodeMemberField> NamedFields { get; set; }
196         internal CodeTypeReference BaseType { get; set; }
197         string classModifier { get; set; }
198
199         public XamlGenerator(
200             string xamlFile,
201             string language,
202             string resourceId,
203             string targetPath,
204             string assemblyName,
205             string outputFile,
206             TaskLoggingHelper logger = null)
207         {
208             XamlFile = xamlFile;
209             Language = language;
210             ResourceId = resourceId;
211             TargetPath = targetPath;
212             AssemblyName = assemblyName;
213             OutputFile = outputFile;
214             Logger = logger;
215         }
216
217         //returns true if a file is generated
218         public bool Execute()
219         {
220             Logger?.LogMessage(MessageImportance.Low, "Source: {0}", XamlFile);
221             Logger?.LogMessage(MessageImportance.Low, " Language: {0}", Language);
222             Logger?.LogMessage(MessageImportance.Low, " ResourceID: {0}", ResourceId);
223             Logger?.LogMessage(MessageImportance.Low, " TargetPath: {0}", TargetPath);
224             Logger?.LogMessage(MessageImportance.Low, " AssemblyName: {0}", AssemblyName);
225             Logger?.LogMessage(MessageImportance.Low, " OutputFile {0}", OutputFile);
226
227             using (StreamReader reader = File.OpenText(XamlFile))
228                 if (!ParseXaml(reader))
229                     return false;
230
231             GenerateCode(OutputFile);
232
233             return true;
234         }
235
236         internal bool ParseXaml(TextReader xaml)
237         {
238             var xmlDoc = new XmlDocument();
239             xmlDoc.Load(xaml);
240
241             // if the following xml processing instruction is present
242             //
243             // <?xaml-comp compile="true" ?>
244             //
245             // we will generate a xaml.g.cs file with the default ctor calling InitializeComponent, and a XamlCompilation attribute
246             var hasXamlCompilationProcessingInstruction = GetXamlCompilationProcessingInstruction(xmlDoc);
247
248             var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
249             nsmgr.AddNamespace("__f__", XamlParser.XFUri);
250
251             var root = xmlDoc.SelectSingleNode("/*", nsmgr);
252             if (root == null) {
253                 Logger?.LogMessage(MessageImportance.Low, " No root node found");
254                 return false;
255             }
256
257             foreach (XmlAttribute attr in root.Attributes) {
258                 if (attr.Name == "xmlns")
259                     nsmgr.AddNamespace("", attr.Value); //Add default xmlns
260                 if (attr.Prefix != "xmlns")
261                     continue;
262                 nsmgr.AddNamespace(attr.LocalName, attr.Value);
263             }
264
265             var rootClass = root.Attributes["Class", XamlParser.X2006Uri]
266                          ?? root.Attributes["Class", XamlParser.X2009Uri];
267
268             if (rootClass != null) {
269                 string rootType, rootNs, rootAsm, targetPlatform;
270                 XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm, out targetPlatform);
271                 RootType = rootType;
272                 RootClrNamespace = rootNs;
273             }
274             else if (hasXamlCompilationProcessingInstruction) {
275                 RootClrNamespace = "__XamlGeneratedCode__";
276                 RootType = $"__Type{generatedTypesCount++}";
277                 GenerateDefaultCtor = true;
278                 AddXamlCompilationAttribute = true;
279                 HideFromIntellisense = true;
280             }
281             else { // rootClass == null && !hasXamlCompilationProcessingInstruction) {
282                 XamlResourceIdOnly = true; //only generate the XamlResourceId assembly attribute
283                 return true;
284             }
285
286             NamedFields = GetCodeMemberFields(root, nsmgr);
287             var typeArguments = GetAttributeValue(root, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
288             classModifier = GetAttributeValue(root, "ClassModifier", XamlParser.X2006Uri, XamlParser.X2009Uri);
289             var xmlType = new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null);
290             BaseType = GetType(xmlType, root.GetNamespaceOfPrefix);
291
292             return true;
293         }
294
295         static System.Version version = typeof(XamlGenerator).Assembly.GetName().Version;
296         static CodeAttributeDeclaration GeneratedCodeAttrDecl =>
297             new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof(GeneratedCodeAttribute).FullName}"),
298                         new CodeAttributeArgument(new CodePrimitiveExpression("Tizen.NUI.Xaml.Build.Tasks.XamlG")),
299                         new CodeAttributeArgument(new CodePrimitiveExpression($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}")));
300
301         void GenerateCode(string outFilePath)
302         {
303             //Create the target directory if required
304             Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outFilePath));
305
306             var ccu = new CodeCompileUnit();
307             ccu.AssemblyCustomAttributes.Add(
308                 new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof(XamlResourceIdAttribute).FullName}"),
309                                              new CodeAttributeArgument(new CodePrimitiveExpression(ResourceId)),
310                                              new CodeAttributeArgument(new CodePrimitiveExpression(TargetPath.Replace('\\', '/'))), //use forward slashes, paths are uris-like
311                                              new CodeAttributeArgument(RootType == null ? (CodeExpression)new CodePrimitiveExpression(null) : new CodeTypeOfExpression($"global::{RootClrNamespace}.{RootType}"))
312                                             ));
313             if (XamlResourceIdOnly)
314                 goto writeAndExit;
315
316             if (RootType == null)
317                 throw new Exception("Something went wrong while executing XamlG");
318
319             var declNs = new CodeNamespace(RootClrNamespace);
320             ccu.Namespaces.Add(declNs);
321
322             var declType = new CodeTypeDeclaration(RootType) {
323                 IsPartial = true,
324                 TypeAttributes = GetTypeAttributes(classModifier),
325                 CustomAttributes = {
326                     new CodeAttributeDeclaration(new CodeTypeReference(XamlCTask.xamlNameSpace + ".XamlFilePathAttribute"),
327                          new CodeAttributeArgument(new CodePrimitiveExpression(XamlFile))),
328                 }
329             };
330             if (AddXamlCompilationAttribute)
331                 declType.CustomAttributes.Add(
332                     new CodeAttributeDeclaration(new CodeTypeReference(XamlCTask.xamlNameSpace + ".XamlCompilationAttribute"),
333                                                  new CodeAttributeArgument(new CodeSnippetExpression($"global::{typeof(XamlCompilationOptions).FullName}.Compile"))));
334             if (HideFromIntellisense)
335                 declType.CustomAttributes.Add(
336                     new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof(System.ComponentModel.EditorBrowsableAttribute).FullName}"),
337                                                  new CodeAttributeArgument(new CodeSnippetExpression($"global::{typeof(System.ComponentModel.EditorBrowsableState).FullName}.{nameof(System.ComponentModel.EditorBrowsableState.Never)}"))));
338
339             declType.BaseTypes.Add(BaseType);
340
341             declNs.Types.Add(declType);
342
343             //Create a default ctor calling InitializeComponent
344             if (GenerateDefaultCtor) {
345                 var ctor = new CodeConstructor {
346                     Attributes = MemberAttributes.Public,
347                     CustomAttributes = { GeneratedCodeAttrDecl },
348                     Statements = {
349                         new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "InitializeComponent")
350                     }
351                 };
352
353                 declType.Members.Add(ctor);
354             }
355
356             //Create InitializeComponent()
357             var initcomp = new CodeMemberMethod {
358                 Name = "InitializeComponent",
359                 CustomAttributes = { GeneratedCodeAttrDecl }
360             };
361
362             declType.Members.Add(initcomp);
363
364             //Create and initialize fields
365
366                         if(0 == XamlOptimization)
367                         {
368                 initcomp.Statements.Add(new CodeMethodInvokeExpression(
369                     new CodeTypeReferenceExpression(new CodeTypeReference($"global::{typeof(Extensions).FullName}")),
370                     "LoadFromXaml", new CodeThisReferenceExpression(), new CodeTypeOfExpression(declType.Name)));
371                         }
372             else
373                         {
374                 var loadExaml_invoke = new CodeMethodInvokeExpression(
375                     new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
376                     "LoadFromEXamlByRelativePath", new CodeThisReferenceExpression(),
377                     new CodeMethodInvokeExpression()
378                     { Method = new CodeMethodReferenceExpression() { MethodName = "GetEXamlPath" } });
379
380                 CodeAssignStatement assignEXamlObject = new CodeAssignStatement(
381                         new CodeVariableReferenceExpression("eXamlData"), loadExaml_invoke);
382
383                 initcomp.Statements.Add(assignEXamlObject);
384                         }
385
386             foreach (var namedField in NamedFields) {
387                 if(namedField.Type.BaseType.Contains("-"))
388                 {
389                     namedField.Type.BaseType = namedField.Type.BaseType.Replace("-", ".");
390                 }
391                 declType.Members.Add(namedField);
392
393                 var find_invoke = new CodeMethodInvokeExpression(
394                     new CodeMethodReferenceExpression(
395                         new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.Binding.NameScopeExtensions")),
396                         "FindByName", namedField.Type),
397                     new CodeThisReferenceExpression(), new CodePrimitiveExpression(namedField.Name));
398
399                 CodeAssignStatement assign = new CodeAssignStatement(
400                     new CodeVariableReferenceExpression(namedField.Name), find_invoke);
401
402                 initcomp.Statements.Add(assign);
403             }
404
405                         if(0 != XamlOptimization)
406                         {
407                 declType.Members.Add(new CodeMemberField
408                 {
409                     Name = "eXamlData",
410                     Type = new CodeTypeReference("System.Object"),
411                     Attributes = MemberAttributes.Private,
412                     CustomAttributes = { GeneratedCodeAttrDecl }
413                 });
414
415                 var getEXamlPathcomp = new CodeMemberMethod()
416                 {
417                     Name = "GetEXamlPath",
418                     ReturnType = new CodeTypeReference(typeof(string)),
419                     CustomAttributes = { GeneratedCodeAttrDecl }
420                 };
421
422                 getEXamlPathcomp.Statements.Add(new CodeMethodReturnStatement(new CodeDefaultValueExpression(new CodeTypeReference(typeof(string)))));
423
424                 declType.Members.Add(getEXamlPathcomp);
425
426                 GenerateMethodExitXaml(declType);
427                         }
428         writeAndExit:
429             //write the result
430             using (var writer = new StreamWriter(outFilePath))
431                 Provider.GenerateCodeFromCompileUnit(ccu, writer, new CodeGeneratorOptions());
432         }
433
434         private void GenerateMethodExitXaml(CodeTypeDeclaration declType)
435         {
436             var removeEventsComp = new CodeMemberMethod()
437             {
438                 Name = "RemoveEventsInXaml",
439                 CustomAttributes = { GeneratedCodeAttrDecl }
440             };
441
442             removeEventsComp.Statements.Add(new CodeMethodInvokeExpression(
443                 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
444                 "RemoveEventsInXaml", new CodeVariableReferenceExpression("eXamlData")));
445
446             declType.Members.Add(removeEventsComp);
447
448             var exitXamlComp = new CodeMemberMethod()
449             {
450                 Name = "ExitXaml",
451                 CustomAttributes = { GeneratedCodeAttrDecl }
452             };
453
454             exitXamlComp.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression()
455             {
456                 MethodName = "RemoveEventsInXaml",
457             }));
458
459             var disposeXamlElements_invoke = new CodeMethodInvokeExpression(
460                 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
461                 "DisposeXamlElements", new CodeThisReferenceExpression());
462
463             exitXamlComp.Statements.Add(disposeXamlElements_invoke);
464
465             CodeAssignStatement eXamlDataAssign = new CodeAssignStatement(
466                     new CodeVariableReferenceExpression("eXamlData"), new CodeDefaultValueExpression(new CodeTypeReference(typeof(object))));
467
468             exitXamlComp.Statements.Add(eXamlDataAssign);
469
470             foreach (var namedField in NamedFields)
471             {
472                 CodeAssignStatement assign = new CodeAssignStatement(
473                     new CodeVariableReferenceExpression(namedField.Name), new CodeDefaultValueExpression(namedField.Type));
474
475                 exitXamlComp.Statements.Add(assign);
476             }
477
478             declType.Members.Add(exitXamlComp);
479         }
480
481         static System.Reflection.TypeAttributes GetTypeAttributes(string classModifier)
482         {
483             var access = System.Reflection.TypeAttributes.Public;
484             if (classModifier != null)
485             {
486                 switch (classModifier.ToLowerInvariant())
487                 {
488                     default:
489                     case "public":
490                         access = System.Reflection.TypeAttributes.Public;
491                         break;
492                     case "internal":
493                     case "notpublic": //WPF syntax
494                         access = System.Reflection.TypeAttributes.NotPublic;
495                         break;
496                 }
497             }
498             return access;
499         }
500
501         static IEnumerable<CodeMemberField> GetCodeMemberFields(XmlNode root, XmlNamespaceManager nsmgr)
502         {
503             var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri);
504             if (xPrefix == null)
505                 yield break;
506
507             XmlNodeList names =
508                 root.SelectNodes(
509                     "//*[@" + xPrefix + ":Name" +
510                     "][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)]", nsmgr);
511             foreach (XmlNode node in names) {
512                 var name = GetAttributeValue(node, "Name", XamlParser.X2006Uri, XamlParser.X2009Uri);
513                 var typeArguments = GetAttributeValue(node, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
514                 var fieldModifier = GetAttributeValue(node, "FieldModifier", XamlParser.X2006Uri, XamlParser.X2009Uri);
515
516                 var xmlType = new XmlType(node.NamespaceURI, node.LocalName,
517                                           typeArguments != null
518                                           ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null)
519                                           : null);
520
521                 var access = MemberAttributes.Public;
522                 if (fieldModifier != null) {
523                     switch (fieldModifier.ToLowerInvariant()) {
524                         default:
525                         case "private":
526                             access = MemberAttributes.Private;
527                             break;
528                         case "public":
529                             access = MemberAttributes.Public;
530                             break;
531                         case "protected":
532                             access = MemberAttributes.Family;
533                             break;
534                         case "internal":
535                         case "notpublic": //WPF syntax
536                             access = MemberAttributes.Assembly;
537                             break;
538                     }
539                 }
540
541                 yield return new CodeMemberField {
542                     Name = name,
543                     Type = GetType(xmlType, node.GetNamespaceOfPrefix),
544                     Attributes = access,
545                     CustomAttributes = { GeneratedCodeAttrDecl }
546                 };
547             }
548         }
549
550         static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
551         {
552             var instruction = xmlDoc.SelectSingleNode("processing-instruction('xaml-comp')") as XmlProcessingInstruction;
553             if (instruction == null)
554                 return false;
555
556             var parts = instruction.Data.Split(' ', '=');
557             string compileValue = null;
558             var indexOfCompile = Array.IndexOf(parts, "compile");
559             if (indexOfCompile != -1)
560                 compileValue = parts[indexOfCompile + 1].Trim('"', '\'');
561             return compileValue.Equals("true", StringComparison.InvariantCultureIgnoreCase);
562         }
563
564         static CodeTypeReference GetType(XmlType xmlType,
565             Func<string, string> getNamespaceOfPrefix = null)
566         {
567             var type = xmlType.Name;
568             if (type.Contains("-"))
569             {
570                 type = type.Replace('-', '.');
571             }
572             var ns = GetClrNamespace(xmlType.NamespaceUri, xmlType.Name);
573             if (ns != null)
574                 type = $"{ns}.{type}";
575
576             if (xmlType.TypeArguments != null)
577                 type = $"{type}`{xmlType.TypeArguments.Count}";
578
579             var returnType = new CodeTypeReference(type);
580             if (ns != null)
581                 returnType.Options |= CodeTypeReferenceOptions.GlobalReference;
582
583             if (xmlType.TypeArguments != null)
584                 foreach (var typeArg in xmlType.TypeArguments)
585                     returnType.TypeArguments.Add(GetType(typeArg, getNamespaceOfPrefix));
586
587             return returnType;
588         }
589
590         static string GetClrNamespace(string namespaceuri, string className)
591         {
592             XmlnsInfo xmlnsInfo = null;
593
594             xmlnsNameToInfo.TryGetValue(namespaceuri, out xmlnsInfo);
595
596             if (null != xmlnsInfo)
597             {
598                 string nameSpace = xmlnsInfo.GetNameSpace(className);
599
600                 if (null != nameSpace)
601                 {
602                     return nameSpace;
603                 }
604             }
605
606             if (namespaceuri == "http://tizen.org/Tizen.NUI/2018/XAML")
607                 return "Tizen.NUI.Xaml";
608             if (namespaceuri == XamlParser.XFUri)
609                 return "Tizen.NUI.Xaml";
610             if (namespaceuri == XamlParser.X2009Uri)
611                 return "System";
612             //if (namespaceuri != XamlParser.X2006Uri && !namespaceuri.StartsWith("clr-namespace", StringComparison.InvariantCulture) && !namespaceuri.StartsWith("using", StringComparison.InvariantCulture))
613             //    throw new Exception($"Can't load types from xmlns {namespaceuri}");
614             return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri);
615         }
616
617         static string GetAttributeValue(XmlNode node, string localName, params string[] namespaceURIs)
618         {
619             if (node == null)
620                 throw new ArgumentNullException(nameof(node));
621             if (localName == null)
622                 throw new ArgumentNullException(nameof(localName));
623             if (namespaceURIs == null)
624                 throw new ArgumentNullException(nameof(namespaceURIs));
625             foreach (var namespaceURI in namespaceURIs) {
626                 var attr = node.Attributes[localName, namespaceURI];
627                 if (attr == null)
628                     continue;
629                 return attr.Value;
630             }
631             return null;
632         }
633     }
634 }
635