1 module arrogant;
2 
3 import arrogant.c.modest;
4 
5 // Public enums & stuffs
6 public import arrogant.c.common;
7 
8 import std.traits;
9 import std.conv : to;
10 import std.typecons : Flag, Yes, No;
11 import std..string : toStringz; 
12 
13 /** Use this enum with `node.byAttribute()` search  */
14 enum AttributeSearchType
15 {
16    exact,            /// 
17    startsWith,       /// 
18    endsWith,         ///
19    contains,         ///
20    spaceSeparated,   ///
21    hypenSeparated    ///
22 }
23 
24 class ArrogantException : Exception 
25 {
26    this(uint err) 
27    { 
28       import std.conv : to; 
29       super("Arrogant exception: " ~ to!string(err)); 
30    }
31 }
32 
33 
34 /** An html attribute of a tag */
35 struct Attribute
36 {
37    /** The attribute key */
38    @property auto key() { return _key; }
39 
40    // The attribute value */
41    @property auto value()  { return _value; }
42 
43    private this(myhtml_tree_attr_t* attr)
44    {
45       {
46          size_t length; 
47          _key = myhtml_attribute_key(attr, &length)[0..length].to!string; 
48       }
49 
50       {
51          size_t length; 
52          _value = myhtml_attribute_value(attr, &length)[0..length].to!string;  
53       }
54    }
55 
56    @disable this();
57    
58    private string _key;
59    private string _value;
60 }
61 
62 
63 
64 /** A HTML Node */
65 struct Node
66 {
67    @disable this();
68 
69    /// Check if node is null / empty   
70    bool isNull() { return myhtml_tree_node == null; }
71 
72 
73    /** 
74    * Get the tag id for this node (ex: a, div, body, ...) 
75    * Examples:
76    * --------------------
77    * tree.body.tag.writeln(); // prints "body"
78    * --------------------
79    */
80    MyHtmlTagId  tagId() { return cast(MyHtmlTagId )myhtml_tree_node.tag_id; }
81    string  tag() { return tagId.to!string; } /// Ditto
82 
83    /** 
84    * "in" operator to check for an attribute inside a node 
85    * Examples:
86    * --------------------
87    * if ("href" in node) writeln("Link: ", node["href"]);
88    * --------------------
89    */ 
90    bool opBinaryRight(string op)(string key) if (op == "in")
91    {
92       auto attr = myhtml_attribute_by_key (myhtml_tree_node, key.toStringz, key.length);
93       return attr !is null;
94    }
95 
96    /** Read an attribute from node */
97    auto opIndex(string attribute) 
98    { 
99       import std.typecons : Nullable;
100 
101       Nullable!string value;
102       auto attr = myhtml_attribute_by_key (myhtml_tree_node, attribute.toStringz, attribute.length);
103 
104       if (attr !is null) 
105       {
106          size_t length; 
107          auto v = myhtml_attribute_value(attr, &length); 
108          return Nullable!string(v[0..length].to!string);
109       }
110 
111       return value;
112    }
113 
114    /** Write an attribute */
115    auto opIndexAssign(string value, string key) 
116    { 
117       removeAttribute(key);
118       myhtml_attribute_add (myhtml_tree_node, key.toStringz, key.length, value.toStringz, value.length, MyEncodingList.default_);
119       return value;
120    }
121 
122    auto opIndexAssign(typeof(null) value, string key) 
123    { 
124       removeAttribute(key);
125       myhtml_attribute_add (myhtml_tree_node, key.toStringz, key.length, null, 0, MyEncodingList.default_);
126       return value;
127    }
128 
129    /** 
130       Remove an attribute 
131       Returns: `true` if attribute exists `false` otherwise.
132    */
133    bool removeAttribute(string key)
134    {
135       auto attr = myhtml_attribute_by_key (myhtml_tree_node, key.toStringz, key.length);
136       if (attr !is null)
137       {
138          myhtml_attribute_delete(myhtml_node_tree(myhtml_tree_node), myhtml_tree_node, attr);
139          return true;
140       }
141 
142       return false;
143    }
144 
145    /** Remove node from tree and delete it */
146    void deleteNode() { myhtml_node_delete_recursive(myhtml_tree_node); }
147    
148    ///
149    Node firstChild()
150    {
151       return Node(myhtml_node_child(myhtml_tree_node));
152    }
153 
154    ///
155    Node lastChild()
156    {
157       return Node(myhtml_node_last_child(myhtml_tree_node));
158    }
159 
160    ///
161    auto parent()
162    {
163       return Node(myhtml_node_parent(myhtml_tree_node));
164    }
165 
166    ///
167    auto next()
168    {
169       return Node(myhtml_node_next(myhtml_tree_node));
170    }
171 
172    ///
173    auto previous()
174    {
175       return Node(myhtml_node_prev(myhtml_tree_node));
176    }
177 
178    /*
179       Get children of this node.
180       Returns: a lazy `ChildrenRange`. If you want to edit children, convert to array before.
181    */
182    auto children()
183    {
184       struct ChildrenRange
185       {
186          @disable this();
187          
188          private this(myhtml_tree_node_t *n) { parent = n; current = myhtml_node_child(parent); }
189 
190          @property empty() {  return current == null; }
191          @property Node front() { return Node(current); }
192          void popFront() { current = myhtml_node_next(current); }
193 
194          void opAssign(ChildrenRange rhs) 
195          { 
196             current = rhs.current;
197             parent = rhs.parent;
198          }
199 
200          private:
201          myhtml_tree_node_t *current;
202          myhtml_tree_node_t *parent;
203       }
204 
205       return ChildrenRange(myhtml_tree_node);
206    }
207 
208    /** All node's attributes 
209       Returns: a lazy range of Attributes
210    */
211    auto attributes()
212    {
213       struct AttributesRange
214       {
215          this(myhtml_tree_node_t *n) { parent = n; current = myhtml_node_attribute_first(parent); }
216 
217          @property empty() {  return current == null; }
218          auto front() { return Attribute(current); }
219          void popFront() { current = myhtml_attribute_next(current); }
220 
221          private:
222          myhtml_tree_attr_t *current;
223          myhtml_tree_node_t *parent;
224       }
225 
226       return AttributesRange(myhtml_tree_node);
227    }
228 
229    /** Get the text of this node. Only for text nodes! */
230    @property string text()
231    {
232       return myhtml_node_text(myhtml_tree_node, null).to!string;
233    }
234 
235    /** Set the text of this node. Only for text nodes! */
236    @property void text(string s)
237    {
238       myhtml_node_text_set(myhtml_tree_node, s.toStringz, s.length, MyEncodingList.default_);
239    }
240 
241    /** Return node html representation */
242    string toString() 
243    {
244       return innerHTML();
245    }
246 
247    /// Ditto
248    @property string innerHTML()
249    {
250       mycore_string_raw_t str_raw;
251       mycore_string_raw_clean_all(&str_raw);
252       scope(exit) mycore_string_raw_destroy(&str_raw, false);
253 
254       if(myhtml_serialization_tree_buffer(myhtml_tree_node, &str_raw)) return "";
255       return str_raw.data[0..str_raw.length].to!string;
256    }
257 
258    /** Set node html. All children will be deleted. */
259    @property void innerHTML(string s)
260    {
261       // Create a new tree to parse fragment
262       auto tree = Tree(myhtml_tree_get_myhtml(myhtml_node_tree(myhtml_tree_node)));
263       tree.parseFragment(s);
264 
265       // Clone fragment and move to current tree
266       auto cloned = tree.first.clone(myhtml_node_tree(myhtml_tree_node));
267 
268       // Delete all children!
269 
270       
271       myhtml_tree_node_t*[] toDelete;
272       
273       for(auto current = myhtml_node_child(myhtml_tree_node); current != null; current = myhtml_node_next(current))
274          toDelete ~= current;
275 
276       foreach(n; toDelete)
277          myhtml_node_delete_recursive(n);
278       
279       // Append new child
280       appendChild(cloned);
281    }
282 
283    /** Set node innerText. All children will be deleted. */
284    @property void innerText(string s)
285    {
286       // Create a text node
287       auto text_node = myhtml_node_create (
288          myhtml_node_tree(myhtml_tree_node),
289          MyHtmlTagId ._text,
290          MyHtmlNamespace.html
291       );
292 
293       Node nodeToAppend = Node(text_node);
294       nodeToAppend.text = s;
295 
296       // Delete all children!
297       
298       myhtml_tree_node_t*[] toDelete;
299       
300       for(auto current = myhtml_node_child(myhtml_tree_node); current != null; current = myhtml_node_next(current))
301          toDelete ~= current;
302 
303       foreach(n; toDelete)
304          myhtml_node_delete_recursive(n);
305 
306       // Append new child
307       appendChild(nodeToAppend);
308    }
309 
310    @property string innerText()
311    {
312       import std.container.dlist;
313       import std.array : Appender, array;
314       import std.algorithm : map;
315 
316       auto appender = Appender!string();
317 
318       auto toExplore = DList!(myhtml_tree_node_t*)();
319       toExplore.insertBack(myhtml_tree_node);
320 
321       while(!toExplore.empty)
322       {
323          auto current = Node(toExplore.front);
324          toExplore.removeFront;
325          if (current.tagId == MyHtmlTagId._text) appender ~= current.text();
326          else toExplore.insertFront(current.children.map!(x => x.myhtml_tree_node));
327       }
328 
329       return appender.data;
330    }
331 
332    /** 
333       Create a copy of this node owned by another tree
334       Returns: a `Node` owned by `destination`
335    */
336    Node clone(Tree destination) { return clone(destination.myhtml_tree); }
337    
338    /// Create a copy of this node
339    Node clone() { return clone(myhtml_node_tree(myhtml_tree_node)); }
340 
341    ///
342    bool isSelfClosing() { return myhtml_node_is_close_self(myhtml_tree_node); }
343    
344    ///
345    bool isVoidElement() { return myhtml_node_is_void_element(myhtml_tree_node); }
346    
347    /** Detach node from tree without destroying */
348    void detach() { myhtml_node_remove(myhtml_tree_node); }
349 
350    /// Fast way to append a text node
351    void appendText(string s) 
352    {
353       // Create a text node
354       auto text_node = myhtml_node_create (
355          myhtml_node_tree(myhtml_tree_node),
356          MyHtmlTagId ._text,
357          MyHtmlNamespace.html
358       );
359 
360       Node nodeToAppend = Node(text_node);
361       nodeToAppend.text = s;
362       appendChild(nodeToAppend);
363    }
364 
365    /// Fast way to append a comment node
366    void appendComment(string s) 
367    {
368       // Create a text node
369       auto text_node = myhtml_node_create (
370          myhtml_node_tree(myhtml_tree_node),
371          MyHtmlTagId ._comment,
372          MyHtmlNamespace.html
373       );
374 
375       Node nodeToAppend = Node(text_node);
376       nodeToAppend.text = s;
377       appendChild(nodeToAppend);
378    }
379 
380    ///
381    void appendChild(Node n) { n.detach(); myhtml_node_append_child(myhtml_tree_node, n.myhtml_tree_node); }
382    
383    ///
384    void insertBefore(Node n) { myhtml_node_insert_before(myhtml_tree_node, n.myhtml_tree_node); }
385    
386    ///
387    void insertAfter(Node n) { myhtml_node_insert_after(myhtml_tree_node, n.myhtml_tree_node); }
388    
389    ///
390    void insertToAppropriatePlace(Node n) { myhtml_node_insert_to_appropriate_place(myhtml_tree_node, n.myhtml_tree_node); }
391 
392    /** 
393       Search children using a css 3.1 selector 
394       Returns: a lazy range of nodes
395       See_Also: byAttribute, byAttributeKey, byTagName, byClass, byId
396    */
397    auto byCssSelector(string selector)
398    {
399       import std.exception : enforce;
400 
401       auto mycss  = mycss_create();
402       mycss_init(mycss);
403      
404       auto entry  = mycss_entry_create();
405       mycss_entry_init(mycss, entry);
406 
407       auto finder = modest_finder_create_simple();
408 
409       mystatus_t out_status;
410       mycss_selectors_list_t *list = mycss_selectors_parse
411       (
412          mycss_entry_selectors(entry),
413          MyEncodingList.default_,
414          selector.toStringz, selector.length,
415          &out_status
416       );
417 
418       enforce(list != null && ((list.flags & mycss_selectors_flags.MyCSS_SELECTORS_FLAGS_SELECTOR_BAD) == 0), "Can't compile css selector: " ~ selector);
419 
420       myhtml_collection_t* collection = null;
421      
422       modest_finder_by_selectors_list(finder, myhtml_tree_node, list, &collection);
423 
424       // Free resources!
425       mycss_selectors_list_destroy(mycss_entry_selectors(entry), list, true);
426       modest_finder_destroy(finder, true);
427       mycss_entry_destroy(entry, true);
428       mycss_destroy(mycss, true);
429 
430       return NodeRange(collection, myhtml_node_tree(myhtml_tree_node));
431    }
432 
433    /** 
434       Search children by tag name
435       Returns: a lazy range of nodes
436       See_Also: byAttribute, byAttributeKey, byClass, byId, byCssSelector
437    */
438    auto byTagName(MyHtmlTagId  name)
439    {
440       mystatus_t status;
441       myhtml_collection_t* myhtml_collection = myhtml_collection_create(0, null);
442       auto collection = NodeRange(myhtml_get_nodes_by_tag_id_in_scope(myhtml_node_tree(myhtml_tree_node), myhtml_collection, myhtml_tree_node, name, &status), myhtml_node_tree(myhtml_tree_node));
443       
444       if (MYHTML_FAILED(status)) throw new ArrogantException(status);
445 
446       return collection;
447    }
448 
449    /// Ditto
450    auto byTagName(string name)
451    {
452       mystatus_t status;
453       myhtml_collection_t* myhtml_collection = myhtml_collection_create(0, null);
454       auto collection = NodeRange(myhtml_get_nodes_by_name_in_scope(myhtml_node_tree(myhtml_tree_node), myhtml_collection, myhtml_tree_node, name.toStringz, name.length, &status), myhtml_node_tree(myhtml_tree_node));
455       if (MYHTML_FAILED(status)) throw new ArrogantException(status);
456 
457       return collection;
458    }
459 
460     /** 
461       Search children by class (space separated)
462       Returns: a lazy range of nodes
463       See_Also: byAttribute, byAttributeKey, byTagName, byId, byCssSelector
464    */
465    auto byClass(string className) { return byAttribute!(AttributeSearchType.spaceSeparated)("class", className);}
466    
467     /** 
468       Search children by id
469       Returns: a lazy range of nodes
470       See_Also: byAttribute, byAttributeKey, byTagName, byClass, byCssSelector
471    */
472    auto byId(string id) { return byAttribute("id", id);}
473    
474    /** 
475       Search children with a specified attribute
476       Returns: a lazy range of nodes
477       See_Also: byAttribute, byTagName, byClass, byId, byCssSelector
478    */
479    auto byAttributeKey(string name)
480    {
481       mystatus_t status;
482       myhtml_collection_t* myhtml_collection = myhtml_collection_create(0, null);
483       auto collection = NodeRange(myhtml_get_nodes_by_attribute_key(myhtml_node_tree(myhtml_tree_node), myhtml_collection, myhtml_tree_node, name.toStringz, name.length, &status), myhtml_node_tree(myhtml_tree_node));
484       if (MYHTML_FAILED(status)) throw new ArrogantException(status);
485 
486       return collection;
487    }
488 
489    /** 
490       Search children by tag attribute key/val. 
491       Returns: a lazy range of nodes
492       See_Also: byAttributeKey, byTagName, byClass, byId, byCssSelector
493    */
494    auto byAttribute(AttributeSearchType st = AttributeSearchType.exact, Flag!"caseInsensitive" caseInsensitive = No.caseInsensitive)(string key, string value)
495    {
496       mystatus_t status;
497       typeof(&myhtml_get_nodes_by_attribute_value) callback;
498 
499       final switch(st)
500       {
501          case AttributeSearchType.exact: callback = &myhtml_get_nodes_by_attribute_value; break;
502          case AttributeSearchType.startsWith: callback = &myhtml_get_nodes_by_attribute_value_begin; break;
503          case AttributeSearchType.endsWith: callback = &myhtml_get_nodes_by_attribute_value_end; break;
504          case AttributeSearchType.contains: callback = &myhtml_get_nodes_by_attribute_value_contain; break;
505          case AttributeSearchType.spaceSeparated : callback = &myhtml_get_nodes_by_attribute_value_whitespace_separated; break;
506          case AttributeSearchType.hypenSeparated: callback = &myhtml_get_nodes_by_attribute_value_hyphen_separated; break;
507       }
508 
509       myhtml_collection_t* myhtml_collection = myhtml_collection_create(0, null);
510 
511       auto collection = NodeRange 
512       (
513          callback
514          (
515             myhtml_node_tree(myhtml_tree_node),
516             myhtml_collection,
517             myhtml_tree_node,
518             caseInsensitive == Yes.caseInsensitive,
519             key.toStringz, key.length,  value.toStringz, value.length, 
520             &status
521          ),
522          myhtml_node_tree(myhtml_tree_node)
523       );
524 
525       if (MYHTML_FAILED(status)) throw new ArrogantException(status);
526       return collection;
527    }
528 
529    /** Create a new html node */
530    this(ref Tree tree, MyHtmlTagId  tag, MyHtmlNamespace ns = MyHtmlNamespace.html)
531    {
532       myhtml_tree_node = myhtml_node_create (
533          tree.myhtml_tree,
534          tag,
535          ns
536       );
537 
538       Tree.acquire(tree.myhtml_tree);
539    }
540 
541    this(this) { Tree.acquire(myhtml_node_tree(myhtml_tree_node)); }
542    
543    void opAssign(Node rhs) 
544    { 
545       Tree.acquire(myhtml_node_tree(rhs.myhtml_tree_node));
546       Tree.release(myhtml_node_tree(myhtml_tree_node));
547       myhtml_tree_node = rhs.myhtml_tree_node;
548    }
549 
550    ~this() { Tree.release(myhtml_node_tree(myhtml_tree_node)); }
551 
552    private:
553 
554    this(myhtml_tree_node_t *node) 
555    { 
556       myhtml_tree_node = node; 
557       Tree.acquire(myhtml_node_tree(node));
558    }
559 
560    Node clone(myhtml_tree_t* destination)
561    {
562 
563       struct CopyQueueItem
564       {
565          myhtml_tree_node_t* destParent;  // Where node will be appended
566          myhtml_tree_node_t* toCopy;      // The node to copy
567       }
568       
569       import std.container.dlist;
570       auto copyQueue = DList!CopyQueueItem();
571 
572       // Clone a single node without children
573       myhtml_tree_node_t* cloneNode(myhtml_tree_t* _destination, myhtml_tree_node_t* _node)
574       {
575          // Create a new node rooted on destination tree
576          auto ret = myhtml_node_create (
577             _destination,
578             _node.tag_id,
579             _node.ns
580          );
581 
582          // Copy text if present
583          {
584             size_t textLength;
585             auto text = myhtml_node_text(_node, &textLength);
586             myhtml_node_text_set(ret, text, textLength, MyEncodingList.default_);
587          }
588 
589          // Clone attributes
590          for (auto attribute = myhtml_node_attribute_first(_node); attribute != null; attribute = myhtml_attribute_next(attribute))
591          {
592             size_t keyLength, valueLength; 
593             auto k = myhtml_attribute_key(attribute, &keyLength); 
594             auto v = myhtml_attribute_value(attribute, &valueLength); 
595             myhtml_attribute_add (ret, k, keyLength, v, valueLength, MyEncodingList.default_);
596          }
597 
598          // Return the filled node.
599          return ret;
600       }
601 
602       // Clone the root
603       auto destinationRoot = cloneNode(destination, myhtml_tree_node);
604       auto currentNode = myhtml_tree_node;
605       auto currentDestNode = destinationRoot;
606     
607       while(true)
608       {
609          // Add children of current node to queue
610          for (auto child = myhtml_node_child(currentNode); child != null; child = myhtml_node_next(child))
611             copyQueue.insertBack(CopyQueueItem(currentDestNode, child));
612 
613          if (copyQueue.empty) break;
614 
615          // Get the first item in queue
616          auto destParent = copyQueue.front.destParent;
617          currentNode = copyQueue.front.toCopy;
618 
619          // Remove first element of list
620          copyQueue.removeFront();
621          
622          // Clone the children and add to new parent
623          currentDestNode = cloneNode(destination, currentNode);
624          myhtml_node_append_child(destParent, currentDestNode);
625       }
626 
627       return Node(destinationRoot);
628    }
629 
630    myhtml_tree_node_t* myhtml_tree_node = null;
631 }
632 
633 import std.stdio;
634 
635 
636 
637 /** A lazy range of nodes, usually returned by a search */
638 struct NodeRange
639 {
640    @disable this();
641 
642    Node opIndex(size_t i)
643    {
644       return Node(myhtml_collection.list[i+idx]);
645    }
646    
647    size_t length() { if (myhtml_collection) return myhtml_collection.length; return 0; }
648 
649    @property Node front() { if (empty) assert(0, "Can't read nodes from an empty collection"); return this[0]; }
650    @property bool empty() { return idx >= length(); }
651 
652    void popFront() { idx++; }
653 
654 
655    ~this() { Tree.release(myhtml_tree); }
656    this(this) { Tree.acquire(myhtml_tree); }
657    
658    void opAssign(NodeRange rhs) 
659    { 
660       Tree.acquire(rhs.myhtml_tree);
661       Tree.release(myhtml_tree);
662       myhtml_tree = rhs.myhtml_tree;
663       myhtml_collection = rhs.myhtml_collection;
664       idx = rhs.idx;
665    }
666 
667 private:
668 
669    this(myhtml_collection_t* collection, myhtml_tree_t* tree)
670    {
671       myhtml_collection = collection;
672       myhtml_tree = tree;
673       Tree.acquire(myhtml_tree);
674    }
675 
676    myhtml_collection_t* myhtml_collection;
677    myhtml_tree_t* myhtml_tree;
678 
679    size_t idx = 0;
680 }
681 
682 /** A html tree */
683 struct Tree
684 {
685    /// Create a new node owned by this tree
686    Node createNode(MyHtmlTagId  tag, MyHtmlNamespace ns = MyHtmlNamespace.html) { return Node(this, tag, ns); }
687    
688    /// Fast way to create a text node
689    Node createTextNode(string text) { auto n = createNode(MyHtmlTagId._text); n.text = text; return n; }
690 
691    /// Fast way to create a comment node
692    Node createCommentNode(string text) { auto n = createNode(MyHtmlTagId._comment); n.text = text; return n; }
693 
694    /// See: `Node.byXXXX`
695    auto byClass(string className) { return document.byClass(className); }
696 
697    
698    /// Ditto
699    auto byId(string id) { return document.byId(id);}
700 
701 
702    /// Ditto
703    auto byCssSelector(string selector) { return document.byCssSelector(selector); }
704 
705    /// Ditto
706    auto byTagName(MyHtmlTagId  name) { return document.byTagName(name); }
707 
708    /// Ditto
709    auto byTagName(string name) { return document.byTagName(name); }
710 
711    /// Ditto
712    auto byAttributeKey(string name) { return document.byAttributeKey(name); }
713 
714    /// Ditto
715    auto byAttribute(AttributeSearchType st = AttributeSearchType.exact, Flag!"caseInsensitive" caseInsensitive = No.caseInsensitive)(string key, string value)
716    {
717       return document.byAttribute!(st, caseInsensitive)(key, value);
718    }
719 
720    /// The document root
721    auto document()
722    {
723       return Node(myhtml_tree_get_document(myhtml_tree));
724    }
725 
726    /// The html node
727    auto html()
728    {
729       return Node(myhtml_tree_get_node_html(myhtml_tree));
730    }
731 
732    /// The head node
733    auto head()
734    {
735       return Node(myhtml_tree_get_node_head(myhtml_tree));
736    }
737 
738    /// The body node
739    auto body()
740    {
741       return Node(myhtml_tree_get_node_body(myhtml_tree));
742    }
743 
744    /// Return the first node
745    auto first()
746    {
747       return Node(myhtml_node_first(myhtml_tree));
748    }
749 
750    string toString() { Node tmp = first(); return tmp.toString(); }
751 
752    this(this) 
753    { 
754       acquire(myhtml_tree);
755       valid = true;
756    }
757    
758    void opAssign(Tree rhs) 
759    {      
760       acquire(rhs.myhtml_tree);
761       
762       if (valid) 
763          release(myhtml_tree);
764       
765       myhtml_tree = rhs.myhtml_tree;
766       valid = true;
767    }
768 
769    ~this() { 
770       if (valid)
771          release(myhtml_tree);
772    }
773 
774    @property isValid() { return valid; }
775 
776 private:
777    
778    
779    void parse(T)(T html, MyEncodingList encoding = MyEncodingList.default_) if (isSomeString!T)
780    {
781       auto status = myhtml_parse(myhtml_tree, encoding, html.toStringz, html.length);
782       if (MYHTML_FAILED(status)) throw new ArrogantException(status);
783    }
784 
785    void parseFragment(T)(T html, MyHtmlTagId  wrap = MyHtmlTagId .div, MyEncodingList encoding = MyEncodingList.default_, MyHtmlNamespace ns = MyHtmlNamespace.html) if (isSomeString!T)
786    {
787       auto status = myhtml_parse_fragment (
788          myhtml_tree,
789          encoding,
790          html.toStringz, html.length,
791          wrap,
792          ns
793       );
794       if (MYHTML_FAILED(status)) throw new ArrogantException(status);
795    }
796    
797    this(ref Arrogant parent) { this(parent.myhtml); }
798    
799    this(myhtml_t* parent)
800    {
801       myhtml_tree = myhtml_tree_create();
802       auto status = myhtml_tree_init(myhtml_tree, parent);
803       if (MYHTML_FAILED(status)) throw new ArrogantException(status);
804       acquire(myhtml_tree);
805       valid = true;
806    }
807 
808    bool valid = false;
809    myhtml_tree_t* myhtml_tree = null;
810 
811    static size_t[myhtml_tree_t*]  refCount;
812 
813    static void acquire(myhtml_tree_t* ptr) { refCount[ptr]++; Arrogant.acquire(myhtml_tree_get_myhtml(ptr)); }
814    static void release(myhtml_tree_t* ptr) 
815    { 
816       size_t cnt = refCount[ptr];
817       assert(cnt > 0);
818       refCount[ptr] = cnt - 1;
819       myhtml_t* myhtml = myhtml_tree_get_myhtml(ptr);
820 
821       if (cnt == 1)
822       {
823          myhtml_tree_destroy(ptr);
824          refCount.remove(ptr);
825       }
826 
827       Arrogant.release(myhtml);
828    }
829 
830 }
831 
832 struct Arrogant
833 {
834    /// Parse a html document
835    Tree parse(T)(T html, MyEncodingList encoding = MyEncodingList.default_) if (isSomeString!T)
836    {
837       if (!myhtml) initArrogant();
838       Tree tree = Tree(myhtml);
839       tree.parse(html, encoding);
840       return tree;
841    }
842 
843    /// Parse a html fragment
844    Tree parseFragment(T)(T html, MyHtmlTagId  wrap = MyHtmlTagId .div, MyEncodingList encoding = MyEncodingList.default_, MyHtmlNamespace ns = MyHtmlNamespace.html,) if (isSomeString!T)
845    {
846       if (!myhtml) initArrogant();
847       Tree tree = Tree(myhtml);
848       tree.parseFragment(html, wrap, encoding, ns);
849       return tree;
850    }
851    
852    
853    void opAssign(Arrogant rhs) 
854    { 
855       acquire(rhs.myhtml);
856       
857       if (valid)
858          release(myhtml);
859       
860       myhtml = rhs.myhtml;
861       valid = true;
862    }
863 
864    ///
865    this(MyHtmlOptions options, size_t threadCount = 1, size_t queueSize = 0) 
866    { 
867       initArrogant(options, threadCount, queueSize);
868    }
869 
870    this(this) { acquire(myhtml); valid = true; }
871    
872    ~this() { if (valid) release(myhtml); }
873    
874    @property isValid() { return valid; }
875    
876    private:
877 
878    void initArrogant(MyHtmlOptions options = MyHtmlOptions.default_, size_t threadCount = 1, size_t queueSize = 0)
879    {
880       if (myhtml) return;
881       
882       myhtml = myhtml_create(); 
883       auto status = myhtml_init(myhtml, options, threadCount, queueSize);
884       
885       acquire(myhtml);
886 
887       if (MYHTML_FAILED(status))
888          throw new ArrogantException(status);
889 
890       valid = true;
891    }
892 
893    myhtml_t* myhtml = null;
894    bool valid = false;
895 
896    static size_t[myhtml_t*]  refCount;
897    static void acquire(myhtml_t* ptr) { refCount[ptr]++; }
898    static void release(myhtml_t* ptr) 
899    {
900       size_t cnt = refCount[ptr];
901    
902       assert(cnt > 0);
903       refCount[ptr] = cnt - 1;
904 
905       if (cnt == 1)
906       {
907          myhtml_destroy(ptr);
908          refCount.remove(ptr);
909       }
910    }
911    
912 }