2 * Copyright(c) 2022 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 using System.CodeDom.Compiler;
20 using System.Collections.Generic;
23 using System.Reflection;
25 using Microsoft.Build.Framework;
26 using Microsoft.Build.Utilities;
27 using Microsoft.CSharp;
29 using Tizen.NUI.Binding;
32 namespace Tizen.NUI.Xaml.Build.Tasks
36 internal XamlGenerator()
40 private class XmlnsInfo
42 public void Add(ModuleDefinition module, string nameSpace, int level)
44 foreach (TypeDefinition type in module.Types)
46 if (type.Namespace == nameSpace
52 bool needUpdate = false;
53 if (true == classNameToNameSpace.ContainsKey(type.Name))
55 NameSpaceInfo info = classNameToNameSpace[type.Name];
57 if (level > info.level)
67 if (true == needUpdate)
69 classNameToNameSpace[type.Name] = new NameSpaceInfo(type.Namespace, level);
75 public string GetNameSpace(string nameSpace)
79 classNameToNameSpace.TryGetValue(nameSpace, out ret);
81 return ret?.nameSpace;
84 private class NameSpaceInfo
86 internal NameSpaceInfo(string nameSpace, int level)
88 this.nameSpace = nameSpace;
92 internal string nameSpace;
96 private Dictionary<string, NameSpaceInfo> classNameToNameSpace = new Dictionary<string, NameSpaceInfo>();
99 static private Dictionary<string, XmlnsInfo> xmlnsNameToInfo = new Dictionary<string, XmlnsInfo>();
101 internal string ReferencePath
105 if (!string.IsNullOrEmpty(value))
107 List<ModuleDefinition> assemblyList = new List<ModuleDefinition>();
109 var paths = value.Replace("//", "/").Split(';');
110 foreach (var p in paths)
112 ModuleDefinition module = ModuleDefinition.ReadModule(p);
114 foreach (var attr in module.Assembly.CustomAttributes)
116 if (attr.AttributeType.FullName == "Tizen.NUI.XmlnsDefinitionAttribute")
118 string xmlNamespace = attr.ConstructorArguments[0].Value as string;
119 string clrNamespace = attr.ConstructorArguments[1].Value as string;
122 string assemblyName = module.Assembly.FullName;
124 if (true == attr.HasProperties)
126 foreach (var property in attr.Properties)
128 if ("Level" == property.Name)
130 level = int.Parse(property.Argument.Value.ToString());
132 if ("AssemblyName" == property.Name)
134 assemblyName = property.Argument.Value as string;
139 XmlnsInfo xmlsInfo = null;
141 if (xmlnsNameToInfo.ContainsKey(xmlNamespace))
143 xmlsInfo = xmlnsNameToInfo[xmlNamespace];
147 xmlsInfo = new XmlnsInfo();
148 xmlnsNameToInfo.Add(xmlNamespace, xmlsInfo);
151 xmlsInfo.Add(module, clrNamespace, level);
159 public XamlGenerator(
164 string ReferencePath,
165 TaskLoggingHelper logger)
169 taskItem.GetMetadata("ManifestResourceName"),
170 taskItem.GetMetadata("TargetPath"),
175 this.ReferencePath = ReferencePath;
178 static int generatedTypesCount;
179 internal static CodeDomProvider Provider = new CSharpCodeProvider();
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; }
199 public XamlGenerator(
206 TaskLoggingHelper logger = null)
210 ResourceId = resourceId;
211 TargetPath = targetPath;
212 AssemblyName = assemblyName;
213 OutputFile = outputFile;
217 //returns true if a file is generated
218 public bool Execute()
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);
227 using (StreamReader reader = File.OpenText(XamlFile))
228 if (!ParseXaml(reader))
231 GenerateCode(OutputFile);
236 internal bool ParseXaml(TextReader xaml)
238 var xmlDoc = new XmlDocument();
241 // if the following xml processing instruction is present
243 // <?xaml-comp compile="true" ?>
245 // we will generate a xaml.g.cs file with the default ctor calling InitializeComponent, and a XamlCompilation attribute
246 var hasXamlCompilationProcessingInstruction = GetXamlCompilationProcessingInstruction(xmlDoc);
248 var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
249 nsmgr.AddNamespace("__f__", XamlParser.XFUri);
251 var root = xmlDoc.SelectSingleNode("/*", nsmgr);
253 Logger?.LogMessage(MessageImportance.Low, " No root node found");
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")
262 nsmgr.AddNamespace(attr.LocalName, attr.Value);
265 var rootClass = root.Attributes["Class", XamlParser.X2006Uri]
266 ?? root.Attributes["Class", XamlParser.X2009Uri];
268 if (rootClass != null) {
269 string rootType, rootNs, rootAsm, targetPlatform;
270 XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm, out targetPlatform);
272 RootClrNamespace = rootNs;
274 else if (hasXamlCompilationProcessingInstruction) {
275 RootClrNamespace = "__XamlGeneratedCode__";
276 RootType = $"__Type{generatedTypesCount++}";
277 GenerateDefaultCtor = true;
278 AddXamlCompilationAttribute = true;
279 HideFromIntellisense = true;
281 else { // rootClass == null && !hasXamlCompilationProcessingInstruction) {
282 XamlResourceIdOnly = true; //only generate the XamlResourceId assembly attribute
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);
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}")));
301 void GenerateCode(string outFilePath)
303 //Create the target directory if required
304 Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outFilePath));
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}"))
313 if (XamlResourceIdOnly)
316 if (RootType == null)
317 throw new Exception("Something went wrong while executing XamlG");
319 var declNs = new CodeNamespace(RootClrNamespace);
320 ccu.Namespaces.Add(declNs);
322 var declType = new CodeTypeDeclaration(RootType) {
324 TypeAttributes = GetTypeAttributes(classModifier),
326 new CodeAttributeDeclaration(new CodeTypeReference($"global::{NUIXamlCTask.xamlNameSpace}.XamlFilePathAttribute"),
327 new CodeAttributeArgument(new CodePrimitiveExpression(XamlFile))),
330 if (AddXamlCompilationAttribute)
331 declType.CustomAttributes.Add(
332 new CodeAttributeDeclaration(new CodeTypeReference($"global::{NUIXamlCTask.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)}"))));
339 declType.BaseTypes.Add(BaseType);
341 declNs.Types.Add(declType);
343 //Create a default ctor calling InitializeComponent
344 if (GenerateDefaultCtor) {
345 var ctor = new CodeConstructor {
346 Attributes = MemberAttributes.Public,
347 CustomAttributes = { GeneratedCodeAttrDecl },
349 new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "InitializeComponent")
353 declType.Members.Add(ctor);
356 //Create InitializeComponent()
357 var initcomp = new CodeMemberMethod {
358 Name = "InitializeComponent",
359 CustomAttributes = { GeneratedCodeAttrDecl }
362 declType.Members.Add(initcomp);
364 //Create and initialize fields
366 if(0 == XamlOptimization)
368 initcomp.Statements.Add(new CodeMethodInvokeExpression(
369 new CodeTypeReferenceExpression(new CodeTypeReference($"global::{typeof(Extensions).FullName}")),
370 "LoadFromXaml", new CodeThisReferenceExpression(), new CodeTypeOfExpression(declType.Name)));
372 var exitXamlComp = new CodeMemberMethod()
375 CustomAttributes = { GeneratedCodeAttrDecl },
376 Attributes = MemberAttributes.Assembly | MemberAttributes.Final
378 declType.Members.Add(exitXamlComp);
382 var loadExaml_invoke = new CodeMethodInvokeExpression(
383 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
384 "LoadFromEXamlByRelativePath", new CodeThisReferenceExpression(),
385 new CodeMethodInvokeExpression()
386 { Method = new CodeMethodReferenceExpression() { MethodName = "GetEXamlPath" } });
388 CodeAssignStatement assignEXamlObject = new CodeAssignStatement(
389 new CodeVariableReferenceExpression("eXamlData"), loadExaml_invoke);
391 initcomp.Statements.Add(assignEXamlObject);
394 foreach (var namedField in NamedFields) {
395 if(namedField.Type.BaseType.Contains("-"))
397 namedField.Type.BaseType = namedField.Type.BaseType.Replace("-", ".");
399 declType.Members.Add(namedField);
401 var find_invoke = new CodeMethodInvokeExpression(
402 new CodeMethodReferenceExpression(
403 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.Binding.NameScopeExtensions")),
404 "FindByName", namedField.Type),
405 new CodeThisReferenceExpression(), new CodePrimitiveExpression(namedField.Name));
407 CodeAssignStatement assign = new CodeAssignStatement(
408 new CodeVariableReferenceExpression(namedField.Name), find_invoke);
410 initcomp.Statements.Add(assign);
413 if(0 != XamlOptimization)
415 declType.Members.Add(new CodeMemberField
418 Type = new CodeTypeReference("System.Object"),
419 Attributes = MemberAttributes.Private,
420 CustomAttributes = { GeneratedCodeAttrDecl }
423 var getEXamlPathcomp = new CodeMemberMethod()
425 Name = "GetEXamlPath",
426 ReturnType = new CodeTypeReference(typeof(string)),
427 CustomAttributes = { GeneratedCodeAttrDecl }
430 getEXamlPathcomp.Statements.Add(new CodeMethodReturnStatement(new CodeDefaultValueExpression(new CodeTypeReference(typeof(string)))));
432 declType.Members.Add(getEXamlPathcomp);
434 GenerateMethodExitXaml(declType);
438 using (var writer = new StreamWriter(outFilePath))
439 Provider.GenerateCodeFromCompileUnit(ccu, writer, new CodeGeneratorOptions());
442 private void GenerateMethodExitXaml(CodeTypeDeclaration declType)
444 var removeEventsComp = new CodeMemberMethod()
446 Name = "RemoveEventsInXaml",
447 CustomAttributes = { GeneratedCodeAttrDecl }
450 removeEventsComp.Statements.Add(new CodeMethodInvokeExpression(
451 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
452 "RemoveEventsInXaml", new CodeVariableReferenceExpression("eXamlData")));
454 declType.Members.Add(removeEventsComp);
456 var exitXamlComp = new CodeMemberMethod()
459 CustomAttributes = { GeneratedCodeAttrDecl },
460 Attributes = MemberAttributes.Assembly | MemberAttributes.Final
463 exitXamlComp.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression()
465 MethodName = "RemoveEventsInXaml",
468 var disposeXamlElements_invoke = new CodeMethodInvokeExpression(
469 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
470 "DisposeXamlElements", new CodeThisReferenceExpression());
472 exitXamlComp.Statements.Add(disposeXamlElements_invoke);
474 CodeAssignStatement eXamlDataAssign = new CodeAssignStatement(
475 new CodeVariableReferenceExpression("eXamlData"), new CodeDefaultValueExpression(new CodeTypeReference(typeof(object))));
477 exitXamlComp.Statements.Add(eXamlDataAssign);
479 foreach (var namedField in NamedFields)
481 CodeAssignStatement assign = new CodeAssignStatement(
482 new CodeVariableReferenceExpression(namedField.Name), new CodeDefaultValueExpression(namedField.Type));
484 exitXamlComp.Statements.Add(assign);
487 declType.Members.Add(exitXamlComp);
490 static System.Reflection.TypeAttributes GetTypeAttributes(string classModifier)
492 var access = System.Reflection.TypeAttributes.Public;
493 if (classModifier != null)
495 switch (classModifier.ToLowerInvariant())
499 access = System.Reflection.TypeAttributes.Public;
502 case "notpublic": //WPF syntax
503 access = System.Reflection.TypeAttributes.NotPublic;
510 static IEnumerable<CodeMemberField> GetCodeMemberFields(XmlNode root, XmlNamespaceManager nsmgr)
512 var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri);
518 "//*[@" + xPrefix + ":Name" +
519 "][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)]", nsmgr);
520 foreach (XmlNode node in names) {
521 var name = GetAttributeValue(node, "Name", XamlParser.X2006Uri, XamlParser.X2009Uri);
522 var typeArguments = GetAttributeValue(node, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
523 var fieldModifier = GetAttributeValue(node, "FieldModifier", XamlParser.X2006Uri, XamlParser.X2009Uri);
525 var xmlType = new XmlType(node.NamespaceURI, node.LocalName,
526 typeArguments != null
527 ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null)
530 var access = MemberAttributes.Public;
531 if (fieldModifier != null) {
532 switch (fieldModifier.ToLowerInvariant()) {
535 access = MemberAttributes.Private;
538 access = MemberAttributes.Public;
541 access = MemberAttributes.Family;
544 case "notpublic": //WPF syntax
545 access = MemberAttributes.Assembly;
550 yield return new CodeMemberField {
552 Type = GetType(xmlType, node.GetNamespaceOfPrefix),
554 CustomAttributes = { GeneratedCodeAttrDecl }
559 static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
561 var instruction = xmlDoc.SelectSingleNode("processing-instruction('xaml-comp')") as XmlProcessingInstruction;
562 if (instruction == null)
565 var parts = instruction.Data.Split(' ', '=');
566 string compileValue = null;
567 var indexOfCompile = Array.IndexOf(parts, "compile");
568 if (indexOfCompile != -1)
569 compileValue = parts[indexOfCompile + 1].Trim('"', '\'');
570 return compileValue.Equals("true", StringComparison.InvariantCultureIgnoreCase);
573 static CodeTypeReference GetType(XmlType xmlType,
574 Func<string, string> getNamespaceOfPrefix = null)
576 var type = xmlType.Name;
577 if (type.Contains("-"))
579 type = type.Replace('-', '.');
581 var ns = GetClrNamespace(xmlType.NamespaceUri, xmlType.Name);
583 type = $"{ns}.{type}";
585 if (xmlType.TypeArguments != null)
586 type = $"{type}`{xmlType.TypeArguments.Count}";
588 var returnType = new CodeTypeReference(type);
590 returnType.Options |= CodeTypeReferenceOptions.GlobalReference;
592 if (xmlType.TypeArguments != null)
593 foreach (var typeArg in xmlType.TypeArguments)
594 returnType.TypeArguments.Add(GetType(typeArg, getNamespaceOfPrefix));
599 static string GetClrNamespace(string namespaceuri, string className)
601 XmlnsInfo xmlnsInfo = null;
603 xmlnsNameToInfo.TryGetValue(namespaceuri, out xmlnsInfo);
605 if (null != xmlnsInfo)
607 string nameSpace = xmlnsInfo.GetNameSpace(className);
609 if (null != nameSpace)
615 if (namespaceuri == "http://tizen.org/Tizen.NUI/2018/XAML")
616 return "Tizen.NUI.Xaml";
617 if (namespaceuri == XamlParser.XFUri)
618 return "Tizen.NUI.Xaml";
619 if (namespaceuri == XamlParser.X2009Uri)
621 //if (namespaceuri != XamlParser.X2006Uri && !namespaceuri.StartsWith("clr-namespace", StringComparison.InvariantCulture) && !namespaceuri.StartsWith("using", StringComparison.InvariantCulture))
622 // throw new Exception($"Can't load types from xmlns {namespaceuri}");
623 return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri);
626 static string GetAttributeValue(XmlNode node, string localName, params string[] namespaceURIs)
629 throw new ArgumentNullException(nameof(node));
630 if (localName == null)
631 throw new ArgumentNullException(nameof(localName));
632 if (namespaceURIs == null)
633 throw new ArgumentNullException(nameof(namespaceURIs));
634 foreach (var namespaceURI in namespaceURIs) {
635 var attr = node.Attributes[localName, namespaceURI];