diff --git a/examples/url-demo/.gitignore b/examples/url-demo/.gitignore new file mode 100644 index 0000000..be431b0 --- /dev/null +++ b/examples/url-demo/.gitignore @@ -0,0 +1,2 @@ +# Ignore compiler output +/www/url-api.* diff --git a/examples/url-demo/README.md b/examples/url-demo/README.md new file mode 100644 index 0000000..1b121a3 --- /dev/null +++ b/examples/url-demo/README.md @@ -0,0 +1,8 @@ +# Upa URL demo source code + +The Upa URL demo is available online at https://upa-url.github.io/demo/ + +How to prepare for the Web: +1. Install and activate Emscripten, more information: https://emscripten.org/docs/getting_started/downloads.html +2. Build `url-api.cpp` with `build-url-api.sh` (`build-url-api.bat` on Windows) script. Output files `url-api.js` and `url-api.wasm` will be written to the `www` directory.. +3. Publish contents of `www` directory to web server. diff --git a/examples/url-demo/build-url-api.bat b/examples/url-demo/build-url-api.bat new file mode 100644 index 0000000..fb7e067 --- /dev/null +++ b/examples/url-demo/build-url-api.bat @@ -0,0 +1,22 @@ +@echo off + +REM the directory path of this file +set p=%~dp0 + +REM CD to repository root folder +%~d0 +cd %p%../.. + +REM Compile +call emcc examples/url-demo/url-api.cpp ^ + src/url.cpp ^ + src/url_ip.cpp ^ + src/url_percent_encode.cpp ^ + src/url_search_params.cpp ^ + src/url_utf.cpp ^ + src/idna.cpp ^ + -Iinclude -std=c++17 -O3 ^ + -lembind ^ + -o examples/url-demo/www/url-api.js + +cd examples/url-demo diff --git a/examples/url-demo/build-url-api.sh b/examples/url-demo/build-url-api.sh new file mode 100755 index 0000000..88a37de --- /dev/null +++ b/examples/url-demo/build-url-api.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# the directory path of this file +# https://stackoverflow.com/q/59895 +p="$(dirname "$0")" + +# CD to repository root folder +cd $p/../.. + +# Compile +emcc examples/url-demo/url-api.cpp \ + src/url.cpp \ + src/url_ip.cpp \ + src/url_percent_encode.cpp \ + src/url_search_params.cpp \ + src/url_utf.cpp \ + src/idna.cpp \ + -Iinclude -std=c++17 -O3 \ + -lembind \ + -o examples/url-demo/www/url-api.js + +cd examples/url-demo diff --git a/examples/url-demo/url-api.cpp b/examples/url-demo/url-api.cpp new file mode 100644 index 0000000..e399b4b --- /dev/null +++ b/examples/url-demo/url-api.cpp @@ -0,0 +1,135 @@ +// Copyright 2024 Rimas Misevičius +// Distributed under the BSD-style license that can be +// found in the LICENSE file. +// +#include "upa/url.h" + +#include + +using namespace emscripten; + +class URL { +public: + URL(const std::string& url_str) { + m_url.parse(url_str, nullptr); + m_base_valid = true; + } + + URL(const std::string& url_str, const std::string& base_str) { + upa::url base; + base.parse(base_str, nullptr); + m_url.parse(url_str, &base); + m_base_valid = base.is_valid(); + } + + std::string get_href() const { + return std::string{ m_url.href() }; + } + void set_href(const std::string& val) { + m_url.href(val); + } + + std::string get_origin() const { + return m_url.origin(); + } + + std::string get_protocol() const { + return std::string{ m_url.protocol() }; + } + void set_protocol(const std::string& val) { + m_url.protocol(val); + } + + std::string get_username() const { + return std::string{ m_url.username() }; + } + void set_username(const std::string& val) { + m_url.username(val); + } + + std::string get_password() const { + return std::string{ m_url.password() }; + } + void set_password(const std::string& val) { + m_url.password(val); + } + + std::string get_host() const { + return std::string{ m_url.host() }; + } + void set_host(const std::string& val) { + m_url.host(val); + } + + std::string get_hostname() const { + return std::string{ m_url.hostname() }; + } + void set_hostname(const std::string& val) { + m_url.hostname(val); + } + + std::string get_port() const { + return std::string{ m_url.port() }; + } + void set_port(const std::string& val) { + m_url.port(val); + } + + std::string get_path() const { + return std::string{ m_url.path() }; + } + + std::string get_pathname() const { + return std::string{ m_url.pathname() }; + } + void set_pathname(const std::string& val) { + m_url.pathname(val); + } + + std::string get_search() const { + return std::string{ m_url.search() }; + } + void set_search(const std::string& val) { + m_url.search(val); + } + + std::string get_hash() const { + return std::string{ m_url.hash() }; + } + void set_hash(const std::string& val) { + m_url.hash(val); + } + + bool valid() const { + return m_url.is_valid(); + } + bool base_valid() const { + return m_base_valid; + } +private: + upa::url m_url; + bool m_base_valid; +}; + + +// Binding code +EMSCRIPTEN_BINDINGS(url_api) { + class_("URL") + .constructor() + .constructor() + .property("href", &URL::get_href, &URL::set_href) + .property("origin", &URL::get_origin) + .property("protocol", &URL::get_protocol, &URL::set_protocol) + .property("username", &URL::get_username, &URL::set_username) + .property("password", &URL::get_password, &URL::set_password) + .property("host", &URL::get_host, &URL::set_host) + .property("hostname", &URL::get_hostname, &URL::set_hostname) + .property("port", &URL::get_port, &URL::set_port) + .property("path", &URL::get_path) + .property("pathname", &URL::get_pathname, &URL::set_pathname) + .property("search", &URL::get_search, &URL::set_search) + .property("hash", &URL::get_hash, &URL::set_hash) + // is valid? + .property("valid", &URL::valid) + .property("base_valid", &URL::base_valid); +} diff --git a/examples/url-demo/www/index.html b/examples/url-demo/www/index.html new file mode 100644 index 0000000..ee74f0f --- /dev/null +++ b/examples/url-demo/www/index.html @@ -0,0 +1,48 @@ + + + + + Upa URL demo + + + + + +

