2 // System.Web.XmlSiteMapProvider
5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Lluis Sanchez Gual (lluis@novell.com)
9 // (C) 2005 Novell, Inc (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // 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.Util;
47 public class XmlSiteMapProvider : StaticSiteMapProvider, IDisposable
49 static readonly char [] seperators = { ';', ',' };
50 static readonly StringComparison stringComparison = HttpRuntime.RunningOnWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
54 SiteMapNode root = null;
55 FileSystemWatcher watcher;
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)
79 base.AddNode (node, parentNode);
82 protected virtual void AddProvider (string providerName, SiteMapNode parentNode)
84 if (parentNode == null)
85 throw new ArgumentNullException ("parentNode");
87 if (parentNode.Provider != this)
88 throw new ArgumentException ("The Provider property of the parentNode does not reference the current provider.", "parentNode");
90 SiteMapProvider smp = SiteMap.Providers [providerName];
92 throw new ProviderException ("Provider with name [" + providerName + "] was not found.");
94 AddNode (smp.GetRootNodeCore ());
95 RegisterChildProvider (providerName, smp);
98 void RegisterChildProvider (string name, SiteMapProvider smp)
100 Dictionary <string, bool> childProvidersPresent = ChildProvidersPresent;
102 if (childProvidersPresent.ContainsKey (name))
105 childProvidersPresent.Add (name, true);
106 ChildProviders.Add (smp);
109 XmlNode FindStartingNode (string file, out bool enableLocalization)
111 if (String.Compare (Path.GetExtension (file), ".sitemap", stringComparison) != 0)
112 throw new InvalidOperationException (
113 String.Format ("The file {0} has an invalid extension, only .sitemap files are allowed in XmlSiteMapProvider.",
114 Path.GetFileName (file)));
116 XmlDocument d = new XmlDocument ();
119 XmlNode enloc = d.DocumentElement.Attributes ["enableLocalization"];
120 if (enloc != null && !String.IsNullOrEmpty (enloc.Value))
121 enableLocalization = (bool) Convert.ChangeType (enloc.Value, typeof (bool));
123 enableLocalization = false;
125 XmlNode nod = d.DocumentElement ["siteMapNode"];
127 throw new HttpException ("Invalid site map file: " + Path.GetFileName (file));
132 public override SiteMapNode BuildSiteMap ()
136 // Whenever you call AddNode, it tries to find dups, and will call this method
137 // Is this a bug in MS??
147 bool enableLocalization;
148 XmlNode node = FindStartingNode (file, out enableLocalization);
149 EnableLocalization = enableLocalization;
150 SiteMapNode builtRoot = BuildSiteMapRecursive (node, EnableLocalization);
152 if (builtRoot != root) {
164 string GetNonEmptyOptionalAttribute (XmlNode n, string name)
166 return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true);
169 string GetOptionalAttribute (XmlNode n, string name)
171 return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true, true);
174 void PutInCollection (string name, string value, ref NameValueCollection coll)
176 PutInCollection (name, null, value, ref coll);
179 void PutInCollection (string name, string classKey, string value, ref NameValueCollection coll)
182 coll = new NameValueCollection ();
183 if (!String.IsNullOrEmpty (classKey))
184 coll.Add (name, classKey);
185 coll.Add (name, value);
188 bool GetAttributeLocalization (string value, out string resClass, out string resKey, out string resDefault)
194 if (String.IsNullOrEmpty (value))
196 string val = value.TrimStart (new char[] {' ', '\t'});
197 if (val.Length < 11 ||
198 String.Compare (val, 0, "$resources:", 0, 11, StringComparison.InvariantCultureIgnoreCase) != 0)
201 val = val.Substring (11);
204 string[] parts = val.Split (',');
205 if (parts.Length < 2)
207 resClass = parts [0].Trim ();
208 resKey = parts [1].Trim ();
209 if (parts.Length == 3)
210 resDefault = parts [2];
211 else if (parts.Length > 3)
212 resDefault = String.Join (",", parts, 2, parts.Length - 2);
217 void CollectLocalizationInfo (XmlNode xmlNode, ref string title, ref string description,
218 ref NameValueCollection attributes,
219 ref NameValueCollection explicitResourceKeys)
225 if (GetAttributeLocalization (title, out resClass, out resKey, out resDefault)) {
226 PutInCollection ("title", resClass, resKey, ref explicitResourceKeys);
230 if (GetAttributeLocalization (description, out resClass, out resKey, out resDefault)) {
231 PutInCollection ("description", resClass, resKey, ref explicitResourceKeys);
232 description = resDefault;
236 foreach (XmlNode att in xmlNode.Attributes) {
237 if (GetAttributeLocalization (att.Value, out resClass, out resKey, out resDefault)) {
238 PutInCollection (att.Name, resClass, resKey, ref explicitResourceKeys);
242 PutInCollection (att.Name, value, ref attributes);
246 SiteMapNode BuildSiteMapRecursive (XmlNode xmlNode, bool localize)
248 if (xmlNode.Name != "siteMapNode")
249 throw new ConfigurationException ("incorrect element name", xmlNode);
251 string provider = GetNonEmptyOptionalAttribute (xmlNode, "provider");
252 string siteMapFile = GetNonEmptyOptionalAttribute (xmlNode, "siteMapFile");
254 if (provider != null) {
255 SiteMapProvider smp = SiteMap.Providers [provider];
257 throw new ProviderException ("Provider with name [" + provider + "] was not found.");
259 smp.ParentProvider = this;
260 SiteMapNode root = smp.GetRootNodeCore();
261 RegisterChildProvider (provider, smp);
264 } else if (siteMapFile != null) {
265 bool enableLocalization;
266 XmlNode node = FindStartingNode (HttpContext.Current.Request.MapPath (siteMapFile),
267 out enableLocalization);
268 return BuildSiteMapRecursive (node, enableLocalization);
270 string url = GetOptionalAttribute (xmlNode, "url");
271 string title = GetOptionalAttribute (xmlNode, "title");
272 string description = GetOptionalAttribute (xmlNode, "description");
273 string keywords = GetOptionalAttribute (xmlNode, "keywords");
274 string roles = GetOptionalAttribute (xmlNode, "roles");
275 string implicitResourceKey = GetOptionalAttribute (xmlNode, "resourceKey");
277 ArrayList keywordsList = new ArrayList ();
278 if (keywords != null && keywords.Length > 0) {
279 foreach (string s in keywords.Split (seperators)) {
280 string ss = s.Trim ();
282 keywordsList.Add (ss);
286 ArrayList rolesList = new ArrayList ();
287 if (roles != null && roles.Length > 0) {
288 foreach (string s in roles.Split (seperators)) {
289 string ss = s.Trim ();
295 if (!string.IsNullOrEmpty (url)) {
296 if (UrlUtils.IsRelativeUrl (url))
297 url = UrlUtils.Combine (HttpRuntime.AppDomainAppVirtualPath, url);
300 NameValueCollection attributes = null;
301 NameValueCollection explicitResourceKeys = null;
303 CollectLocalizationInfo (xmlNode, ref title, ref description, ref attributes,
304 ref explicitResourceKeys);
306 foreach (XmlNode att in xmlNode.Attributes)
307 PutInCollection (att.Name, att.Value, ref attributes);
309 string key = Guid.NewGuid ().ToString ();
310 SiteMapNode node = new SiteMapNode (this, key, url, title, description,
311 ArrayList.ReadOnly (rolesList),
313 explicitResourceKeys,
314 implicitResourceKey);
316 foreach (XmlNode child in xmlNode.ChildNodes) {
317 if (child.NodeType != XmlNodeType.Element)
319 AddNode (BuildSiteMapRecursive (child, EnableLocalization), node);
326 protected override void Clear ()
330 ChildProviders.Clear ();
331 ChildProvidersPresent.Clear ();
334 protected virtual void Dispose (bool disposing)
340 public void Dispose ()
345 public override SiteMapNode FindSiteMapNode (string rawUrl)
347 SiteMapNode node = base.FindSiteMapNode (rawUrl);
351 foreach (SiteMapProvider smp in ChildProviders) {
352 node = smp.FindSiteMapNode (rawUrl);
360 public override SiteMapNode FindSiteMapNodeFromKey (string key)
362 SiteMapNode node = base.FindSiteMapNodeFromKey (key);
366 foreach (SiteMapProvider smp in ChildProviders) {
367 node = smp.FindSiteMapNodeFromKey (key);
375 public override void Initialize (string name, NameValueCollection attributes)
377 base.Initialize (name, attributes);
378 file = attributes ["siteMapFile"];
379 if (String.IsNullOrEmpty (file))
380 throw new ArgumentException ("The siteMapFile attribute must be specified on the XmlSiteMapProvider.");
382 HttpContext ctx = HttpContext.Current;
383 HttpRequest req = ctx != null ? ctx.Request : null;
386 file = req.MapPath (file, HttpRuntime.AppDomainAppVirtualPath, false);
388 throw new InvalidOperationException ("Request is missing - cannot map paths.");
390 if (File.Exists (file)) {
391 ResourceKey = Path.GetFileName (file);
393 watcher = new FileSystemWatcher ();
394 watcher.NotifyFilter |= NotifyFilters.Size;
395 watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
396 watcher.Filter = Path.GetFileName (file);
397 watcher.Changed += new FileSystemEventHandler (OnFileChanged);
398 watcher.EnableRaisingEvents = true;
402 protected override void RemoveNode (SiteMapNode node)
404 base.RemoveNode (node);
407 [MonoTODO ("Not implemented")]
408 protected virtual void RemoveProvider (string providerName)
410 throw new NotImplementedException ();
413 void OnFileChanged (object sender, FileSystemEventArgs args)
418 public override SiteMapNode RootNode {
425 protected internal override SiteMapNode GetRootNodeCore ()
427 return BuildSiteMap ();