|
@@ -2,27 +2,15 @@ using Xml;
|
|
|
using Invercargill;
|
|
using Invercargill;
|
|
|
using Invercargill.DataStructures;
|
|
using Invercargill.DataStructures;
|
|
|
|
|
|
|
|
-namespace Astralis.Document {
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Error domain for HTML document operations
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public errordomain HtmlError {
|
|
|
|
|
- PARSE_ERROR,
|
|
|
|
|
- FILE_NOT_FOUND,
|
|
|
|
|
- INVALID_HTML,
|
|
|
|
|
- NODE_NOT_FOUND,
|
|
|
|
|
- INVALID_OPERATION
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+namespace Astralis {
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Represents an HTML element node in the DOM with convenient manipulation methods
|
|
/// Represents an HTML element node in the DOM with convenient manipulation methods
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public class HtmlNode : GLib.Object {
|
|
|
|
|
|
|
+ public class MarkupNode : GLib.Object {
|
|
|
private Xml.Node* xml_node;
|
|
private Xml.Node* xml_node;
|
|
|
- private HtmlDocument document;
|
|
|
|
|
|
|
+ private MarkupDocument document;
|
|
|
|
|
|
|
|
- internal HtmlNode(HtmlDocument doc, Xml.Node* node) {
|
|
|
|
|
|
|
+ internal MarkupNode(MarkupDocument doc, Xml.Node* node) {
|
|
|
this.document = doc;
|
|
this.document = doc;
|
|
|
this.xml_node = node;
|
|
this.xml_node = node;
|
|
|
}
|
|
}
|
|
@@ -183,62 +171,62 @@ namespace Astralis.Document {
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the parent element, or null if this is the root
|
|
/// Gets the parent element, or null if this is the root
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode? parent {
|
|
|
|
|
|
|
+ public MarkupNode? parent {
|
|
|
owned get {
|
|
owned get {
|
|
|
var parent_node = xml_node->parent;
|
|
var parent_node = xml_node->parent;
|
|
|
if (parent_node == null || parent_node->type == ElementType.DOCUMENT_NODE) {
|
|
if (parent_node == null || parent_node->type == ElementType.DOCUMENT_NODE) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- return new HtmlNode(document, parent_node);
|
|
|
|
|
|
|
+ return new MarkupNode(document, parent_node);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the first child element of this node
|
|
/// Gets the first child element of this node
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode? first_element_child {
|
|
|
|
|
|
|
+ public MarkupNode? first_element_child {
|
|
|
owned get {
|
|
owned get {
|
|
|
var child = xml_node->first_element_child();
|
|
var child = xml_node->first_element_child();
|
|
|
- return child != null ? new HtmlNode(document, child) : null;
|
|
|
|
|
|
|
+ return child != null ? new MarkupNode(document, child) : null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the last child element of this node
|
|
/// Gets the last child element of this node
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode? last_element_child {
|
|
|
|
|
|
|
+ public MarkupNode? last_element_child {
|
|
|
owned get {
|
|
owned get {
|
|
|
var child = xml_node->last_element_child();
|
|
var child = xml_node->last_element_child();
|
|
|
- return child != null ? new HtmlNode(document, child) : null;
|
|
|
|
|
|
|
+ return child != null ? new MarkupNode(document, child) : null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the next sibling element
|
|
/// Gets the next sibling element
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode? next_element_sibling {
|
|
|
|
|
|
|
+ public MarkupNode? next_element_sibling {
|
|
|
owned get {
|
|
owned get {
|
|
|
var sibling = xml_node->next_element_sibling();
|
|
var sibling = xml_node->next_element_sibling();
|
|
|
- return sibling != null ? new HtmlNode(document, sibling) : null;
|
|
|
|
|
|
|
+ return sibling != null ? new MarkupNode(document, sibling) : null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the previous sibling element
|
|
/// Gets the previous sibling element
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode? previous_element_sibling {
|
|
|
|
|
|
|
+ public MarkupNode? previous_element_sibling {
|
|
|
owned get {
|
|
owned get {
|
|
|
var sibling = xml_node->previous_element_sibling();
|
|
var sibling = xml_node->previous_element_sibling();
|
|
|
- return sibling != null ? new HtmlNode(document, sibling) : null;
|
|
|
|
|
|
|
+ return sibling != null ? new MarkupNode(document, sibling) : null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets all child elements of this node
|
|
/// Gets all child elements of this node
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNodeList children {
|
|
|
|
|
|
|
+ public MarkupNodeList children {
|
|
|
owned get {
|
|
owned get {
|
|
|
- var list = new HtmlNodeList(document);
|
|
|
|
|
|
|
+ var list = new MarkupNodeList(document);
|
|
|
for (var child = xml_node->children; child != null; child = child->next) {
|
|
for (var child = xml_node->children; child != null; child = child->next) {
|
|
|
if (child->type == ElementType.ELEMENT_NODE) {
|
|
if (child->type == ElementType.ELEMENT_NODE) {
|
|
|
list.add_node(child);
|
|
list.add_node(child);
|
|
@@ -251,9 +239,9 @@ namespace Astralis.Document {
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Appends a new child element with the given tag name
|
|
/// Appends a new child element with the given tag name
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode append_child_element(string tag_name) {
|
|
|
|
|
|
|
+ public MarkupNode append_child_element(string tag_name) {
|
|
|
var new_node = xml_node->new_child(null, tag_name);
|
|
var new_node = xml_node->new_child(null, tag_name);
|
|
|
- return new HtmlNode(document, new_node);
|
|
|
|
|
|
|
+ return new MarkupNode(document, new_node);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -266,9 +254,9 @@ namespace Astralis.Document {
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Creates and appends a child element with text content
|
|
/// Creates and appends a child element with text content
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode append_child_with_text(string tag_name, string text) {
|
|
|
|
|
|
|
+ public MarkupNode append_child_with_text(string tag_name, string text) {
|
|
|
var new_node = xml_node->new_text_child(null, tag_name, text);
|
|
var new_node = xml_node->new_text_child(null, tag_name, text);
|
|
|
- return new HtmlNode(document, new_node);
|
|
|
|
|
|
|
+ return new MarkupNode(document, new_node);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -363,7 +351,7 @@ namespace Astralis.Document {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- internal HtmlDocument doc {
|
|
|
|
|
|
|
+ internal MarkupDocument doc {
|
|
|
get { return document; }
|
|
get { return document; }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -372,14 +360,15 @@ namespace Astralis.Document {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// A list of HTML nodes supporting iteration and manipulation
|
|
/// A list of HTML nodes supporting iteration and manipulation
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public class HtmlNodeList : GLib.Object {
|
|
|
|
|
- private HtmlDocument document;
|
|
|
|
|
|
|
+ public class MarkupNodeList : GLib.Object {
|
|
|
|
|
+ private MarkupDocument document;
|
|
|
private List<Xml.Node*> nodes;
|
|
private List<Xml.Node*> nodes;
|
|
|
|
|
|
|
|
- internal HtmlNodeList(HtmlDocument doc) {
|
|
|
|
|
|
|
+ internal MarkupNodeList(MarkupDocument doc) {
|
|
|
this.document = doc;
|
|
this.document = doc;
|
|
|
this.nodes = new List<Xml.Node*>();
|
|
this.nodes = new List<Xml.Node*>();
|
|
|
}
|
|
}
|
|
@@ -398,37 +387,37 @@ namespace Astralis.Document {
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets a node at the specified index
|
|
/// Gets a node at the specified index
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public new HtmlNode? get(int index) {
|
|
|
|
|
|
|
+ public new MarkupNode? get(int index) {
|
|
|
unowned List<Xml.Node*> item = nodes.nth((uint)index);
|
|
unowned List<Xml.Node*> item = nodes.nth((uint)index);
|
|
|
if (item == null) {
|
|
if (item == null) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- return new HtmlNode(document, item.data);
|
|
|
|
|
|
|
+ return new MarkupNode(document, item.data);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the first node, or null if empty
|
|
/// Gets the first node, or null if empty
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode? first {
|
|
|
|
|
|
|
+ public MarkupNode? first {
|
|
|
owned get {
|
|
owned get {
|
|
|
if (nodes.is_empty()) {
|
|
if (nodes.is_empty()) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
unowned List<Xml.Node*> item = nodes.first();
|
|
unowned List<Xml.Node*> item = nodes.first();
|
|
|
- return item != null ? new HtmlNode(document, item.data) : null;
|
|
|
|
|
|
|
+ return item != null ? new MarkupNode(document, item.data) : null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the last node, or null if empty
|
|
/// Gets the last node, or null if empty
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- public HtmlNode? last {
|
|
|
|
|
|
|
+ public MarkupNode? last {
|
|
|
owned get {
|
|
owned get {
|
|
|
if (nodes.is_empty()) {
|
|
if (nodes.is_empty()) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
unowned List<Xml.Node*> item = nodes.last();
|
|
unowned List<Xml.Node*> item = nodes.last();
|
|
|
- return item != null ? new HtmlNode(document, item.data) : null;
|
|
|
|
|
|
|
+ return item != null ? new MarkupNode(document, item.data) : null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -446,7 +435,7 @@ namespace Astralis.Document {
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public void add_class_all(string class_name) {
|
|
public void add_class_all(string class_name) {
|
|
|
foreach (var node in nodes) {
|
|
foreach (var node in nodes) {
|
|
|
- var wrapper = new HtmlNode(document, node);
|
|
|
|
|
|
|
+ var wrapper = new MarkupNode(document, node);
|
|
|
wrapper.add_class(class_name);
|
|
wrapper.add_class(class_name);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -456,7 +445,7 @@ namespace Astralis.Document {
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public void remove_class_all(string class_name) {
|
|
public void remove_class_all(string class_name) {
|
|
|
foreach (var node in nodes) {
|
|
foreach (var node in nodes) {
|
|
|
- var wrapper = new HtmlNode(document, node);
|
|
|
|
|
|
|
+ var wrapper = new MarkupNode(document, node);
|
|
|
wrapper.remove_class(class_name);
|
|
wrapper.remove_class(class_name);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -489,14 +478,14 @@ namespace Astralis.Document {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
- /// Enumerator for iterating over HtmlNodeList
|
|
|
|
|
|
|
+ /// Enumerator for iterating over MarkupNodeList
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public class Enumerator {
|
|
public class Enumerator {
|
|
|
- private HtmlDocument document;
|
|
|
|
|
|
|
+ private MarkupDocument document;
|
|
|
private unowned List<Xml.Node*> nodes;
|
|
private unowned List<Xml.Node*> nodes;
|
|
|
private unowned List<Xml.Node*>? current;
|
|
private unowned List<Xml.Node*>? current;
|
|
|
|
|
|
|
|
- internal Enumerator(HtmlDocument doc, List<Xml.Node*> nodes) {
|
|
|
|
|
|
|
+ internal Enumerator(MarkupDocument doc, List<Xml.Node*> nodes) {
|
|
|
this.document = doc;
|
|
this.document = doc;
|
|
|
this.nodes = nodes;
|
|
this.nodes = nodes;
|
|
|
this.current = null;
|
|
this.current = null;
|
|
@@ -511,11 +500,11 @@ namespace Astralis.Document {
|
|
|
return current != null;
|
|
return current != null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public HtmlNode? get() {
|
|
|
|
|
|
|
+ public MarkupNode? get() {
|
|
|
if (current == null) {
|
|
if (current == null) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- return new HtmlNode(document, current.data);
|
|
|
|
|
|
|
+ return new MarkupNode(document, current.data);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public void reset() {
|
|
public void reset() {
|
|
@@ -523,360 +512,4 @@ namespace Astralis.Document {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Represents an HTML document that can be loaded, manipulated, and rendered
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public class HtmlDocument : GLib.Object {
|
|
|
|
|
- private Xml.Doc* doc;
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Creates a new empty HTML document
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlDocument() {
|
|
|
|
|
- doc = new Xml.Doc();
|
|
|
|
|
- // Create basic HTML structure
|
|
|
|
|
- var html = doc->new_node(null, "html");
|
|
|
|
|
- doc->set_root_element(html);
|
|
|
|
|
-
|
|
|
|
|
- var head = html->new_child(null, "head");
|
|
|
|
|
- var meta = head->new_child(null, "meta");
|
|
|
|
|
- meta->set_prop("charset", "UTF-8");
|
|
|
|
|
- head->new_child(null, "title");
|
|
|
|
|
-
|
|
|
|
|
- html->new_child(null, "body");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Loads an HTML document from a file
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlDocument.from_file(string filepath) throws GLib.Error {
|
|
|
|
|
- var file = File.new_for_path(filepath);
|
|
|
|
|
- if (!file.query_exists()) {
|
|
|
|
|
- throw new HtmlError.FILE_NOT_FOUND("File not found: %s".printf(filepath));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint8[] contents;
|
|
|
|
|
- string etag_out;
|
|
|
|
|
- file.load_contents(null, out contents, out etag_out);
|
|
|
|
|
-
|
|
|
|
|
- parse_html((string)contents);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Loads an HTML document from a string
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlDocument.from_string(string html) throws GLib.Error {
|
|
|
|
|
- parse_html(html);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void parse_html(string html) throws GLib.Error {
|
|
|
|
|
- // Use XML parser with HTML recovery options
|
|
|
|
|
- // Note: libxml2's HTML parser is in a separate library
|
|
|
|
|
- // For now, we'll use the XML parser with some tolerance
|
|
|
|
|
- Parser.init();
|
|
|
|
|
-
|
|
|
|
|
- int options = (int)(ParserOption.RECOVER |
|
|
|
|
|
- ParserOption.NOERROR |
|
|
|
|
|
- ParserOption.NOWARNING |
|
|
|
|
|
- ParserOption.NOBLANKS |
|
|
|
|
|
- ParserOption.NONET);
|
|
|
|
|
-
|
|
|
|
|
- doc = Parser.read_memory(html, html.length, null, null, options);
|
|
|
|
|
-
|
|
|
|
|
- if (doc == null) {
|
|
|
|
|
- // Try parsing as a fragment wrapped in a basic structure
|
|
|
|
|
- string wrapped = "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"/></head><body>%s</body></html>".printf(html);
|
|
|
|
|
- doc = Parser.read_memory(wrapped, wrapped.length, null, null, options);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (doc == null) {
|
|
|
|
|
- throw new HtmlError.PARSE_ERROR("Failed to parse HTML document");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ~HtmlDocument() {
|
|
|
|
|
- if (doc != null) {
|
|
|
|
|
- delete doc;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Gets the root html element of the document
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNode? root {
|
|
|
|
|
- owned get {
|
|
|
|
|
- var root = doc->get_root_element();
|
|
|
|
|
- return root != null ? new HtmlNode(this, root) : null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Gets the head element of the document
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNode? head {
|
|
|
|
|
- owned get {
|
|
|
|
|
- var root = doc->get_root_element();
|
|
|
|
|
- if (root == null) return null;
|
|
|
|
|
-
|
|
|
|
|
- for (var child = root->children; child != null; child = child->next) {
|
|
|
|
|
- if (child->name == "head") {
|
|
|
|
|
- return new HtmlNode(this, child);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Gets the body element of the document
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNode? body {
|
|
|
|
|
- owned get {
|
|
|
|
|
- var root = doc->get_root_element();
|
|
|
|
|
- if (root == null) return null;
|
|
|
|
|
-
|
|
|
|
|
- for (var child = root->children; child != null; child = child->next) {
|
|
|
|
|
- if (child->name == "body") {
|
|
|
|
|
- return new HtmlNode(this, child);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Gets or sets the document title
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public string? title {
|
|
|
|
|
- owned get {
|
|
|
|
|
- var head_node = head;
|
|
|
|
|
- if (head_node == null) return null;
|
|
|
|
|
-
|
|
|
|
|
- for (var child = head_node.native->children; child != null; child = child->next) {
|
|
|
|
|
- if (child->name == "title") {
|
|
|
|
|
- return child->get_content();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- set {
|
|
|
|
|
- var head_node = head;
|
|
|
|
|
- if (head_node == null) return;
|
|
|
|
|
-
|
|
|
|
|
- // Find existing title or create one
|
|
|
|
|
- for (var child = head_node.native->children; child != null; child = child->next) {
|
|
|
|
|
- if (child->name == "title") {
|
|
|
|
|
- if (value != null) {
|
|
|
|
|
- child->set_content(value);
|
|
|
|
|
- } else {
|
|
|
|
|
- child->unlink();
|
|
|
|
|
- delete child;
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (value != null) {
|
|
|
|
|
- head_node.native->new_text_child(null, "title", value);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Selects elements using an XPath expression
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNodeList select(string xpath) {
|
|
|
|
|
- var context = new XPath.Context(doc);
|
|
|
|
|
- var result = context.eval(xpath);
|
|
|
|
|
-
|
|
|
|
|
- var list = new HtmlNodeList(this);
|
|
|
|
|
-
|
|
|
|
|
- if (result != null && result->nodesetval != null) {
|
|
|
|
|
- var nodeset = result->nodesetval;
|
|
|
|
|
- int len = nodeset->length();
|
|
|
|
|
- for (int i = 0; i < len; i++) {
|
|
|
|
|
- var node = nodeset->item(i);
|
|
|
|
|
- if (node != null) {
|
|
|
|
|
- list.add_node(node);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- delete result;
|
|
|
|
|
- return list;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Selects a single element using an XPath expression, returns first match or null
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNode? select_one(string xpath) {
|
|
|
|
|
- var results = select(xpath);
|
|
|
|
|
- return results.first;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Gets an element by its ID
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNode? get_element_by_id(string id) {
|
|
|
|
|
- return select_one("//*[@id='%s']".printf(id));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Gets all elements with a specific tag name
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNodeList get_elements_by_tag_name(string tag_name) {
|
|
|
|
|
- return select("//%s".printf(tag_name));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Gets all elements with a specific class name
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNodeList get_elements_by_class_name(string class_name) {
|
|
|
|
|
- return select("//*[contains(concat(' ', normalize-space(@class), ' '), ' %s ')]".printf(class_name));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Creates a new element with the given tag name
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlNode create_element(string tag_name) {
|
|
|
|
|
- var node = doc->new_node(null, tag_name);
|
|
|
|
|
- return new HtmlNode(this, node);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Creates a new text node
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public Xml.Node* create_text_node(string text) {
|
|
|
|
|
- return doc->new_text(text);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- internal Xml.Node* import_node(Xml.Node* node, bool deep = true) {
|
|
|
|
|
- return node->copy(deep ? 1 : 0);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Converts the document to an HTML string
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public string to_html() {
|
|
|
|
|
- string buffer;
|
|
|
|
|
- doc->dump_memory(out buffer);
|
|
|
|
|
- return buffer;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Converts the document to a formatted HTML string with indentation
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public string to_pretty_html() {
|
|
|
|
|
- string buffer;
|
|
|
|
|
- int len;
|
|
|
|
|
- doc->dump_memory_format(out buffer, out len, true);
|
|
|
|
|
- return buffer;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Saves the document to a file
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public void save(string filepath) throws GLib.Error {
|
|
|
|
|
- doc->save_file_enc(filepath, "UTF-8");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Creates an HttpResult from this document
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlResult to_result(StatusCode status = StatusCode.OK) {
|
|
|
|
|
- return new HtmlResult(this, status);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Sets the text content of an element by ID
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public bool set_element_text(string id, string text) {
|
|
|
|
|
- var element = get_element_by_id(id);
|
|
|
|
|
- if (element == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- element.text_content = text;
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Sets an attribute on an element by ID
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public bool set_element_attribute(string id, string attr_name, string attr_value) {
|
|
|
|
|
- var element = get_element_by_id(id);
|
|
|
|
|
- if (element == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- element.set_attribute(attr_name, attr_value);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Adds a class to an element by ID
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public bool add_element_class(string id, string class_name) {
|
|
|
|
|
- var element = get_element_by_id(id);
|
|
|
|
|
- if (element == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- element.add_class(class_name);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Removes a class from an element by ID
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public bool remove_element_class(string id, string class_name) {
|
|
|
|
|
- var element = get_element_by_id(id);
|
|
|
|
|
- if (element == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- element.remove_class(class_name);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// Sets the inner HTML of an element by ID
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public bool set_element_html(string id, string html) {
|
|
|
|
|
- var element = get_element_by_id(id);
|
|
|
|
|
- if (element == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- element.inner_html = html;
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- internal Xml.Doc* native_doc {
|
|
|
|
|
- get { return doc; }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// An HttpResult that renders an HTML document
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public class HtmlResult : HttpResult {
|
|
|
|
|
- private HtmlDocument document;
|
|
|
|
|
-
|
|
|
|
|
- internal HtmlResult(HtmlDocument doc, StatusCode status = StatusCode.OK) {
|
|
|
|
|
- base(status);
|
|
|
|
|
- this.document = doc;
|
|
|
|
|
- set_header("Content-Type", "text/html; charset=UTF-8");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// The HTML document being served
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public HtmlDocument html_document {
|
|
|
|
|
- get { return document; }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public async override void send_body(AsyncOutput output) throws GLib.Error {
|
|
|
|
|
- var html = document.to_html();
|
|
|
|
|
- var bytes = new ByteBuffer.from_byte_array(html.data);
|
|
|
|
|
- content_length = bytes.length;
|
|
|
|
|
- yield output.write(bytes);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+}
|