//All code is copyright � 2007, InsomniaSoftware | Manuel Then, All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using InsomniaSoftware.Server.Tools;
namespace InsomniaSoftware.Server.Sources
{
/// <summary>
/// A source which enables you to construct html structures. The final html source is build by this class.
/// </summary>
public class HtmlSource : Source
{
/// <summary>
/// Gets the page's root (html-element)
/// </summary>
public HtmlNode root
{
get
{
return _root;
}
}
HtmlNode _root;
/// <summary>
/// Gets the page's head node
/// </summary>
public HtmlNode head
{
get
{
return _head;
}
}
HtmlNode _head;
/// <summary>
/// Gets the page's body node
/// </summary>
public HtmlNode body
{
get
{
return _body;
}
}
HtmlNode _body;
/// <summary>
/// Gets/sets the node which has been added most recently. curNode is very useful to build complex page structures without saving every node handle. Call MoveCurNodeLevelUp() to go to its parent.
/// </summary>
public HtmlNode curNode;
MemoryStream dataStream;
internal bool dataChanged = true;
SourceInfo sourceInfo = null;
/// <summary>
/// Initializes a new HtmlSource
/// </summary>
/// <param name="pageTitle">Title of the page to be created</param>
public HtmlSource(string pageTitle)
{
this._root = new HtmlNode("html", this);
_head = _root.AddSubNode(new HtmlNode("head"));
_head.AddSubNode(new HtmlNode("title", pageTitle));
_body = _root.AddSubNode(new HtmlNode("body"));
curNode = _body;
}
/// <summary>
/// Initializes a new HtmlSource
/// </summary>
/// <param name="pageTitle">Title of the page to be created</param>
/// <param name="sourceInfo">SourceInfo for this source</param>
public HtmlSource(string pageTitle, SourceInfo sourceInfo) : this(pageTitle)
{
this.sourceInfo = sourceInfo;
}
/// <summary>
/// Rebuilds the data stream if necessarry
/// </summary>
void BuildDataStream()
{
if (dataStream == null || dataChanged)
{
dataStream = new MemoryStream(GetSubNodesLength(_root));
BuildDataStreamRecursion(dataStream, _root);
dataChanged = false;
}
}
void BuildDataStreamRecursion(MemoryStream dataStream, HtmlNode node)
{
string openTagString = "";
if (node.name.Length > 0)
{
openTagString = "<" + node.name;
LinkedListNode<KeyValuePair<string, string>> curAttribute = node.attributes.First;
while (curAttribute != null)
{
openTagString += " " + curAttribute.Value.Key + "=\"" + curAttribute.Value.Value + "\"";
curAttribute = curAttribute.Next;
}
openTagString += ">";
}
openTagString += HttpTools.EncodeHtmlString(node.value);
byte[] buffer = Encoding.ASCII.GetBytes(openTagString);
dataStream.Write(buffer, 0, buffer.Length);
buffer = null;
if (node.name.Length > 0)
{
if (node.hasEndTag)
{
LinkedListNode<HtmlNode> curNode = node.childNodes.First;
while (curNode != null)
{
BuildDataStreamRecursion(dataStream, curNode.Value);
curNode = curNode.Next;
}
buffer = Encoding.ASCII.GetBytes("</" + node.name + ">");
dataStream.Write(buffer, 0, buffer.Length);
}
}
}
int GetSubNodesLength(HtmlNode node)
{
int length = node.approximateLength;
LinkedListNode<HtmlNode> curNode = node.childNodes.First;
while (curNode != null)
{
length += GetSubNodesLength(curNode.Value);
curNode = curNode.Next;
}
return length;
}
/// <summary>
/// Sets the curNode handle to its parent node
/// </summary>
public void MoveCurNodeLevelUp()
{
if (curNode.parentNode != null)
curNode = curNode.parentNode;
}
/// <summary>
/// Gets the length of the source's content
/// </summary>
/// <returns>Content length</returns>
public override int GetLength()
{
BuildDataStream();
return (int)dataStream.Length;
}
/// <summary>
/// Moves current the position withing the binary data
/// </summary>
/// <param name="offset">Offset by which the location will move</param>
/// <param name="origin">Position to start moving at</param>
/// <returns>The absolute position within the data</returns>
public override int Seek(int offset, SeekOrigin origin)
{
return (int)dataStream.Seek(offset, origin);
}
/// <summary>
/// Reads a specified number of bytes into the given buffer
/// </summary>
/// <param name="buffer">Buffer to be filled</param>
/// <param name="offset">First position in the buffer to be filled</param>
/// <param name="count">Number of bytes to be read</param>
/// <returns>The actual number of bytes read</returns>
public override int Read(ref byte[] buffer, int offset, int count)
{
return dataStream.Read(buffer, offset, count);
}
/// <summary>
/// Gets the content type of this source's content
/// </summary>
/// <returns>ContentType instance</returns>
public override ContentType GetContentType()
{
return ContentType.FromType(ContentType.Type.Html);
}
/// <summary>
/// Gets the SourceInfo
/// </summary>
/// <returns>SourceInfo of this StreamSource</returns>
public override SourceInfo GetInformation()
{
if (sourceInfo != null)
return sourceInfo;
else
return new SourceInfo();
}
}
/// <summary>
/// Class that represents a single element node within an html page
/// </summary>
public class HtmlNode
{
internal int approximateLength
{
get
{
return _approximateLength;
}
}
int _approximateLength = 5;
/// <summary>
/// Gets/sets the name of this node
/// </summary>
public string name
{
get
{
return _nodeName;
}
set
{
lock (this)
{
_approximateLength -= _nodeName.Length * (hasEndTag ? 2 : 1);
_nodeName = value;
_approximateLength += _nodeName.Length * (hasEndTag ? 2 : 1);
if (belongingSource != null)
belongingSource.dataChanged = true;
}
}
}
string _nodeName = "";
internal LinkedList<KeyValuePair<string, string>> attributes = new LinkedList<KeyValuePair<string, string>>();
/// <summary>
/// Gets/sets the value of this node. Remark: Nodes, containing sub nodes must not have a value.
/// </summary>
public string value
{
get
{
return _value;
}
set
{
lock (this)
{
if (childNodes.Count > 0)
throw new Exception("Nodes with sub nodes can not have a value");
if (!hasEndTag)
throw new Exception("Nodes without an end tag must not have a value");
_approximateLength -= _value.Length;
_value = value;
_approximateLength += _value.Length;
if (belongingSource != null)
belongingSource.dataChanged = true;
}
}
}
string _value = "";
internal LinkedList<HtmlNode> childNodes = new LinkedList<HtmlNode>();
internal bool hasEndTag;
HtmlSource belongingSource;
/// <summary>
/// Gets this node's parent node
/// </summary>
public HtmlNode parentNode
{
get
{
return _parentNode;
}
}
HtmlNode _parentNode;
internal HtmlNode(string name, HtmlSource belongingSource)
: this(name, true)
{
this.belongingSource = belongingSource;
}
/// <summary>
/// Initializes a new HtmlNode
/// </summary>
/// <param name="name">Node's name</param>
public HtmlNode(string name)
: this(name, true)
{ }
/// <summary>
/// Initializes a new HtmlNode
/// </summary>
/// <param name="name">Node's name</param>
/// <param name="hasEndTag">Specifies whether this node has an end tag. For example "br" or "img" do not have one. Remark: Nodes without an end tag must not have a value or sub nodes.</param>
public HtmlNode(string name, bool hasEndTag)
{
if (name == null)
throw new Exception("Node's name must not be null");
this.name = name;
this.hasEndTag = hasEndTag;
}
/// <summary>
/// Initializes a new HtmlNode
/// </summary>
/// <param name="name">Node's name</param>
/// <param name="value">Node's value. Remark: Nodes, containing sub nodes must not have a value.</param>
public HtmlNode(string name, string value)
: this(name)
{
this.value = value;
}
/// <summary>
/// Adds a new sub node
/// </summary>
/// <param name="newSubNode">Node to be added</param>
/// <returns>The added node</returns>
public HtmlNode AddSubNode(HtmlNode newSubNode)
{
AddSubNodeValidate();
if (newSubNode == null)
throw new Exception("New sub node must not be null");
childNodes.AddLast(newSubNode);
NewNodeUpdates(newSubNode);
return newSubNode;
}
/// <summary>
/// Adds a new sub node
/// </summary>
/// <param name="nodeName">Element name of the new node. Does the same as: AddSubNode(new HtmlNode(nodeName))</param>
/// <returns>The added node</returns>
public HtmlNode AddSubNode(string nodeName)
{
return AddSubNode(new HtmlNode(nodeName));
}
/// <summary>
/// Adds a new sub node
/// </summary>
/// <param name="nodeName">Element name of the new node. Does the same as: AddSubNode(new HtmlNode(nodeName))</param>
/// <param name="hasEndTag">Specifies whether the new node has an end tag</param>
/// <returns>The added node</returns>
public HtmlNode AddSubNode(string nodeName, bool hasEndTag)
{
return AddSubNode(new HtmlNode(nodeName, hasEndTag));
}
/// <summary>
/// Adds a new sub node
/// </summary>
/// <param name="nodeName">Element name of the new node. Does the same as: AddSubNode(new HtmlNode(nodeName))</param>
/// <param name="value">New node's value</param>
/// <returns>The added node</returns>
public HtmlNode AddSubNode(string nodeName, string value)
{
return AddSubNode(new HtmlNode(nodeName, value));
}
/// <summary>
/// Adds a new sub node after an existing one
/// </summary>
/// <param name="newSubNode">Node to be added</param>
/// <param name="nodeAfterWhichItWillBeInserted">Node, after which the new one is inserted</param>
/// <returns>The added node</returns>
public HtmlNode AddSubNodeAfter(HtmlNode newSubNode, HtmlNode nodeAfterWhichItWillBeInserted)
{
AddSubNodeValidate();
if (newSubNode == null || nodeAfterWhichItWillBeInserted == null)
throw new Exception("Parameters must not be null");
LinkedListNode<HtmlNode> insertNode = childNodes.First;
while (insertNode != null)
{
if (insertNode.Value == nodeAfterWhichItWillBeInserted)
break;
insertNode = insertNode.Next;
}
if (insertNode == null)
throw new Exception("Node after which one the new was to be inserted could not be found");
childNodes.AddAfter(insertNode, newSubNode);
NewNodeUpdates(newSubNode);
return newSubNode;
}
/// <summary>
/// Adds a new sub node before an existing one
/// </summary>
/// <param name="newSubNode">Node to be added</param>
/// <param name="nodeBeforeWhichItWillBeInserted">Node, before which the new one is inserted</param>
/// <returns>The added node</returns>
public HtmlNode AddSubNodeBefore(HtmlNode newSubNode, HtmlNode nodeBeforeWhichItWillBeInserted)
{
AddSubNodeValidate();
if (newSubNode == null || nodeBeforeWhichItWillBeInserted == null)
throw new Exception("Parameters must not be null");
LinkedListNode<HtmlNode> insertNode = childNodes.First;
while (insertNode != null)
{
if (insertNode.Value == nodeBeforeWhichItWillBeInserted)
break;
insertNode = insertNode.Next;
}
if (insertNode == null)
throw new Exception("Node before which one the new was to be inserted could not be found");
childNodes.AddBefore(insertNode, newSubNode);
NewNodeUpdates(newSubNode);
return newSubNode;
}
void AddSubNodeValidate()
{
if (_value.Length > 0)
throw new Exception("Nodes that have a value must not contain sub nodes");
if (!hasEndTag)
throw new Exception("Nodes without an end tag must not contain child nodes");
}
void NewNodeUpdates(HtmlNode newNode)
{
newNode._parentNode = this;
newNode.belongingSource = belongingSource;
if (belongingSource != null)
{
belongingSource.dataChanged = true;
if (newNode.hasEndTag)
belongingSource.curNode = newNode;
}
}
/// <summary>
/// Removes a sub node
/// </summary>
/// <param name="nodeToRemove">Node which is to be removed</param>
public void RemoveSubNode(HtmlNode nodeToRemove)
{
childNodes.Remove(nodeToRemove);
belongingSource.dataChanged = true;
}
/// <summary>
/// Removes all sub nodes
/// </summary>
public void RemoveSubNodes()
{
childNodes.Clear();
belongingSource.dataChanged = true;
}
/// <summary>
/// Queries all sub nodes with a specific name
/// </summary>
/// <param name="name">Name of the sub nodes to get. If the parameter is null all sub nodes are returned</param>
/// <returns>Array, containing the requested nodes</returns>
public HtmlNode[] GetSubNodes(string name)
{
ArrayList foundNodes = new ArrayList();
LinkedListNode<HtmlNode> curNode = childNodes.First;
while (curNode != null)
{
if (curNode.Value._nodeName == name || name == null)
foundNodes.Add(curNode.Value);
curNode = curNode.Next;
}
return (HtmlNode[])foundNodes.ToArray(typeof(HtmlNode));
}
/// <summary>
/// Requests all sub nodes
/// </summary>
/// <returns>Array, containing all sub nodes</returns>
public HtmlNode[] GetSubNodes()
{
return GetSubNodes(null);
}
/// <summary>
/// Sets an attribute
/// </summary>
/// <param name="name">Attribute's name</param>
/// <param name="value">Value to set. If this is null the attribute is removed.</param>
public void SetAttribute(string name, string value)
{
if (name == null)
throw new Exception("Attribute's name must not be null");
lock (this)
{
LinkedListNode<KeyValuePair<string, string>> curNode = attributes.First;
while (curNode != null)
{
if (curNode.Value.Key == name)
{
if (value == null)
{
_approximateLength -= CalculateAttributeLength(curNode.Value);
attributes.Remove(curNode);
if (belongingSource != null)
belongingSource.dataChanged = true;
}
else
{
_approximateLength -= CalculateAttributeLength(curNode.Value);
curNode.Value = new KeyValuePair<string, string>(name.ToLower(), value);
_approximateLength += CalculateAttributeLength(curNode.Value);
if (belongingSource != null)
belongingSource.dataChanged = true;
}
return;
}
curNode = curNode.Next;
}
curNode = attributes.AddLast(new KeyValuePair<string, string>(name.ToLower(), value));
_approximateLength += CalculateAttributeLength(curNode.Value);
if (belongingSource != null)
belongingSource.dataChanged = true;
}
}
int CalculateAttributeLength(KeyValuePair<string, string> attribute)
{
return attribute.Key.Length + attribute.Value.Length + 4;
}
/// <summary>
/// Gets the value of an attribute
/// </summary>
/// <param name="name">The name of the attribute to be queried</param>
/// <returns>The attribute's value. Returns null if the attribute was not found.</returns>
public string GetAttribute(string name)
{
LinkedListNode<KeyValuePair<string, string>> curNode = attributes.First;
while (curNode != null)
{
if (curNode.Value.Key == name)
return curNode.Value.Value;
curNode = curNode.Next;
}
return null;
}
}
/// <summary>
/// A collection of static functions to create common html elements
/// </summary>
public static class HtmlObjects
{
/// <summary>
/// Returns a text element
/// </summary>
/// <param name="text">Text to be added</param>
/// <returns>Text node</returns>
public static HtmlNode SimpleText(string text)
{
return new HtmlNode("", text);
}
/// <summary>
/// Returns a line break element ("br")
/// </summary>
/// <returns>BR node</returns>
public static HtmlNode LineBreak()
{
return new HtmlNode("br", false);
}
/// <summary>
/// Returns a link element ("a")
/// </summary>
/// <param name="destination">Link destination</param>
/// <returns>Link node</returns>
public static HtmlNode Link(string destination)
{
HtmlNode link = new HtmlNode("a");
link.SetAttribute("href", destination);
return link;
}
/// <summary>
/// Returns a image element ("img")
/// </summary>
/// <param name="src">Image source</param>
/// <returns>Image node</returns>
public static HtmlNode Image(string src)
{
HtmlNode img = new HtmlNode("img", false);
img.SetAttribute("src", src);
return img;
}
/// <summary>
/// Returns a image element ("img")
/// </summary>
/// <param name="src">Image source</param>
/// <param name="border">Border size</param>
/// <returns>Image node</returns>
public static HtmlNode Image(string src, int border)
{
HtmlNode img = new HtmlNode("img", false);
img.SetAttribute("src", src);
img.SetAttribute("border", border.ToString());
return img;
}
/// <summary>
/// Returns a element representing bold text ("b")
/// </summary>
/// <param name="text">Text to be added</param>
/// <returns>Text node</returns>
public static HtmlNode BoldText(string text)
{
return new HtmlNode("b", text);
}
/// <summary>
/// Returns a element representing italic text ("i")
/// </summary>
/// <param name="text">Text to be added</param>
/// <returns>Text node</returns>
public static HtmlNode ItalicText(string text)
{
return new HtmlNode("i", text);
}
/// <summary>
/// Returns a element representing underlined text ("u")
/// </summary>
/// <param name="text">Text to be added</param>
/// <returns>Text node</returns>
public static HtmlNode UnderlinedText(string text)
{
return new HtmlNode("u", text);
}
/// <summary>
/// Returns a CSS (cascading style sheet) element ("style")
/// </summary>
/// <param name="sheetContent">Style sheet's content</param>
/// <returns>Style element</returns>
public static HtmlNode CSS(string sheetContent)
{
HtmlNode newNode = new HtmlNode("style", "<!--" + sheetContent + "-->");
newNode.SetAttribute("type", "text/css");
return newNode;
}
/// <summary>
/// Returns a JavaScript element ("script")
/// </summary>
/// <param name="script">Script's content</param>
/// <returns>Script element</returns>
public static HtmlNode JavaScript(string script)
{
HtmlNode newNode = new HtmlNode("script", "<!--" + script + "-->");
newNode.SetAttribute("type", "text/javascript");
return newNode;
}
/// <summary>
/// Returns a form element
/// </summary>
/// <param name="method">Submit method. "get" or "post"</param>
/// <param name="destinationURL">URL to be called on submit</param>
/// <returns>Form element</returns>
public static HtmlNode Form(string method, string destinationURL)
{
HtmlNode newNode = new HtmlNode("form");
newNode.SetAttribute("method", method);
newNode.SetAttribute("action", destinationURL);
return newNode;
}
/// <summary>
/// Returns an text input element
/// </summary>
/// <param name="name">The element's name</param>
/// <param name="value">Initial value</param>
/// <returns>Text input element</returns>
public static HtmlNode TextInput(string name, string value)
{
HtmlNode newNode = new HtmlNode("input", false);
newNode.SetAttribute("name", name);
newNode.SetAttribute("value", value);
newNode.SetAttribute("type", "text");
return newNode;
}
/// <summary>
/// Returns an text input element
/// </summary>
/// <param name="name">The element's name</param>
/// <returns>Text input element</returns>
public static HtmlNode TextInput(string name)
{
return TextInput(name, "");
}
/// <summary>
/// Returns a hidden input element
/// </summary>
/// <param name="name">The element's name</param>
/// <param name="value">Element's value</param>
/// <returns>Hidden input element</returns>
public static HtmlNode HiddenInput(string name, string value)
{
HtmlNode newNode = new HtmlNode("input", false);
newNode.SetAttribute("name", name);
newNode.SetAttribute("value", value);
newNode.SetAttribute("type", "hidden");
return newNode;
}
/// <summary>
/// Returns a password input element
/// </summary>
/// <param name="name">The element's name</param>
/// <returns>Text input element</returns>
public static HtmlNode PasswordInput(string name)
{
HtmlNode newNode = new HtmlNode("input", false);
newNode.SetAttribute("name", name);
newNode.SetAttribute("type", "password");
return newNode;
}
/// <summary>
/// Returns a submit button element
/// </summary>
/// <returns>Submit button element</returns>
public static HtmlNode SubmitButtom()
{
HtmlNode newNode = new HtmlNode("input", false);
newNode.SetAttribute("type", "submit");
return newNode;
}
/// <summary>
/// Returns a submit button element
/// <param name="value">Text displayed on the button</param>
/// </summary>
/// <returns>Submit button element</returns>
public static HtmlNode SubmitButtom(string value)
{
HtmlNode newNode = SubmitButtom();
newNode.SetAttribute("value", value);
return newNode;
}
/// <summary>
/// Returns a reset button element
/// </summary>
/// <returns>Reset button element</returns>
public static HtmlNode ResetButtom()
{
HtmlNode newNode = new HtmlNode("input", false);
newNode.SetAttribute("type", "reset");
return newNode;
}
/// <summary>
/// Returns a reset button element
/// </summary>
/// <param name="value">Text displayed on the button</param>
/// <returns>Reset button element</returns>
public static HtmlNode ResetButtom(string value)
{
HtmlNode newNode = ResetButtom();
newNode.SetAttribute("value", value);
return newNode;
}
}
}