2017-06-12 20:45:27 +00:00
|
|
|
using System;
|
2018-03-09 20:56:32 +00:00
|
|
|
using System.Collections;
|
2017-06-12 20:45:27 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
namespace Ooui
|
|
|
|
{
|
2017-06-15 01:24:59 +00:00
|
|
|
public abstract class Node : EventTarget
|
2017-06-12 20:45:27 +00:00
|
|
|
{
|
|
|
|
readonly List<Node> children = new List<Node> ();
|
|
|
|
|
2017-06-17 00:33:27 +00:00
|
|
|
public IReadOnlyList<Node> Children {
|
|
|
|
get {
|
|
|
|
lock (children) {
|
2018-03-09 20:56:32 +00:00
|
|
|
return new ReadOnlyList<Node> (children);
|
2017-06-17 00:33:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-13 03:31:47 +00:00
|
|
|
|
2017-06-24 23:25:14 +00:00
|
|
|
public Node FirstChild {
|
|
|
|
get {
|
|
|
|
lock (children) {
|
|
|
|
if (children.Count > 0)
|
|
|
|
return children[0];
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-14 04:17:50 +00:00
|
|
|
public virtual string Text {
|
2017-06-17 00:33:27 +00:00
|
|
|
get { return String.Join ("", from c in Children select c.Text); }
|
2017-06-13 07:51:24 +00:00
|
|
|
set {
|
2017-06-14 04:17:50 +00:00
|
|
|
ReplaceAll (new TextNode (value ?? ""));
|
2017-06-13 07:51:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-15 06:20:51 +00:00
|
|
|
protected Node (string tagName)
|
|
|
|
: base (tagName)
|
2018-03-09 20:56:32 +00:00
|
|
|
{
|
2017-06-12 20:45:27 +00:00
|
|
|
}
|
|
|
|
|
2017-06-15 09:39:19 +00:00
|
|
|
public override EventTarget GetElementById (string id)
|
|
|
|
{
|
|
|
|
if (id == Id) return this;
|
|
|
|
foreach (var c in Children) {
|
|
|
|
if (c is Element e) {
|
|
|
|
var r = e.GetElementById (id);
|
|
|
|
if (r != null)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-06-12 20:45:27 +00:00
|
|
|
public Node AppendChild (Node newChild)
|
|
|
|
{
|
|
|
|
return InsertBefore (newChild, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Node InsertBefore (Node newChild, Node referenceChild)
|
|
|
|
{
|
2017-06-17 00:33:27 +00:00
|
|
|
if (newChild == null)
|
|
|
|
return null;
|
|
|
|
lock (children) {
|
|
|
|
if (referenceChild == null) {
|
|
|
|
children.Add (newChild);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var index = children.IndexOf (referenceChild);
|
|
|
|
if (index < 0) {
|
|
|
|
throw new ArgumentException ("Reference must be a child of this element", nameof (referenceChild));
|
|
|
|
}
|
|
|
|
children.Insert (index, newChild);
|
|
|
|
}
|
2017-06-12 20:45:27 +00:00
|
|
|
}
|
2017-06-17 00:33:27 +00:00
|
|
|
newChild.MessageSent += HandleChildMessageSent;
|
2017-12-10 22:21:49 +00:00
|
|
|
Call ("insertBefore", newChild, referenceChild);
|
2017-12-09 21:19:32 +00:00
|
|
|
OnChildInsertedBefore (newChild, referenceChild);
|
2017-06-12 20:45:27 +00:00
|
|
|
return newChild;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Node RemoveChild (Node child)
|
|
|
|
{
|
2017-06-17 00:07:30 +00:00
|
|
|
if (child == null)
|
|
|
|
return null;
|
2017-06-17 00:33:27 +00:00
|
|
|
lock (children) {
|
|
|
|
if (!children.Remove (child)) {
|
2018-03-09 20:56:32 +00:00
|
|
|
throw new ArgumentException ("Child not contained in this element", nameof (child));
|
2017-06-17 00:33:27 +00:00
|
|
|
}
|
2017-06-12 20:45:27 +00:00
|
|
|
}
|
2017-06-17 00:07:30 +00:00
|
|
|
child.MessageSent -= HandleChildMessageSent;
|
2017-12-10 22:21:49 +00:00
|
|
|
Call ("removeChild", child);
|
2017-12-09 21:19:32 +00:00
|
|
|
OnChildRemoved (child);
|
2017-06-12 20:45:27 +00:00
|
|
|
return child;
|
|
|
|
}
|
|
|
|
|
2017-12-09 21:19:32 +00:00
|
|
|
protected virtual void OnChildInsertedBefore (Node newChild, Node referenceChild)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void OnChildRemoved (Node child)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-06-13 07:51:24 +00:00
|
|
|
protected void ReplaceAll (Node newNode)
|
|
|
|
{
|
2017-06-17 00:33:27 +00:00
|
|
|
var toRemove = new List<Node> ();
|
|
|
|
lock (children) {
|
|
|
|
toRemove.AddRange (children);
|
|
|
|
children.Clear ();
|
|
|
|
}
|
|
|
|
foreach (var child in toRemove) {
|
|
|
|
child.MessageSent -= HandleChildMessageSent;
|
2017-12-10 22:21:49 +00:00
|
|
|
Call ("removeChild", child);
|
2017-06-17 00:33:27 +00:00
|
|
|
}
|
2017-06-13 07:51:24 +00:00
|
|
|
InsertBefore (newNode, null);
|
|
|
|
}
|
2017-06-15 09:39:19 +00:00
|
|
|
|
2017-06-17 00:07:30 +00:00
|
|
|
void HandleChildMessageSent (Message message)
|
|
|
|
{
|
|
|
|
Send (message);
|
|
|
|
}
|
|
|
|
|
2017-06-24 20:34:47 +00:00
|
|
|
protected override bool SaveStateMessageIfNeeded (Message message)
|
2017-06-15 09:39:19 +00:00
|
|
|
{
|
2017-06-24 20:34:47 +00:00
|
|
|
if (message.TargetId == Id) {
|
|
|
|
switch (message.MessageType) {
|
|
|
|
case MessageType.Call when message.Key == "insertBefore":
|
|
|
|
AddStateMessage (message);
|
|
|
|
break;
|
|
|
|
case MessageType.Call when message.Key == "removeChild" && message.Value is Array ma && ma.Length == 1:
|
|
|
|
UpdateStateMessages (state => {
|
|
|
|
var mchild = ma.GetValue (0);
|
2018-03-09 20:56:32 +00:00
|
|
|
Node nextChild = null;
|
|
|
|
for (var i = 0; i < state.Count;) {
|
2017-06-24 20:34:47 +00:00
|
|
|
var x = state[i];
|
|
|
|
if (x.Key == "insertBefore" && x.Value is Array xa && xa.Length == 2 && ReferenceEquals (xa.GetValue (0), mchild)) {
|
|
|
|
// Remove any inserts for this node
|
|
|
|
nextChild = xa.GetValue (1) as Node;
|
|
|
|
state.RemoveAt (i);
|
|
|
|
}
|
|
|
|
else if (x.Key == "insertBefore" && x.Value is Array ya && ya.Length == 2 && ReferenceEquals (ya.GetValue (1), mchild)) {
|
|
|
|
// Replace inserts that reference this node
|
|
|
|
state[i] = Message.Call (Id, "insertBefore", ya.GetValue (0), nextChild);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
i++;
|
|
|
|
}
|
2017-06-18 06:21:49 +00:00
|
|
|
}
|
2017-06-24 20:34:47 +00:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
base.SaveStateMessageIfNeeded (message);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var ch = Children;
|
|
|
|
for (var i = 0; i < ch.Count; i++) {
|
|
|
|
if (ch[i].SaveStateMessageIfNeeded (message))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2017-06-15 09:39:19 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-24 19:58:14 +00:00
|
|
|
|
|
|
|
protected override bool TriggerEventFromMessage (Message message)
|
|
|
|
{
|
2017-06-24 20:34:47 +00:00
|
|
|
if (message.TargetId == Id) {
|
|
|
|
if (base.TriggerEventFromMessage (message))
|
2017-06-24 19:58:14 +00:00
|
|
|
return true;
|
|
|
|
}
|
2017-06-24 20:34:47 +00:00
|
|
|
else {
|
|
|
|
var ch = Children;
|
|
|
|
for (var i = 0; i < ch.Count; i++) {
|
|
|
|
if (ch[i].TriggerEventFromMessage (message))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2017-06-24 19:58:14 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-02-02 04:18:16 +00:00
|
|
|
|
|
|
|
public virtual string OuterHtml {
|
|
|
|
get {
|
|
|
|
using (var stream = new System.IO.MemoryStream ()) {
|
|
|
|
var settings = new System.Xml.XmlWriterSettings {
|
|
|
|
OmitXmlDeclaration = true,
|
|
|
|
ConformanceLevel = System.Xml.ConformanceLevel.Fragment,
|
|
|
|
CloseOutput = false,
|
|
|
|
};
|
|
|
|
using (var w = System.Xml.XmlWriter.Create (stream, settings)) {
|
|
|
|
WriteOuterHtml (w);
|
|
|
|
}
|
|
|
|
stream.Position = 0;
|
|
|
|
return new System.IO.StreamReader (stream).ReadToEnd ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public abstract void WriteOuterHtml (System.Xml.XmlWriter w);
|
2017-06-12 20:45:27 +00:00
|
|
|
}
|
2018-03-09 20:56:32 +00:00
|
|
|
|
|
|
|
class ReadOnlyList<T> : IReadOnlyList<T>
|
|
|
|
{
|
|
|
|
readonly List<T> list;
|
|
|
|
|
|
|
|
public ReadOnlyList (List<T> items)
|
|
|
|
{
|
|
|
|
list = new List<T> (items);
|
|
|
|
}
|
|
|
|
|
|
|
|
T IReadOnlyList<T>.this[int index] => list[index];
|
|
|
|
|
|
|
|
int IReadOnlyCollection<T>.Count => list.Count;
|
|
|
|
|
|
|
|
IEnumerator<T> IEnumerable<T>.GetEnumerator ()
|
|
|
|
{
|
|
|
|
return ((IEnumerable<T>)list).GetEnumerator ();
|
|
|
|
}
|
|
|
|
|
|
|
|
IEnumerator IEnumerable.GetEnumerator ()
|
|
|
|
{
|
|
|
|
return ((IEnumerable)list).GetEnumerator ();
|
|
|
|
}
|
|
|
|
}
|
2017-06-12 20:45:27 +00:00
|
|
|
}
|