Upa URL demo

+

Try the Upa URL library that runs in the browser.

+ +
+ + + + + + +
+ + +
+ +
+ +

Parsing result:

+ + + + + + + + + + + + +
href
protocol
username
password
hostname
port
pathname
hash
origin
+ +
+ + + + + diff --git a/examples/url-demo/www/url-demo.css b/examples/url-demo/www/url-demo.css new file mode 100644 index 0000000..8b7b5ef --- /dev/null +++ b/examples/url-demo/www/url-demo.css @@ -0,0 +1,47 @@ +* { box-sizing: border-box; } + +body { + font-family: Arial; + max-width: 70em; + margin: 16px; +} + +h2 { + font-size: 1.4em; + margin-top: 0.3em; + margin-bottom: 0.2em; +} + +form .label { + display: block; + margin-bottom: 6px; +} + +form .field { + width: 100%; + margin-bottom: 20px; + padding: 3px; +} + +.output { + background-color: #F7F0E0; + border: 1px solid #D2691E; + width: 100%; + padding: 1em; + margin: 0; +} + +.output th { + width: 80px; + text-align: right; + padding-right: 10px; +} + +.output td { + font-family: 'Bitstream Vera Sans Mono', 'Andale Mono', 'Lucida Console', monospace, fixed; + white-space: pre-wrap; +} + +.output.error { + color: #B90000; +} diff --git a/examples/url-demo/www/url-demo.js b/examples/url-demo/www/url-demo.js new file mode 100644 index 0000000..4f0de87 --- /dev/null +++ b/examples/url-demo/www/url-demo.js @@ -0,0 +1,81 @@ +const component_list = [ + "href", + "protocol", + "username", + "password", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + "origin" +]; + +function getUrl() { + // construct URL + const url = elemBase.value.length !== 0 + ? new Module.URL(elemUrl.value, elemBase.value) + : new Module.URL(elemUrl.value); + // apply setter + if (elemSetter.value.length !== 0) + url[elemSetter.value] = elemSetterInp.value; + return url; +} + +function showResult(url) { + const output = document.getElementById("url-output"); + const error = document.getElementById("url-error"); + if (url.valid) { + output.hidden = false; + error.hidden = true; + for (const component of component_list) { + const trComponent = output.querySelector(`.${component}`); + if (trComponent) { + const elemComponent = trComponent.querySelector("td"); + elemComponent.textContent = url[component]; + } + } + } else { + output.hidden = true; + error.hidden = false; + error.textContent = url.base_valid ? "URL parse error" : "Base URL parse error"; + } +} + +function onInpChange() { + showResult(getUrl()); +} + +function onSetterChange() { + elemSetterInp.disabled = elemSetter.value.length === 0; + onInpChange(); +} + +// Input elements +const elemUrl = document.getElementById("url"); +const elemBase = document.getElementById("base"); +const elemSetter = document.getElementById("setter"); +const elemSetterInp = document.getElementById("setter-inp"); + +// After text change +elemUrl.addEventListener("input", onInpChange); +elemBase.addEventListener("input", onInpChange); +elemSetter.addEventListener("change", onSetterChange); +elemSetterInp.addEventListener("input", onInpChange); + +// Fill setters list +for (var component of component_list) { + if (component !== "origin") { + var option = document.createElement("option"); + option.text = component; + option.value = component; + elemSetter.add(option); + } +} + +// Parse initial URL +if (Module.URL) + onSetterChange(); +else + Module['onRuntimeInitialized'] = onSetterChange;