using System;
using System.Xml;
using System.Xml.Serialization;
using SharpVectors.Dom;
using SharpVectors.Dom.Stylesheets;
#if TEST
using NUnit.Framework;
#endif
namespace SharpVectors.Dom.Css{
public delegate void NodeChangeHandler(Object src, XmlNodeChangedEventArgs args);
public delegate void CssChangeHandler();
public class CssXmlElement : Element, IElementCssInlineStyle
{
#region Constructors
protected internal CssXmlElement(string prefix, string localname, string ns, CssXmlDocument doc) : base(prefix, localname, ns, doc)
{
}
#endregion
#region Style attribute
private ICssStyleDeclaration style;
public ICssStyleDeclaration Style
{
get
{
if(style == null)
{
style = new CssStyleDeclaration(GetAttribute("style", String.Empty), null, false, CssStyleSheetType.Inline);
}
return style;
}
}
#endregion
#region GetComputedStyle
protected ICssStyleDeclaration cachedCSD;
public virtual ICssStyleDeclaration GetComputedStyle(string pseudoElt)
{
if(cachedCSD == null)
{
CssCollectedStyleDeclaration csd = new CssCollectedStyleDeclaration(this);
MediaList currentMedia = OwnerDocument.Media;
if(OwnerDocument.UserAgentStyleSheet != null)
{
OwnerDocument.UserAgentStyleSheet.GetStylesForElement(this, pseudoElt, currentMedia, csd);
}
((StyleSheetList)OwnerDocument.StyleSheets).GetStylesForElement(this, pseudoElt, csd);
((CssStyleDeclaration)Style).GetStylesForElement(csd, 0);
if(OwnerDocument.UserStyleSheet != null)
{
OwnerDocument.UserStyleSheet.GetStylesForElement(this, pseudoElt, currentMedia, csd);
}
cachedCSD = csd;
}
return cachedCSD;
}
public virtual string GetComputedStringValue(string propertyName, string pseudoElt)
{
ICssStyleDeclaration csd = GetComputedStyle(pseudoElt);
return csd.GetPropertyValue(propertyName);
}
public virtual ICssValue GetComputedCssValue(string propertyName, string pseudoElt)
{
ICssStyleDeclaration csd = GetComputedStyle(pseudoElt);
return csd.GetPropertyCssValue(propertyName);
}
#endregion
#region OwnerDocument
public new CssXmlDocument OwnerDocument
{
get
{
return (CssXmlDocument)base.OwnerDocument;
}
}
#endregion
#region Supports()
public override bool Supports(string feature, string version)
{
if((feature == "StyleSheets" || feature == "CSS" ) && version == "2.0")
{
return true;
}
return base.Supports(feature, version);
}
#endregion
#region Update handling
public virtual void CssInvalidate()
{
// TODO: why is this being called during load?
foreach(XmlNode child in ChildNodes)
{
if (child is CssXmlElement)
((CssXmlElement)child).CssInvalidate();
}
// Kill the cache
cachedCSD = null;
// Notify
FireCssChange();
}
/// <summary>
/// Called when this element is changing in one of the following ways
/// <list type="">
/// <item>Text child added/removed/changed</item>
/// <item>Element moved in the tree</item>
/// </list>
/// </summary>
public virtual void ElementChange(Object src, XmlNodeChangedEventArgs args)
{
// Invalidate the CSS, the cascade for the CSS heirarchy will need to be recomputed
CssInvalidate();
// Notify any listeners
FireElementChange(src, args);
FireParentNodeChange(src, args, false);
FireChildNodeChange(src, args, false);
}
/// <summary>
/// Called when any parent element is changing. If an element is moved the CSS heirarchy for that element
/// will need to change.
/// </summary>
public virtual void ParentNodeChange(Object src, XmlNodeChangedEventArgs args)
{
FireParentNodeChange(src, args, true);
}
/// <summary>
/// Called when any attribute is changing. This is typically triggered by calls to
/// setAttribute() and should only be called from the CssXmlDocument.
/// </summary>
/// <see cref="CssXmlDocument"/>
public virtual void AttributeChange(Object src, XmlNodeChangedEventArgs args)
{
// Invalidate the CSS, the cascade for the CSS heirarchy will need to be recomputed
// We do this before and after the change because we need to invalidate the old and new locations
CssInvalidate();
XmlAttribute attribute = src as XmlAttribute;
if(attribute != null)
{
HandleAttributeChange(attribute);
}
// Notify any listeners
FireAttributeChange(src, args);
FireParentNodeChange(src, args, false);
FireChildNodeChange(src, args, false);
// Invalidate the CSS, the cascade for the CSS heirarchy will need to be recomputed
CssInvalidate();
}
/// <summary>
/// This function allows each element to handle it's own behaviors for
/// attribute changing. By default, the cached computed style is invalidated
/// because most attributes refer to style properties.
/// </summary>
/// <param name="attribute">The attribute that is changing.</param>
public virtual void HandleAttributeChange(XmlAttribute attribute)
{
if(attribute.NamespaceURI.Length == 0)
{
switch (attribute.LocalName)
{
case "style":
style = null;
break;
}
}
}
/// <summary>
/// Called when any child node is changing. If an element is moved the CSS heirarchy for that element
/// will need to change. This is mainly useful when one of the child nodes parent is a
/// referenced node (for example in a <use> element.
/// </summary>
public virtual void ChildNodeChange(Object src, XmlNodeChangedEventArgs args)
{
FireChildNodeChange(src, args, true);
}
protected void FireCssChange()
{
if (cssChangeHandler != null)
{
cssChangeHandler();
}
}
protected void FireAttributeChange(Object src, XmlNodeChangedEventArgs args)
{
if (attributeChangeHandler != null)
{
attributeChangeHandler(src, args);
}
}
protected void FireElementChange(Object src, XmlNodeChangedEventArgs args)
{
if (elementChangeHandler != null)
{
elementChangeHandler(src, args);
}
}
protected void FireParentNodeChange(Object src, XmlNodeChangedEventArgs args, bool fireEvent)
{
if (fireEvent && parentNodeChangeHandler != null)
{
parentNodeChangeHandler(src, args);
}
foreach(XmlNode child in ChildNodes)
{
if (child.NodeType != XmlNodeType.Element) continue;
CssXmlElement cssChild = child as CssXmlElement;
if(cssChild != null)
{
cssChild.ParentNodeChange(src, args);
}
}
}
protected void FireChildNodeChange(Object src, XmlNodeChangedEventArgs args, bool fireEvent)
{
if (fireEvent && childNodeChangeHandler != null)
{
childNodeChangeHandler(src, args);
}
CssXmlElement cssParent = ParentNode as CssXmlElement;
if(cssParent != null)
{
cssParent.ChildNodeChange(src, args);
}
}
public virtual event NodeChangeHandler attributeChangeHandler;
public virtual event NodeChangeHandler elementChangeHandler;
public virtual event NodeChangeHandler parentNodeChangeHandler;
public virtual event NodeChangeHandler childNodeChangeHandler;
public virtual event CssChangeHandler cssChangeHandler;
#endregion
#region Unit tests
#if TEST
[TestFixture]
public class CssXmlElementTests
{
protected virtual XmlElement getElm(string content, string style, string localname)
{
return TestUtil.GetXmlElement(content, style, localname);
}
protected virtual Type elmType
{
get
{
return typeof(CssXmlElement);
}
}
[Test]
public virtual void TestTypeFromDocument()
{
CssXmlDocument doc = new CssXmlDocument();
doc.LoadXml("<dummy />");
XmlElement elm = doc.DocumentElement;
Assert.AreEqual(elmType, elm.GetType());
}
[Test]
public virtual void TestTypeFromCreateElement()
{
CssXmlDocument doc = new CssXmlDocument();
XmlElement elm = doc.CreateElement("", "dummy", "");
Assert.AreEqual(elm.GetType(), elmType);
}
private Object lastSrc;
private XmlNode lastNode;
private XmlNodeChangedAction lastAction;
private void clearAllLast()
{
lastSrc = null;
lastNode = null;
lastAction = 0;
}
private void elementChangeEvent(Object src, XmlNodeChangedEventArgs args)
{
lastSrc = src;
lastNode = args.Node;
lastAction = args.Action;
}
/*[Test]
public void TestElementChangeNewChild()
{
clearAllLast();
CssXmlElement elm = getElm("<dummy />", "", "dummy") as CssXmlElement;
//elm.ElementChange += new ElementChangeHandler(elementChangeEvent);
elm.AppendChild(elm.OwnerDocument.CreateElement("dd"));
Console.WriteLine("lastSrc: " + lastSrc);
Console.WriteLine("lastAction: " + lastAction);
Assert.AreEqual(elm, lastNode);
}*/
#region Style attribute tests
[Test]
public void TestStyleNoStyleAttr()
{
CssXmlElement elm = getElm("<a />", "", "a") as CssXmlElement;
CssStyleDeclaration csd = (CssStyleDeclaration)elm.Style;
Assert.AreEqual("", csd.CssText);
Assert.AreEqual(0, csd.Length);
Assert.AreEqual(CssStyleSheetType.Inline, csd.Origin);
}
[Test]
public void TestStyleEmptyStyleAttr()
{
CssXmlElement elm = getElm("<a style='' />", "", "a") as CssXmlElement;
CssStyleDeclaration csd = (CssStyleDeclaration)elm.Style;
Assert.AreEqual("", csd.CssText);
Assert.AreEqual(0, csd.Length);
Assert.AreEqual(CssStyleSheetType.Inline, csd.Origin);
}
[Test]
public void TestStyleSingle()
{
CssXmlElement elm = getElm("<a style='foo:bar' />", "", "a") as CssXmlElement;
CssStyleDeclaration csd = (CssStyleDeclaration)elm.Style;
Assert.AreEqual("foo:bar;", csd.CssText);
Assert.AreEqual(1, csd.Length);
Assert.AreEqual("foo", csd[0]);
Assert.AreEqual("bar", csd.GetPropertyValue("foo"));
Assert.AreEqual(CssStyleSheetType.Inline, csd.Origin);
}
[Test]
public void TestStyleMultiple()
{
CssXmlElement elm = getElm("<a style='foo:bar; kalle:roffe' />", "", "a") as CssXmlElement;
CssStyleDeclaration csd = (CssStyleDeclaration)elm.Style;
if(!csd.CssText.Equals("foo:bar;kalle:roffe;") &&
!csd.CssText.Equals("kalle:roffe;foo:bar;"))
{
Assert.Fail();
}
Assert.AreEqual(2, csd.Length);
Assert.AreEqual("bar", csd.GetPropertyValue("foo"));
Assert.AreEqual("roffe", csd.GetPropertyValue("kalle"));
Assert.AreEqual(CssStyleSheetType.Inline, csd.Origin);
}
[Test]
public void TestStyleMultipleWithSame()
{
CssXmlElement elm = getElm("<a style='foo:bar; kalle:roffe;foo:newvalue' />", "", "a") as CssXmlElement;
ICssStyleDeclaration csd = elm.Style;
if(!csd.CssText.Equals("kalle:roffe;foo:newvalue;") &&
!csd.CssText.Equals("foo:newvalue;kalle:roffe;"))
{
Assert.Fail();
}
Assert.AreEqual(2, csd.Length);
Assert.AreEqual("newvalue", csd.GetPropertyValue("foo"));
}
[Test]
public void TestStyleUpdate()
{
CssXmlElement elm = getElm("<a style='foo:bar' />", "", "a") as CssXmlElement;
Assert.AreEqual("foo:bar;", elm.Style.CssText);
elm.SetAttribute("style", "run:ar");
Assert.AreEqual("run:ar;", elm.Style.CssText);
}
[Test]
public void TestStyleRemove()
{
CssXmlElement elm = getElm("<a style='foo:bar' />", "", "a") as CssXmlElement;
Assert.AreEqual("foo:bar;", elm.Style.CssText);
elm.RemoveAttribute("style");
Assert.AreEqual("", elm.Style.CssText);
}
[Test]
public void TestStyleCreate()
{
CssXmlElement elm = getElm("<a />", "", "a") as CssXmlElement;
Assert.AreEqual("", elm.Style.CssText);
elm.SetAttribute("style", "run:ar");
Assert.AreEqual("run:ar;", elm.Style.CssText);
}
#endregion
}
#endif
#endregion
}
}
|