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 string fileVirtualPath;
55 SiteMapNode root = null;
56 List <FileSystemWatcher> watchers;
57 Dictionary <string, bool> _childProvidersPresent;
58 List <SiteMapProvider> _childProviders;
60 Dictionary <string, bool> ChildProvidersPresent {
62 if (_childProvidersPresent == null)
63 _childProvidersPresent = new Dictionary <string, bool> ();
65 return _childProvidersPresent;
69 List <SiteMapProvider> ChildProviders {
71 if (_childProviders == null)
72 _childProviders = new List <SiteMapProvider> ();
74 return _childProviders;
78 protected internal override void AddNode (SiteMapNode node, SiteMapNode parentNode)
80 base.AddNode (node, parentNode);
83 protected virtual void AddProvider (string providerName, SiteMapNode parentNode)
85 if (parentNode == null)
86 throw new ArgumentNullException ("parentNode");
88 if (parentNode.Provider != this)
89 throw new ArgumentException ("The Provider property of the parentNode does not reference the current provider.", "parentNode");
91 SiteMapProvider smp = SiteMap.Providers [providerName];
93 throw new ProviderException ("Provider with name [" + providerName + "] was not found.");
95 AddNode (smp.GetRootNodeCore ());
96 RegisterChildProvider (providerName, smp);
99 void RegisterChildProvider (string name, SiteMapProvider smp)
101 Dictionary <string, bool> childProvidersPresent = ChildProvidersPresent;
103 if (childProvidersPresent.ContainsKey (name))
106 childProvidersPresent.Add (name, true);
107 ChildProviders.Add (smp);
110 XmlNode FindStartingNode (string file, string virtualPath, out bool enableLocalization)
112 if (String.Compare (Path.GetExtension (file), ".sitemap", stringComparison) != 0)
113 throw new InvalidOperationException (
114 String.Format ("The file {0} has an invalid extension, only .sitemap files are allowed in XmlSiteMapProvider.",
115 String.IsNullOrEmpty (virtualPath) ? Path.GetFileName (file) : virtualPath));
116 if (!File.Exists (file))
117 throw new InvalidOperationException (
118 String.Format ("The file '{0}' required by XmlSiteMapProvider does not exist.",
119 String.IsNullOrEmpty (virtualPath) ? Path.GetFileName (file) : virtualPath));
121 XmlDocument d = new XmlDocument ();
124 XmlNode enloc = d.DocumentElement.Attributes ["enableLocalization"];
125 if (enloc != null && !String.IsNullOrEmpty (enloc.Value))
126 enableLocalization = (bool) Convert.ChangeType (enloc.Value, typeof (bool));
128 enableLocalization = false;
130 XmlNode nod = d.DocumentElement ["siteMapNode"];
132 throw new HttpException ("Invalid site map file: " + Path.GetFileName (file));
137 public override SiteMapNode BuildSiteMap ()
141 // Whenever you call AddNode, it tries to find dups, and will call this method
142 // Is this a bug in MS??
152 bool enableLocalization;
153 XmlNode node = FindStartingNode (file, fileVirtualPath, out enableLocalization);
154 EnableLocalization = enableLocalization;
155 SiteMapNode builtRoot = BuildSiteMapRecursive (node, EnableLocalization);
157 if (builtRoot != root) {
169 string GetNonEmptyOptionalAttribute (XmlNode n, string name)
171 return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true);
174 string GetOptionalAttribute (XmlNode n, string name)
176 return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true, true);
179 void PutInCollection (string name, string value, ref NameValueCollection coll)
181 PutInCollection (name, null, value, ref coll);
184 void PutInCollection (string name, string classKey, string value, ref NameValueCollection coll)
187 coll = new NameValueCollection ();
188 if (!String.IsNullOrEmpty (classKey))
189 coll.Add (name, classKey);
190 coll.Add (name, value);
193 bool GetAttributeLocalization (string value, out string resClass, out string resKey, out string resDefault)
199 if (String.IsNullOrEmpty (value))
201 string val = value.TrimStart (new char[] {' ', '\t'});
202 if (val.Length < 11 ||
203 String.Compare (val, 0, "$resources:", 0, 11, StringComparison.InvariantCultureIgnoreCase) != 0)
206 val = val.Substring (11);
209 string[] parts = val.Split (',');
210 if (parts.Length < 2)
212 resClass = parts [0].Trim ();
213 resKey = parts [1].Trim ();
214 if (parts.Length == 3)
215 resDefault = parts [2];
216 else if (parts.Length > 3)
217 resDefault = String.Join (",", parts, 2, parts.Length - 2);
222 void CollectLocalizationInfo (XmlNode xmlNode, ref string title, ref string description,
223 ref NameValueCollection attributes,
224 ref NameValueCollection explicitResourceKeys)
230 if (GetAttributeLocalization (title, out resClass, out resKey, out resDefault)) {
231 PutInCollection ("title", resClass, resKey, ref explicitResourceKeys);
235 if (GetAttributeLocalization (description, out resClass, out resKey, out resDefault)) {
236 PutInCollection ("description", resClass, resKey, ref explicitResourceKeys);
237 description = resDefault;
241 foreach (XmlNode att in xmlNode.Attributes) {
242 if (GetAttributeLocalization (att.Value, out resClass, out resKey, out resDefault)) {
243 PutInCollection (att.Name, resClass, resKey, ref explicitResourceKeys);
247 PutInCollection (att.Name, value, ref attributes);
251 SiteMapNode BuildSiteMapRecursive (XmlNode xmlNode, bool localize)
253 if (xmlNode.Name != "siteMapNode")
254 throw new ConfigurationException ("incorrect element name", xmlNode);
256 string provider = GetNonEmptyOptionalAttribute (xmlNode, "provider");
257 string siteMapFile = GetNonEmptyOptionalAttribute (xmlNode, "siteMapFile");
259 if (provider != null) {
260 SiteMapProvider smp = SiteMap.Providers [provider];
262 throw new ProviderException ("Provider with name [" + provider + "] was not found.");
264 smp.ParentProvider = this;
265 SiteMapNode root = smp.GetRootNodeCore();
266 RegisterChildProvider (provider, smp);
269 } else if (siteMapFile != null) {
270 if (file.Length == 0)
271 throw new InvalidOperationException ("The 'siteMapFile' attribute cannot be an empty string.");
272 string realPath = HttpContext.Current.Request.MapPath (siteMapFile);
273 bool enableLocalization;
274 XmlNode node = FindStartingNode (realPath, siteMapFile, out enableLocalization);
276 CreateWatcher (realPath);
277 return BuildSiteMapRecursive (node, enableLocalization);
279 string url = GetOptionalAttribute (xmlNode, "url");
280 string title = GetOptionalAttribute (xmlNode, "title");
281 string description = GetOptionalAttribute (xmlNode, "description");
282 string keywords = GetOptionalAttribute (xmlNode, "keywords");
283 string roles = GetOptionalAttribute (xmlNode, "roles");
284 string implicitResourceKey = GetOptionalAttribute (xmlNode, "resourceKey");
286 ArrayList keywordsList = new ArrayList ();
287 if (keywords != null && keywords.Length > 0) {
288 foreach (string s in keywords.Split (seperators)) {
289 string ss = s.Trim ();
291 keywordsList.Add (ss);
295 ArrayList rolesList = new ArrayList ();
296 if (roles != null && roles.Length > 0) {
297 foreach (string s in roles.Split (seperators)) {
298 string ss = s.Trim ();
304 if (!string.IsNullOrEmpty (url)) {
305 if (UrlUtils.IsRelativeUrl (url))
306 url = UrlUtils.Combine (HttpRuntime.AppDomainAppVirtualPath, url);
309 NameValueCollection attributes = null;
310 NameValueCollection explicitResourceKeys = null;
312 CollectLocalizationInfo (xmlNode, ref title, ref description, ref attributes,
313 ref explicitResourceKeys);
315 foreach (XmlNode att in xmlNode.Attributes)
316 PutInCollection (att.Name, att.Value, ref attributes);
318 string key = Guid.NewGuid ().ToString ();
319 SiteMapNode node = new SiteMapNode (this, key, url, title, description,
320 ArrayList.ReadOnly (rolesList),
322 explicitResourceKeys,
323 implicitResourceKey);
325 foreach (XmlNode child in xmlNode.ChildNodes) {
326 if (child.NodeType != XmlNodeType.Element)
328 AddNode (BuildSiteMapRecursive (child, EnableLocalization), node);
335 protected override void Clear ()
339 ChildProviders.Clear ();
340 ChildProvidersPresent.Clear ();
343 protected virtual void Dispose (bool disposing)
346 foreach (FileSystemWatcher watcher in watchers)
352 public void Dispose ()
357 public override SiteMapNode FindSiteMapNode (string rawUrl)
359 SiteMapNode node = base.FindSiteMapNode (rawUrl);
363 foreach (SiteMapProvider smp in ChildProviders) {
364 node = smp.FindSiteMapNode (rawUrl);
372 public override SiteMapNode FindSiteMapNodeFromKey (string key)
374 SiteMapNode node = base.FindSiteMapNodeFromKey (key);
378 foreach (SiteMapProvider smp in ChildProviders) {
379 node = smp.FindSiteMapNodeFromKey (key);
387 public override void Initialize (string name, NameValueCollection attributes)
389 base.Initialize (name, attributes);
390 fileVirtualPath = attributes ["siteMapFile"];
391 if (String.IsNullOrEmpty (fileVirtualPath))
392 throw new ArgumentException ("The siteMapFile attribute must be specified on the XmlSiteMapProvider.");
394 HttpContext ctx = HttpContext.Current;
395 HttpRequest req = ctx != null ? ctx.Request : null;
398 file = req.MapPath (fileVirtualPath, HttpRuntime.AppDomainAppVirtualPath, false);
400 throw new InvalidOperationException ("Request is missing - cannot map paths.");
402 if (File.Exists (file)) {
403 ResourceKey = Path.GetFileName (file);
404 CreateWatcher (file);
408 void CreateWatcher (string file)
410 var watcher = new FileSystemWatcher ();
411 watcher.NotifyFilter |= NotifyFilters.Size;
412 watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
413 watcher.Filter = Path.GetFileName (file);
414 watcher.Changed += new FileSystemEventHandler (OnFileChanged);
415 watcher.EnableRaisingEvents = true;
417 if (watchers == null)
418 watchers = new List <FileSystemWatcher> ();
420 watchers.Add (watcher);
423 protected override void RemoveNode (SiteMapNode node)
425 base.RemoveNode (node);
428 [MonoTODO ("Not implemented")]
429 protected virtual void RemoveProvider (string providerName)
431 throw new NotImplementedException ();
434 void OnFileChanged (object sender, FileSystemEventArgs args)
439 public override SiteMapNode RootNode {
446 protected internal override SiteMapNode GetRootNodeCore ()
448 return BuildSiteMap ();