Files
Umbraco-CMS/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNode.cs
agrath@gmail.com 71c5e35443 Added DynamicNodeWalker and implementation on DynamicNode and DynamicNodeList
DynamicNodeWalker is our secret weapon in the fight against the Rebel XSLT Alliance
Navigate nodes by calling Up(), Down(), Next() and Previous() on them
Next(1) will jump two items along within the current list, whereas Next() will walk by one within the list
Previous(1) will move two items backwards within the current list
Up() is a special wrapper around .Parent which has an overload .Up(int) to replace @Model.Parent.Parent.Parent... [.Up(2)]
Down() will take you to the first Child item and is equivilent to .Children.First(), use .Down(1) to replace .Children.First().Children
If one of the NodeWalker functions fails to find a node at the requested position, it will return null
2011-02-24 17:19:50 -13:00

703 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using umbraco.interfaces;
using System.Collections;
using System.Reflection;
using umbraco.cms.businesslogic.web;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.property;
using umbraco.BusinessLogic;
using umbraco.DataLayer;
using umbraco.cms.businesslogic;
using System.Xml;
using System.Xml.Linq;
namespace umbraco.MacroEngines
{
public class DynamicNode : DynamicObject
{
internal DynamicNodeList ownerList;
internal readonly INode n;
public DynamicNode(INode n)
{
if (n != null)
this.n = n;
else
throw new ArgumentNullException("n", "A node must be provided to make a dynamic instance");
}
public DynamicNode(int NodeId)
{
this.n = new NodeFactory.Node(NodeId);
}
public DynamicNode(string NodeId)
{
int iNodeId = 0;
if (int.TryParse(NodeId, out iNodeId))
{
this.n = new NodeFactory.Node(iNodeId);
}
}
public DynamicNode(object NodeId)
{
int iNodeId = 0;
if (int.TryParse(string.Format("{0}", NodeId), out iNodeId))
{
this.n = new NodeFactory.Node(iNodeId);
}
}
public DynamicNode()
{
//Empty constructor for a special case with Generic Methods
}
public DynamicNode Up()
{
return DynamicNodeWalker.Up(this);
}
public DynamicNode Up(int number)
{
return DynamicNodeWalker.Up(this, number);
}
public DynamicNode Down()
{
return DynamicNodeWalker.Down(this);
}
public DynamicNode Down(int number)
{
return DynamicNodeWalker.Down(this, number);
}
public DynamicNode Next()
{
return DynamicNodeWalker.Next(this);
}
public DynamicNode Next(int number)
{
return DynamicNodeWalker.Next(this, number);
}
public DynamicNode Previous()
{
return DynamicNodeWalker.Previous(this);
}
public DynamicNode Previous(int number)
{
return DynamicNodeWalker.Previous(this, number);
}
public DynamicNodeList GetChildrenAsList
{
get
{
List<INode> children = n.ChildrenAsList;
//testing
if (children.Count == 0 && n.Id == 0)
{
return new DynamicNodeList(new List<INode> { this.n });
}
return new DynamicNodeList(n.ChildrenAsList);
}
}
public DynamicNodeList XPath(string xPath)
{
//if this DN was initialized with an underlying NodeFactory.Node
if (n != null)
{
//get the underlying xml content
XmlDocument doc = umbraco.content.Instance.XmlContent;
if (doc != null)
{
//get n as a XmlNode (to be used as the context point for the xpath)
//rather than just applying the xPath to the root node, this lets us use .. etc from the DynamicNode point
//in test mode, n.Id is 0, let this always succeed
if (n.Id == 0)
{
List<DynamicNode> selfList = new List<DynamicNode>() { this };
return new DynamicNodeList(selfList);
}
XmlNode node = doc.SelectSingleNode(string.Format("//*[@id='{0}']", n.Id));
if (node != null)
{
//got the current node (within the XmlContent instance)
XmlNodeList nodes = node.SelectNodes(xPath);
if (nodes.Count > 0)
{
//we got some resulting nodes
List<NodeFactory.Node> nodeFactoryNodeList = new List<NodeFactory.Node>();
//attempt to convert each node in the set to a NodeFactory.Node
foreach (XmlNode nodeXmlNode in nodes)
{
try
{
nodeFactoryNodeList.Add(new NodeFactory.Node(nodeXmlNode));
}
catch (Exception) { } //swallow the exceptions - the returned nodes might not be full nodes, e.g. property
}
//Wanted to do this, but because we return DynamicNodeList here, the only
//common parent class is DynamicObject
//maybe some future refactoring will solve this?
//if (nodeFactoryNodeList.Count == 0)
//{
// //if the xpath resulted in a node set, but none of them could be converted to NodeFactory.Node
// XElement xElement = XElement.Parse(node.OuterXml);
// //return
// return new DynamicXml(xElement);
//}
//convert the NodeFactory nodelist to IEnumerable<DynamicNode> and return it as a DynamicNodeList
return new DynamicNodeList(nodeFactoryNodeList.ConvertAll(nfNode => new DynamicNode(nfNode)));
}
else
{
throw new NullReferenceException("XPath returned no nodes");
}
}
else
{
throw new NullReferenceException("Couldn't locate the DynamicNode within the XmlContent");
}
}
else
{
throw new NullReferenceException("umbraco.content.Instance.XmlContent is null");
}
}
else
{
throw new NullReferenceException("DynamicNode wasn't initialized with an underlying NodeFactory.Node");
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;
if (name == "ChildrenAsList" || name == "Children")
{
result = GetChildrenAsList;
return true;
}
if (n != null)
{
var data = n.GetProperty(name);
// check for nicer support of Pascal Casing EVEN if alias is camelCasing:
if (data == null && name.Substring(0, 1).ToUpper() == name.Substring(0, 1))
{
data = n.GetProperty(name.Substring(0, 1).ToLower() + name.Substring((1)));
}
if (data != null)
{
result = data.Value;
//special casing for true/false properties
//int/decimal are handled by ConvertPropertyValueByDataType
//fallback is stringT
Guid dataType = ContentType.GetDataType(n.NodeTypeAlias, data.Alias);
//convert the string value to a known type
return ConvertPropertyValueByDataType(ref result, name, dataType);
}
//check if the alias is that of a child type
var typeChildren = n.ChildrenAsList
.Where(x => MakePluralName(x.NodeTypeAlias) == name || x.NodeTypeAlias == name);
if (typeChildren.Any())
{
result = new DynamicNodeList(typeChildren);
return true;
}
try
{
result = n.GetType().InvokeMember(binder.Name,
System.Reflection.BindingFlags.GetProperty |
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic,
null,
n,
null);
return true;
}
catch
{
//result = null;
//return false;
}
}
//if property access, type lookup and member invoke all failed
//at this point, we're going to return null
//instead, we return an empty list
//this will let things like Model.ChildItem work and return nothing instead of crashing
result = new DynamicNodeList(new List<INode>());
//changed this to a return true because it breaks testing when using .Children().Random().propertyName
return true;
}
private bool ConvertPropertyValueByDataType(ref object result, string name, Guid dataType)
{
//the resulting property is a string, but to support some of the nice linq stuff in .Where
//we should really check some more types
umbraco.editorControls.yesno.YesNoDataType yesnoType = new editorControls.yesno.YesNoDataType();
//boolean
if (dataType == yesnoType.Id)
{
bool parseResult;
if (result.ToString() == "") result = "0";
if (Boolean.TryParse(result.ToString().Replace("1", "true").Replace("0", "false"), out parseResult))
{
result = parseResult;
}
return true;
}
//integer
int iResult = 0;
if (int.TryParse(string.Format("{0}", result), out iResult))
{
result = iResult;
return true;
}
//decimal
decimal dResult = 0;
if (decimal.TryParse(string.Format("{0}", result), out dResult))
{
result = dResult;
return true;
}
//date
DateTime dtResult = DateTime.MinValue;
if (DateTime.TryParse(string.Format("{0}", result), out dtResult))
{
result = dtResult;
return true;
}
if (string.Equals("true", string.Format("{0}", result), StringComparison.CurrentCultureIgnoreCase))
{
result = true;
return true;
}
if (string.Equals("false", string.Format("{0}", result), StringComparison.CurrentCultureIgnoreCase))
{
result = false;
return true;
}
if (result != null)
{
string sResult = string.Format("{0}", result).Trim();
//a really rough check to see if this may be valid xml
if (sResult.StartsWith("<") && sResult.EndsWith(">") && sResult.Contains("/"))
{
try
{
XElement e = XElement.Parse(sResult, LoadOptions.None);
if (e != null)
{
//check that the document element is not one of the disallowed elements
//allows RTE to still return as html if it's valid xhtml
string documentElement = e.Name.LocalName;
if (!UmbracoSettings.NotDynamicXmlDocumentElements.Any(tag =>
string.Equals(tag, documentElement, StringComparison.CurrentCultureIgnoreCase)))
{
result = new DynamicXml(e);
return true;
}
else
{
//we will just return this as a string
return true;
}
}
}
catch (Exception)
{
//we will just return this as a string
return true;
}
}
}
return true;
}
public DynamicMedia Media(string propertyAlias)
{
if (n != null)
{
IProperty prop = n.GetProperty(propertyAlias);
if (prop != null)
{
int mediaNodeId;
if (int.TryParse(prop.Value, out mediaNodeId))
{
return new DynamicMedia(mediaNodeId);
}
}
return null;
}
return null;
}
public bool IsProtected()
{
if (n != null)
{
return umbraco.library.IsProtected(n.Id, n.Path);
}
return false;
}
public bool HasAccess()
{
if (n != null)
{
return umbraco.library.HasAccess(n.Id, n.Path);
}
return true;
}
public string Media(string propertyAlias, string mediaPropertyAlias)
{
if (n != null)
{
IProperty prop = n.GetProperty(propertyAlias);
if (prop == null && propertyAlias.Substring(0, 1).ToUpper() == propertyAlias.Substring(0, 1))
{
prop = n.GetProperty(propertyAlias.Substring(0, 1).ToLower() + propertyAlias.Substring((1)));
}
if (prop != null)
{
int mediaNodeId;
if (int.TryParse(prop.Value, out mediaNodeId))
{
umbraco.cms.businesslogic.media.Media media = new cms.businesslogic.media.Media(mediaNodeId);
if (media != null)
{
Property mprop = media.getProperty(mediaPropertyAlias);
// check for nicer support of Pascal Casing EVEN if alias is camelCasing:
if (prop == null && mediaPropertyAlias.Substring(0, 1).ToUpper() == mediaPropertyAlias.Substring(0, 1))
{
mprop = media.getProperty(mediaPropertyAlias.Substring(0, 1).ToLower() + mediaPropertyAlias.Substring((1)));
}
if (mprop != null)
{
return string.Format("{0}", mprop.Value);
}
}
}
}
}
return null;
}
//this is from SqlMetal and just makes it a bit of fun to allow pluralisation
private static string MakePluralName(string name)
{
if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("ss", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase)))
{
name = name + "es";
return name;
}
if ((name.EndsWith("y", StringComparison.OrdinalIgnoreCase) && (name.Length > 1)) && !IsVowel(name[name.Length - 2]))
{
name = name.Remove(name.Length - 1, 1);
name = name + "ies";
return name;
}
if (!name.EndsWith("s", StringComparison.OrdinalIgnoreCase))
{
name = name + "s";
}
return name;
}
private static bool IsVowel(char c)
{
switch (c)
{
case 'O':
case 'U':
case 'Y':
case 'A':
case 'E':
case 'I':
case 'o':
case 'u':
case 'y':
case 'a':
case 'e':
case 'i':
return true;
}
return false;
}
public DynamicNode AncestorOrSelf()
{
return AncestorOrSelf(node => node.Level == 1);
}
public DynamicNode AncestorOrSelf(Func<DynamicNode, bool> func)
{
var node = this;
while (node != null)
{
if (func(node)) return node;
DynamicNode parent = node.Parent;
if (parent != null)
{
if (this != parent)
{
node = parent;
}
else
{
return node;
}
}
else
{
return node;
}
}
return node;
}
public DynamicNodeList AncestorsOrSelf
{
get
{
List<DynamicNode> ancestorList = new List<DynamicNode>();
var node = this;
ancestorList.Add(node);
while (node != null)
{
if (node.Level == 1) break;
DynamicNode parent = node.Parent;
if (parent != null)
{
if (this != parent)
{
node = parent;
ancestorList.Add(node);
}
else
{
break;
}
}
else
{
break;
}
}
ancestorList.Reverse();
return new DynamicNodeList(ancestorList);
}
}
public DynamicNodeList Descendants
{
get
{
var flattenedNodes = this.n.ChildrenAsList.Map(p => true, (INode n) => { return n.ChildrenAsList; });
return new DynamicNodeList(flattenedNodes.ToList().ConvertAll(iNode => new DynamicNode(iNode)));
}
}
public DynamicNodeList DescendantsOrSelf
{
get
{
if (this.n != null)
{
var thisNode = new List<INode>();
thisNode.Add(this.n);
var flattenedNodes = this.n.ChildrenAsList.Map(p => true, (INode n) => { return n.ChildrenAsList; });
return new DynamicNodeList(thisNode.Concat(flattenedNodes).ToList().ConvertAll(iNode => new DynamicNode(iNode)));
}
return new DynamicNodeList(new List<INode>());
}
}
public DynamicNodeList Ancestors
{
get
{
DynamicNodeList innerList = AncestorsOrSelf;
return new DynamicNodeList(innerList.Items.Skip(1));
}
}
public DynamicNode Parent
{
get
{
if (n == null)
{
return null;
}
if (n.Parent != null)
{
return new DynamicNode(n.Parent);
}
if (n != null && n.Id == 0)
{
return this;
}
return null;
}
}
public DynamicNode NodeById(int Id)
{
return new DynamicNode(Id);
}
public DynamicNode NodeById(string Id)
{
return new DynamicNode(Id);
}
public DynamicNode NodeById(object Id)
{
return new DynamicNode(Id);
}
public DynamicMedia MediaById(int Id)
{
return new DynamicMedia(Id);
}
public DynamicMedia MediaById(string Id)
{
return new DynamicMedia(Id);
}
public DynamicMedia MediaById(object Id)
{
return new DynamicMedia(Id);
}
public int Id
{
get { if (n == null) return 0; return n.Id; }
}
public int template
{
get { if (n == null) return 0; return n.template; }
}
public int SortOrder
{
get { if (n == null) return 0; return n.SortOrder; }
}
public string Name
{
get { if (n == null) return null; return n.Name; }
}
public bool Visible
{
get;
set;
}
public string Url
{
get { if (n == null) return null; return n.Url; }
}
public string UrlName
{
get { if (n == null) return null; return n.UrlName; }
}
public string NodeTypeAlias
{
get { if (n == null) return null; return n.NodeTypeAlias; }
}
public string WriterName
{
get { if (n == null) return null; return n.WriterName; }
}
public string CreatorName
{
get { if (n == null) return null; return n.CreatorName; }
}
public int WriterID
{
get { if (n == null) return 0; return n.WriterID; }
}
public int CreatorID
{
get { if (n == null) return 0; return n.CreatorID; }
}
public string Path
{
get { return n.Path; }
}
public DateTime CreateDate
{
get { if (n == null) return DateTime.MinValue; return n.CreateDate; }
}
public DateTime UpdateDate
{
get { if (n == null) return DateTime.MinValue; return n.UpdateDate; }
}
public Guid Version
{
get { if (n == null) return Guid.Empty; return n.Version; }
}
public string NiceUrl
{
get { if (n == null) return null; return n.NiceUrl; }
}
public int Level
{
get { if (n == null) return 0; return n.Level; }
}
public List<IProperty> PropertiesAsList
{
get { if (n == null) return null; return n.PropertiesAsList; }
}
public List<INode> ChildrenAsList
{
get { if (n == null) return null; return n.ChildrenAsList; }
}
public IProperty GetProperty(string alias)
{
if (n == null) return null;
return n.GetProperty(alias);
}
public System.Data.DataTable ChildrenAsTable()
{
if (n == null) return null;
return n.ChildrenAsTable();
}
public System.Data.DataTable ChildrenAsTable(string nodeTypeAliasFilter)
{
if (n == null) return null;
return n.ChildrenAsTable(nodeTypeAliasFilter);
}
}
}