[gxml/gxml-0.12] GOM: Adding new independent DOM4 implementation



commit c26ec9964c8b8ab4367fddfbed814a2d95d178fb
Author: Daniel Espinosa <esodan gmail com>
Date:   Wed Oct 26 16:22:10 2016 -0500

    GOM: Adding new independent DOM4 implementation
    
    GXML Object Model, is a set of new GObject classes
    implementing DOM4, without libxml2.
    
    Read and Write of XML documents will be replicated
    from TDocument implementation. Plans include to
    create an interface to read and write documents,
    implementing DOM4 interfaces, allowing to use
    different engines like libxml2 or GLib's GMarkup.

 gxml/DomNode.vala     |   64 +++++++++-
 gxml/GomDocument.vala |  218 ++++++++++++++++++++++++++++++++
 gxml/GomElement.vala  |  109 ++++++++++++++++
 gxml/GomNode.vala     |  264 +++++++++++++++++++++++++++++++++++++++
 gxml/GomObject.vala   |  328 +++++++++++++++++++++++++++++++++++++++++++++++++
 gxml/GomText.vala     |   97 +++++++++++++++
 6 files changed, 1078 insertions(+), 2 deletions(-)
---
diff --git a/gxml/DomNode.vala b/gxml/DomNode.vala
index 54355f4..db097ef 100644
--- a/gxml/DomNode.vala
+++ b/gxml/DomNode.vala
@@ -1,4 +1,4 @@
-/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 2 -*- */
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
 /*
  *
  * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
@@ -56,7 +56,6 @@ public interface GXml.DomNode : GLib.Object, GXml.DomEventTarget {
   public abstract bool has_child_nodes ();
   public abstract void normalize ();
 
-  public abstract DomNode clone_node (bool deep = false);
   public abstract bool is_equal_node (DomNode? node);
 
   [Flags]
@@ -80,6 +79,67 @@ public interface GXml.DomNode : GLib.Object, GXml.DomEventTarget {
   public abstract DomNode append_child (DomNode node) throws GLib.Error;
   public abstract DomNode replace_child (DomNode node, DomNode child) throws GLib.Error;
   public abstract DomNode remove_child (DomNode child) throws GLib.Error;
+  public abstract DomNode clone_node (bool deep = false) {
+    /**
+   * Copy a {@link GXml.DomNode} relaying on {@link GXml.DomDocument} to other {@link GXml.DomNode}.
+   *
+   * {@link node} could belongs from different {@link GXml.DomDocument}, while source is a node
+   * belonging to given document.
+   *
+   * Only {@link GXml.DomElement} objects are supported. For attributes, use
+   * {@link GXml.DomElement.set_attr} method, passing source's name and value as arguments.
+   *
+   * @param doc a {@link GXml.DomDocument} owning destiny node
+   * @param node a {@link GXml.DomElement} to copy nodes to
+   * @param source a {@link GXml.DomElement} to copy nodes from, it could be holded by different {@link 
GXml.DomDocument}
+   */
+  public static bool copy (GXml.DomDocument doc, GXml.DomNode node, GXml.DomNode source, bool deep)
+  {
+#if DEBUG
+    GLib.message ("Copying GXml.Node");
+#endif
+    if (node is GXml.DomDocument) return false;
+    if (source is GXml.DomElement && node is GXml.DomElement) {
+#if DEBUG
+    GLib.message ("Copying source and destiny nodes are GXml.Elements... copying...");
+    GLib.message ("Copying source's attributes to destiny node");
+#endif
+      foreach (GXml.DomNode p in source._attributes.values) {
+        ((GXml.DomElement) node).set_attribute (p.node_name, p.node_value); // TODO: Namespace
+      }
+      if (!deep) return true;
+#if DEBUG
+      GLib.message ("Copying source's child nodes to destiny node");
+#endif
+      foreach (DomNode c in source.child_nodes) {
+        if (c is DomElement) {
+          if (c.node_name == null) continue;
+#if DEBUG
+            GLib.message (@"Copying child Element node: $(c.node_name)");
+#endif
+          try {
+            var e = doc.create_element (c.node_name); // TODO: Namespace
+            node.child_nodes.add (e);
+            copy (doc, e, c, deep);
+          } catch {}
+        }
+        if (c is DomText) {
+          if ((c as DomText).data == null) {
+            GLib.warning (_("Text node with NULL string"));
+            continue;
+          }
+          var t = doc.create_text ((c as DomText).data);
+          node.child_nodes.add (t);
+#if DEBUG
+          GLib.message (@"Copying source's Text node '$(source.node_name)' to destiny node with text: 
$(c.node_value) : Size= $(node.child_nodes.size)");
+          GLib.message (@"Added Text: $(node.child_nodes.get (node.child_nodes.size - 1))");
+#endif
+        }
+      }
+    }
+    return false;
+  }
+  }
 }
 
 public errordomain GXml.DomError {
diff --git a/gxml/GomDocument.vala b/gxml/GomDocument.vala
new file mode 100644
index 0000000..bfc9df1
--- /dev/null
+++ b/gxml/GomDocument.vala
@@ -0,0 +1,218 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using GXml;
+
+public class GXml.GomDocument : GomNode,
+                              DomParentNode,
+                              DomNonElementParentNode
+{
+  protected
+  // DomDocument implementation
+  protected DomImplementation _implementation = new GomImplementation ();
+  protected string _url = "about:blank";
+  protected string _origin = "";
+  protected string _compat_mode = "";
+  protected string _character_set = "utf-8";
+  protected string _content_type = "application/xml";
+  public DomImplementation implementation { get { return _implementation; } }
+  public string url { get { return _url; } }
+  public string document_uri { get { return _url; } }
+  public string origin { get { return _origin; } }
+  public string compat_mode { get { return _compat_mode; } }
+  public string character_set { get { return _character_set; } }
+  public string content_type { get { return _content_type; } }
+
+  public DomDocumentType? doctype {
+    owned get {
+      foreach (DomNode n in child_nodes) {
+        if (n is DomDocumentType) return (DomDocumentType) n;
+      }
+      return null;
+    }
+  }
+  public DomElement? document_element {
+    owned get {
+      if (child_nodes.size == 0) return null;
+      child_nodes[0];
+    }
+  }
+
+  public DomElement GXml.DomDocument.create_element (string local_name) throws GLib.Error {
+      var e = new GomElement ();
+      e._document = this;
+      e._local_name = local_name;
+  }
+  public DomElement GXml.DomDocument.create_element_ns (string? ns, string qualified_name) throws GLib.Error
+  {
+    string n = "";
+    string nsp = "";
+    if (":" in qualified_name) {
+      var s = qualified_name.split (":");
+      if (s.length != 2)
+        throw new DomError.NOT_SUPPORTED_ERROR (_("Invalid node name"));
+      nsp = s[0];
+      n = s[1];
+    } else
+      n = qualified_name;
+    if (nsp == "" && ns == null)
+      throw new DomError.NAMESPACE_ERROR (_("Invalid namespace"));
+      // TODO: check for xmlns https://www.w3.org/TR/dom/#dom-document-createelementns
+    var e = create_element (n);
+    e._namespaces.set (ns, nsp);
+    e._prefix = nsp;
+    e._namespace_uri = ns;
+  }
+
+  public DomHTMLCollection get_elements_by_tag_name (string local_name) {
+      var l = new GDomHTMLCollection ();
+      if (document_element == null) return l;
+      l.add_all (document_element.get_elements_by_tag_name (local_name));
+      return l;
+  }
+  public DomHTMLCollection get_elements_by_tag_name_ns (string? ns, string local_name) {
+      var l = new GDomHTMLCollection ();
+      if (document_element == null) return l;
+      l.add_all (document_element.get_elements_by_tag_name_ns (ns, local_name));
+      return l;
+  }
+  public DomHTMLCollection get_elements_by_class_name(string class_names) {
+      var l = new GDomHTMLCollection ();
+      if (document_element == null) return l;
+      l.add_all (document_element.get_elements_by_class_name (class_names));
+      return l;
+  }
+
+  public DomDocumentFragment create_document_fragment() {
+    return new GDocumentFragment (this);
+  }
+  public DomText create_text_node (string data) throws GLib.Error {
+    var t = new GomText ();
+    t._document = this;
+    t.data = data;
+  }
+  public DomComment GXml.DomDocument.create_comment (string data) throws GLib.Error {
+    var t = new GomComment ();
+    t._document = this;
+    t.data = data;
+  }
+  public DomProcessingInstruction create_processing_instruction (string target, string data) throws 
GLib.Error {
+    var pi = new GomProcessingInstruction ();
+    pi._document = this;
+    pi.data = data;
+    pi._target = target;
+  }
+
+  public DomNode import_node (DomNode node, bool deep = false) throws GLib.Error {
+      if (node is DomDocument)
+        throw new GXml.DomError.NOT_SUPPORTED_ERROR (_("Can't import a Document"));
+      if (!(node is DomElement) && this.document_element == null)
+        throw new GXml.DomError.HIERARCHY_REQUEST_ERROR (_("Can't import a non Element type node to a 
Document"));
+      GXml.DomNode dst = null;
+      if (node is DomElement) {
+        dst = (this as DomDocument).create_element (node.node_name);
+        GXml.DomNode.copy (this, dst, node, deep);
+        if (document_element == null) {
+          this.append_child (dst);
+          return dst;
+        }
+      }
+      if (node is DomText)
+        dst = this.create_text_node ((node as DomText).data);
+      if (node is DomComment)
+        dst = (this as DomDocument).create_comment ((node as DomComment).data);
+      if (node is DomProcessingInstruction)
+        dst = this.create_processing_instruction ((node as DomProcessingInstruction).target, (node as 
DomProcessingInstruction).data);
+      if (dst != null) {
+        document_element.append_child (dst as DomNode);
+        return dst;
+      }
+      return node;
+  }
+  public DomNode adopt_node (DomNode node) throws GLib.Error {
+      if (node is DomDocument)
+        throw new GXml.DomError.NOT_SUPPORTED_ERROR (_("Can't adopt a Document"));
+      if (this == node.owner_document) return node;
+      var dst = this.create_element (node.node_name);
+      GXml.DomNode.copy (this, dst, node, true);
+      if (node.parent_node != null)
+        node.parent_node.child_nodes.remove_at (node.parent_node.child_nodes.index_of (node));
+      if (this.document_element == null)
+        this.append_child (dst as DomNode);
+      else
+        document_element.append_child (dst as DomNode);
+      return (DomNode) dst;
+  }
+
+  protected GXml.DomEvent _constructor;
+  public DomEvent create_event (string iface) {
+      var s = iface.down ();
+      if (s == "customevent") _constructor = new GXml.GDomCustomEvent ();
+      if (s == "event") _constructor = new GXml.GDomCustomEvent ();
+      if (s == "events") _constructor = new GXml.GDomCustomEvent ();
+      if (s == "htmlevents") _constructor = new GXml.GDomCustomEvent ();
+      if (s == "keyboardevent") _constructor = null;
+      if (s == "keyevents") _constructor = null;
+      if (s == "messageevent") _constructor = null;
+      if (s == "mouseevent") _constructor = null;
+      if (s == "mouseevents") _constructor = null;
+      if (s == "touchevent") _constructor = null;
+      if (s == "uievent") _constructor = null;
+      if (s == "uievents") _constructor = null;
+      return _constructor;
+  }
+
+  public DomRange create_range() {
+      return new GDomRange (this);
+  }
+
+  // NodeFilter.SHOW_ALL = 0xFFFFFFFF
+  public DomNodeIterator create_node_iterator (DomNode root, ulong what_to_show = (ulong) 0xFFFFFFFF, 
DomNodeFilter? filter = null)
+  {
+    return new GDomNodeIterator (root, what_to_show, filter);
+  }
+  public DomTreeWalker create_tree_walker (DomNode root, ulong what_to_show = (ulong) 0xFFFFFFFF, 
DomNodeFilter? filter = null) {
+      return new GDomTreeWalker (root, what_to_show, filter);
+  }
+  // DomParentNode
+  public DomHTMLCollection children { owned get { return (DomHTMLCollection) children_nodes; } }
+  public DomElement? first_element_child {
+    owned get { return (DomElement) (this as Document).children_nodes.first (); }
+  }
+  public DomElement? last_element_child {
+    owned get { return (DomElement) (this as Document).children_nodes.last (); }
+  }
+  public ulong child_element_count { get { return (ulong) children_nodes.size; } }
+
+  public DomElement? query_selector (string selectors) throws GLib.Error {
+    return null; // FIXME
+  }
+  public DomNodeList query_selector_all (string selectors) throws GLib.Error  {
+    return null; // FIXME
+  }
+  // DomNonElementParentNode
+  public DomElement? get_element_by_id (string element_id) throws GLib.Error {
+    var l = this.get_elements_by_property_value ("id", element_id);
+    if (l.size > 0) return (DomElement) l[0];
+    return null;
+  }
+}
diff --git a/gxml/GomElement.vala b/gxml/GomElement.vala
new file mode 100644
index 0000000..de69838
--- /dev/null
+++ b/gxml/GomElement.vala
@@ -0,0 +1,109 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using GXml;
+
+/**
+ * A GXml Object Model (GOM) represents a {@link DomElement}. It has attributes
+ * and children. All object's properties are handled as attributes if they are
+ * basic types like integers, strings, enums and others; {@link SerializableProperty}
+ * objects are handled as attributes too. If object's attribute is a {@link GLib.Object}
+ * it is handled as node's child, but only if it is a {@link GomElement} object,
+ * other wise it is ignored when this object is used as {@link DomNode} in XML
+ * documents.
+ */
+public interface GXml.GomElement : GLib.Object,
+                                  DomNode,
+                                  DomElement {
+  /**
+   * Search for properties in objects, it should be
+   * an {@link GLib.Object}'s property. If found a
+   * property with given name its value is returned
+   * as string representation.
+   *
+   * If property is a {@link SerializableProperty}
+   * returned value is a string representation according
+   * with object implementation.
+   *
+   * If given property name is not found, then {@link DomElement.get_attribute}
+   * is called.
+   *
+   * By default all {@link GLib.Object} are children of
+   * this object, see {@link get_child}
+   */
+  public virtual string? get_attribute (string name) {
+    var prop = get_class ().find_property_spec (name);
+    if (prop != null) {
+      if (prop.value_type is SerializableProperty) {
+        var ov = get_property (name, out ov);
+        SerializableProperty so = (Object) ov;
+        if (so == null) return null;
+        return so.get_serializable_property_value ();
+      }
+    }
+    return (this as DomElement).get_attribute ();
+  }
+  /**
+   * Search for a {@link GLib.Object} property with
+   * given name, if found, given string representation
+   * is used as value to property, using any required
+   * transformation from string.
+   *
+   * By default all {@link GLib.Object} are children of
+   * this object, see {@link set_child}
+   */
+  public virtual void set_attribute (string name, string val) {
+    var prop = get_class ().find_property_spec (name);
+    if (prop != null) {
+      if (prop.value_type is SerializableProperty) {
+        var ov = get_property (name, out ov);
+        SerializableProperty so = (Object) ov;
+        if (so == null) return;
+        so.set_serializable_property_value (val);
+      }
+    }
+    return (this as DomElement).set_attribute (name, val);
+  }
+  /**
+   * Search a {@link GLib.Object} property with given name
+   * and returns it, if it is a {@link DomElement}. If not found,
+   * {@link DomNode.get_elements_by_tag_name} is called, returning
+   * first node found. Tag name to use, is the given name parameter.
+   *
+   * @param name a name of this object's property of type {@link DomElement} or
+   * first {@link DomNode} with that name in child nodes.
+   *
+   */
+  public virtual DomElement get_child (string name) {
+    var prop = get_class ().find_property_spec (name);
+    if (prop != null) {
+      if (prop.value_type is DomElement) {
+        var vo = Value(prop.value_type);
+        get_property (name, out vo);
+        return (DomElement) ((Object) vo);
+      }
+    }
+    if ((this as DomNode).has_child_nodes ()) {
+      return (this as DomElement).child_nodes.named_item (name);
+    }
+  }
+}
diff --git a/gxml/GomNode.vala b/gxml/GomNode.vala
new file mode 100644
index 0000000..9a0c8a4
--- /dev/null
+++ b/gxml/GomNode.vala
@@ -0,0 +1,264 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using GXml;
+
+public class GXml.GomNode : Object,
+                            DomEventTarget,
+                            DomNode {
+// DomNode
+  protected string _local_name = "";
+  protected string _prefix = null;
+  protected DomNode.NodeType _node_type = DomNode.INVALID;
+  public DomNode.NodeType node_type { return _node_type; }
+  public string node_name {
+    owned get {
+      if (_prefix == null) return _local_name;
+      return _prefix+":"+_local_name;
+    }
+  }
+
+  protected string _base_uri = null;
+  public string? base_uri { get { return _base_uri; } }
+
+  protected GXml.DomDocument _document;
+  public DomDocument? owner_document { get { return (GXml.DomDocument?) _document; } }
+
+  protected GXml.DomNode _parent;
+  public DomNode? parent_node { owned get { return _parent as DomNode?; } }
+  public DomElement? parent_element {
+    owned get {
+      if (parent is DomElement) return parent as DomElement?;
+      return null;
+    }
+  }
+
+  protected class NodeList : Gee.ArrayList<DomNode>, DomNodeList {
+    public DomNode? item (ulong index) { return base.get ((int) index); }
+    public abstract ulong length { get { return (ulong) base.size; } }
+  }
+  protected NodeList _child_nodes = new NodeList ();
+  public DomNodeList child_nodes { owned get { return _child_nodes as DomNodeList; } }
+  public DomNode? first_child {
+    owned get {
+    if (_children_nodes.size == 0) return null;
+    return children_nodes[0];
+    }
+  }
+  public DomNode? last_child {
+    owned get {
+      if (_children_nodes.size == 0) return null;
+      return children_nodes[children_nodes.size - 1];
+    }
+  }
+  public DomNode? previous_sibling {
+    owned get {
+     if (_parent == null) return null;
+     if (_parent.child_nodes == null) return null;
+     if (_parent.child_nodes.length == 0) return null;
+     var pos = (_parent.child_nodes as ArrayList<DomNode>).index_of (this);
+     if (pos == 0) return null;
+     if ((pos - 1) > 0 && (pos - 1) < _parent._children_nodes.size)
+      return _parent._children_nodes[pos - 1];
+    }
+  }
+  public DomNode? next_sibling {
+    owned get {
+     if (_parent == null) return null;
+     if (_parent.child_nodes == null) return null;
+     if (_parent.child_nodes.length == 0) return null;
+     var pos = (_parent.child_nodes as ArrayList<DomNode>).index_of (this);
+     if (pos == 0) return null;
+     if ((pos + 1) > 0 && (pos + 1) < _parent._children_nodes.size)
+      return _parent._children_nodes[pos + 1];
+    }
+  }
+
+  string _node_value = null;
+       public string? node_value { owned get { return _node_value; } set { _node_value = value; } }
+       public string? text_content {
+         owned get {
+           string t = null;
+             foreach (GXml.DomNode n in children_nodes) {
+          if (n is GXml.DomText) {
+            if (t == null) t = n.node_value;
+            else t += n.node_value;
+          }
+             }
+           }
+           return t;
+         }
+         set {
+           var t = owner_document.create_text (text_content);
+           _children_nodes.add (t);
+         }
+       }
+
+  public bool has_child_nodes () { return (children_nodes.size > 0); }
+  public void normalize () {
+    try {
+    for (int i = 0; i < children_nodes.size; i++) {
+      var n = children_nodes.get (i);
+      if (n is DomText) {
+        child_nodes.remove_at (i);
+      }
+    }
+    } catch {}
+  }
+
+  public DomNode clone_node (bool deep = false) {
+    // FIXME:
+    return new GomElement ();
+  }
+  public bool is_equal_node (DomNode? node) {
+    if (!(node is GXml.DomNode)) return false;
+    if (node == null) return false;
+    if (this.children_nodes.size != (node as Node).children_nodes.size) return false;
+    foreach (GXml.DomNode a in attrs.values) {
+      if (!(node as GXml.Node?).attrs.has_key (a.name)) return false;
+      if (a.value != (node as GXml.Node).attrs.get (a.name).value) return false;
+    }
+    for (int i=0; i < children_nodes.size; i++) {
+      if (!(children_nodes[i] as GXml.DomNode).is_equal_node ((node as GXml.Node?).children_nodes[i] as 
GXml.DomNode?)) return false;
+    }
+    return true;
+  }
+
+  public DomNode.DocumentPosition compare_document_position (DomNode other) {
+    if ((this as GXml.DomNode) == other) return DomNode.DocumentPosition.NONE;
+    if (this.document != (other as GXml.Node).document || other.parent_node == null) {
+      var p = DomNode.DocumentPosition.DISCONNECTED & DomNode.DocumentPosition.IMPLEMENTATION_SPECIFIC;
+      if ((&this) > (&other))
+        p = p & DomNode.DocumentPosition.PRECEDING;
+      else
+       p = p & DomNode.DocumentPosition.FOLLOWING;
+      return p;
+    }
+    if ((this as DomNode).contains (other))
+      return DomNode.DocumentPosition.CONTAINED_BY & DomNode.DocumentPosition.FOLLOWING;
+    if (this.parent_node.contains (other)) {
+      var par = this.parent_node;
+      if (par.child_nodes.index_of (this) > par.child_nodes.index_of (other))
+        return DomNode.DocumentPosition.PRECEDING;
+      else
+        return DomNode.DocumentPosition.FOLLOWING;
+    }
+    if (other.contains (this))
+      return DomNode.DocumentPosition.CONTAINS & DomNode.DocumentPosition.PRECEDING;
+    GLib.warning (_("Can't find node position"));
+    return DomNode.DocumentPosition.NONE;
+  }
+  public bool contains (DomNode? other) {
+    if (other == null) return false;
+    return this.child_nodes.contains (other);
+  }
+
+  public string? lookup_prefix (string? nspace) {
+    if (_node == null) return null;
+    if (parent == null) return null;
+    if (this is GXml.DocumentType || this is GXml.DocumentFragment) return null;
+    // FIXME:
+    return null;
+  }
+  public string? lookup_namespace_uri (string? prefix) {
+    if (this is GXml.DocumentType || this is GXml.DocumentFragment) return null;
+    // FIXME;
+  }
+  public bool is_default_namespace (string? nspace) {
+    var ns = lookup_namespace_uri (null);
+    if (ns == nspace) return true;
+    return false;
+  }
+
+  public DomNode insert_before (DomNode node, DomNode? child) throws GLib.Error {
+    if (!(node is GXml.GNode))
+      throw new DomError.INVALID_NODE_TYPE_ERROR (_("Invalid attempt to add invalid node type"));
+    if (child != null && !this.contains (child))
+      throw new DomError.NOT_FOUND_ERROR (_("Can't find child to insert node before"));
+    if (!(this is DomDocument
+          || this is DomElement
+          || this is DomDocumentFragment))
+      throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid attempt to insert a node"));
+    if (!(node is DomDocumentFragment
+          || node is DomDocumentType
+          || node is DomElement
+          || node is DomText
+          || node is DomProcessingInstruction
+          || node is DomComment))
+      throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid attempt to insert an invalid node type"));
+    if ((node is DomText && this is DomDocument)
+          || (node is DomDocumentType && !(this is DomDocument)))
+      throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid attempt to insert a document's type or text 
node to a invalid parent"));
+    //FIXME: We should follow steps for DOM4 observers in https://www.w3.org/TR/dom/#concept-node-pre-insert
+    if (child != null) {
+      int i = this.children_nodes.index_of (child as GXml.Node);
+      children_nodes.insert (i, (node as GXml.Node));
+      return node;
+    }
+    children_nodes.add ((node as GXml.Node));
+    return node;
+  }
+  public DomNode append_child (DomNode node) throws GLib.Error {
+    return insert_before (node, null);
+  }
+  public DomNode replace_child (DomNode node, DomNode child) throws GLib.Error {
+    if (!(node is GXml.GNode))
+      throw new DomError.INVALID_NODE_TYPE_ERROR (_("Invalid attempt to add invalid node type"));
+    if (child == null || !this.contains (child))
+      throw new DomError.NOT_FOUND_ERROR (_("Can't find child node to replace or child have a different 
parent"));
+    if (!(this is DomDocument
+          || this is DomElement
+          || this is DomDocumentFragment))
+      throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid attempt to insert a node"));
+    if (!(node is DomDocumentFragment
+          || node is DomDocumentType
+          || node is DomElement
+          || node is DomText
+          || node is DomProcessingInstruction
+          || node is DomComment))
+      throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid attempt to insert an invalid node type"));
+    if ((node is DomText && this is DomDocument)
+          || (node is DomDocumentType && !(this is DomDocument)))
+      throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid attempt to insert a document's type or text 
node to a invalid parent"));
+    //FIXME: Checks for HierarchyRequestError for https://www.w3.org/TR/dom/#concept-node-replace
+    int i = children_nodes.index_of ((child as GXml.Node));
+    children_nodes.remove_at (i);
+    if (i < children_nodes.size)
+      children_nodes.insert (i, (node as GXml.Node));
+    if (i >= children_nodes.size)
+      child_nodes.add (node);
+    return child;
+  }
+  public DomNode remove_child (DomNode child) throws GLib.Error {
+    if (!this.contains (child))
+      throw new DomError.NOT_FOUND_ERROR (_("Can't find child node to remove or child have a different 
parent"));
+    int i = children_nodes.index_of ((child as GXml.Node));
+    return (DomNode) children_nodes.remove_at (i);
+  }
+  // DomEventTarget implementation
+  public void add_event_listener (string type, DomEventListener? callback, bool capture = false)
+  { return; } // FIXME:
+  public void remove_event_listener (string type, DomEventListener? callback, bool capture = false)
+  { return; } // FIXME:
+  public bool dispatch_event (DomEvent event)
+  { return false; } // FIXME:
+}
diff --git a/gxml/GomObject.vala b/gxml/GomObject.vala
new file mode 100644
index 0000000..caee228
--- /dev/null
+++ b/gxml/GomObject.vala
@@ -0,0 +1,328 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using GXml;
+/**
+ * A GXml Object Model (GOM) implementation of {@link GomElement}.It can be used
+ * transparently as {@link DomElement} in a XML tree.
+ */
+public abstract class GXml.GomObject : GomNode,
+                                  DomChildNode,
+                                  DomNonDocumentTypeChildNode,
+                                  DomParentNode,
+                                  DomElement,
+                                  GomElement {
+  /**
+   * Holds namespaces in current node, using URI as key and prefix as value
+   */
+  protected Gee.HashMap<string?,string> _namespaces = Gee.HashMap<string?,string> ();
+
+  construct {
+    _node_type = DomNode.NodeType.ELEMENT_NODE;
+  }
+
+  // DomChildNode
+  public void remove () {
+    if (parent_node != null) {
+      var i = parent_node.child_nodes.index_of (this);
+      parent_node.child_nodes.remove_at (i);
+    }
+  }
+  // DomNonDocumentTypeChildNode
+  public DomElement? previous_element_sibling {
+    get {
+      if (parent_node != null) {
+        var i = parent_node.child_nodes.index_of (this);
+        if (i == 0) return null;
+        var n = parent_node.child_nodes.item (i - 1);
+        if (n is DomElement) return (DomElement) n;
+        return null;
+      }
+      return null;
+    }
+  }
+  public DomElement? next_element_sibling {
+    get {
+      if (parent_node != null) {
+        var i = parent_node.child_nodes.index_of (this);
+        if (i == parent_node.child_nodes.length - 1) return null;
+        var n = parent_node.child_nodes.item (i + 1);
+        if (n is DomElement) return (DomElement) n;
+        return null;
+      }
+      return null;
+    }
+  }
+
+  // DomParentNode
+  public new DomHTMLCollection children {
+    owned get {
+      var l = new DomElementList ();
+      foreach (GXml.DomNode n in child_nodes) {
+        if (n is DomElement) l.add ((DomElement) n);
+      }
+      return l;
+    }
+  }
+  public DomElement? first_element_child { owned get { return (DomElement) children.first (); } }
+  public DomElement? last_element_child { owned get { return (DomElement) children.last (); } }
+  public ulong child_element_count { get { return (ulong) children.size; } }
+
+  public DomElement? query_selector (string selectors) throws GLib.Error {
+  // FIXME:
+    throw new DomError.SYNTAX_ERROR (_("DomElement query_selector is not implemented"));
+  }
+  public DomNodeList query_selector_all (string selectors) throws GLib.Error {
+  // FIXME:
+    throw new DomError.SYNTAX_ERROR (_("DomElement query_selector_all is not implemented"));
+  }
+  // GXml.DomElement
+  protected string _namespace_uri = null;
+  public string? namespace_uri { owned get { return _namespace_uri.dup (); } }
+  protected string _prefix = null;
+  public string? prefix { owned get { return _prefix; } }
+  /**
+   * Derived classes should define this value at construction time.
+   */
+  protected string _local_name = "";
+  public string local_name {
+    owned get {
+      return _local_name;
+    }
+  }
+
+  public string tag_name { owned get { return _local_name; } }
+
+  public string? id {
+    owned get { return (this as GomElement).get_attribute ("id"); }
+    set { (this as GomElement).set_attribute ("id", value); }
+  }
+  public string? class_name {
+    owned get { return (this as GomElement).get_attribute ("class"); }
+    set { this as GomElement).set_attribute ("class", value); }
+  }
+  public DomTokenList class_list {
+    owned get {
+      return new GDomTokenList (this, "class");
+    }
+  }
+
+  /**
+   * Holds attributes in current node, using attribute's name as key
+   * and it's value as value. Appends namespace prefix to attribute's name as
+   * key if a namespaced attribute.
+   */
+  protected class Attributes : HashMap<string,string>, DomNamedNodeMap  {
+    public ulong length { get { return (ulong) size; } }
+    public DomNode? item (ulong index) { return null; }
+    public DomNode? get_named_item (string name) {
+      if (":" in name) return null;
+      var v = get (name);
+      if (v == null) return null;
+      var n = new GomNode ();
+      n._node_value = v;
+      n._local_name = name;
+      return n;
+    }
+    public DomNode? set_named_item (DomNode node) throws GLib.Error {
+      if (":" in node.node_name) return null;
+      set (node.node_name, node.node_value);
+      var n = new GomNode ();
+      n._node_value = node.node_value;
+      n._local_name = node.node_name;
+    }
+    public DomNode? remove_named_item (string name) throws GLib.Error {
+      if (":" in name) return null;
+      var v = get (name);
+      if (v == null) return null;
+      var n = new GomNode ();
+      n._node_value = v;
+      n._local_name = name;
+      set (name, null);
+      return n;
+    }
+    // Introduced in DOM Level 2:
+    public DomNode? remove_named_item_ns (string namespace_uri, string local_name) throws GLib.Error {
+      if (":" in local_name) return null;
+      var nsp = _name_spaces.get (namespace_uri);
+      if (nsp == null) return null;
+      var v = get (nsp+":"+local_name);
+      if (v == null) return null;
+      var n = new GomNode ();
+      n._node_value = v;
+      n._local_name = name;
+      set (name, null);
+      return n;
+    }
+    // Introduced in DOM Level 2:
+    public DomNode? get_named_item_ns (string namespace_uri, string local_name) throws GLib.Error {
+      if (":" in local_name) return null;
+      var nsp = _name_spaces.get (namespace_uri);
+      if (nsp == null) return null;
+      var v = get (nsp+":"+local_name);
+      if (v == null) return null;
+      var n = new GomNode ();
+      n._node_value = v;
+      n._local_name = local_name;
+      return n;
+    }
+    // Introduced in DOM Level 2:
+    public DomNode? set_named_item_ns (DomNode node) throws GLib.Error {
+      /* TODO:Ç
+      WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this 
map.
+
+NO_MODIFICATION_ALLOWED_ERR: Raised if this map is readonly.
+
+INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object. The 
DOM user must explicitly clone Attr nodes to re-use them in other elements.
+
+HIERARCHY_REQUEST_ERR: Raised if an attempt is made to add a node doesn't belong in this NamedNodeMap. 
Examples would include trying to insert something other than an Attr node into an Element's map of 
attributes, or a non-Entity node into the DocumentType's map of Entities.
+
+NOT_SUPPORTED_ERR: May be raised if the implementation does not support the feature "XML" and the language 
exposed through the Document does not support XML Namespaces (such as [HTML 4.01]).
+      */
+      string ns, ln, nsp;
+      if (node is DomElement) {
+        ns = (node as DomElement).namespace_uri;
+        ln = (node as DomElement).local_name;
+        nsp = (node as DomElement).prefix;
+      } else
+        return null;
+      if (":" in ln)
+        throw new DomError.NOT_SUPPORTED_ERROR (_("Invalid local name"));
+      var nsp = _name_spaces.get (ns);
+      if (nsp == null && _namespaces.size == 0) {
+        _namespaces.set (ns, "");
+      }
+      if (nsp == null) nsp = "";
+      set (nsp+":"+local_name, node.node_value);
+      var n = new GomNode ();
+      n._node_value = v;
+      n._local_name = name;
+      return n;
+    }
+  }
+  protected Attributes _attributes = new Attributes ();
+  public DomNamedNodeMap attributes { owned get { return (DomNamedNodeMap) _attributes; } }
+  public string? get_attribute (string name) { return (this as GomElement).get_attribute (name); }
+  public string? get_attribute_ns (string? namespace, string local_name) {
+    var p = _attributes.get_named_item_ns (namespace, local_name);
+    if (p == null) return null;
+    return p.node_value;
+  }
+  public void set_attribute (string name, string? value) { (this as GomElement).set_attribute (name, value); 
}
+  public void set_attribute_ns (string? namespace, string name, string? value) {
+    string p = "";
+    string n = name;
+    if (":" in name) {
+      var s = name.split (":");
+      if (s.length != 2) return null;
+      p = s[0];
+      n = s[1];
+    } else
+      n = name;
+      //TODO: Throw errors on xmlns and no MXLNS https://www.w3.org/TR/dom/#dom-element-setattributens
+    var nsp = _namespaces.get (namespace);
+    if (nsp != p) {
+      _namespaces.set (namespace, p); // Set Default
+    }
+    _attributes.set (p+":"+n, value);
+  }
+  public void remove_attribute (string name) {
+    (this as GomElement).remove_attribute (name);
+  }
+  public void remove_attribute_ns (string? namespace, string local_name) {
+    if (":" in local_name) return;
+    var nsp = _namespaces.get (namespace);
+    if (nsp == null) return;
+    var a = _attributes.get (nsp+local_name);
+    if (a == null) return;
+    set (nsp+local_name, null);
+  }
+  public bool has_attribute (string name) {
+    return _attributes.has_key (name);
+  }
+  public bool has_attribute_ns (string? namespace, string local_name) {
+    var nsp = _namespaces.get (namespace);
+    if (nsp == null) return false;
+    var a = get (nsp+local_name);
+    if (a != null) return true;
+  }
+
+
+  public DomHTMLCollection get_elements_by_tag_name (string local_name) {
+    var l = new GDomHTMLCollection ();
+    //FIXME: quircks mode not considered
+    foreach (GXml.DomElement n in child_nodes) {
+      if (n.node_name == local_name)
+        l.add (n);
+      l.add_all (n.get_elements_by_tag_name (local_name));
+    }
+    return l;
+  }
+  public DomHTMLCollection get_elements_by_tag_name_ns (string? namespace, string local_name) {
+    var l = new GDomHTMLCollection ();
+    //FIXME: quircks mode not considered
+    foreach (GXml.DomElement n in child_nodes) {
+      if (n.node_name == local_name
+          && n.namespace_uri == namespace)
+        l.add (n);
+      l.add_all (n.get_elements_by_tag_name_ns (namespace, local_name));
+    }
+    return l;
+  }
+  public DomHTMLCollection get_elements_by_class_name (string class_names) {
+    var l = new GDomHTMLCollection ();
+    if (class_names == "") return l;
+    string[] cs = {};
+    if (" " in class_names) {
+      cs = class_names.split (" ");
+    } else
+      cs += class_names;
+    foreach (GXml.DomElement n in child_nodes) {
+      string cls = n.get_attribute ("class");
+      if (cls != null) {
+        string[] ncls = {};
+        if (" " in cls)
+          ncls = cls.split (" ");
+        else
+          ncls += cls;
+        int found = 0;
+        foreach (string cl in cs) {
+          foreach (string ncl in ncls) {
+            if (cl == ncl) {
+              found++;
+            }
+          }
+        }
+        if (found == cs.length) {
+          if (l.size == 0)
+            l.add (n);
+          else
+            l.insert (0, n);
+        }
+      }
+      l.add_all (n.get_elements_by_class_name (class_names));
+    }
+    return l;
+  }
+}
+
+
diff --git a/gxml/GomText.vala b/gxml/GomText.vala
new file mode 100644
index 0000000..99c6d8f
--- /dev/null
+++ b/gxml/GomText.vala
@@ -0,0 +1,97 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using GXml;
+
+public class GXml.GomText : GomNode,
+                          DomCharacterData,
+                          DomNode,
+                          DomNonDocumentTypeChildNode,
+                          DomChildNode,
+                          DomText
+
+{
+  construct {
+    _node_type = DomNode.NodeType.TEXT_NODE;
+    _local_name = "#TEXT";
+  }
+  // DomCharacterData
+  public string data { owned get; set; }
+  // DomNonDocumentTypeChildNode
+  public DomElement? previous_element_sibling {
+    get {
+      if (parent_node != null) {
+        var i = parent_node.child_nodes.index_of (this);
+        if (i == 0) return null;
+        var n = parent_node.child_nodes.item (i - 1);
+        if (n is DomElement) return (DomElement) n;
+        return null;
+      }
+      return null;
+    }
+  }
+  public DomElement? next_element_sibling {
+    get {
+      if (parent_node != null) {
+        var i = parent_node.child_nodes.index_of (this);
+        if (i == parent_node.child_nodes.length - 1) return null;
+        var n = parent_node.child_nodes.item (i + 1);
+        if (n is DomElement) return (DomElement) n;
+        return null;
+      }
+      return null;
+    }
+  }
+  // DomChildNode
+  public void remove () {
+    if (parent_node != null) {
+      var i = parent_node.child_nodes.index_of (this);
+      parent_node.child_nodes.remove_at (i);
+    }
+  }
+}
+
+public class GXml.GomProcessingInstruction : GomNode,
+              DomCharacterData,
+              DomProcessingInstruction
+{
+  protected string _target = null;
+  // DomCharacterData
+  public string data { owned get { return _node_value; } set { _node_value = value; } }
+
+  construct {
+    _node_type = DomNode.NodeType.PROCESSING_INSTRUCTION_NODE;
+    _local_name = "#PROCESSING_INSTRUCTION";
+  }
+}
+
+public class GXml.GomComment : GomNode,
+                                  DomCharacterData,
+                                  DomComment
+{
+  // DomCharacterData
+  public string data { owned get; set; }
+  construct {
+    _node_type = DomNode.NodeType.COMMENT_NODE;
+    _local_name = "#COMMENT";
+  }
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]