1+ #nullable enable
2+ using System . CodeDom ;
3+ using System . Collections . Generic ;
4+ using System . Linq ;
5+ using System . Reflection ;
6+ using System . Text . RegularExpressions ;
7+ using Scriban ;
8+
9+ namespace Xml . Schema . Linq . CodeGen . Scriban ;
10+
11+ static class ScribanGlobals
12+ {
13+ public static void Comments ( TemplateContext ctx , object target )
14+ {
15+ var comments = target switch
16+ {
17+ CodeTypeDeclaration decl => decl . Comments ,
18+ CodeTypeMember m => m . Comments ,
19+ _ => null ,
20+ } ;
21+
22+ if ( comments == null || comments . Count == 0 )
23+ return ;
24+
25+ foreach ( CodeCommentStatement c in comments )
26+ ctx . Write ( $ "/// { c . Comment . Text . Replace ( "\n " , "\n ///" ) } \n ") ;
27+ ctx . ResetPreviousNewLine ( ) ;
28+ ctx . Write ( ctx . CurrentIndent ) ;
29+ }
30+
31+ public static IEnumerable < CodeTypeDeclaration > Classes ( CodeTypeDeclaration type )
32+ {
33+ return type . Members
34+ . OfType < CodeTypeDeclaration > ( )
35+ . Where ( x => ! x . IsEnum && ! x . Name . EndsWith ( "EnumValidator" ) ) ;
36+ }
37+
38+ public static CodeTypeDeclaration EnumDecl ( string enumName , CodeTypeDeclaration type )
39+ {
40+ enumName = enumName . TrimEnd ( '?' ) ;
41+ return type . Members
42+ . OfType < CodeTypeDeclaration > ( )
43+ . First ( x => x . IsEnum && x . Name == enumName ) ;
44+ }
45+
46+ public static CodeConstructor ? Ctor ( CodeTypeDeclaration type , int args = 0 )
47+ {
48+ return type . Members
49+ . OfType < CodeConstructor > ( )
50+ . FirstOrDefault ( x => x . Parameters . Count == args ) ;
51+ }
52+
53+ public static IEnumerable < CodeMemberProperty > Properties ( CodeTypeDeclaration type )
54+ {
55+ return type . Members
56+ . OfType < CodeMemberProperty > ( )
57+ . Where ( x =>
58+ // Exclude properties like SchemaName, TypeOrigin or TypeManager
59+ ! x . CustomAttributes . Cast < CodeAttributeDeclaration > ( ) . Any ( a => a . Name == "DebuggerBrowsable" ) &&
60+ x . Name != "TypedValue" ) ;
61+ }
62+
63+ public static bool IsList ( CodeMemberProperty prop )
64+ {
65+ return prop . Type . BaseType == "IList`1" ;
66+ }
67+
68+ public static bool IsElement ( CodeMemberProperty prop )
69+ {
70+ return IsList ( prop ) ||
71+ prop . GetStatements [ 0 ] is CodeVariableDeclarationStatement { Type . BaseType : "XElement" } ;
72+ }
73+
74+ public static IEnumerable < CodeMemberProperty > Elements ( CodeTypeDeclaration type )
75+ {
76+ return Properties ( type ) . Where ( IsElement ) ;
77+ }
78+
79+ public static bool HasElements ( CodeTypeDeclaration type )
80+ {
81+ return Elements ( type ) . Any ( ) ;
82+ }
83+
84+ public static bool IsOptional ( CodeMemberProperty prop )
85+ {
86+ return prop . Comments
87+ . Cast < CodeCommentStatement > ( )
88+ . Any ( x => x . Comment . Text . Contains ( "Occurrence: optional" ) ) ;
89+ }
90+
91+ public static bool IsTypeDefinition ( CodeTypeDeclaration type )
92+ {
93+ return type . TypeAttributes . HasFlag ( TypeAttributes . Sealed ) ;
94+ }
95+
96+ public static string ? Validator ( CodeTypeDeclaration type )
97+ {
98+ var statement = type . Members
99+ . OfType < CodeMemberProperty > ( )
100+ . First ( x => x . Name == "TypedValue" )
101+ . SetStatements [ 0 ] ;
102+
103+ if ( statement is not CodeExpressionStatement
104+ {
105+ Expression : CodeMethodInvokeExpression
106+ {
107+ Method . MethodName : "SetValueWithValidation" ,
108+ Parameters : [ _, _, CodeFieldReferenceExpression
109+ {
110+ TargetObject : CodeTypeReferenceExpression
111+ {
112+ Type : var typeRef
113+ }
114+ } ]
115+ }
116+ } )
117+ return null ;
118+
119+ return typeRef . BaseType ;
120+ }
121+
122+ public static string ? SimpleType ( CodeTypeDeclaration type )
123+ {
124+ var typeDecl = type . Members
125+ . OfType < CodeMemberProperty > ( )
126+ . FirstOrDefault ( x => x . Name == "TypedValue" )
127+ ? . Type ;
128+ return typeDecl != null ? TypeName ( typeDecl , nullable : false ) : null ;
129+ }
130+
131+ public static IEnumerable < string > EnumValues ( CodeTypeDeclaration enumType )
132+ {
133+ return enumType . Members
134+ . OfType < CodeMemberField > ( )
135+ . Select ( x => x . Name ) ;
136+ }
137+
138+ public static string ? DefaultValue ( CodeMemberProperty prop , CodeTypeDeclaration type )
139+ {
140+ var name = prop . Name + "DefaultValue" ;
141+ var init = type . Members
142+ . OfType < CodeMemberField > ( )
143+ . FirstOrDefault ( x => x . Name == name )
144+ ? . InitExpression ;
145+
146+ return init switch
147+ {
148+ CodeMethodInvokeExpression i => $ "{ i . Method . MethodName } (\" { ( ( CodePrimitiveExpression ) i . Parameters [ 0 ] ) . Value } \" )",
149+ CodePrimitiveExpression p => ( string ) p . Value ,
150+ CodeFieldReferenceExpression f => f . FieldName ,
151+ _ => null ,
152+ } ;
153+ }
154+
155+ public static string LocalName ( CodeTypeDeclaration type , string name )
156+ {
157+ var init = type . Members
158+ . OfType < CodeMemberField > ( )
159+ . FirstOrDefault ( x => x . Name == name )
160+ ? . InitExpression as CodeMethodInvokeExpression ;
161+ if ( init is null ) return "TODO: review null" ;
162+ return ( string ) ( init . Parameters [ 0 ] as CodePrimitiveExpression ) ! . Value ;
163+ }
164+
165+ public static string Namespace ( CodeTypeDeclaration type , string name )
166+ {
167+ var init = type . Members
168+ . OfType < CodeMemberField > ( )
169+ . FirstOrDefault ( x => x . Name == name )
170+ ? . InitExpression as CodeMethodInvokeExpression ;
171+ if ( init is null ) return "TODO: review null" ;
172+ return ( string ) ( init . Parameters [ 1 ] as CodePrimitiveExpression ) ! . Value ;
173+ }
174+
175+ public static bool HasContentModel ( CodeTypeDeclaration type )
176+ {
177+ return type . Members
178+ . OfType < CodeMemberField > ( )
179+ . Any ( x => x . Name == "contentModel" ) ;
180+ }
181+
182+ public static string TypeName ( CodeTypeReference type , bool nullable = true )
183+ {
184+ if ( type . ArrayElementType != null )
185+ return TypeName ( type . ArrayElementType ) + "[]" ;
186+
187+ var name = type . BaseType ;
188+
189+ if ( type . TypeArguments . Count > 0 )
190+ {
191+ return Regex . Replace ( name , @"`\d+$" , "" )
192+ + "<"
193+ + string . Join ( ", " , type . TypeArguments . Cast < CodeTypeReference > ( ) . Select ( x => TypeName ( x ) ) )
194+ + ">" ;
195+ }
196+
197+ if ( ! nullable )
198+ name = name . TrimEnd ( '?' ) ;
199+
200+ return name switch
201+ {
202+ "System.Boolean" => "bool" ,
203+ "System.Byte" => "byte" ,
204+ "System.Int32" => "int" ,
205+ "System.String" => "string" ,
206+ _ => name ,
207+ } ;
208+ }
209+
210+ public static string ListElement ( string typeName )
211+ {
212+ return Regex . Match ( typeName , "^IList<([^>]+)>$" ) is { Success : true , Groups : var g }
213+ ? g [ 1 ] . Value
214+ : typeName ;
215+ }
216+
217+ public static string XmlTypeCode ( object type , string ? name = null )
218+ {
219+ // HACK: this is plain wrong but a shortcut to make the proof of concept match 100% without going to deep in CodeDom analysis
220+ if ( name == "language" ) return "XmlTypeCode.Language" ;
221+
222+ if ( type is CodeTypeReference typeRef )
223+ type = TypeName ( typeRef , nullable : false ) ;
224+
225+ return type switch
226+ {
227+ "bool" => "XmlTypeCode.Boolean" ,
228+ "byte[]" => "XmlTypeCode.Base64Binary" ,
229+ "int" => "XmlTypeCode.Int" ,
230+ "string" => "XmlTypeCode.String" ,
231+ _ => "TODO" ,
232+ } ;
233+ }
234+
235+ public static IEnumerable < CodeTypeDeclaration > AllTypes ( CodeNamespace ns )
236+ {
237+ return ns . Types
238+ . Cast < CodeTypeDeclaration > ( )
239+ . Where ( x => x . Name is not ( "XRootNamespace" or "XRoot" or "LinqToXsdTypeManager" ) ) ;
240+ }
241+
242+ public static IEnumerable < CodeTypeDeclaration > ElementTypes ( CodeNamespace ns )
243+ {
244+ return AllTypes ( ns ) . Where ( x => ! IsTypeDefinition ( x ) ) ;
245+ }
246+ }
0 commit comments