//
// System.Web.XmlSiteMapProvider
//
// Authors:
// Ben Maurer (bmaurer@users.sourceforge.net)
// Lluis Sanchez Gual (lluis@novell.com)
// Marek Habersack <mhabersack@novell.com>
//
// (C) 2003 Ben Maurer
// (C) 2005-2009 Novell, Inc (http://www.novell.com)
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Globalization;
using System.Text;
using System.Xml;
using System.Web.Hosting;
using System.Web.Util;
using System.IO;
namespace System.Web{
public class XmlSiteMapProvider : StaticSiteMapProvider, IDisposable
{
static readonly char [] seperators = { ';', ',' };
bool initialized;
string fileVirtualPath;
SiteMapNode root = null;
List <FileSystemWatcher> watchers;
Dictionary <string, bool> _childProvidersPresent;
List <SiteMapProvider> _childProviders;
Dictionary <string, bool> ChildProvidersPresent {
get {
if (_childProvidersPresent == null)
_childProvidersPresent = new Dictionary <string, bool> ();
return _childProvidersPresent;
}
}
List <SiteMapProvider> ChildProviders {
get {
if (_childProviders == null)
_childProviders = new List <SiteMapProvider> ();
return _childProviders;
}
}
protected internal override void AddNode (SiteMapNode node, SiteMapNode parentNode)
{
if (node == null)
throw new ArgumentNullException ("node");
if (parentNode == null)
throw new ArgumentNullException ("parentNode");
SiteMapProvider nodeProvider = node.Provider;
if (nodeProvider != this)
throw new ArgumentException ("SiteMapNode '" + node + "' cannot be found in current provider, only nodes in the same provider can be added.",
"node");
SiteMapProvider parentNodeProvider = parentNode.Provider;
if (nodeProvider != parentNodeProvider)
throw new ArgumentException ("SiteMapNode '" + parentNode + "' cannot be found in current provider, only nodes in the same provider can be added.",
"parentNode");
AddNodeNoCheck (node, parentNode);
}
void AddNodeNoCheck (SiteMapNode node, SiteMapNode parentNode)
{
base.AddNode (node, parentNode);
SiteMapProvider nodeProvider = node.Provider;
if (nodeProvider != this)
RegisterChildProvider (nodeProvider.Name, nodeProvider);
}
protected virtual void AddProvider (string providerName, SiteMapNode parentNode)
{
if (parentNode == null)
throw new ArgumentNullException ("parentNode");
if (parentNode.Provider != this)
throw new ArgumentException ("The Provider property of the parentNode does not reference the current provider.", "parentNode");
SiteMapProvider smp = SiteMap.Providers [providerName];
if (smp == null)
throw new ProviderException ("Provider with name [" + providerName + "] was not found.");
AddNode (smp.GetRootNodeCore ());
RegisterChildProvider (providerName, smp);
}
void RegisterChildProvider (string name, SiteMapProvider smp)
{
Dictionary <string, bool> childProvidersPresent = ChildProvidersPresent;
if (childProvidersPresent.ContainsKey (name))
return;
childProvidersPresent.Add (name, true);
ChildProviders.Add (smp);
}
XmlNode FindStartingNode (string virtualPath, out bool enableLocalization)
{
XmlDocument d = GetConfigDocument (virtualPath);
XmlElement docElement = d.DocumentElement;
if (String.Compare ("siteMap", docElement.Name, StringComparison.Ordinal) != 0)
throw new ConfigurationErrorsException ("Top element must be 'siteMap'");
XmlNode enloc = docElement.Attributes ["enableLocalization"];
if (enloc != null && !String.IsNullOrEmpty (enloc.Value))
enableLocalization = (bool) Convert.ChangeType (enloc.Value, typeof (bool));
else
enableLocalization = false;
XmlNodeList childNodes = docElement.ChildNodes;
XmlNode node = null;
foreach (XmlNode child in childNodes) {
if (String.Compare ("siteMapNode", child.Name, StringComparison.Ordinal) != 0)
// Only <siteMapNode> is allowed at the top
throw new ConfigurationErrorsException ("Only <siteMapNode> elements are allowed at the document top level.");
if (node != null)
// Only one <siteMapNode> is allowed at the top
throw new ConfigurationErrorsException ("Only one <siteMapNode> element is allowed at the document top level.");
node = child;
}
if (node == null)
throw new ConfigurationErrorsException ("Missing <siteMapNode> element at the document top level.");
return node;
}
XmlDocument GetConfigDocument (string virtualPath)
{
if (String.IsNullOrEmpty (virtualPath))
throw new ArgumentException ("The siteMapFile attribute must be specified on the XmlSiteMapProvider");
string file = HostingEnvironment.MapPath (virtualPath);
if (file == null)
throw new HttpException ("Virtual path '" + virtualPath + "' cannot be mapped to physical path.");
if (String.Compare (Path.GetExtension (file), ".sitemap", RuntimeHelpers.StringComparison) != 0)
throw new InvalidOperationException (String.Format ("The file {0} has an invalid extension, only .sitemap files are allowed in XmlSiteMapProvider.",
String.IsNullOrEmpty (virtualPath) ? Path.GetFileName (file) : virtualPath));
if (!File.Exists (file))
throw new InvalidOperationException (String.Format ("The file '{0}' required by XmlSiteMapProvider does not exist.",
String.IsNullOrEmpty (virtualPath) ? Path.GetFileName (file) : virtualPath));
ResourceKey = Path.GetFileName (file);
CreateWatcher (file);
XmlDocument d = new XmlDocument ();
d.Load (file);
return d;
}
public override SiteMapNode BuildSiteMap ()
{
if (root != null)
return root;
// Whenever you call AddNode, it tries to find dups, and will call this method
// Is this a bug in MS??
lock (this_lock) {
if (root != null)
return root;
Clear ();
bool enableLocalization;
XmlNode node = FindStartingNode (fileVirtualPath, out enableLocalization);
EnableLocalization = enableLocalization;
BuildSiteMapRecursive (node, null);
// if (builtRoot != root) {
// root = builtRoot;
// AddNode (root);
// }
return root;
}
}
SiteMapNode ConvertToSiteMapNode (XmlNode xmlNode)
{
bool localize = EnableLocalization;
string url = GetOptionalAttribute (xmlNode, "url");
string title = GetOptionalAttribute (xmlNode, "title");
string description = GetOptionalAttribute (xmlNode, "description");
string roles = GetOptionalAttribute (xmlNode, "roles");
string implicitResourceKey = GetOptionalAttribute (xmlNode, "resourceKey");
// var keywordsList = new List <string> ();
// if (keywords != null && keywords.Length > 0) {
// foreach (string s in keywords.Split (seperators)) {
// string ss = s.Trim ();
// if (ss.Length > 0)
// keywordsList.Add (ss);
// }
// }
var rolesList = new List <string> ();
if (roles != null && roles.Length > 0) {
foreach (string s in roles.Split (seperators)) {
string ss = s.Trim ();
if (ss.Length > 0)
rolesList.Add (ss);
}
}
url = base.MapUrl (url);
NameValueCollection attributes = null;
NameValueCollection explicitResourceKeys = null;
if (localize)
CollectLocalizationInfo (xmlNode, ref title, ref description, ref attributes, ref explicitResourceKeys);
else
foreach (XmlNode att in xmlNode.Attributes)
PutInCollection (att.Name, att.Value, ref attributes);
string key = Guid.NewGuid ().ToString ();
return new SiteMapNode (this, key, url, title, description, rolesList.AsReadOnly (),
attributes, explicitResourceKeys, implicitResourceKey);
}
void BuildSiteMapRecursive (XmlNode xmlNode, SiteMapNode parent)
{
if (xmlNode.Name != "siteMapNode")
throw new ConfigurationException ("incorrect element name", xmlNode);
string attrValue = GetNonEmptyOptionalAttribute (xmlNode, "provider");
if (attrValue != null) {
SiteMapProvider provider = SiteMap.Providers [attrValue];
if (provider == null)
throw new ProviderException ("Provider with name [" + attrValue + "] was not found.");
provider.ParentProvider = this;
SiteMapNode providerRoot = provider.GetRootNodeCore();
if (parent == null)
root = providerRoot;
else
AddNodeNoCheck (providerRoot, parent);
return;
}
attrValue = GetNonEmptyOptionalAttribute (xmlNode, "siteMapFile");
if (attrValue != null) {
var nvc = new NameValueCollection ();
nvc.Add ("siteMapFile", attrValue);
string description = GetOptionalAttribute (xmlNode, "description");
if (!String.IsNullOrEmpty (description))
nvc.Add ("description", description);
string name = MapUrl (attrValue);
var provider = new XmlSiteMapProvider ();
provider.Initialize (name, nvc);
SiteMapNode providerRoot = provider.GetRootNodeCore ();
if (parent == null)
root = providerRoot;
else
AddNodeNoCheck (providerRoot, parent);
return;
}
SiteMapNode curNode = ConvertToSiteMapNode (xmlNode);
if (parent == null)
root = curNode;
else
AddNodeNoCheck (curNode, parent);
XmlNodeList childNodes = xmlNode.ChildNodes;
if (childNodes == null || childNodes.Count < 1)
return;
foreach (XmlNode child in childNodes) {
if (child.NodeType != XmlNodeType.Element)
continue;
BuildSiteMapRecursive (child, curNode);
}
}
string GetNonEmptyOptionalAttribute (XmlNode n, string name)
{
return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true);
}
string GetOptionalAttribute (XmlNode n, string name)
{
return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true, true);
}
void PutInCollection (string name, string value, ref NameValueCollection coll)
{
PutInCollection (name, null, value, ref coll);
}
void PutInCollection (string name, string classKey, string value, ref NameValueCollection coll)
{
if (coll == null)
coll = new NameValueCollection ();
if (!String.IsNullOrEmpty (classKey))
coll.Add (name, classKey);
coll.Add (name, value);
}
bool GetAttributeLocalization (string value, out string resClass, out string resKey, out string resDefault)
{
resClass = null;
resKey = null;
resDefault = null;
if (String.IsNullOrEmpty (value))
return false;
string val = value.TrimStart (new char[] {' ', '\t'});
if (val.Length < 11 ||
String.Compare (val, 0, "$resources:", 0, 11, StringComparison.InvariantCultureIgnoreCase) != 0)
return false;
val = val.Substring (11);
if (val.Length == 0)
return false;
string[] parts = val.Split (',');
if (parts.Length < 2)
return false;
resClass = parts [0].Trim ();
resKey = parts [1].Trim ();
if (parts.Length == 3)
resDefault = parts [2];
else if (parts.Length > 3)
resDefault = String.Join (",", parts, 2, parts.Length - 2);
return true;
}
void CollectLocalizationInfo (XmlNode xmlNode, ref string title, ref string description,
ref NameValueCollection attributes,
ref NameValueCollection explicitResourceKeys)
{
string resClass;
string resKey;
string resDefault;
if (GetAttributeLocalization (title, out resClass, out resKey, out resDefault)) {
PutInCollection ("title", resClass, resKey, ref explicitResourceKeys);
title = resDefault;
}
if (GetAttributeLocalization (description, out resClass, out resKey, out resDefault)) {
PutInCollection ("description", resClass, resKey, ref explicitResourceKeys);
description = resDefault;
}
string value;
foreach (XmlNode att in xmlNode.Attributes) {
if (GetAttributeLocalization (att.Value, out resClass, out resKey, out resDefault)) {
PutInCollection (att.Name, resClass, resKey, ref explicitResourceKeys);
value = resDefault;
} else
value = att.Value;
PutInCollection (att.Name, value, ref attributes);
}
}
protected override void Clear ()
{
base.Clear ();
root = null;
ChildProviders.Clear ();
ChildProvidersPresent.Clear ();
}
protected virtual void Dispose (bool disposing)
{
if (disposing) {
foreach (FileSystemWatcher watcher in watchers)
watcher.Dispose ();
watchers = null;
}
}
public void Dispose ()
{
Dispose (true);
}
public override SiteMapNode FindSiteMapNode (string rawUrl)
{
string url = base.MapUrl (rawUrl);
SiteMapNode node = base.FindSiteMapNode (url);
if (node != null)
return node;
foreach (SiteMapProvider smp in ChildProviders) {
node = smp.FindSiteMapNode (url);
if (node != null)
return node;
}
return null;
}
public override SiteMapNode FindSiteMapNodeFromKey (string key)
{
SiteMapNode node = base.FindSiteMapNodeFromKey (key);
if (node != null)
return node;
foreach (SiteMapProvider smp in ChildProviders) {
node = smp.FindSiteMapNodeFromKey (key);
if (node != null)
return node;
}
return null;
}
public override void Initialize (string name, NameValueCollection attributes)
{
if (initialized)
throw new InvalidOperationException ("XmlSiteMapProvider cannot be initialized twice.");
initialized = true;
if (attributes != null) {
foreach (string key in attributes.AllKeys) {
switch (key) {
case "siteMapFile":
fileVirtualPath = base.MapUrl (attributes ["siteMapFile"]);
break;
case "description":
case "securityTrimmingEnabled":
break;
default:
throw new ConfigurationErrorsException ("The attribute '" + key + "' is unexpected in the configuration of the '" + name + "' provider.");
}
}
}
base.Initialize (name, attributes != null ? attributes : new NameValueCollection ());
}
void CreateWatcher (string file)
{
var watcher = new FileSystemWatcher ();
watcher.NotifyFilter |= NotifyFilters.Size;
watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
watcher.Filter = Path.GetFileName (file);
watcher.Changed += new FileSystemEventHandler (OnFileChanged);
watcher.EnableRaisingEvents = true;
if (watchers == null)
watchers = new List <FileSystemWatcher> ();
watchers.Add (watcher);
}
protected override void RemoveNode (SiteMapNode node)
{
base.RemoveNode (node);
}
[MonoTODO ("Not implemented")]
protected virtual void RemoveProvider (string providerName)
{
throw new NotImplementedException ();
}
void OnFileChanged (object sender, FileSystemEventArgs args)
{
Clear ();
}
public override SiteMapNode RootNode {
get {
BuildSiteMap ();
return root;
}
}
protected internal override SiteMapNode GetRootNodeCore ()
{
return BuildSiteMap ();
}
}
}
|