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;
22 using System.Reflection;
24 using Microsoft.Build.Framework;
25 using Microsoft.Build.Utilities;
26 using Microsoft.CSharp;
28 using Tizen.NUI.Binding;
31 namespace Tizen.NUI.Xaml.Build.Tasks
35 internal XamlGenerator()
39 private class XmlnsInfo
41 public void Add(ModuleDefinition module, string nameSpace, int level)
43 foreach (TypeDefinition type in module.Types)
45 if (type.Namespace == nameSpace
51 bool needUpdate = false;
52 if (true == classNameToNameSpace.ContainsKey(type.Name))
54 NameSpaceInfo info = classNameToNameSpace[type.Name];
56 if (level > info.level)
66 if (true == needUpdate)
68 classNameToNameSpace[type.Name] = new NameSpaceInfo(type.Namespace, level);
74 public string GetNameSpace(string nameSpace)
78 classNameToNameSpace.TryGetValue(nameSpace, out ret);
80 return ret?.nameSpace;
83 private class NameSpaceInfo
85 internal NameSpaceInfo(string nameSpace, int level)
87 this.nameSpace = nameSpace;
91 internal string nameSpace;
95 private Dictionary<string, NameSpaceInfo> classNameToNameSpace = new Dictionary<string, NameSpaceInfo>();
98 static private Dictionary<string, XmlnsInfo> xmlnsNameToInfo = new Dictionary<string, XmlnsInfo>();
100 internal string ReferencePath
104 if (!string.IsNullOrEmpty(value))
106 List<ModuleDefinition> assemblyList = new List<ModuleDefinition>();
108 var paths = value.Replace("//", "/").Split(';');
109 foreach (var p in paths)
111 ModuleDefinition module = ModuleDefinition.ReadModule(p);
113 foreach (var attr in module.Assembly.CustomAttributes)
115 if (attr.AttributeType.FullName == "Tizen.NUI.XmlnsDefinitionAttribute")
117 string xmlNamespace = attr.ConstructorArguments[0].Value as string;
118 string clrNamespace = attr.ConstructorArguments[1].Value as string;
121 string assemblyName = module.Assembly.FullName;
123 if (true == attr.HasProperties)
125 foreach (var property in attr.Properties)
127 if ("Level" == property.Name)
129 level = int.Parse(property.Argument.Value.ToString());
131 if ("AssemblyName" == property.Name)
133 assemblyName = property.Argument.Value as string;
138 XmlnsInfo xmlsInfo = null;
140 if (xmlnsNameToInfo.ContainsKey(xmlNamespace))
142 xmlsInfo = xmlnsNameToInfo[xmlNamespace];
146 xmlsInfo = new XmlnsInfo();
147 xmlnsNameToInfo.Add(xmlNamespace, xmlsInfo);
150 xmlsInfo.Add(module, clrNamespace, level);
158 public XamlGenerator(
163 string ReferencePath,
164 TaskLoggingHelper logger)
168 taskItem.GetMetadata("ManifestResourceName"),
169 taskItem.GetMetadata("TargetPath"),
174 this.ReferencePath = ReferencePath;
177 static int generatedTypesCount;
178 internal static CodeDomProvider Provider = new CSharpCodeProvider();
180 public string XamlFile { get; }
181 public string Language { get; }
182 public string ResourceId { get; }
183 public string TargetPath { get; }
184 public string AssemblyName { get; }
185 public string OutputFile { get; }
186 public TaskLoggingHelper Logger { get; }
187 public string RootClrNamespace { get; private set; }
188 public string RootType { get; private set; }
189 public bool AddXamlCompilationAttribute { get; set; }
190 bool GenerateDefaultCtor { get; set; }
191 bool HideFromIntellisense { get; set; }
192 bool XamlResourceIdOnly { get; set; }
193 internal IEnumerable<CodeMemberField> NamedFields { get; set; }
194 internal CodeTypeReference BaseType { get; set; }
196 public XamlGenerator(
203 TaskLoggingHelper logger = null)
207 ResourceId = resourceId;
208 TargetPath = targetPath;
209 AssemblyName = assemblyName;
210 OutputFile = outputFile;
214 //returns true if a file is generated
215 public bool Execute()
217 Logger?.LogMessage(MessageImportance.Low, "Source: {0}", XamlFile);
218 Logger?.LogMessage(MessageImportance.Low, " Language: {0}", Language);
219 Logger?.LogMessage(MessageImportance.Low, " ResourceID: {0}", ResourceId);
220 Logger?.LogMessage(MessageImportance.Low, " TargetPath: {0}", TargetPath);
221 Logger?.LogMessage(MessageImportance.Low, " AssemblyName: {0}", AssemblyName);
222 Logger?.LogMessage(MessageImportance.Low, " OutputFile {0}", OutputFile);
224 using (StreamReader reader = File.OpenText(XamlFile))
225 if (!ParseXaml(reader))
228 GenerateCode(OutputFile);
233 internal bool ParseXaml(TextReader xaml)
235 var xmlDoc = new XmlDocument();
238 // if the following xml processing instruction is present
240 // <?xaml-comp compile="true" ?>
242 // we will generate a xaml.g.cs file with the default ctor calling InitializeComponent, and a XamlCompilation attribute
243 var hasXamlCompilationProcessingInstruction = GetXamlCompilationProcessingInstruction(xmlDoc);
245 var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
246 nsmgr.AddNamespace("__f__", XamlParser.XFUri);
248 var root = xmlDoc.SelectSingleNode("/*", nsmgr);
250 Logger?.LogMessage(MessageImportance.Low, " No root node found");
254 foreach (XmlAttribute attr in root.Attributes) {
255 if (attr.Name == "xmlns")
256 nsmgr.AddNamespace("", attr.Value); //Add default xmlns
257 if (attr.Prefix != "xmlns")
259 nsmgr.AddNamespace(attr.LocalName, attr.Value);
262 var rootClass = root.Attributes["Class", XamlParser.X2006Uri]
263 ?? root.Attributes["Class", XamlParser.X2009Uri];
265 if (rootClass != null) {
266 string rootType, rootNs, rootAsm, targetPlatform;
267 XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm, out targetPlatform);
269 RootClrNamespace = rootNs;
271 else if (hasXamlCompilationProcessingInstruction) {
272 RootClrNamespace = "__XamlGeneratedCode__";
273 RootType = $"__Type{generatedTypesCount++}";
274 GenerateDefaultCtor = true;
275 AddXamlCompilationAttribute = true;
276 HideFromIntellisense = true;
278 else { // rootClass == null && !hasXamlCompilationProcessingInstruction) {
279 XamlResourceIdOnly = true; //only generate the XamlResourceId assembly attribute
283 NamedFields = GetCodeMemberFields(root, nsmgr);
284 var typeArguments = GetAttributeValue(root, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
285 var xmlType = new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null);
286 BaseType = GetType(xmlType, root.GetNamespaceOfPrefix);
291 static System.Version version = typeof(XamlGenerator).Assembly.GetName().Version;
292 static CodeAttributeDeclaration GeneratedCodeAttrDecl =>
293 new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof(GeneratedCodeAttribute).FullName}"),
294 new CodeAttributeArgument(new CodePrimitiveExpression("Tizen.NUI.Xaml.Build.Tasks.XamlG")),
295 new CodeAttributeArgument(new CodePrimitiveExpression($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}")));
297 void GenerateCode(string outFilePath)
299 //Create the target directory if required
300 Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outFilePath));
302 var ccu = new CodeCompileUnit();
303 ccu.AssemblyCustomAttributes.Add(
304 new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof(XamlResourceIdAttribute).FullName}"),
305 new CodeAttributeArgument(new CodePrimitiveExpression(ResourceId)),
306 new CodeAttributeArgument(new CodePrimitiveExpression(TargetPath.Replace('\\', '/'))), //use forward slashes, paths are uris-like
307 new CodeAttributeArgument(RootType == null ? (CodeExpression)new CodePrimitiveExpression(null) : new CodeTypeOfExpression($"global::{RootClrNamespace}.{RootType}"))
309 if (XamlResourceIdOnly)
312 if (RootType == null)
313 throw new Exception("Something went wrong while executing XamlG");
315 var declNs = new CodeNamespace(RootClrNamespace);
316 ccu.Namespaces.Add(declNs);
318 var declType = new CodeTypeDeclaration(RootType) {
321 new CodeAttributeDeclaration(new CodeTypeReference(XamlCTask.xamlNameSpace + ".XamlFilePathAttribute"),
322 new CodeAttributeArgument(new CodePrimitiveExpression(XamlFile))),
325 if (AddXamlCompilationAttribute)
326 declType.CustomAttributes.Add(
327 new CodeAttributeDeclaration(new CodeTypeReference(XamlCTask.xamlNameSpace + ".XamlCompilationAttribute"),
328 new CodeAttributeArgument(new CodeSnippetExpression($"global::{typeof(XamlCompilationOptions).FullName}.Compile"))));
329 if (HideFromIntellisense)
330 declType.CustomAttributes.Add(
331 new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof(System.ComponentModel.EditorBrowsableAttribute).FullName}"),
332 new CodeAttributeArgument(new CodeSnippetExpression($"global::{typeof(System.ComponentModel.EditorBrowsableState).FullName}.{nameof(System.ComponentModel.EditorBrowsableState.Never)}"))));
334 declType.BaseTypes.Add(BaseType);
336 declNs.Types.Add(declType);
338 //Create a default ctor calling InitializeComponent
339 if (GenerateDefaultCtor) {
340 var ctor = new CodeConstructor {
341 Attributes = MemberAttributes.Public,
342 CustomAttributes = { GeneratedCodeAttrDecl },
344 new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "InitializeComponent")
348 declType.Members.Add(ctor);
351 //Create InitializeComponent()
352 var initcomp = new CodeMemberMethod {
353 Name = "InitializeComponent",
354 CustomAttributes = { GeneratedCodeAttrDecl }
357 declType.Members.Add(initcomp);
359 //Create and initialize fields
360 var loadExaml_invoke = new CodeMethodInvokeExpression(
361 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
362 "LoadFromEXamlByRelativePath", new CodeThisReferenceExpression(),
363 new CodeMethodInvokeExpression()
364 { Method = new CodeMethodReferenceExpression() { MethodName = "GetEXamlPath" } });
366 CodeAssignStatement assignEXamlObject = new CodeAssignStatement(
367 new CodeVariableReferenceExpression("eXamlData"), loadExaml_invoke);
369 initcomp.Statements.Add(assignEXamlObject);
371 foreach (var namedField in NamedFields) {
372 declType.Members.Add(namedField);
374 var find_invoke = new CodeMethodInvokeExpression(
375 new CodeMethodReferenceExpression(
376 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.Binding.NameScopeExtensions")),
377 "FindByName", namedField.Type),
378 new CodeThisReferenceExpression(), new CodePrimitiveExpression(namedField.Name));
380 CodeAssignStatement assign = new CodeAssignStatement(
381 new CodeVariableReferenceExpression(namedField.Name), find_invoke);
383 initcomp.Statements.Add(assign);
386 declType.Members.Add(new CodeMemberField
389 Type = new CodeTypeReference("System.Object"),
390 Attributes = MemberAttributes.Private,
391 CustomAttributes = { GeneratedCodeAttrDecl }
394 var getEXamlPathcomp = new CodeMemberMethod()
396 Name = "GetEXamlPath",
397 ReturnType = new CodeTypeReference(typeof(string)),
398 CustomAttributes = { GeneratedCodeAttrDecl }
401 getEXamlPathcomp.Statements.Add(new CodeMethodReturnStatement(new CodeDefaultValueExpression(new CodeTypeReference(typeof(string)))));
403 declType.Members.Add(getEXamlPathcomp);
405 GenerateMethodExitXaml(declType);
409 using (var writer = new StreamWriter(outFilePath))
410 Provider.GenerateCodeFromCompileUnit(ccu, writer, new CodeGeneratorOptions());
413 private static void GenerateMethodExitXaml(CodeTypeDeclaration declType)
415 var removeEventsComp = new CodeMemberMethod()
417 Name = "RemoveEventsInXaml",
418 CustomAttributes = { GeneratedCodeAttrDecl }
421 removeEventsComp.Statements.Add(new CodeMethodInvokeExpression(
422 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
423 "RemoveEventsInXaml", new CodeVariableReferenceExpression("eXamlData")));
425 declType.Members.Add(removeEventsComp);
427 var exitXamlComp = new CodeMemberMethod()
430 CustomAttributes = { GeneratedCodeAttrDecl }
433 exitXamlComp.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression()
435 MethodName = "RemoveEventsInXaml",
438 var disposeXamlElements_invoke = new CodeMethodInvokeExpression(
439 new CodeTypeReferenceExpression(new CodeTypeReference($"global::Tizen.NUI.EXaml.EXamlExtensions")),
440 "DisposeXamlElements", new CodeThisReferenceExpression());
442 exitXamlComp.Statements.Add(disposeXamlElements_invoke);
444 declType.Members.Add(exitXamlComp);
447 static IEnumerable<CodeMemberField> GetCodeMemberFields(XmlNode root, XmlNamespaceManager nsmgr)
449 var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri);
455 "//*[@" + xPrefix + ":Name" +
456 "][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)]", nsmgr);
457 foreach (XmlNode node in names) {
458 var name = GetAttributeValue(node, "Name", XamlParser.X2006Uri, XamlParser.X2009Uri);
459 var typeArguments = GetAttributeValue(node, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
460 var fieldModifier = GetAttributeValue(node, "FieldModifier", XamlParser.X2006Uri, XamlParser.X2009Uri);
462 var xmlType = new XmlType(node.NamespaceURI, node.LocalName,
463 typeArguments != null
464 ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null)
467 var access = MemberAttributes.Public;
468 if (fieldModifier != null) {
469 switch (fieldModifier.ToLowerInvariant()) {
472 access = MemberAttributes.Private;
475 access = MemberAttributes.Public;
478 access = MemberAttributes.Family;
481 case "notpublic": //WPF syntax
482 access = MemberAttributes.Assembly;
487 yield return new CodeMemberField {
489 Type = GetType(xmlType, node.GetNamespaceOfPrefix),
491 CustomAttributes = { GeneratedCodeAttrDecl }
496 static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
498 var instruction = xmlDoc.SelectSingleNode("processing-instruction('xaml-comp')") as XmlProcessingInstruction;
499 if (instruction == null)
502 var parts = instruction.Data.Split(' ', '=');
503 string compileValue = null;
504 var indexOfCompile = Array.IndexOf(parts, "compile");
505 if (indexOfCompile != -1)
506 compileValue = parts[indexOfCompile + 1].Trim('"', '\'');
507 return compileValue.Equals("true", StringComparison.InvariantCultureIgnoreCase);
510 static CodeTypeReference GetType(XmlType xmlType,
511 Func<string, string> getNamespaceOfPrefix = null)
513 var type = xmlType.Name;
514 var ns = GetClrNamespace(xmlType.NamespaceUri, xmlType.Name);
516 type = $"{ns}.{type}";
518 if (xmlType.TypeArguments != null)
519 type = $"{type}`{xmlType.TypeArguments.Count}";
521 var returnType = new CodeTypeReference(type);
523 returnType.Options |= CodeTypeReferenceOptions.GlobalReference;
525 if (xmlType.TypeArguments != null)
526 foreach (var typeArg in xmlType.TypeArguments)
527 returnType.TypeArguments.Add(GetType(typeArg, getNamespaceOfPrefix));
532 static string GetClrNamespace(string namespaceuri, string className)
534 XmlnsInfo xmlnsInfo = null;
536 xmlnsNameToInfo.TryGetValue(namespaceuri, out xmlnsInfo);
538 if (null != xmlnsInfo)
540 string nameSpace = xmlnsInfo.GetNameSpace(className);
542 if (null != nameSpace)
548 if (namespaceuri == "http://tizen.org/Tizen.NUI/2018/XAML")
549 return "Tizen.NUI.Xaml";
550 if (namespaceuri == XamlParser.XFUri)
551 return "Tizen.NUI.Xaml";
552 if (namespaceuri == XamlParser.X2009Uri)
554 //if (namespaceuri != XamlParser.X2006Uri && !namespaceuri.StartsWith("clr-namespace", StringComparison.InvariantCulture) && !namespaceuri.StartsWith("using", StringComparison.InvariantCulture))
555 // throw new Exception($"Can't load types from xmlns {namespaceuri}");
556 return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri);
559 static string GetAttributeValue(XmlNode node, string localName, params string[] namespaceURIs)
562 throw new ArgumentNullException(nameof(node));
563 if (localName == null)
564 throw new ArgumentNullException(nameof(localName));
565 if (namespaceURIs == null)
566 throw new ArgumentNullException(nameof(namespaceURIs));
567 foreach (var namespaceURI in namespaceURIs) {
568 var attr = node.Attributes[localName, namespaceURI];