2 // System.Web.XmlSiteMapProvider
5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Lluis Sanchez Gual (lluis@novell.com)
7 // Marek Habersack <mhabersack@novell.com>
10 // (C) 2005-2009 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System
.Collections
;
35 using System
.Collections
.Generic
;
36 using System
.Collections
.Specialized
;
37 using System
.Configuration
;
38 using System
.Configuration
.Provider
;
39 using System
.Globalization
;
42 using System
.Web
.Hosting
;
43 using System
.Web
.Util
;
48 public class XmlSiteMapProvider
: StaticSiteMapProvider
, IDisposable
50 static readonly char [] seperators
= { ';', ',' }
;
53 string fileVirtualPath
;
54 SiteMapNode root
= null;
55 List
<FileSystemWatcher
> watchers
;
56 Dictionary
<string, bool> _childProvidersPresent
;
57 List
<SiteMapProvider
> _childProviders
;
59 Dictionary
<string, bool> ChildProvidersPresent
{
61 if (_childProvidersPresent
== null)
62 _childProvidersPresent
= new Dictionary
<string, bool> ();
64 return _childProvidersPresent
;
68 List
<SiteMapProvider
> ChildProviders
{
70 if (_childProviders
== null)
71 _childProviders
= new List
<SiteMapProvider
> ();
73 return _childProviders
;
77 protected internal override void AddNode (SiteMapNode node
, SiteMapNode parentNode
)
80 throw new ArgumentNullException ("node");
82 if (parentNode
== null)
83 throw new ArgumentNullException ("parentNode");
85 SiteMapProvider nodeProvider
= node
.Provider
;
86 if (nodeProvider
!= this)
87 throw new ArgumentException ("SiteMapNode '" + node
+ "' cannot be found in current provider, only nodes in the same provider can be added.",
90 SiteMapProvider parentNodeProvider
= parentNode
.Provider
;
91 if (nodeProvider
!= parentNodeProvider
)
92 throw new ArgumentException ("SiteMapNode '" + parentNode
+ "' cannot be found in current provider, only nodes in the same provider can be added.",
95 AddNodeNoCheck (node
, parentNode
);
98 void AddNodeNoCheck (SiteMapNode node
, SiteMapNode parentNode
)
100 base.AddNode (node
, parentNode
);
101 SiteMapProvider nodeProvider
= node
.Provider
;
102 if (nodeProvider
!= this)
103 RegisterChildProvider (nodeProvider
.Name
, nodeProvider
);
106 protected virtual void AddProvider (string providerName
, SiteMapNode parentNode
)
108 if (parentNode
== null)
109 throw new ArgumentNullException ("parentNode");
111 if (parentNode
.Provider
!= this)
112 throw new ArgumentException ("The Provider property of the parentNode does not reference the current provider.", "parentNode");
114 SiteMapProvider smp
= SiteMap
.Providers
[providerName
];
116 throw new ProviderException ("Provider with name [" + providerName
+ "] was not found.");
118 AddNode (smp
.GetRootNodeCore ());
119 RegisterChildProvider (providerName
, smp
);
122 void RegisterChildProvider (string name
, SiteMapProvider smp
)
124 Dictionary
<string, bool> childProvidersPresent
= ChildProvidersPresent
;
126 if (childProvidersPresent
.ContainsKey (name
))
129 childProvidersPresent
.Add (name
, true);
130 ChildProviders
.Add (smp
);
133 XmlNode
FindStartingNode (string virtualPath
, out bool enableLocalization
)
135 XmlDocument d
= GetConfigDocument (virtualPath
);
136 XmlElement docElement
= d
.DocumentElement
;
138 if (String
.Compare ("siteMap", docElement
.Name
, StringComparison
.Ordinal
) != 0)
139 throw new ConfigurationErrorsException ("Top element must be 'siteMap'");
141 XmlNode enloc
= docElement
.Attributes
["enableLocalization"];
142 if (enloc
!= null && !String
.IsNullOrEmpty (enloc
.Value
))
143 enableLocalization
= (bool) Convert
.ChangeType (enloc
.Value
, typeof (bool));
145 enableLocalization
= false;
147 XmlNodeList childNodes
= docElement
.ChildNodes
;
150 foreach (XmlNode child
in childNodes
) {
151 if (String
.Compare ("siteMapNode", child
.Name
, StringComparison
.Ordinal
) != 0)
152 // Only <siteMapNode> is allowed at the top
153 throw new ConfigurationErrorsException ("Only <siteMapNode> elements are allowed at the document top level.");
156 // Only one <siteMapNode> is allowed at the top
157 throw new ConfigurationErrorsException ("Only one <siteMapNode> element is allowed at the document top level.");
163 throw new ConfigurationErrorsException ("Missing <siteMapNode> element at the document top level.");
168 XmlDocument
GetConfigDocument (string virtualPath
)
170 if (String
.IsNullOrEmpty (virtualPath
))
171 throw new ArgumentException ("The siteMapFile attribute must be specified on the XmlSiteMapProvider");
173 string file
= HostingEnvironment
.MapPath (virtualPath
);
175 throw new HttpException ("Virtual path '" + virtualPath
+ "' cannot be mapped to physical path.");
177 if (String
.Compare (Path
.GetExtension (file
), ".sitemap", RuntimeHelpers
.StringComparison
) != 0)
178 throw new InvalidOperationException (String
.Format ("The file {0} has an invalid extension, only .sitemap files are allowed in XmlSiteMapProvider.",
179 String
.IsNullOrEmpty (virtualPath
) ? Path
.GetFileName (file
) : virtualPath
));
181 if (!File
.Exists (file
))
182 throw new InvalidOperationException (String
.Format ("The file '{0}' required by XmlSiteMapProvider does not exist.",
183 String
.IsNullOrEmpty (virtualPath
) ? Path
.GetFileName (file
) : virtualPath
));
185 ResourceKey
= Path
.GetFileName (file
);
186 CreateWatcher (file
);
188 XmlDocument d
= new XmlDocument ();
194 public override SiteMapNode
BuildSiteMap ()
199 // Whenever you call AddNode, it tries to find dups, and will call this method
200 // Is this a bug in MS??
206 bool enableLocalization
;
207 XmlNode node
= FindStartingNode (fileVirtualPath
, out enableLocalization
);
208 EnableLocalization
= enableLocalization
;
209 BuildSiteMapRecursive (node
, null);
211 // if (builtRoot != root) {
220 SiteMapNode
ConvertToSiteMapNode (XmlNode xmlNode
)
222 bool localize
= EnableLocalization
;
223 string url
= GetOptionalAttribute (xmlNode
, "url");
224 string title
= GetOptionalAttribute (xmlNode
, "title");
225 string description
= GetOptionalAttribute (xmlNode
, "description");
226 string roles
= GetOptionalAttribute (xmlNode
, "roles");
227 string implicitResourceKey
= GetOptionalAttribute (xmlNode
, "resourceKey");
229 // var keywordsList = new List <string> ();
230 // if (keywords != null && keywords.Length > 0) {
231 // foreach (string s in keywords.Split (seperators)) {
232 // string ss = s.Trim ();
233 // if (ss.Length > 0)
234 // keywordsList.Add (ss);
238 var rolesList
= new List
<string> ();
239 if (roles
!= null && roles
.Length
> 0) {
240 foreach (string s
in roles
.Split (seperators
)) {
241 string ss
= s
.Trim ();
247 url
= base.MapUrl (url
);
249 NameValueCollection attributes
= null;
250 NameValueCollection explicitResourceKeys
= null;
252 CollectLocalizationInfo (xmlNode
, ref title
, ref description
, ref attributes
, ref explicitResourceKeys
);
254 foreach (XmlNode att
in xmlNode
.Attributes
)
255 PutInCollection (att
.Name
, att
.Value
, ref attributes
);
257 string key
= Guid
.NewGuid ().ToString ();
258 return new SiteMapNode (this, key
, url
, title
, description
, rolesList
.AsReadOnly (),
259 attributes
, explicitResourceKeys
, implicitResourceKey
);
262 void BuildSiteMapRecursive (XmlNode xmlNode
, SiteMapNode parent
)
264 if (xmlNode
.Name
!= "siteMapNode")
265 throw new ConfigurationException ("incorrect element name", xmlNode
);
267 string attrValue
= GetNonEmptyOptionalAttribute (xmlNode
, "provider");
268 if (attrValue
!= null) {
269 SiteMapProvider provider
= SiteMap
.Providers
[attrValue
];
270 if (provider
== null)
271 throw new ProviderException ("Provider with name [" + attrValue
+ "] was not found.");
273 provider
.ParentProvider
= this;
274 SiteMapNode providerRoot
= provider
.GetRootNodeCore();
279 AddNodeNoCheck (providerRoot
, parent
);
283 attrValue
= GetNonEmptyOptionalAttribute (xmlNode
, "siteMapFile");
284 if (attrValue
!= null) {
285 var nvc
= new NameValueCollection ();
286 nvc
.Add ("siteMapFile", attrValue
);
288 string description
= GetOptionalAttribute (xmlNode
, "description");
289 if (!String
.IsNullOrEmpty (description
))
290 nvc
.Add ("description", description
);
292 string name
= MapUrl (attrValue
);
293 var provider
= new XmlSiteMapProvider ();
294 provider
.Initialize (name
, nvc
);
296 SiteMapNode providerRoot
= provider
.GetRootNodeCore ();
300 AddNodeNoCheck (providerRoot
, parent
);
304 SiteMapNode curNode
= ConvertToSiteMapNode (xmlNode
);
308 AddNodeNoCheck (curNode
, parent
);
310 XmlNodeList childNodes
= xmlNode
.ChildNodes
;
311 if (childNodes
== null || childNodes
.Count
< 1)
314 foreach (XmlNode child
in childNodes
) {
315 if (child
.NodeType
!= XmlNodeType
.Element
)
318 BuildSiteMapRecursive (child
, curNode
);
322 string GetNonEmptyOptionalAttribute (XmlNode n
, string name
)
324 return System
.Web
.Configuration
.HandlersUtil
.ExtractAttributeValue (name
, n
, true);
327 string GetOptionalAttribute (XmlNode n
, string name
)
329 return System
.Web
.Configuration
.HandlersUtil
.ExtractAttributeValue (name
, n
, true, true);
332 void PutInCollection (string name
, string value, ref NameValueCollection coll
)
334 PutInCollection (name
, null, value, ref coll
);
337 void PutInCollection (string name
, string classKey
, string value, ref NameValueCollection coll
)
340 coll
= new NameValueCollection ();
341 if (!String
.IsNullOrEmpty (classKey
))
342 coll
.Add (name
, classKey
);
343 coll
.Add (name
, value);
346 bool GetAttributeLocalization (string value, out string resClass
, out string resKey
, out string resDefault
)
352 if (String
.IsNullOrEmpty (value))
354 string val
= value.TrimStart (new char[] {' ', '\t'}
);
355 if (val
.Length
< 11 ||
356 String
.Compare (val
, 0, "$resources:", 0, 11, StringComparison
.InvariantCultureIgnoreCase
) != 0)
359 val
= val
.Substring (11);
362 string[] parts
= val
.Split (',');
363 if (parts
.Length
< 2)
365 resClass
= parts
[0].Trim ();
366 resKey
= parts
[1].Trim ();
367 if (parts
.Length
== 3)
368 resDefault
= parts
[2];
369 else if (parts
.Length
> 3)
370 resDefault
= String
.Join (",", parts
, 2, parts
.Length
- 2);
375 void CollectLocalizationInfo (XmlNode xmlNode
, ref string title
, ref string description
,
376 ref NameValueCollection attributes
,
377 ref NameValueCollection explicitResourceKeys
)
383 if (GetAttributeLocalization (title
, out resClass
, out resKey
, out resDefault
)) {
384 PutInCollection ("title", resClass
, resKey
, ref explicitResourceKeys
);
388 if (GetAttributeLocalization (description
, out resClass
, out resKey
, out resDefault
)) {
389 PutInCollection ("description", resClass
, resKey
, ref explicitResourceKeys
);
390 description
= resDefault
;
394 foreach (XmlNode att
in xmlNode
.Attributes
) {
395 if (GetAttributeLocalization (att
.Value
, out resClass
, out resKey
, out resDefault
)) {
396 PutInCollection (att
.Name
, resClass
, resKey
, ref explicitResourceKeys
);
400 PutInCollection (att
.Name
, value, ref attributes
);
404 protected override void Clear ()
408 ChildProviders
.Clear ();
409 ChildProvidersPresent
.Clear ();
412 protected virtual void Dispose (bool disposing
)
415 foreach (FileSystemWatcher watcher
in watchers
)
421 public void Dispose ()
426 public override SiteMapNode
FindSiteMapNode (string rawUrl
)
428 SiteMapNode node
= base.FindSiteMapNode (rawUrl
);
433 string url
= MapUrl (rawUrl
);
435 if (String
.Compare (url
, node
.Url
, RuntimeHelpers
.StringComparison
) == 0)
439 foreach (SiteMapProvider smp
in ChildProviders
) {
440 node
= smp
.FindSiteMapNode (url
);
448 public override SiteMapNode
FindSiteMapNodeFromKey (string key
)
450 SiteMapNode node
= base.FindSiteMapNodeFromKey (key
);
454 foreach (SiteMapProvider smp
in ChildProviders
) {
455 node
= smp
.FindSiteMapNodeFromKey (key
);
463 public override void Initialize (string name
, NameValueCollection attributes
)
466 throw new InvalidOperationException ("XmlSiteMapProvider cannot be initialized twice.");
469 if (attributes
!= null) {
470 foreach (string key
in attributes
.AllKeys
) {
473 fileVirtualPath
= base.MapUrl (attributes
["siteMapFile"]);
477 case "securityTrimmingEnabled":
481 throw new ConfigurationErrorsException ("The attribute '" + key
+ "' is unexpected in the configuration of the '" + name
+ "' provider.");
486 base.Initialize (name
, attributes
!= null ? attributes
: new NameValueCollection ());
489 void CreateWatcher (string file
)
491 var watcher
= new FileSystemWatcher ();
492 watcher
.NotifyFilter
|= NotifyFilters
.Size
;
493 watcher
.Path
= Path
.GetFullPath (Path
.GetDirectoryName (file
));
494 watcher
.Filter
= Path
.GetFileName (file
);
495 watcher
.Changed
+= new FileSystemEventHandler (OnFileChanged
);
496 watcher
.EnableRaisingEvents
= true;
498 if (watchers
== null)
499 watchers
= new List
<FileSystemWatcher
> ();
501 watchers
.Add (watcher
);
504 protected override void RemoveNode (SiteMapNode node
)
506 base.RemoveNode (node
);
509 [MonoTODO ("Not implemented")]
510 protected virtual void RemoveProvider (string providerName
)
512 throw new NotImplementedException ();
515 void OnFileChanged (object sender
, FileSystemEventArgs args
)
520 public override SiteMapNode RootNode
{
527 protected internal override SiteMapNode
GetRootNodeCore ()
529 return BuildSiteMap ();