summaryrefslogtreecommitdiffstats
path: root/gxml/GomElement.vala
blob: f1c66753971c0cefdf4adf9a61b711ba1f225dce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
/* -*- 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;
using Gee;

/**
 * A DOM4 implementation of {@link DomElement}, for one-step-parsing.
 *
 * This object avoids pre and post XML parsing, by using a one step parsing
 * to translate text XML tree to an GObject based tree.
 *
 * A GXml Object Model (GOM) implementation of {@link GomElement}.It can be used
 * transparently as {@link DomElement} in a XML tree.
 *
 * It also allows delayed parsing, so you can read large documents by parsing
 * just a XML element node and its attributes but not its childs; save its childs
 * as a text, for a post-on-step-parsing.
 */
public class GXml.GomElement : GomNode,
                              DomChildNode,
                              DomNonDocumentTypeChildNode,
                              DomParentNode,
                              DomElement,
                              GomObject {
  /**
   * Reference to {@link Attributes} for element's attributes.
   * Derived classes should avoid to modify it.
   */
  protected Attributes _attributes;
  // Convenient Serialization methods
  /**
   * Parsing a URI file.
   */
  public void read_from_uri (string uri) throws GLib.Error {
    this.read_from_file (File.new_for_uri (uri));
  }
  /**
   * Parsing asinchronically a URI file.
   */
  public async void read_from_uri_async (string uri) throws GLib.Error {
    yield this.read_from_file_async (File.new_for_uri (uri));
  }
  /**
   * Parses an XML file, deserializing it over {@link GomElement}.
   */
  public void read_from_file (GLib.File f,
                      GLib.Cancellable? cancellable = null) throws GLib.Error {
    var parser = new XParser (this);
    parser.read_file (f, cancellable);
  }
  /**
   * Parses asinchronically an XML file, deserializing it over {@link GomElement}.
   */
  public async void read_from_file_async (GLib.File f,
                      GLib.Cancellable? cancellable = null) throws GLib.Error {
    var parser = new XParser (this);
    yield parser.read_file_async (f, cancellable);
  }
  /**
   * Parses an XML over a {@link GLib.InputStream}, deserializing it over {@link GomElement}.
   */
  public void read_from_stream (GLib.InputStream istream,
                      GLib.Cancellable? cancellable = null) throws GLib.Error {
    var parser = new XParser (this);
    parser.read_stream (istream, cancellable);
  }
  /**
   * Parses asynchronically an XML over a {@link GLib.InputStream}, deserializing it over {@link GomElement}.
   */
  public async void read_from_stream_async (GLib.InputStream istream,
                      GLib.Cancellable? cancellable = null) throws GLib.Error {
    var parser = new XParser (this);
    yield parser.read_stream_async (istream, cancellable);
  }
  /**
   * Parses an XML string, deserializing it over {@link GomElement}.
   */
  public void read_from_string (string str) throws GLib.Error {
    var parser = new XParser (this);
    parser.read_string (str, null);
  }
  /**
   * Parses an XML string, deserializing it over {@link GomElement}.
   */
  public async void read_from_string_async (string str) throws GLib.Error {
    var parser = new XParser (this);
    yield parser.read_string_async (str, null);
  }
  /**
   * Serialize {@link GomElement} to a string.
   */
  public string write_string () throws GLib.Error {
    var parser = new XParser (this);
    return parser.write_string ();
  }
  /**
   * Serialize asinchronically {@link GomElement} to a string.
   */
  public async string write_string_async () throws GLib.Error {
    var parser = new XParser (this);
    return yield parser.write_string_async ();
  }
  /**
   * Uses element's {@link GomDocument} to write an XML to a file, serializing it.
   */
  public void write_file (GLib.File f) throws GLib.Error {
    (this.owner_document as GomDocument).write_file (f);
  }
  /**
   * Uses element's {@link GomDocument} to write asynchronically an XML to a file, serializing it.
   */
  public async void write_file_async (GLib.File f) throws GLib.Error {
    yield (this.owner_document as GomDocument).write_file_async (f);
  }
  /**
   * Uses element's {@link GomDocument} to write an XML to a stream, serializing it.
   */
  public void write_stream (GLib.OutputStream stream) throws GLib.Error {
    (this.owner_document as GomDocument).write_stream (stream);
  }
  /**
   * Uses element's {@link GomDocument} to write an XML to a stream, serializing it.
   */
  public async void write_stream_async (GLib.OutputStream stream) throws GLib.Error {
    yield (this.owner_document as GomDocument).write_stream_async (stream);
  }
  /**
   * Creates an {@link GLib.InputStream} to write a string representation
   * in XML of {@link GomElement} using node's {@link GomDocument}
   */
  public InputStream create_stream () throws GLib.Error {
    return (this.owner_document as GomDocument).create_stream ();
  }
  /**
   * Creates an {@link GLib.InputStream} to write a string representation
   * in XML of {@link GomElement} using node's {@link GomDocument}
   */
  public async InputStream create_stream_async () throws GLib.Error {
    return yield (this.owner_document as GomDocument).create_stream_async ();
  }
  // DomNode overrides
  public new string? lookup_prefix (string? nspace) {
    if (_namespace_uri == nspace)
      return _prefix;
    foreach (string k in _attributes.keys) {
      if (!("xmlns" in k)) continue;
      string ns_uri = _attributes.get (k);
      if (ns_uri == null) {
        GLib.warning (_("Invalid namespace URI stored in element's attribute"));
        return null;
      }
      if (ns_uri == nspace) {
        if (":" in k) {
          string[] sa = k.split (":");
          if (sa.length > 2) {
            GLib.warning (_("Invalid attribute name in element's attributes list"));
            return null;
          }
          return sa[1];
        }
      }
    }
    if (parent_node == null) return null;
    return parent_node.lookup_prefix (nspace);
  }
  public new string? lookup_namespace_uri (string? prefix) {
    if (_namespace_uri != null && _prefix == prefix)
      return namespace_uri;
    string s = "";
    if (prefix != null) s = prefix;
    foreach (string k in attributes.keys) {
      if (!("xmlns" in k)) continue;
#if DEBUG
      GLib.message ("Attribute: "+k);
#endif
      string nsp = null;
      if (":" in k) {
        string[] sa = k.split (":");
        if (sa.length > 2) {
          GLib.warning (_("Invalid attribute name in element's attributes list"));
          return null;
        }
        nsp = sa[1];
      }
      if (nsp != prefix) continue;
      return _attributes.get (k);
    }
    if (parent_node == null) return null;
    return parent_node.lookup_namespace_uri (prefix);
  }

  // DomChildNode
  public void remove () {
    if (parent_node != null) {
      var i = parent_node.child_nodes.index_of (this);
      if (i < 0 || i > parent_node.child_nodes.length)
        warning (_("Can't locate child node to remove"));
      parent_node.child_nodes.remove_at (i);
    }
  }
  // DomNonDocumentTypeChildNode
  public DomElement? previous_element_sibling {
    owned get {
      if (parent_node != null) {
        var i = parent_node.child_nodes.index_of (this);
        if (i == 0)
          return null;
        for (var j = i; j >= 1; j--) {
          var n = parent_node.child_nodes.item (j - 1);
          if (n is DomElement)
			return n as DomElement;
        }
      }
      return null;
    }
  }
  public DomElement? next_element_sibling {
    owned get {
      if (parent_node != null) {
        var i = parent_node.child_nodes.index_of (this);
        if (i == parent_node.child_nodes.length - 1)
          return null;
        for (var j = i; j < parent_node.child_nodes.length - 1; j--) {
          var n = parent_node.child_nodes.item (j + 1);
          if (n is DomElement)
            return (DomElement) n;
        }
      }
      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 int child_element_count { get { return children.size; } }

  public DomNodeList query_selector_all (string selectors) throws GLib.Error {
    var cs = new CssSelectorParser ();
    cs.parse (selectors);
    var l = new GomNodeList ();
    foreach (DomNode e in child_nodes) {
      if (!(e is DomElement)) continue;
      if (cs.match (e as DomElement))
        l.add (e);
      l.add_all ((e as DomElement).query_selector_all (selectors));
    }
    return l;
  }
  // GXml.DomElement
  /**
   * Use this field to set node's namespace URI. Can used to set it at construction time.
   */
  protected string _namespace_uri = null;
  public string? namespace_uri { owned get { return _namespace_uri.dup (); } }
  public string? prefix { owned get { return _prefix; } }
  /**
   * Derived classes should define it at construction time, using
   * {@link GomNode._local_name} field. This is the node's name.
   */
  public string local_name {
    owned get {
      return _local_name;
    }
  }

  public string tag_name { owned get { return _local_name; } }

  /**
   * An attribute called 'id'.
   */
  public string? id {
    owned get { return (this as GomElement).get_attribute ("id"); }
    set { (this as GomObject).set_attribute ("id", value); }
  }
  /**
   * An attribute called 'class'.
   */
  public string? class_name {
    owned get { return (this as GomElement).get_attribute ("class"); }
    set { (this as GomObject).set_attribute ("class", value); }
  }
  /**
   * A list of values of all attributes called 'class'.
   */
  public DomTokenList class_list {
    owned get {
      return new GDomTokenList (this, "class");
    }
  }

  construct {
    _node_type = DomNode.NodeType.ELEMENT_NODE;
    _attributes = new Attributes (this);
    _local_name = "";
  }
  /**
   * Convenient function to initialize, at construction time, a {@link GomElement}
   * using given local name. If {@link GomElement.initialize_with_namespace}
   * has been called in any base class, this method just change elment node's name
   * and keeps previous namespace and prefix.
   *
   * No {@link DomDocument} is set by default, if this is a top level element in a
   * document, you can call {@link DomNode.owner_document} to set one if not set
   * already.
   *
   * Any instance properties of type {@link GomElement} or {@link GomCollection}
   * should be initialized using {@link GomObject.set_instance_property}
   */
  public void initialize (string local_name) {
    _local_name = local_name;
  }
  /**
   * Convenient function to initialize, at construction time, a {@link GomElement}
   * using given local name and document.
   */
  public void initialize_document (DomDocument doc, string local_name) {
    _document = doc;
    _local_name = local_name;
  }
  /**
   * Convenient function to initialize, at construction time, a {@link GomElement}
   * using given local name and namespace.
   */
  public void initialize_with_namespace (string? namespace_uri,
                              string? prefix, string local_name) {
    _local_name = local_name;
    _namespace_uri = namespace_uri;
    _prefix = prefix;
  }
  /**
   * Convenient function to initialize, at construction time, a {@link GomElement}
   * using given local name, document and namespace.
   */
  public void initialize_document_with_namespace (DomDocument doc, string? namespace_uri,
                              string? prefix, string local_name) {
    _document = doc;
    _local_name = local_name;
    _namespace_uri = namespace_uri;
    _prefix = prefix;
  }

  /**
   * 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.
   */
  public class Attributes : HashMap<string,string>, DomNamedNodeMap  {
    /**
     * Holds {@link GomElement} refrence to attributes' parent element.
     * Derived classes should not modify, but set at construction time.
     */
    protected GomElement _element;
    public int length { get { return size; } }
    public DomNode? item (int index) { return null; }

    public Attributes (GomElement element) {
      _element = element;
    }

    public DomNode? get_named_item (string name) {
      if (name == "") return null;
      var ov = (_element as GomObject).get_attribute (name);
      if (ov != null) {
        return new GomAttr (_element, name, ov);
      }
      string p = "";
      string ns = null;
      string n = name;
      if (":" in name) {
        string[] s = name.split (":");
        if (s.length > 2) return null;
        p = s[0];
        n = s[1];
        if (p == "xml")
          ns = "http://www.w3.org/2000/xmlns/";
        if (p == "xmlns")
          ns = _element.lookup_namespace_uri (n);
        if (p != "xmlns" && p != "xml")
          ns =  _element.lookup_namespace_uri (p);
      }
      string val = get (name);
      if (val == null) return null;
      DomNode attr = null;
      if (p == null || p == "")
        attr = new GomAttr (_element, n, val);
      else
        attr = new GomAttr.namespace (_element, ns, p, n, val);

#if DEBUG
      GLib.message ("Return: "+ attr.node_name+"="+attr.node_value);
#endif
      return attr;
    }
    /**
     * Takes given {@link DomNode} as a {@link DomAttr} and use its name and
     * value to set a property in {@link DomElement} ignoring node's prefix and
     * namespace
     */
    public DomNode? set_named_item (DomNode node) throws GLib.Error {
      if ((":" in (node as DomAttr).local_name)
          || (node as DomAttr).local_name == "")
        throw new DomError.INVALID_CHARACTER_ERROR (_("Invalid attribute name: %s"), (node as DomAttr).local_name);
      if (!(node is DomAttr))
        throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid node type. DomAttr was expected"));
      set ((node as DomAttr).local_name, node.node_value);
      return new GomAttr (_element, (node as DomAttr).local_name, node.node_value);
    }
    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 GomAttr (_element, name, v);
      unset (name);
      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 = _element.lookup_prefix (namespace_uri);
      if (nsp == null || nsp == "") return null;
      var v = get (nsp+":"+local_name);
      if (v == null) return null;
      var n = new GomAttr.namespace (_element, namespace_uri, nsp, local_name, v);
      unset (nsp+":"+local_name);
      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 = _element.lookup_prefix (namespace_uri);
      if (nsp == null) return null;
      var v = get (nsp+":"+local_name);
      if (v == null) return null;
      var n = new GomAttr.namespace (_element, namespace_uri, nsp, local_name, v);
      return n;
    }
    // Introduced in DOM Level 2:
    public DomNode? set_named_item_ns (DomNode node) throws GLib.Error {
      if ((node as DomAttr).local_name == "" || ":" in (node as DomAttr).local_name)
        throw new DomError.INVALID_CHARACTER_ERROR (_("Invalid attribute name: %s"),(node as DomAttr).local_name);
      if (!(node is DomAttr))
        throw new DomError.HIERARCHY_REQUEST_ERROR (_("Invalid node type. DomAttr was expected"));

      if ((node as DomAttr).prefix == "xmlns"
          && (node as DomAttr).namespace_uri != "http://www.w3.org/2000/xmlns/"
              && (node as DomAttr).namespace_uri != "http://www.w3.org/2000/xmlns")
        throw new DomError.NAMESPACE_ERROR (_("Namespace attributes prefixed with xmlns should use a namespace uri http://www.w3.org/2000/xmlns"));
      if ((node as DomAttr).prefix == ""
          || (node as DomAttr).prefix == null
          && (node as DomAttr).local_name != "xmlns") {
        string s = (node as DomAttr).local_name;
        if ((node as DomAttr).namespace_uri != null)
          s += "=<"+(node as DomAttr).namespace_uri+">";
        throw new DomError.NAMESPACE_ERROR (_("Namespaced attributes should provide a non-null, non-empty prefix: %s"),s);
      }
      if ((node as DomAttr).prefix == "xmlns"
          && (node as DomAttr).local_name == "xmlns")
        throw new DomError.NAMESPACE_ERROR (_("Invalid namespace attribute's name."));
      if ((node as DomAttr).prefix == "xmlns"
          || (node as DomAttr).local_name == "xmlns") {
        string asp = _element.get_attribute_ns (node.node_value,
                                          (node as DomAttr).local_name);
        if (asp != null) return node;
      }
      if ((node as DomAttr).namespace_uri == "http://www.w3.org/2000/xmlns/"
          || (node as DomAttr).namespace_uri == "http://www.w3.org/2000/xmlns") {
#if DEBUG
        GLib.message ("Searching for duplicated ns..."+node.node_value
                  +" NS: "+(node as DomAttr).namespace_uri);
#endif
        if ((node as DomAttr).local_name == "xmlns") {
          string ns_uri = _element.lookup_namespace_uri (null);
          if (ns_uri != null && ns_uri != node.node_value) {
#if DEBUG
            GLib.message ("Error: NSURI: "+ns_uri+" NSURI Attr:"+node.node_value);
#endif
            throw new DomError.NAMESPACE_ERROR
                      (_("Redefinition of default namespace for %s")
                        .printf (node.node_value));
          }
        }
        if ((node as DomAttr).prefix == "xmlns") {
#if DEBUG
          GLib.message ("Attr Prefix = "+(node as DomAttr).prefix
                      + "Attr Name: "+(node as DomAttr).local_name);
#endif
          string nsprefix = _element.lookup_prefix (node.node_value);
          string nsuri = _element.lookup_namespace_uri ((node as DomAttr).local_name);

#if DEBUG
          if (nsprefix != null || nsuri != null)
            GLib.message ("Ns Prefix = "+nsprefix
                      + "Ns URI: "+nsuri);
#endif
          if ((nsprefix != null || nsuri != null)
              && (nsprefix != (node as DomAttr).local_name
                  || nsuri != node.node_value)) {
#if DEBUG
            GLib.message ("Prefix: "+nsprefix+" Prefix Attr:"+(node as DomAttr).local_name);
#endif
            throw new DomError.NAMESPACE_ERROR
                      (_("Redefinition of namespace's prefix for %s")
                        .printf (node.node_value));
          }
        }
      }
      if ((node as DomAttr).namespace_uri != "http://www.w3.org/2000/xmlns/"
          && (node as DomAttr).namespace_uri != "http://www.w3.org/2000/xmlns"){
#if DEBUG
        GLib.message ("No namespace attribute: "+(node as DomAttr).namespace_uri
                    + ":"+(node as DomAttr).prefix);
#endif
        string nsn = _element.lookup_namespace_uri ((node as DomAttr).prefix);
        string nspn = _element.lookup_prefix (nsn);
        if (nspn != (node as DomAttr).prefix
            && nsn != (node as DomAttr).namespace_uri)
          throw new DomError.NAMESPACE_ERROR
                  (_("Trying to add an attribute with an undefined namespace prefix"));
        nspn = _element.lookup_prefix ((node as DomAttr).namespace_uri);
        nsn = _element.lookup_namespace_uri (nspn);
        if (nspn != (node as DomAttr).prefix
            && nsn != (node as DomAttr).namespace_uri)
          throw new DomError.NAMESPACE_ERROR
                  (_("Trying to add an attribute with a non found namespace URI"));
      }

      string p = "";
      if ((node as DomAttr).prefix != null
          && (node as DomAttr).prefix != "")
        p = (node as DomAttr).prefix + ":";
#if DEBUG
      GLib.message ("Attribute to set: "+p+(node as DomAttr).local_name
                    +"="+node.node_value);
#endif
      set (p+(node as DomAttr).local_name,
          node.node_value);

      var attr = new GomAttr.namespace (_element,
                                    (node as DomAttr).namespace_uri,
                                    (node as DomAttr).prefix,
                                    (node as DomAttr).local_name,
                                    node.node_value);
      return attr;
    }
  }
  public DomNamedNodeMap attributes { owned get { return (DomNamedNodeMap) _attributes; } }
  public string? get_attribute (string name) {
#if DEBUG
    message ("Searching attribute: "+name);
#endif
    string s = (this as GomObject).get_attribute (name);
#if DEBUG
    message ("Found as GomObject Property?: "+(s != null).to_string ());
#endif
    if (s != null) return s;
    return _attributes.get (name);
  }
  public string? get_attribute_ns (string? namespace_uri, string local_name) {
    string nsp = null;
    if (namespace_uri == "http://www.w3.org/2000/xmlns/"
        && local_name != "xmlns")
      nsp = "xmlns";
    else
      nsp = lookup_prefix (namespace_uri);
    string name = local_name;
    if (nsp != null)
      name = nsp + ":" + local_name;
    return _attributes.get (name);
  }
  public void set_attribute (string name, string value) throws GLib.Error {
    bool res = (this as GomObject).set_attribute (name, value);
    if (res) return;
    var a = new GomAttr (this, name, value);
    attributes.set_named_item (a);
  }
  public void set_attribute_ns (string? namespace_uri,
                                string name, string value) throws GLib.Error {
    string p = "";
    string n = name;
    if (":" in name) {
      var s = name.split (":");
      if (s.length != 2)
        throw new DomError.NAMESPACE_ERROR (_("Invalid attribute name. Just one prefix is allowed"));
      p = s[0];
      n = s[1];
      if (":" in n)
        throw new DomError.NAMESPACE_ERROR (_("Invalid attribute name. Invalid use of colon: %s"), n);
    } else
      n = name;
    if (namespace_uri == null && p == "")
       throw new DomError.NAMESPACE_ERROR (_("Invalid namespace. If prefix is null, namespace URI should not be null"));
    if (p == "xml" && namespace_uri != "http://www.w3.org/2000/xmlns/")
       throw new DomError.NAMESPACE_ERROR (_("Invalid namespace. If prefix is xml, namespace URI should be http://www.w3.org/2000/xmlns/"));
    if (p == "xmlns" && (namespace_uri != "http://www.w3.org/2000/xmlns/"
            && namespace_uri != "http://www.w3.org/2000/xmlns"))
       throw new DomError.NAMESPACE_ERROR (_("Invalid namespace. If attribute's prefix is xmlns, namespace URI should be http://www.w3.org/2000/xmlns/"));
    if (p == "" && n == "xmlns"
        && (namespace_uri != "http://www.w3.org/2000/xmlns/"
            && namespace_uri != "http://www.w3.org/2000/xmlns"))
       throw new DomError.NAMESPACE_ERROR (_("Invalid namespace. If attribute's name is xmlns, namespace URI should be http://www.w3.org/2000/xmlns/"));
    if (p == "" && n != "xmlns" && n != "xml")
      throw new DomError.NAMESPACE_ERROR (_("Invalid attribute name. No prefixed attributes should use xmlns name"));
    var a = new GomAttr.namespace (this, namespace_uri, p, n, value);
    try { _attributes.set_named_item_ns (a); }
    catch (GLib.Error e) {
      throw new DomError.NAMESPACE_ERROR (_("Setting namespaced property error: ")+e.message);
    }
  }
  public void remove_attribute (string name) {
    if ((this as GomObject).remove_attribute (name)) return;
    try { attributes.remove_named_item (name); }
    catch (GLib.Error e)
      { warning (_("Removing attribute Error: ")+e.message); }
  }
  public void remove_attribute_ns (string? namespace_uri, string local_name) {
    try { attributes.remove_named_item_ns (namespace_uri, local_name); }
    catch (GLib.Error e)
      { warning (_("Removing namespaced attribute Error: ")+e.message); }
  }
  public bool has_attribute (string name) {
    return _attributes.has_key (name);
  }
  public bool has_attribute_ns (string? namespace_uri, string local_name) {
    var p = lookup_prefix (namespace_uri);
    if (p == null) return false;
    return attributes.has_key (p+":"+local_name);
  }


  public DomHTMLCollection get_elements_by_tag_name (string local_name) {
    var l = new GDomHTMLCollection ();
    //FIXME: quircks mode not considered
    foreach (GXml.DomNode n in child_nodes) {
      if (!(n is DomElement)) continue;
      if (n.node_name == local_name)
        l.add (n as DomElement);
      l.add_all ((n as DomElement).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.DomNode n in child_nodes) {
      if (!(n is DomElement)) continue;
      if (n.node_name == local_name
          && (n as DomElement).namespace_uri == namespace)
        l.add (n as DomElement);
      l.add_all ((n as DomElement).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.DomNode n in child_nodes) {
      if (!(n is DomElement)) continue;
      string cls = (n as DomElement).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 as DomElement);
          else
            l.insert (0, n as DomElement);
        }
      }
      l.add_all ((n as DomElement).get_elements_by_class_name (class_names));
    }
    return l;
  }
  /**
   * If true all children are parsed. If false, all its children are stored
   * as plain string in {@link unparsed}. In order to generate an XML tree
   * use {@link read_unparsed}.
   */
  public bool parse_children { get; set; default = true; }
  /**
   * Temporally stores, all unparsed children as plain string. See {@link parse_children}.
   *
   * If it is null, means all children have been already parsed.
   */
  public string unparsed { get; set; }
  /**
   * Parse all children, adding them to current node, stored in {@link unparsed}.
   * Once it finish, sets {@link unparsed} to null.
   */
  public void read_unparsed () throws GLib.Error {
    if (unparsed == null) return;
    var parser = new XParser (this);
    parser.read_child_nodes_string (unparsed, null);
    unparsed = null;
  }
}