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(XamlCTask.xamlNameSpace + ".XamlFilePathAttribute"),
327 new CodeAttributeArgument(new CodePrimitiveExpression(XamlFile))),
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)}"))));
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)));
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" } });
380 CodeAssignStatement assignEXamlObject = new CodeAssignStatement(
381 new CodeVariableReferenceExpression("eXamlData"), loadExaml_invoke);
383 initcomp.Statements.Add(assignEXamlObject);
386 foreach (var namedField in NamedFields) {
387 if(namedField.Type.BaseType.Contains("-"))
389 namedField.Type.BaseType = namedField.Type.BaseType.Replace("-", ".");
391 declType.Members.Add(namedField);
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));
399 CodeAssignStatement assign = new CodeAssignStatement(
400 new CodeVariableReferenceExpression(namedField.Name), find_invoke);
402 initcomp.Statements.Add(assign);
405 if(0 != XamlOptimization)
407 declType.Members.Add(new CodeMemberField
410 Type = new CodeTypeReference("System.Object"),
411 Attributes = MemberAttributes.Private,
412 CustomAttributes = { GeneratedCodeAttrDecl }
415 var getEXamlPathcomp = new CodeMemberMethod()
417 Name = "GetEXamlPath",
418 ReturnType = new CodeTypeReference(typeof(string)),
419 CustomAttributes = { GeneratedCodeAttrDecl }
422 getEXamlPathcomp.Statements.Add(new CodeMethodReturnStatement(new CodeDefaultValueExpression(new CodeTypeReference(typeof(string)))));
424 declType.Members.Add(getEXamlPathcomp);
426 GenerateMethodExitXaml(declType);
430 using (var writer = new StreamWriter(outFilePath))
431 Provider.GenerateCodeFromCompileUnit(ccu, writer, new CodeGeneratorOptions());
434 private void GenerateMethodExitXaml(CodeTypeDeclaration declType)
436 var removeEventsComp = new CodeMemberMethod()
438 Name = "RemoveEventsInXaml",
439 CustomAttributes = { GeneratedCodeAttrDecl }
442 removeEventsComp.Statements.Add(new CodeMethodInvokeExpression(
443 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
444 "RemoveEventsInXaml", new CodeVariableReferenceExpression("eXamlData")));
446 declType.Members.Add(removeEventsComp);
448 var exitXamlComp = new CodeMemberMethod()
451 CustomAttributes = { GeneratedCodeAttrDecl },
452 Attributes = MemberAttributes.Assembly | MemberAttributes.Final
455 exitXamlComp.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression()
457 MethodName = "RemoveEventsInXaml",
460 var disposeXamlElements_invoke = new CodeMethodInvokeExpression(
461 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
462 "DisposeXamlElements", new CodeThisReferenceExpression());
464 exitXamlComp.Statements.Add(disposeXamlElements_invoke);
466 CodeAssignStatement eXamlDataAssign = new CodeAssignStatement(
467 new CodeVariableReferenceExpression("eXamlData"), new CodeDefaultValueExpression(new CodeTypeReference(typeof(object))));
469 exitXamlComp.Statements.Add(eXamlDataAssign);
471 foreach (var namedField in NamedFields)
473 CodeAssignStatement assign = new CodeAssignStatement(
474 new CodeVariableReferenceExpression(namedField.Name), new CodeDefaultValueExpression(namedField.Type));
476 exitXamlComp.Statements.Add(assign);
479 declType.Members.Add(exitXamlComp);
482 static System.Reflection.TypeAttributes GetTypeAttributes(string classModifier)
484 var access = System.Reflection.TypeAttributes.Public;
485 if (classModifier != null)
487 switch (classModifier.ToLowerInvariant())
491 access = System.Reflection.TypeAttributes.Public;
494 case "notpublic": //WPF syntax
495 access = System.Reflection.TypeAttributes.NotPublic;
502 static IEnumerable<CodeMemberField> GetCodeMemberFields(XmlNode root, XmlNamespaceManager nsmgr)
504 var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri);
510 "//*[@" + xPrefix + ":Name" +
511 "][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)]", nsmgr);
512 foreach (XmlNode node in names) {
513 var name = GetAttributeValue(node, "Name", XamlParser.X2006Uri, XamlParser.X2009Uri);
514 var typeArguments = GetAttributeValue(node, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
515 var fieldModifier = GetAttributeValue(node, "FieldModifier", XamlParser.X2006Uri, XamlParser.X2009Uri);
517 var xmlType = new XmlType(node.NamespaceURI, node.LocalName,
518 typeArguments != null
519 ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null)
522 var access = MemberAttributes.Public;
523 if (fieldModifier != null) {
524 switch (fieldModifier.ToLowerInvariant()) {
527 access = MemberAttributes.Private;
530 access = MemberAttributes.Public;
533 access = MemberAttributes.Family;
536 case "notpublic": //WPF syntax
537 access = MemberAttributes.Assembly;
542 yield return new CodeMemberField {
544 Type = GetType(xmlType, node.GetNamespaceOfPrefix),
546 CustomAttributes = { GeneratedCodeAttrDecl }
551 static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
553 var instruction = xmlDoc.SelectSingleNode("processing-instruction('xaml-comp')") as XmlProcessingInstruction;
554 if (instruction == null)
557 var parts = instruction.Data.Split(' ', '=');
558 string compileValue = null;
559 var indexOfCompile = Array.IndexOf(parts, "compile");
560 if (indexOfCompile != -1)
561 compileValue = parts[indexOfCompile + 1].Trim('"', '\'');
562 return compileValue.Equals("true", StringComparison.InvariantCultureIgnoreCase);
565 static CodeTypeReference GetType(XmlType xmlType,
566 Func<string, string> getNamespaceOfPrefix = null)
568 var type = xmlType.Name;
569 if (type.Contains("-"))
571 type = type.Replace('-', '.');
573 var ns = GetClrNamespace(xmlType.NamespaceUri, xmlType.Name);
575 type = $"{ns}.{type}";
577 if (xmlType.TypeArguments != null)
578 type = $"{type}`{xmlType.TypeArguments.Count}";
580 var returnType = new CodeTypeReference(type);
582 returnType.Options |= CodeTypeReferenceOptions.GlobalReference;
584 if (xmlType.TypeArguments != null)
585 foreach (var typeArg in xmlType.TypeArguments)
586 returnType.TypeArguments.Add(GetType(typeArg, getNamespaceOfPrefix));
591 static string GetClrNamespace(string namespaceuri, string className)
593 XmlnsInfo xmlnsInfo = null;
595 xmlnsNameToInfo.TryGetValue(namespaceuri, out xmlnsInfo);
597 if (null != xmlnsInfo)
599 string nameSpace = xmlnsInfo.GetNameSpace(className);
601 if (null != nameSpace)
607 if (namespaceuri == "http://tizen.org/Tizen.NUI/2018/XAML")
608 return "Tizen.NUI.Xaml";
609 if (namespaceuri == XamlParser.XFUri)
610 return "Tizen.NUI.Xaml";
611 if (namespaceuri == XamlParser.X2009Uri)
613 //if (namespaceuri != XamlParser.X2006Uri && !namespaceuri.StartsWith("clr-namespace", StringComparison.InvariantCulture) && !namespaceuri.StartsWith("using", StringComparison.InvariantCulture))
614 // throw new Exception($"Can't load types from xmlns {namespaceuri}");
615 return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri);
618 static string GetAttributeValue(XmlNode node, string localName, params string[] namespaceURIs)
621 throw new ArgumentNullException(nameof(node));
622 if (localName == null)
623 throw new ArgumentNullException(nameof(localName));
624 if (namespaceURIs == null)
625 throw new ArgumentNullException(nameof(namespaceURIs));
626 foreach (var namespaceURI in namespaceURIs) {
627 var attr = node.Attributes[localName, namespaceURI];