2 // DocumentReferenceManager.cs: Handles web type lookups for ASP.NET documents.
5 // Michael Hutchinson <mhutchinson@novell.com>
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
10 // This source code is licenced under The MIT License:
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System
.Collections
.Generic
;
34 using System
.Globalization
;
36 using MonoDevelop
.Core
;
37 using MonoDevelop
.AspNet
.Parser
.Dom
;
38 using MonoDevelop
.Projects
.Text
;
39 using MonoDevelop
.Projects
.Dom
;
40 using MonoDevelop
.Projects
.Dom
.Output
;
41 using MonoDevelop
.Projects
.Dom
.Parser
;
42 using MonoDevelop
.Projects
.Gui
.Completion
;
43 using MonoDevelop
.Ide
.Gui
;
45 namespace MonoDevelop
.AspNet
.Parser
49 public class DocumentReferenceManager
51 protected List
<RegisterDirective
> pageRefsList
= new List
<RegisterDirective
> ();
52 protected Document doc
;
54 public DocumentReferenceManager (Document doc
)
62 ReferenceVisitor visitor
= new ReferenceVisitor (this);
63 pageRefsList
.Clear ();
64 doc
.RootNode
.AcceptVisit (visitor
);
67 public string GetTypeName (string tagPrefix
, string tagName
)
69 return GetTypeName (tagPrefix
, tagName
, null);
72 public string GetTypeName (string tagPrefix
, string tagName
, string htmlTypeAttribute
)
74 if (tagPrefix
== null || tagPrefix
.Length
< 1)
75 return WebTypeManager
.HtmlControlLookup (tagName
, htmlTypeAttribute
);
77 if (0 == string.Compare (tagPrefix
, "asp", StringComparison
.OrdinalIgnoreCase
)) {
78 string systemType
= WebTypeManager
.SystemTypeNameLookup (tagName
, doc
.Project
);
79 if (!string.IsNullOrEmpty (systemType
))
83 foreach (RegisterDirective directive
in pageRefsList
) {
84 AssemblyRegisterDirective ard
= directive
as AssemblyRegisterDirective
;
85 if (ard
!= null && ard
.TagPrefix
== tagPrefix
) {
87 ProjectDom dom
= WebTypeManager
.ResolveAssembly (doc
.Project
, ard
.Assembly
);
91 string fullName
= WebTypeManager
.AssemblyTypeNameLookup (dom
, ard
.Namespace
, tagName
);
96 ControlRegisterDirective crd
= directive
as ControlRegisterDirective
;
97 if (crd
!= null && crd
.TagPrefix
== tagPrefix
) {
98 string fullName
= WebTypeManager
.GetUserControlTypeName (doc
.Project
, doc
.FilePath
, crd
.Src
);
104 string globalLookup
= WebTypeManager
.GetRegisteredTypeName (doc
.Project
,
105 System
.IO
.Path
.GetDirectoryName (doc
.FilePath
), tagPrefix
, tagName
);
107 //returns null if type not found
111 public IEnumerable
<CompletionData
> GetControlCompletionData ()
113 return GetControlCompletionData (new DomType ("System.Web.UI.Control"));
116 public IEnumerable
<CompletionData
> GetControlCompletionData (IType baseType
)
118 bool isSWC
= baseType
.FullName
== "System.Web.UI.Control";
120 string aspPrefix
= "asp:";
121 foreach (IType cls
in WebTypeManager
.ListSystemControlClasses (baseType
, doc
.Project
))
122 yield return new AspTagCompletionData (aspPrefix
, cls
);
124 foreach (RegisterDirective rd
in pageRefsList
) {
128 AssemblyRegisterDirective ard
= rd
as AssemblyRegisterDirective
;
130 ProjectDom dom
= WebTypeManager
.ResolveAssembly (doc
.Project
, ard
.Assembly
);
134 string prefix
= ard
.TagPrefix
+ ":";
135 foreach (IType cls
in WebTypeManager
.ListControlClasses (baseType
, dom
, ard
.Namespace
))
136 yield return new AspTagCompletionData (prefix
, cls
);
143 ControlRegisterDirective cd
= rd
as ControlRegisterDirective
;
145 yield return new CompletionData (string.Concat (cd
.TagPrefix
, ":", cd
.TagName
),
146 Gtk
.Stock
.GoForward
);
150 //return controls from web.config
151 string webDirectory
= System
.IO
.Path
.GetDirectoryName (doc
.FilePath
);
152 foreach (CompletionData cd
in WebTypeManager
.GetRegisteredTypeCompletionData (doc
.Project
, webDirectory
, baseType
)) {
157 public IType
GetControlType (string tagPrefix
, string tagName
)
159 if (String
.IsNullOrEmpty (tagPrefix
))
163 if (0 == string.Compare (tagPrefix
, "asp", StringComparison
.OrdinalIgnoreCase
)) {
164 type
= WebTypeManager
.SystemTypeLookup (tagName
, WebTypeManager
.GetProjectTargetFramework (doc
.Project
));
169 foreach (RegisterDirective rd
in pageRefsList
) {
170 if (string.Compare (rd
.TagPrefix
, tagPrefix
, StringComparison
.OrdinalIgnoreCase
) != 0)
173 AssemblyRegisterDirective ard
= rd
as AssemblyRegisterDirective
;
175 string assembly
= ard
.Assembly
;
176 ProjectDom dom
= WebTypeManager
.ResolveAssembly (doc
.Project
, ard
.Assembly
);
179 type
= WebTypeManager
.AssemblyTypeLookup (dom
, ard
.Namespace
, tagName
);
185 ControlRegisterDirective crd
= rd
as ControlRegisterDirective
;
186 if (crd
!= null && string.Compare (crd
.TagName
, tagName
, StringComparison
.OrdinalIgnoreCase
) != 0) {
187 return WebTypeManager
.GetUserControlType (doc
.Project
, crd
.Src
, System
.IO
.Path
.GetDirectoryName (doc
.FilePath
));
191 IType globalLookup
= WebTypeManager
.GetRegisteredType (doc
.Project
,
192 System
.IO
.Path
.GetDirectoryName (doc
.FilePath
), tagPrefix
, tagName
);
194 //returns null if type not found
198 public string GetTagPrefix (IType control
)
200 if (control
.Namespace
== "System.Web.UI.WebControls")
202 else if (control
.Namespace
== "System.Web.UI.HtmlControls")
205 foreach (RegisterDirective rd
in pageRefsList
) {
206 AssemblyRegisterDirective ard
= rd
as AssemblyRegisterDirective
;
207 if (ard
!= null && ard
.Namespace
== control
.Namespace
)
208 return ard
.TagPrefix
;
211 string globalPrefix
= WebTypeManager
.GetControlPrefix (doc
.Project
, System
.IO
.Path
.GetDirectoryName (doc
.FilePath
), control
);
212 if (globalPrefix
!= null)
218 IEnumerable
<RegisterDirective
> GetDirectivesForPrefix (string prefix
)
220 foreach (RegisterDirective rd
in pageRefsList
)
221 if (string.Equals (rd
.TagPrefix
, prefix
, StringComparison
.OrdinalIgnoreCase
))
225 #region "Refactoring" operations -- things that modify the file
227 public string AddAssemblyReferenceToDocument (IType control
, string assemblyName
)
229 return AddAssemblyReferenceToDocument (control
, assemblyName
, null);
232 public string AddAssemblyReferenceToDocument (IType control
, string assemblyName
, string desiredPrefix
)
234 string existingPrefix
= GetTagPrefix (control
);
235 if (existingPrefix
!= null)
236 return existingPrefix
;
238 //TODO: detect control name conflicts
239 string prefix
= desiredPrefix
;
240 if (desiredPrefix
== null)
241 prefix
= GetPrefix (control
);
243 System
.Reflection
.AssemblyName an
= MonoDevelop
.Core
.Runtime
.SystemAssemblyService
.ParseAssemblyName (assemblyName
);
245 string directive
= string.Format ("{0}<%@ Register TagPrefix=\"{1}\" Namespace=\"{2}\" Assembly=\"{3}\" %>",
246 Environment
.NewLine
, prefix
, control
.Namespace
, an
.Name
);
248 //inset a directive into the document
249 InsertDirective (directive
);
254 public void AddAssemblyReferenceToProject (string assemblyName
, string assemblyLocation
)
256 //build an reference to the assembly
257 MonoDevelop
.Projects
.ProjectReference pr
;
258 if (string.IsNullOrEmpty (assemblyLocation
)) {
259 pr
= new MonoDevelop
.Projects
.ProjectReference
260 (MonoDevelop
.Projects
.ReferenceType
.Gac
, assemblyName
);
262 pr
= new MonoDevelop
.Projects
.ProjectReference
263 (MonoDevelop
.Projects
.ReferenceType
.Assembly
, assemblyLocation
);
266 //add the reference if it doesn't match an existing one
268 foreach (MonoDevelop
.Projects
.ProjectReference p
in doc
.Project
.References
)
272 doc
.Project
.References
.Add (pr
);
275 string GetPrefix (IType control
)
277 //FIXME: make this work
279 foreach (IAttributeSection attSec in control.CompilationUnit.Attributes) {
280 foreach (IAttribute att in attSec.Attributes) {
281 if (att.PositionalArguments != null && att.PositionalArguments.Length == 2
282 && ExprToStr (att.PositionalArguments[0]) == control.Namespace) {
283 string prefix = ExprToStr (att.PositionalArguments [1]);
288 if (att.Name == "System.Web.UI.TagPrefixAttribute") {
290 foreach (NamedAttributeArgument arg in att.NamedArguments) {
291 if (arg.Name == "NamespaceName"
292 && ExprToStr (arg.Expression) == control.Namespace) {
297 foreach (NamedAttributeArgument arg in att.NamedArguments) {
298 if (arg.Name == "TagPrefix") {
299 string prefix = ExprToStr (arg.Expression);
308 //generate a new prefix base on initials of namespace
309 string[] namespaces
= control
.Namespace
.Split ('.');
310 char[] charr
= new char[namespaces
.Length
];
311 for (int i
= 0; i
< charr
.Length
; i
++)
312 charr
[i
] = char.ToLower (namespaces
[i
][0]);
314 //find a variant that doesn't match an existing prefix
315 string trialPrefix
= new string (charr
);
316 string trial
= trialPrefix
;
318 for (int trialSuffix
= 1; trialSuffix
< int.MaxValue
; trialSuffix
++) {
319 using (IEnumerator
<RegisterDirective
> en
= GetDirectivesForPrefix (trial
).GetEnumerator())
322 trial
= trialPrefix
+ trialSuffix
;
324 throw new InvalidOperationException ("Ran out of integer suffixes for tag prefixes");
327 string ExprToStr (System
.CodeDom
.CodeExpression expr
)
329 System
.CodeDom
.CodePrimitiveExpression p
= expr
as System
.CodeDom
.CodePrimitiveExpression
;
330 return p
!= null? p
.Value
as string : null;
333 void InsertDirective (string directive
)
335 DirectiveNode node
= GetPageDirective ();
339 IEditableTextFile textFile
=
340 MonoDevelop
.DesignerSupport
.OpenDocumentFileProvider
.Instance
.GetEditableTextFile (doc
.FilePath
);
341 if (textFile
== null)
342 textFile
= new TextFile (doc
.FilePath
);
344 int pos
= textFile
.GetPositionFromLineColumn (node
.Location
.EndLine
, node
.Location
.EndColumn
);
345 textFile
.InsertText (pos
, directive
);
348 DirectiveNode
GetPageDirective ()
350 PageDirectiveVisitor v
= new PageDirectiveVisitor ();
351 doc
.RootNode
.AcceptVisit (v
);
352 return v
.DirectiveNode
;
357 #region directive classes
359 protected abstract class RegisterDirective
361 private DirectiveNode node
;
363 public RegisterDirective (DirectiveNode node
)
368 public DirectiveNode Node
{
372 public string TagPrefix
{
373 get { return (string) node.Attributes ["TagPrefix"]; }
374 set { node.Attributes ["TagPrefix"] = value; }
377 public virtual bool IsValid ()
379 if (string.IsNullOrEmpty (TagPrefix
))
382 foreach (char c
in TagPrefix
)
383 if (!Char
.IsLetterOrDigit (c
))
390 protected class AssemblyRegisterDirective
: RegisterDirective
392 public AssemblyRegisterDirective (DirectiveNode node
)
397 public string Namespace
{
398 get { return (string) Node.Attributes ["Namespace"]; }
399 set { Node.Attributes ["Namespace"] = value; }
402 public string Assembly
{
403 get { return (string) Node.Attributes ["Assembly"]; }
404 set { Node.Attributes ["Assembly"] = value; }
407 public override string ToString ()
409 return String
.Format ("<%@ Register {0}=\"{1}\" {2}=\"{3}\" {4}=\"{5}\" %>", "TagPrefix", TagPrefix
, "Namespace", Namespace
, "Assembly", Assembly
);
412 public override bool IsValid ()
414 if (string.IsNullOrEmpty (Assembly
) || string.IsNullOrEmpty (Namespace
) || !base.IsValid ())
420 protected class ControlRegisterDirective
: RegisterDirective
422 public ControlRegisterDirective (DirectiveNode node
)
427 public string TagName
{
428 get { return (string) Node.Attributes ["TagName"]; }
429 set { Node.Attributes ["TagName"] = value; }
433 get { return (string) Node.Attributes ["Src"]; }
434 set { Node.Attributes ["Src"] = value; }
437 public override string ToString ()
439 return String
.Format ("<%@ Register {0}=\"{1}\" {2}=\"{3}\" {4}=\"{5}\" %>", "TagPrefix", TagPrefix
, "TagName", TagName
, "Src", Src
);
442 public override bool IsValid ()
444 if (string.IsNullOrEmpty (TagName
) || string.IsNullOrEmpty (Src
) || !base.IsValid ())
450 private class ReferenceVisitor
: Visitor
452 DocumentReferenceManager parent
;
454 public ReferenceVisitor (DocumentReferenceManager parent
)
456 this.parent
= parent
;
459 public override void Visit (DirectiveNode node
)
461 if ((String
.Compare (node
.Name
, "register", true) != 0) || (node
.Attributes
["TagPrefix"] == null))
464 if ((node
.Attributes
["TagName"] != null) && (node
.Attributes
["Src"] != null))
465 parent
.pageRefsList
.Add (new ControlRegisterDirective (node
));
466 else if ((node
.Attributes
["Namespace"] != null) && (node
.Attributes
["Assembly"] != null))
467 parent
.pageRefsList
.Add (new AssemblyRegisterDirective (node
));
475 class AspTagCompletionData
: CompletionData
479 public AspTagCompletionData (string prefix
, IType cls
)
480 : base (prefix
+ cls
.Name
, Gtk
.Stock
.GoForward
)
485 public override string Description
{
487 if (base.Description
== null && cls
!= null)
488 base.Description
= DocumentationService
.GetCodeCompletionDescription (
489 cls
, AmbienceService
.DefaultAmbience
);
490 return base.Description
;
492 set { base.Description = value; }