using Astralis; using Invercargill; using Invercargill.DataStructures; /** * DocumentBuilderTemplate Example * * Demonstrates loading an existing HTML template and modifying elements * using the DocumentModel classes. Shows how to: * - Load HTML from a string template * - Use XPath selectors to find specific elements * - Modify element content, attributes, and classes * - Add new elements dynamically * - Handle form POST to update the document state * * This example complements DocumentBuilder.vala by showing template-based * document manipulation rather than building from scratch. * * Usage: document-builder-template [port] */ // Application state - a simple counter class AppState : Object { public int counter { get; set; } public int total_changes { get; set; } public string last_action { get; set; } public DateTime last_update { get; set; } public AppState() { counter = 0; total_changes = 0; last_action = "Initialized"; last_update = new DateTime.now_local(); } public void increment() { counter++; total_changes++; last_action = "Incremented"; last_update = new DateTime.now_local(); } public void decrement() { counter--; total_changes++; last_action = "Decremented"; last_update = new DateTime.now_local(); } public void reset() { counter = 0; total_changes++; last_action = "Reset"; last_update = new DateTime.now_local(); } } // Global app state AppState app_state; // HTML template loaded as a constant private const string HTML_TEMPLATE = """ Document Template Example

📄 Template Document Builder

This page demonstrates loading an HTML template and modifying it dynamically.

Current Value
0
Status
Zero
Last Action
Initialized
Last Update
--:--:--
Total Changes
0

How It Works

The server loads the HTML template and uses XPath selectors to find and modify elements:

// Load the template
var doc = new MarkupDocument.from_string(HTML_TEMPLATE);

// Find and modify elements using XPath
var counter_el = doc.select_one("//div[@id='counter-value']");
counter_el.text_content = counter.to_string();

// Add/remove classes based on state
if (counter > 0) {
    counter_el.add_class("status-positive");
}

DocumentModel Features Used:

"""; // Main page endpoint - loads template and modifies it class HomePageEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error { // Load the HTML template var doc = new MarkupDocument.from_string(HTML_TEMPLATE); // Update the counter value var counter_el = doc.select_one("//div[@id='counter-value']"); if (counter_el != null) { counter_el.text_content = app_state.counter.to_string(); // Update status class based on counter value counter_el.remove_class("status-positive"); counter_el.remove_class("status-negative"); counter_el.remove_class("status-zero"); if (app_state.counter > 0) { counter_el.add_class("status-positive"); } else if (app_state.counter < 0) { counter_el.add_class("status-negative"); } else { counter_el.add_class("status-zero"); } } // Update status text var status_el = doc.select_one("//div[@id='status-value']"); if (status_el != null) { if (app_state.counter > 0) { status_el.text_content = "Positive"; status_el.remove_class("status-negative"); status_el.remove_class("status-zero"); status_el.add_class("status-positive"); } else if (app_state.counter < 0) { status_el.text_content = "Negative"; status_el.remove_class("status-positive"); status_el.remove_class("status-zero"); status_el.add_class("status-negative"); } else { status_el.text_content = "Zero"; status_el.remove_class("status-positive"); status_el.remove_class("status-negative"); status_el.add_class("status-zero"); } } // Update last action var action_el = doc.select_one("//div[@id='action-value']"); if (action_el != null) { action_el.text_content = app_state.last_action; } // Update timestamp var time_el = doc.select_one("//div[@id='time-value']"); if (time_el != null) { time_el.text_content = app_state.last_update.format("%H:%M:%S"); } // Update total changes count var changes_el = doc.select_one("//div[@id='changes-value']"); if (changes_el != null) { changes_el.text_content = app_state.total_changes.to_string(); } return doc.to_result(); } } // Increment endpoint class IncrementEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error { app_state.increment(); return new HttpStringResult("", (StatusCode)302) .set_header("Location", "/"); } } // Decrement endpoint class DecrementEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error { app_state.decrement(); return new HttpStringResult("", (StatusCode)302) .set_header("Location", "/"); } } // Reset endpoint class ResetEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error { app_state.reset(); return new HttpStringResult("", (StatusCode)302) .set_header("Location", "/"); } } // Raw HTML endpoint - shows the unmodified template class RawHtmlEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error { // Load template without modifications var doc = new MarkupDocument.from_string(HTML_TEMPLATE); // Add a notice at the top var body = doc.body; if (body != null) { var notice = body.append_child_element("div"); notice.set_attribute("style", "background: #fff3cd; color: #856404; padding: 15px; margin: 10px 0; border-radius: 8px; border: 1px solid #ffc107;"); notice.append_text("⚠️ This is the raw template without dynamic modifications. "); var link = notice.append_child_element("a"); link.set_attribute("href", "/"); link.append_text("← Back to dynamic version"); } return doc.to_result(); } } // API endpoint that demonstrates modifying multiple elements at once class BulkUpdateEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error { var doc = new MarkupDocument.from_string(HTML_TEMPLATE); // Demonstrate selecting multiple elements var all_info_values = doc.select("//div[contains(@class, 'info-value')]"); // Update all info values to show they were bulk-modified all_info_values.set_text_all("BULK UPDATED"); all_info_values.add_class_all("status-positive"); // Also demonstrate setting attributes on multiple elements var all_cards = doc.select("//div[contains(@class, 'card')]"); all_cards.set_attribute_all("data-bulk-update", "true"); // Update title to indicate bulk operation var title = doc.select_one("//h1[@id='page-title']"); if (title != null) { title.text_content = "📄 Bulk Update Demo"; } // Add explanation var desc = doc.select_one("//p[@id='description']"); if (desc != null) { desc.text_content = "This demonstrates MarkupNodeList operations: set_text_all(), add_class_all(), and set_attribute_all()."; } return doc.to_result(); } } void main(string[] args) { int port = args.length > 1 ? int.parse(args[1]) : 8080; // Initialize app state app_state = new AppState(); print("╔══════════════════════════════════════════════════════════════╗\n"); print("║ Astralis DocumentBuilderTemplate Example ║\n"); print("╠══════════════════════════════════════════════════════════════╣\n"); print(@"║ Port: $port"); for (int i = 0; i < 50 - port.to_string().length - 7; i++) print(" "); print(" ║\n"); print("╠══════════════════════════════════════════════════════════════╣\n"); print("║ Endpoints: ║\n"); print("║ / - Counter page (template with modifications) ║\n"); print("║ /increment - Increase counter (POST) ║\n"); print("║ /decrement - Decrease counter (POST) ║\n"); print("║ /reset - Reset counter (POST) ║\n"); print("║ /raw - Raw template (no modifications) ║\n"); print("║ /bulk - Bulk update demo ║\n"); print("╚══════════════════════════════════════════════════════════════╝\n"); print("\nPress Ctrl+C to stop the server\n\n"); try { var application = new WebApplication(port); // Register compression components application.container.register_singleton(() => new GzipCompressor()); application.container.register_singleton(() => new ZstdCompressor()); application.container.register_singleton(() => new BrotliCompressor()); // Register endpoints application.add_endpoint(new EndpointRoute("/")); application.add_endpoint(new EndpointRoute("/increment")); application.add_endpoint(new EndpointRoute("/decrement")); application.add_endpoint(new EndpointRoute("/reset")); application.add_endpoint(new EndpointRoute("/raw")); application.add_endpoint(new EndpointRoute("/bulk")); application.run(); } catch (Error e) { printerr("Error: %s\n", e.message); Process.exit(1); } }