text inputs, GET form submit, UI change
UI more like Malvim now. fixed HTML parsing bugs, added qol stuff, slight code refactor/cleaning
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -560,7 +560,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "koxinga"
|
name = "koxinga"
|
||||||
version = "0.1.1"
|
version = "0.2.0-beta.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ming-wm-lib",
|
"ming-wm-lib",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -604,9 +604,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ming-wm-lib"
|
name = "ming-wm-lib"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cc80b6035509629ecba931bc6851513fca9fa8bef2be965b8bd437dd00b3930"
|
checksum = "729186cd7726de48643a22cd4af3e66d5198c8a6a9e08f214aa4602ce625b2ad"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
|
|||||||
14
Cargo.toml
14
Cargo.toml
@@ -3,10 +3,16 @@ name = "koxinga"
|
|||||||
version = "0.2.0-beta.0"
|
version = "0.2.0-beta.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
len_zero = "allow"
|
||||||
|
comparison_to_empty = "allow"
|
||||||
|
redundant_static_lifetimes = "allow"
|
||||||
|
vec_box = "allow"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ming-wm-lib = "0.2.3"
|
||||||
|
reqwest = { version = "0.12", features = [ "blocking" ] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mingInternet_Koxinga_Browser"
|
name = "mingInternet_Koxinga_Browser"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
ming-wm-lib = "0.2.2"
|
|
||||||
reqwest = { version = "0.12", features = [ "blocking" ] }
|
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ Koxinga is a web browser supporting text and links.
|
|||||||
|
|
||||||
- `u`: URL mode, where a URL can be inputted. Hit enter/return to go to that page.
|
- `u`: URL mode, where a URL can be inputted. Hit enter/return to go to that page.
|
||||||
- `l`: Link mode. The page will now show numbers in front of any links. Input the number corresponding to the link to navigate to, then hit enter/return.
|
- `l`: Link mode. The page will now show numbers in front of any links. Input the number corresponding to the link to navigate to, then hit enter/return.
|
||||||
- `s`: Search mode. Search engine is https://old-search.marginalia.nu
|
- `i`: Input mode. Fill in text inputs using the format "0,inputname=input value".
|
||||||
|
- `f`: Submit Form mode. Enter in form number to submit.
|
||||||
|
- `s`: Search mode. Search for text on the page
|
||||||
- `j`, `k` to scroll page.
|
- `j`, `k` to scroll page.
|
||||||
- `0`: Go to top of page.
|
- `<num>j`, `<num>k` to move down/up <num> lines.
|
||||||
|
- `gg`: Go to top of page.
|
||||||
- `G`: Go to bottom of page.
|
- `G`: Go to bottom of page.
|
||||||
|
|||||||
BIN
koxinga_hn.png
BIN
koxinga_hn.png
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 144 KiB |
BIN
koxinga_wiki.png
BIN
koxinga_wiki.png
Binary file not shown.
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 364 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 220 KiB |
@@ -24,4 +24,6 @@ impl HttpClient {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo: form submit (get/post)
|
||||||
}
|
}
|
||||||
|
|||||||
386
src/main.rs
386
src/main.rs
@@ -2,36 +2,32 @@ use std::vec::Vec;
|
|||||||
use std::vec;
|
use std::vec;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
//use ming_wm_lib::logging::log;
|
//use ming_wm_lib::logging::log;
|
||||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
||||||
use ming_wm_lib::utils::Substring;
|
use ming_wm_lib::utils::{ get_rest_of_split, Substring };
|
||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::{ Dimensions, RGBColor };
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
use ming_wm_lib::fonts::measure_text;
|
use ming_wm_lib::fonts::{ CachedFontCharGetter, measure_text, measure_text_with_cache };
|
||||||
use ming_wm_lib::ipc::listen;
|
use ming_wm_lib::ipc::listen;
|
||||||
|
|
||||||
mod http;
|
mod http;
|
||||||
use crate::http::HttpClient;
|
use crate::http::HttpClient;
|
||||||
mod xml;
|
mod xml;
|
||||||
use crate::xml::{ parse, Node, OutputType };
|
use crate::xml::{ parse, remove_quotes, Form, FormSubmitMethod, Node, OutputType };
|
||||||
|
mod url;
|
||||||
|
use crate::url::Url;
|
||||||
|
|
||||||
const LINE_HEIGHT: usize = 18;
|
const LINE_HEIGHT: usize = 18;
|
||||||
|
const BAND_HEIGHT: usize = 19;
|
||||||
|
|
||||||
fn get_base_url(url: &str) -> String {
|
#[derive(Default, PartialEq)]
|
||||||
let mut base_url = String::new();
|
enum State {
|
||||||
let mut slash = 0;
|
#[default]
|
||||||
for c in url.chars() {
|
None,
|
||||||
if c == '/' {
|
Maybeg,
|
||||||
slash += 1;
|
|
||||||
if slash == 3 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
base_url += &c.to_string();
|
|
||||||
}
|
|
||||||
base_url
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
@@ -41,6 +37,8 @@ enum Mode {
|
|||||||
Url,
|
Url,
|
||||||
Link,
|
Link,
|
||||||
Search,
|
Search,
|
||||||
|
FormSubmit,
|
||||||
|
FormInput, //(input elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Mode {
|
impl fmt::Display for Mode {
|
||||||
@@ -50,24 +48,57 @@ impl fmt::Display for Mode {
|
|||||||
Mode::Url => "URL",
|
Mode::Url => "URL",
|
||||||
Mode::Link => "LINK",
|
Mode::Link => "LINK",
|
||||||
Mode::Search => "SEARCH",
|
Mode::Search => "SEARCH",
|
||||||
|
Mode::FormSubmit => "FORM SUBMIT",
|
||||||
|
Mode::FormInput => "FORM INPUT",
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum Subtype {
|
||||||
|
Text,
|
||||||
|
Link,
|
||||||
|
TextInput,
|
||||||
|
Button,
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Subtype {
|
||||||
|
pub fn to_rgb(&self, theme_info: &ThemeInfo) -> RGBColor {
|
||||||
|
match self {
|
||||||
|
Self::Text => theme_info.text,
|
||||||
|
Self::Link => theme_info.alt_text,
|
||||||
|
Self::TextInput => theme_info.alt_secondary,
|
||||||
|
Self::Button => theme_info.alt_secondary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
pub fn is_one_off(&self) -> bool {
|
||||||
|
//button, text input, stuff that we don't expect other subtypes to be in (well, buttons might, but whatever)
|
||||||
|
self == &Subtype::TextInput || self == &Subtype::Button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct KoxingaBrowser {
|
struct KoxingaBrowser {
|
||||||
client: HttpClient,
|
client: HttpClient,
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
|
fonts: Vec<String>,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
state: State,
|
||||||
max_lines: usize,
|
max_lines: usize,
|
||||||
top_line_no: usize,
|
top_line_no: usize,
|
||||||
url: Option<String>,
|
url: Option<Url>,
|
||||||
input: String,
|
input: String,
|
||||||
|
maybe_num: Option<usize>,
|
||||||
links: Vec<String>,
|
links: Vec<String>,
|
||||||
|
forms: Vec<Form>,
|
||||||
|
form_inputs: HashMap<(usize, String), String>, //form #+input name, input value
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
top_level_nodes: Vec<Box<Node>>,
|
top_level_nodes: Vec<Box<Node>>,
|
||||||
page: Vec<(usize, usize, String, bool)>, //x, y, text, link colour or not
|
page: Vec<(usize, usize, String, Subtype)>, //x, y, text, subtype
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowLike for KoxingaBrowser {
|
impl WindowLike for KoxingaBrowser {
|
||||||
@@ -79,93 +110,198 @@ impl WindowLike for KoxingaBrowser {
|
|||||||
},
|
},
|
||||||
WindowMessage::ChangeDimensions(dimensions) => {
|
WindowMessage::ChangeDimensions(dimensions) => {
|
||||||
self.dimensions = dimensions;
|
self.dimensions = dimensions;
|
||||||
self.calc_page();
|
self.calc_page(false);
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
},
|
},
|
||||||
WindowMessage::KeyPress(key_press) => {
|
WindowMessage::KeyPress(key_press) => {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Normal => {
|
Mode::Normal => {
|
||||||
let max_lines_screen = (self.dimensions[1] - 2) / LINE_HEIGHT - 1;
|
let max_lines_screen = (self.dimensions[1] - 2) / LINE_HEIGHT - 2;
|
||||||
|
if self.state == State::Maybeg && key_press.key != 'g' {
|
||||||
|
self.state = State::None;
|
||||||
|
}
|
||||||
if key_press.key == 'u' {
|
if key_press.key == 'u' {
|
||||||
self.mode = Mode::Url;
|
self.mode = Mode::Url;
|
||||||
self.input = self.url.clone().unwrap_or(String::new());
|
self.input = self.url.clone().unwrap_or(Url::new(String::new())).to_string();
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else if key_press.key == 'l' && self.url.is_some() {
|
} else if key_press.key == 'l' && self.url.is_some() {
|
||||||
self.mode = Mode::Link;
|
self.mode = Mode::Link;
|
||||||
self.calc_page();
|
self.calc_page(false);
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else if key_press.key == 'f' {
|
||||||
|
self.mode = Mode::FormSubmit;
|
||||||
|
self.calc_page(false);
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else if key_press.key == 's' {
|
} else if key_press.key == 's' {
|
||||||
self.mode = Mode::Search;
|
self.mode = Mode::Search;
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else if key_press.key == 'k' {
|
} else if key_press.key == 'f' && self.url.is_some() {
|
||||||
if self.top_line_no > 0 {
|
self.mode = Mode::FormSubmit;
|
||||||
self.top_line_no -= 1;
|
self.calc_page(false);
|
||||||
}
|
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else if key_press.key == 'j' {
|
} else if key_press.key == 'i' && self.url.is_some() {
|
||||||
if self.top_line_no < self.max_lines - max_lines_screen + 1 {
|
self.mode = Mode::FormInput;
|
||||||
self.top_line_no += 1;
|
self.calc_page(false);
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else if key_press.key == 'j' || key_press.key == 'k' {
|
||||||
|
let num = self.maybe_num.unwrap_or(1);
|
||||||
|
self.maybe_num = None;
|
||||||
|
if key_press.key == 'j' {
|
||||||
|
let max_top = self.max_lines - max_lines_screen + 1;
|
||||||
|
if self.top_line_no + num < max_top {
|
||||||
|
self.top_line_no += num;
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else if self.top_line_no != max_top {
|
||||||
|
self.top_line_no = max_top;
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else {
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.top_line_no > num {
|
||||||
|
self.top_line_no -= num;
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else if self.top_line_no > 0 {
|
||||||
|
self.top_line_no = 0;
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else {
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if key_press.key == 'g' {
|
||||||
|
if self.state == State::Maybeg {
|
||||||
|
self.top_line_no = 0;
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else {
|
} else {
|
||||||
|
self.state = State::Maybeg;
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
}
|
}
|
||||||
} else if key_press.key == '0' {
|
|
||||||
self.top_line_no = 0;
|
|
||||||
WindowMessageResponse::JustRedraw
|
|
||||||
} else if key_press.key == 'G' {
|
} else if key_press.key == 'G' {
|
||||||
self.top_line_no = self.max_lines - max_lines_screen + 1;
|
self.top_line_no = self.max_lines - max_lines_screen + 1;
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else if key_press.key.is_ascii_digit() {
|
||||||
|
self.maybe_num = Some(self.maybe_num.unwrap_or(0) * 10 + key_press.key.to_digit(10).unwrap() as usize);
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
} else if self.maybe_num.is_some() {
|
||||||
|
self.maybe_num = None;
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
} else {
|
} else {
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Mode::Url | Mode::Search | Mode::Link => {
|
//all modes besides normal, which use the bottom input
|
||||||
|
_ => {
|
||||||
if key_press.is_enter() && self.input.len() > 0 {
|
if key_press.is_enter() && self.input.len() > 0 {
|
||||||
let new_url = if self.mode == Mode::Search {
|
if self.mode == Mode::Url || self.mode == Mode::Link {
|
||||||
"https://old-search.marginalia.nu/search?query=".to_string() + &self.input
|
let new_url = if self.mode == Mode::Link {
|
||||||
} else if self.mode == Mode::Link {
|
self.mode = Mode::Normal;
|
||||||
self.mode = Mode::Normal;
|
let link_index = self.input.parse::<usize>().unwrap();
|
||||||
let link_index = self.input.parse::<usize>().unwrap();
|
let mut url = self.url.as_ref().unwrap().clone();
|
||||||
let url = self.url.as_ref().unwrap();
|
if link_index < self.links.len() {
|
||||||
let mut link;
|
let mut link = self.links[link_index].clone();
|
||||||
if link_index < self.links.len() {
|
if link.chars().count() >= 2 {
|
||||||
link = self.links[link_index].clone();
|
link = remove_quotes(link);
|
||||||
if link.chars().count() >= 2 {
|
}
|
||||||
//remove the quotes
|
if link.starts_with("/") {
|
||||||
link = link.substring(1, link.len() - 1).to_string();
|
url.pop_to_root();
|
||||||
}
|
url.append(link);
|
||||||
if link.starts_with("/") {
|
} else if !link.starts_with("http://") && !link.starts_with("https://") {
|
||||||
link = get_base_url(&url) + &link;
|
if !link.starts_with("?") && !link.starts_with("#") {
|
||||||
} else if !link.starts_with("http://") && !link.starts_with("https://") {
|
url.pop();
|
||||||
link = url.clone() + if url.ends_with("/") { "" } else { "/" } + &link;
|
}
|
||||||
|
url.append(link);
|
||||||
|
} else {
|
||||||
|
url = Url::new(link);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return WindowMessageResponse::DoNothing
|
||||||
}
|
}
|
||||||
|
url
|
||||||
} else {
|
} else {
|
||||||
return WindowMessageResponse::DoNothing
|
//if Mode::Url
|
||||||
|
Url::new(self.input.clone())
|
||||||
|
};
|
||||||
|
if let Some(text) = self.client.get(&new_url.to_string()) {
|
||||||
|
self.change_url(new_url, text);
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else {
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
}
|
||||||
|
} else if self.mode == Mode::FormSubmit || self.mode == Mode::FormInput {
|
||||||
|
if self.mode == Mode::FormInput {
|
||||||
|
//this shouldn't be able to panic I hope
|
||||||
|
//get_rest_of_split may return an empty string, but it won't panic
|
||||||
|
let mut splitted = self.input.split("=");
|
||||||
|
let first = splitted.next().unwrap();
|
||||||
|
let input_value = get_rest_of_split(&mut splitted, Some("="));
|
||||||
|
let mut first_splitted = first.split(",");
|
||||||
|
let form_count = first_splitted.next().unwrap().parse::<usize>();
|
||||||
|
//form count is not a number
|
||||||
|
if form_count.is_err() {
|
||||||
|
self.input = String::new();
|
||||||
|
return WindowMessageResponse::JustRedraw;
|
||||||
|
}
|
||||||
|
let form_count = form_count.unwrap();
|
||||||
|
let input_name = get_rest_of_split(&mut first_splitted, None); //I mean, there shouldn't be a comma in the input name, right?
|
||||||
|
//insert overwrites
|
||||||
|
//todo: check if exists first
|
||||||
|
self.form_inputs.insert((form_count, input_name), input_value);
|
||||||
|
self.input = String::new();
|
||||||
|
self.calc_page(false);
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else {
|
||||||
|
//form submit
|
||||||
|
let form_index = self.input.parse::<usize>().unwrap();
|
||||||
|
if form_index < self.forms.len() {
|
||||||
|
let form_info = &self.forms[form_index];
|
||||||
|
match form_info.method {
|
||||||
|
FormSubmitMethod::Get => {
|
||||||
|
//construct url to redirect to
|
||||||
|
let mut form_url = Url::new_maybe_relative(form_info.action.clone(), self.url.clone().unwrap());
|
||||||
|
//key aka name attr
|
||||||
|
for key in &form_info.input_names {
|
||||||
|
if let Some(value) = self.form_inputs.get(&(form_index, key.clone())) {
|
||||||
|
form_url.append_query(&key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//log(&format!("{}", form_url.clone()));
|
||||||
|
if let Some(text) = self.client.get(&form_url.to_string()) {
|
||||||
|
self.change_url(form_url, text);
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else {
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FormSubmitMethod::Post => {
|
||||||
|
//todo. maybe later
|
||||||
|
//
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WindowMessageResponse::DoNothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
link
|
|
||||||
} else {
|
|
||||||
//if Mode::Url
|
|
||||||
self.input.clone()
|
|
||||||
};
|
|
||||||
if let Some(text) = self.client.get(&new_url) {
|
|
||||||
self.url = Some(new_url.clone());
|
|
||||||
self.top_line_no = 0;
|
|
||||||
//log(&text);
|
|
||||||
self.top_level_nodes = parse(&text);
|
|
||||||
self.input = String::new();
|
|
||||||
self.calc_page();
|
|
||||||
self.mode = Mode::Normal;
|
|
||||||
WindowMessageResponse::JustRedraw
|
|
||||||
} else {
|
} else {
|
||||||
|
//Mode::Search
|
||||||
|
for p in &self.page {
|
||||||
|
let line_no = (p.1 - 2) / LINE_HEIGHT;
|
||||||
|
if line_no > self.top_line_no {
|
||||||
|
//p.2 is the text
|
||||||
|
if p.2.contains(&self.input) {
|
||||||
|
self.top_line_no = line_no;
|
||||||
|
return WindowMessageResponse::JustRedraw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
}
|
}
|
||||||
} else if key_press.is_escape() {
|
} else if key_press.is_escape() {
|
||||||
let is_link_mode = self.mode == Mode::Link;
|
|
||||||
self.mode = Mode::Normal;
|
self.mode = Mode::Normal;
|
||||||
if is_link_mode {
|
|
||||||
self.calc_page();
|
|
||||||
}
|
|
||||||
self.input = String::new();
|
self.input = String::new();
|
||||||
|
if self.mode == Mode::Link || self.mode == Mode::FormSubmit || self.mode == Mode::FormInput {
|
||||||
|
self.calc_page(false);
|
||||||
|
}
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
} else if key_press.is_backspace() && self.input.len() > 0 {
|
} else if key_press.is_backspace() && self.input.len() > 0 {
|
||||||
self.input = self.input.remove_last();
|
self.input = self.input.remove_last();
|
||||||
@@ -185,26 +321,39 @@ impl WindowLike for KoxingaBrowser {
|
|||||||
|
|
||||||
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
let max_lines_screen = (self.dimensions[1] - 2) / LINE_HEIGHT - 1;
|
let max_lines_screen = (self.dimensions[1] - 2) / LINE_HEIGHT - 2;
|
||||||
for p in &self.page {
|
for p in &self.page {
|
||||||
let line_no = (p.1 - 2) / LINE_HEIGHT;
|
let line_no = (p.1 - 2) / LINE_HEIGHT;
|
||||||
if line_no >= self.top_line_no + max_lines_screen {
|
if line_no >= self.top_line_no + max_lines_screen {
|
||||||
break;
|
break;
|
||||||
} else if line_no >= self.top_line_no && line_no < self.top_line_no + max_lines_screen {
|
} else if line_no >= self.top_line_no && line_no < self.top_line_no + max_lines_screen {
|
||||||
instructions.push(DrawInstructions::Text([p.0, p.1 - LINE_HEIGHT * self.top_line_no], vec!["nimbus-roman".to_string()], p.2.clone(), if p.3 { theme_info.top_text } else { theme_info.text }, theme_info.background, Some(1), None));
|
let subtype = p.3;
|
||||||
|
let top_left = [p.0, p.1 - LINE_HEIGHT * self.top_line_no];
|
||||||
|
let bg_colour = if subtype == Subtype::TextInput || subtype == Subtype::Button {
|
||||||
|
Some(theme_info.alt_background)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(bg_colour) = bg_colour {
|
||||||
|
let width = measure_text(&self.fonts, &p.2, Some(1)).width;
|
||||||
|
instructions.push(DrawInstructions::Rect([top_left[0] - 2, top_left[1] - 2], [width, LINE_HEIGHT], bg_colour));
|
||||||
|
}
|
||||||
|
instructions.push(DrawInstructions::Text(top_left, self.fonts.clone(), p.2.clone(), subtype.to_rgb(&theme_info), bg_colour.unwrap_or(theme_info.background), Some(1), None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//mode
|
//mode, in a blue band
|
||||||
|
instructions.push(DrawInstructions::Rect([0, self.dimensions[1] - BAND_HEIGHT * 2], [self.dimensions[0], BAND_HEIGHT], theme_info.top));
|
||||||
let mut bottom_text = self.mode.to_string() + ": ";
|
let mut bottom_text = self.mode.to_string() + ": ";
|
||||||
if self.mode == Mode::Normal && self.dimensions[0] >= 500 {
|
if self.mode == Mode::Normal && self.dimensions[0] >= 300 {
|
||||||
bottom_text += "u (url), s (search)";
|
bottom_text += "u(rl)";
|
||||||
if self.url.is_some() {
|
if self.url.is_some() && self.dimensions[0] >= 640 {
|
||||||
bottom_text += ", l (link), j (down), k (up)";
|
bottom_text += ", s(earch), l(ink), i(nput), f(orm), j, k";
|
||||||
}
|
}
|
||||||
} else {
|
} else if self.mode == Mode::FormInput && self.dimensions[0] > 500 {
|
||||||
bottom_text += &self.input;
|
bottom_text += "syntax is eg \"0,inputname=input value\"";
|
||||||
}
|
}
|
||||||
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - LINE_HEIGHT], vec!["nimbus-roman".to_string()], bottom_text, theme_info.text, theme_info.background, Some(1), Some(11)));
|
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - LINE_HEIGHT * 2], vec!["nimbus-romono".to_string()], bottom_text, theme_info.top_text, theme_info.top, Some(1), Some(11)));
|
||||||
|
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - LINE_HEIGHT], vec!["nimbus-romono".to_string()], self.input.clone(), theme_info.text, theme_info.background, Some(1), Some(11)));
|
||||||
instructions
|
instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +371,7 @@ impl WindowLike for KoxingaBrowser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
|
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
|
||||||
[500, 410]
|
[650, 410]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resizable(&self) -> bool {
|
fn resizable(&self) -> bool {
|
||||||
@@ -231,14 +380,29 @@ impl WindowLike for KoxingaBrowser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KoxingaBrowser {
|
impl KoxingaBrowser {
|
||||||
pub fn new() -> Self {
|
pub fn new(fonts: Vec<String>) -> Self {
|
||||||
Default::default()
|
let mut s: Self = Default::default();
|
||||||
|
s.fonts = fonts;
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calc_page(&mut self) {
|
pub fn change_url(&mut self, new_url: Url, text: String) {
|
||||||
|
self.url = Some(new_url);
|
||||||
|
self.top_line_no = 0;
|
||||||
|
self.top_level_nodes = parse(&text);
|
||||||
|
self.input = String::new();
|
||||||
|
self.calc_page(true);
|
||||||
|
self.mode = Mode::Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calc_page(&mut self, new_page: bool) {
|
||||||
self.title = None;
|
self.title = None;
|
||||||
self.page = Vec::new();
|
self.page = Vec::new();
|
||||||
self.links = Vec::new();
|
self.links = Vec::new();
|
||||||
|
self.forms = Vec::new();
|
||||||
|
if new_page {
|
||||||
|
self.form_inputs = HashMap::new();
|
||||||
|
}
|
||||||
let mut outputs = Vec::new();
|
let mut outputs = Vec::new();
|
||||||
if self.top_level_nodes.len() > 0 {
|
if self.top_level_nodes.len() > 0 {
|
||||||
let html_index = self.top_level_nodes.iter().position(|n| n.tag_name == "html");
|
let html_index = self.top_level_nodes.iter().position(|n| n.tag_name == "html");
|
||||||
@@ -247,10 +411,8 @@ impl KoxingaBrowser {
|
|||||||
if n.tag_name == "head" {
|
if n.tag_name == "head" {
|
||||||
//look for title, if any
|
//look for title, if any
|
||||||
for hn in &n.children {
|
for hn in &n.children {
|
||||||
if hn.tag_name == "title" {
|
if hn.tag_name == "title" && hn.children.len() > 0 && hn.children[0].text_node {
|
||||||
if hn.children[0].text_node {
|
self.title = Some(hn.children[0].tag_name.clone());
|
||||||
self.title = Some(hn.children[0].tag_name.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if n.tag_name == "body" {
|
} else if n.tag_name == "body" {
|
||||||
@@ -264,11 +426,13 @@ impl KoxingaBrowser {
|
|||||||
let mut x = 2;
|
let mut x = 2;
|
||||||
let mut indent = 0;
|
let mut indent = 0;
|
||||||
let mut line_count = 0;
|
let mut line_count = 0;
|
||||||
let mut colour = false;
|
|
||||||
let mut link_counter = 0;
|
let mut link_counter = 0;
|
||||||
|
let mut form_counter = 0;
|
||||||
|
let mut subtype = Subtype::Text;
|
||||||
|
let mut fc_getter = CachedFontCharGetter::new(81); //all eng alpha + numbers + 19
|
||||||
for o in outputs {
|
for o in outputs {
|
||||||
//each char is width of 13
|
//each char is width of 13
|
||||||
let os = if let OutputType::Text(ref s) = o {
|
let output_string = if let OutputType::Text(ref s) = o {
|
||||||
let s = if s.starts_with(" ") {
|
let s = if s.starts_with(" ") {
|
||||||
" ".to_string()
|
" ".to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -280,7 +444,7 @@ impl KoxingaBrowser {
|
|||||||
};
|
};
|
||||||
Some(s)
|
Some(s)
|
||||||
} else if let OutputType::StartLink(link) = &o {
|
} else if let OutputType::StartLink(link) = &o {
|
||||||
colour = true;
|
subtype = Subtype::Link;
|
||||||
if self.mode == Mode::Link {
|
if self.mode == Mode::Link {
|
||||||
self.links.push(link.to_string());
|
self.links.push(link.to_string());
|
||||||
let s = link_counter.to_string() + ":";
|
let s = link_counter.to_string() + ":";
|
||||||
@@ -293,10 +457,32 @@ impl KoxingaBrowser {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
} else if let OutputType::Form(form) = &o {
|
||||||
|
//yeah, in future properly render the submit button
|
||||||
|
subtype = Subtype::Button;
|
||||||
|
self.forms.push(form.clone());
|
||||||
|
let t = if self.mode == Mode::FormSubmit {
|
||||||
|
form_counter.to_string() + ":"
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
} + "Submit Form";
|
||||||
|
form_counter += 1;
|
||||||
|
Some(t)
|
||||||
|
} else if let OutputType::TextInput(name) = &o {
|
||||||
|
subtype = Subtype::TextInput;
|
||||||
|
if new_page {
|
||||||
|
self.form_inputs.insert((form_counter, name.to_string()), String::new());
|
||||||
|
}
|
||||||
|
let t = if self.mode == Mode::FormInput || self.mode == Mode::FormSubmit {
|
||||||
|
format!("{},{}={}", form_counter.to_string(), name, self.form_inputs.get(&(form_counter, name.to_owned())).unwrap())
|
||||||
|
} else {
|
||||||
|
name.to_owned()
|
||||||
|
};
|
||||||
|
Some(t)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if let Some(s) = os {
|
if let Some(s) = output_string {
|
||||||
//leading and trailing whitespace is probably a mistake
|
//leading and trailing whitespace is probably a mistake
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
if x == 2 {
|
if x == 2 {
|
||||||
@@ -304,10 +490,10 @@ impl KoxingaBrowser {
|
|||||||
}
|
}
|
||||||
let mut start_x = x;
|
let mut start_x = x;
|
||||||
for c in s.chars() {
|
for c in s.chars() {
|
||||||
let c_width = measure_text(&["nimbus-roman".to_string(), "shippori-mincho".to_string()], c.to_string()).width + 1; //+1 for horiz spacing
|
let c_width = measure_text_with_cache(&mut fc_getter, &self.fonts, &c.to_string(), None).width + 1; //+1 for horiz spacing
|
||||||
if x + c_width > self.dimensions[0] {
|
if x + c_width > self.dimensions[0] {
|
||||||
//full line, add draw instruction
|
//full line, add draw instruction
|
||||||
self.page.push((start_x, y, line, colour));
|
self.page.push((start_x, y, line, subtype));
|
||||||
line = String::new();
|
line = String::new();
|
||||||
x = 2 + indent;
|
x = 2 + indent;
|
||||||
start_x = x;
|
start_x = x;
|
||||||
@@ -318,7 +504,13 @@ impl KoxingaBrowser {
|
|||||||
x += c_width;
|
x += c_width;
|
||||||
}
|
}
|
||||||
if line.len() > 0 {
|
if line.len() > 0 {
|
||||||
self.page.push((start_x, y, line, colour));
|
self.page.push((start_x, y, line, subtype));
|
||||||
|
}
|
||||||
|
if subtype.is_one_off() {
|
||||||
|
//so button and textinput subtypes don't persist
|
||||||
|
//really we should allow multiple subtypes at once or something, idk
|
||||||
|
//but this is fine for now
|
||||||
|
subtype = Subtype::Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let OutputType::Indent(space) = o {
|
if let OutputType::Indent(space) = o {
|
||||||
@@ -332,7 +524,7 @@ impl KoxingaBrowser {
|
|||||||
y += LINE_HEIGHT;
|
y += LINE_HEIGHT;
|
||||||
line_count += 1;
|
line_count += 1;
|
||||||
} else if o == OutputType::EndLink {
|
} else if o == OutputType::EndLink {
|
||||||
colour = false;
|
subtype = Subtype::Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.max_lines = line_count;
|
self.max_lines = line_count;
|
||||||
@@ -340,5 +532,5 @@ impl KoxingaBrowser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
listen(KoxingaBrowser::new());
|
listen(KoxingaBrowser::new(vec!["nimbus-roman".to_string(), "shippori-mincho".to_string()]));
|
||||||
}
|
}
|
||||||
|
|||||||
71
src/url.rs
Normal file
71
src/url.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
use std::vec::Vec;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
//for the moment, we don't care about query params or fragments and the like
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Url {
|
||||||
|
scheme: String, //http or https, probably
|
||||||
|
hostname: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
query: Option<String>, //empty or somethign like ?value1=yes&value2=abcd
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Url {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
if self.scheme != "" {
|
||||||
|
fmt.write_str(&(self.scheme.clone() + "://" + &self.hostname + "/" + &self.path.join("/") + &self.query.as_ref().map_or("", |v| v)))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Url {
|
||||||
|
pub fn new(url: String) -> Url {
|
||||||
|
let mut queries = url.split("?");
|
||||||
|
let mut p = queries.next().unwrap().split("://");
|
||||||
|
let scheme = p.next().unwrap_or("").to_string();
|
||||||
|
p = p.next().unwrap_or("").split("/");
|
||||||
|
let hostname = p.next().unwrap_or("").to_string();
|
||||||
|
let path = p.filter(|s| *s != "").map(|s| s.to_string()).collect();
|
||||||
|
let query = match queries.next() {
|
||||||
|
Some(q) => Some(q.to_string()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Self { scheme, hostname, path, query }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_maybe_relative(url: String, current_url: Url) -> Url {
|
||||||
|
if url.split("://").count() < 2 {
|
||||||
|
let mut use_url = current_url;
|
||||||
|
if url.starts_with("/") {
|
||||||
|
use_url.pop_to_root();
|
||||||
|
};
|
||||||
|
use_url.append(url);
|
||||||
|
use_url
|
||||||
|
} else {
|
||||||
|
Url::new(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) {
|
||||||
|
self.path.pop();
|
||||||
|
self.query = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_to_root(&mut self) {
|
||||||
|
self.path = Vec::new();
|
||||||
|
self.query = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(&mut self, path: String) {
|
||||||
|
self.path.extend(path.split("/").filter(|s| *s != "").map(|s| s.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_query(&mut self, key: &str, value: &str) {
|
||||||
|
if self.query.is_none() {
|
||||||
|
self.query = Some(format!("?{}={}", key, value));
|
||||||
|
} else {
|
||||||
|
self.query = Some(format!("{}&{}={}", self.query.as_ref().unwrap(), key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
src/xml.rs
144
src/xml.rs
@@ -4,6 +4,8 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use ming_wm_lib::utils::Substring;
|
use ming_wm_lib::utils::Substring;
|
||||||
|
|
||||||
|
//use ming_wm_lib::logging::log;
|
||||||
|
|
||||||
//try to be xhtml compliant?
|
//try to be xhtml compliant?
|
||||||
|
|
||||||
//fuck these mfers. self close with a FUCKING slash man.
|
//fuck these mfers. self close with a FUCKING slash man.
|
||||||
@@ -34,14 +36,39 @@ fn handle_escaped(s: &str) -> String {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_quotes(s: String) -> String {
|
||||||
|
//todo: remove only if quotes
|
||||||
|
let s_len = s.len();
|
||||||
|
if s_len > 1 {
|
||||||
|
s.substring(1, s.len() - 1).to_string()
|
||||||
|
} else {
|
||||||
|
s //length is 0 or 1, can't strip no quotes...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum FormSubmitMethod {
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct Form {
|
||||||
|
pub action: String, //url
|
||||||
|
pub method: FormSubmitMethod,
|
||||||
|
pub input_names: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum OutputType {
|
pub enum OutputType {
|
||||||
StartLink(String),
|
StartLink(String), //url
|
||||||
EndLink,
|
EndLink,
|
||||||
Text(String),
|
Text(String),
|
||||||
Newline,
|
Newline,
|
||||||
//only support one per line, once indented, will keep being indented until overriden, for now
|
//only support one per line, once indented, will keep being indented until overriden, for now
|
||||||
Indent(usize),
|
Indent(usize),
|
||||||
|
TextInput(String),
|
||||||
|
Form(Form),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
@@ -57,7 +84,11 @@ impl Node {
|
|||||||
pub fn to_output(&self) -> Vec<OutputType> {
|
pub fn to_output(&self) -> Vec<OutputType> {
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let mut link = false;
|
let mut link = false;
|
||||||
if self.text_node {
|
let mut form = None;
|
||||||
|
let mut input_names = Vec::new();
|
||||||
|
if Some(&"\"true\"".to_string()) == self.attributes.get("aria-hidden") {
|
||||||
|
return output;
|
||||||
|
} else if self.text_node {
|
||||||
output.push(OutputType::Text(handle_escaped(&self.tag_name.clone())));
|
output.push(OutputType::Text(handle_escaped(&self.tag_name.clone())));
|
||||||
return output;
|
return output;
|
||||||
} else if self.tag_name == "script" || self.tag_name == "style" {
|
} else if self.tag_name == "script" || self.tag_name == "style" {
|
||||||
@@ -70,25 +101,68 @@ impl Node {
|
|||||||
output.push(OutputType::StartLink(href.to_string()));
|
output.push(OutputType::StartLink(href.to_string()));
|
||||||
} else if let Some(indent) = self.attributes.get("indent") {
|
} else if let Some(indent) = self.attributes.get("indent") {
|
||||||
//non-standard indent attribute, basically just to support HN
|
//non-standard indent attribute, basically just to support HN
|
||||||
//remove quotes
|
let indent = remove_quotes(indent.to_string());
|
||||||
let indent = indent.substring(1, indent.len() - 1);
|
|
||||||
if let Ok(indent) = indent.parse::<usize>() {
|
if let Ok(indent) = indent.parse::<usize>() {
|
||||||
output.push(OutputType::Indent(indent * 32));
|
output.push(OutputType::Indent(indent * 32));
|
||||||
}
|
}
|
||||||
|
} else if self.tag_name == "input" || self.tag_name == "textarea" {
|
||||||
|
if let Some(name) = self.attributes.get("name") {
|
||||||
|
//unwrap_or is painful so compiler suggested map_or
|
||||||
|
let input_type = remove_quotes(self.attributes.get("type").map_or("\"text\"".to_string(), |v| v.to_string()));
|
||||||
|
if input_type == "text" || input_type == "search" {
|
||||||
|
output.push(OutputType::TextInput(remove_quotes(name.to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if self.tag_name == "form" {
|
||||||
|
if let Some(action) = self.attributes.get("action") {
|
||||||
|
let method = if let Some(m) = self.attributes.get("method") {
|
||||||
|
let m = remove_quotes(m.to_string()).to_lowercase();
|
||||||
|
match m.as_str() {
|
||||||
|
//todo: POST is currently not implemented. maybe later
|
||||||
|
//"post" => Some(FormSubmitMethod::Post),
|
||||||
|
"get" => Some(FormSubmitMethod::Get),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(FormSubmitMethod::Get)
|
||||||
|
};
|
||||||
|
if let Some(method) = method {
|
||||||
|
form = Some(Form {
|
||||||
|
action: remove_quotes(action.to_string()),
|
||||||
|
method,
|
||||||
|
input_names: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for c in &self.children {
|
for c in &self.children {
|
||||||
output.extend(c.to_output());
|
let children_output = c.to_output();
|
||||||
|
if form.is_some() {
|
||||||
|
for cc in &children_output {
|
||||||
|
if let OutputType::TextInput(name) = cc {
|
||||||
|
input_names.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.extend(children_output);
|
||||||
}
|
}
|
||||||
if BLOCK_LEVEL.contains(&self.tag_name.as_str()) {
|
if BLOCK_LEVEL.contains(&self.tag_name.as_str()) {
|
||||||
output.push(OutputType::Newline);
|
output.push(OutputType::Newline);
|
||||||
} else if link {
|
} else if link {
|
||||||
output.push(OutputType::EndLink);
|
output.push(OutputType::EndLink);
|
||||||
|
} else if let Some(form) = form {
|
||||||
|
let form = Form {
|
||||||
|
action: form.action,
|
||||||
|
method: form.method,
|
||||||
|
input_names,
|
||||||
|
};
|
||||||
|
output.push(OutputType::Form(form));
|
||||||
}
|
}
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_to_parent(top_level_nodes: &mut Vec<Box<Node>>, parent_location: &Vec<usize>, node: Node) -> usize {
|
fn add_to_parent(top_level_nodes: &mut Vec<Box<Node>>, parent_location: &[usize], node: Node) -> usize {
|
||||||
if parent_location.len() == 0 {
|
if parent_location.len() == 0 {
|
||||||
top_level_nodes.push(Box::new(node));
|
top_level_nodes.push(Box::new(node));
|
||||||
top_level_nodes.len() - 1
|
top_level_nodes.len() - 1
|
||||||
@@ -157,9 +231,7 @@ pub fn parse(xml_string: &str) -> Vec<Box<Node>> {
|
|||||||
let end = so_far.chars().count();
|
let end = so_far.chars().count();
|
||||||
if so_far.substring(end - end_len, end) == "</".to_string() + &n.tag_name + ">" {
|
if so_far.substring(end - end_len, end) == "</".to_string() + &n.tag_name + ">" {
|
||||||
current_node = None;
|
current_node = None;
|
||||||
let mut n2: Node = Default::default();
|
let n2: Node = Node { text_node: true, tag_name: so_far.substring(0, end - end_len).to_string(), ..Default::default() };
|
||||||
n2.text_node = true;
|
|
||||||
n2.tag_name = so_far.substring(0, end - end_len).to_string();
|
|
||||||
add_to_parent(&mut top_level_nodes, &parent_location, n2);
|
add_to_parent(&mut top_level_nodes, &parent_location, n2);
|
||||||
parent_location.pop();
|
parent_location.pop();
|
||||||
recording_tag_name = false;
|
recording_tag_name = false;
|
||||||
@@ -167,7 +239,7 @@ pub fn parse(xml_string: &str) -> Vec<Box<Node>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if c == ' ' && recording_tag_name && !n.text_node {
|
} else if (c == ' ' || c == '\n') && recording_tag_name && !n.text_node {
|
||||||
recording_tag_name = false;
|
recording_tag_name = false;
|
||||||
} else if c == '>' || (c == '/' && chars.peek().unwrap_or(&' ') == &'>') || (n.text_node && chars.peek().unwrap_or(&' ') == &'<') {
|
} else if c == '>' || (c == '/' && chars.peek().unwrap_or(&' ') == &'>') || (n.text_node && chars.peek().unwrap_or(&' ') == &'<') {
|
||||||
if n.text_node {
|
if n.text_node {
|
||||||
@@ -189,12 +261,14 @@ pub fn parse(xml_string: &str) -> Vec<Box<Node>> {
|
|||||||
current_node = None;
|
current_node = None;
|
||||||
} else if recording_tag_name {
|
} else if recording_tag_name {
|
||||||
n.tag_name += &c.to_string();
|
n.tag_name += &c.to_string();
|
||||||
} else if c == ' ' && !in_string && recording_attribute_value {
|
} else if c == ' ' && !in_string {
|
||||||
//catch attributes like disabled with no = or value
|
//catch attributes like disabled with no = or value
|
||||||
if attribute_name.len() > 0 && !recording_attribute_value {
|
if attribute_name.len() > 0 && !recording_attribute_value {
|
||||||
n.attributes.entry(attribute_name.clone()).insert_entry(String::new());
|
n.attributes.entry(attribute_name.clone()).insert_entry(String::new());
|
||||||
|
} else if recording_attribute_value {
|
||||||
|
//^this can just be an "else", not an "else if", probably
|
||||||
|
recording_attribute_value = false;
|
||||||
}
|
}
|
||||||
recording_attribute_value = false;
|
|
||||||
attribute_name = String::new();
|
attribute_name = String::new();
|
||||||
} else if recording_attribute_value {
|
} else if recording_attribute_value {
|
||||||
if c == '"' {
|
if c == '"' {
|
||||||
@@ -215,9 +289,7 @@ pub fn parse(xml_string: &str) -> Vec<Box<Node>> {
|
|||||||
//skip the rest of the </ >
|
//skip the rest of the </ >
|
||||||
loop {
|
loop {
|
||||||
let c2 = chars.next();
|
let c2 = chars.next();
|
||||||
if c2.is_none() {
|
if c2.is_none() || c2.unwrap() == '>' {
|
||||||
break;
|
|
||||||
} else if c2.unwrap() == '>' {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,9 +304,7 @@ pub fn parse(xml_string: &str) -> Vec<Box<Node>> {
|
|||||||
whitespace_only = false;
|
whitespace_only = false;
|
||||||
}
|
}
|
||||||
//text node
|
//text node
|
||||||
let mut n: Node = Default::default();
|
let n: Node = Node { tag_name: c.to_string(), text_node: true, ..Default::default() };
|
||||||
n.tag_name = c.to_string();
|
|
||||||
n.text_node = true;
|
|
||||||
if chars.peek().unwrap_or(&' ') == &'<' {
|
if chars.peek().unwrap_or(&' ') == &'<' {
|
||||||
add_to_parent(&mut top_level_nodes, &parent_location, n);
|
add_to_parent(&mut top_level_nodes, &parent_location, n);
|
||||||
} else {
|
} else {
|
||||||
@@ -301,6 +371,42 @@ fn test_comments_xml_parse() {
|
|||||||
assert!(nodes[1].children[0].tag_name == " afterwards");
|
assert!(nodes[1].children[0].tag_name == " afterwards");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_weird_attr() {
|
||||||
|
//weird order
|
||||||
|
let nodes = parse("<input type=\"text\" disabled name=\"one\">");
|
||||||
|
assert!(nodes[0].attributes.get("type").unwrap() == "\"text\"");
|
||||||
|
assert!(nodes[0].attributes.get("disabled").is_some());
|
||||||
|
assert!(nodes[0].attributes.get("name").unwrap() == "\"one\"");
|
||||||
|
//newlines in tag and shit
|
||||||
|
let nodes = parse("<input
|
||||||
|
title=\"invalid text\"
|
||||||
|
|
||||||
|
name=\"one\">");
|
||||||
|
assert!(nodes[0].tag_name == "input");
|
||||||
|
//assert!(nodes[0].attributes.get("title").unwrap() == "\"invalid text\"\n"); //current has newline at end I think (TODO: fix)
|
||||||
|
//
|
||||||
|
assert!(nodes[0].attributes.get("name").unwrap() == "\"one\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_form_parse_and_output() {
|
||||||
|
let nodes = parse("<form method=\"get\" action=\"/test\">
|
||||||
|
<div><input type=\"search\" name=\"search1\"></div>
|
||||||
|
<label>Field 1:</label> test <input type=\"text\" name=\"field1\">
|
||||||
|
<label>Field 2:</label> yeah <input type=\"text\" name=\"field2\">
|
||||||
|
</form>");
|
||||||
|
assert!(nodes.len() == 1);
|
||||||
|
assert!(nodes[0].tag_name == "form");
|
||||||
|
assert!(nodes[0].children[0].children[0].tag_name == "input");
|
||||||
|
assert!(nodes[0].children[1].tag_name == "label");
|
||||||
|
assert!(nodes[0].children[3].tag_name == "input");
|
||||||
|
assert!(nodes[0].children[4].tag_name == "label");
|
||||||
|
//check .to_output()
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*#[test]
|
/*#[test]
|
||||||
fn test_real() {
|
fn test_real() {
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
@@ -309,5 +415,3 @@ fn test_real() {
|
|||||||
println!("{:?}", nodes[1].children[1].to_output());
|
println!("{:?}", nodes[1].children[1].to_output());
|
||||||
println!("{}", nodes[1].children[1].tag_name);
|
println!("{}", nodes[1].children[1].tag_name);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
//more tests 100% needed. yoink from news.ycombinator.com and en.wikipedia.org
|
|
||||||
|
|||||||
Reference in New Issue
Block a user