diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-04-12 20:57:20 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-04-12 20:57:20 +0800 |
| commit | 5f459c7eaf9577292dd7bbf4b30164b3f53e79f3 (patch) | |
| tree | 5f213c704a6f348ebe584d4bfbdca8313b6fd8c4 /src | |
| parent | 32bce7c278e8f52d96579650088f610d62920f00 (diff) | |
| download | rwebbrowser-5f459c7eaf9577292dd7bbf4b30164b3f53e79f3.tar.gz rwebbrowser-5f459c7eaf9577292dd7bbf4b30164b3f53e79f3.tar.xz | |
feat: implement form submission support by tracking input fields and form context during rendering
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/browser.cc | 19 | ||||
| -rw-r--r-- | src/render/renderer.cc | 107 |
2 files changed, 91 insertions, 35 deletions
diff --git a/src/core/browser.cc b/src/core/browser.cc index e3a739a..0f6d9d2 100644 --- a/src/core/browser.cc +++ b/src/core/browser.cc @@ -33,7 +33,7 @@ struct PageData { std::vector<std::string> lines; std::vector<std::string> links; - std::vector<std::string> inputs; + std::vector<render::InputField> inputs; std::string title; }; @@ -390,7 +390,7 @@ InputAction handle_form_input( const tui::Terminal& terminal, return InputAction::None; } - std::string target_url; + const auto& target_input = page_data.inputs[index - 1]; std::string encoded_query = query; for ( char& ch : encoded_query ) { if ( ch == ' ' ) { @@ -398,12 +398,21 @@ InputAction handle_form_input( const tui::Terminal& terminal, } } - if ( current_url.contains( "wikipedia.org" ) ) { - target_url = "https://en.wikipedia.org/w/index.php?search=" + encoded_query; + std::string action = target_input.action; + if ( action.empty() ) { + action = current_url; } else { - target_url = "https://lite.duckduckgo.com/lite/?q=" + encoded_query; + action = normalize_url( action, current_url ); } + std::string param_name = target_input.name; + if ( param_name.empty() ) { + param_name = "q"; + } + + const char sep = action.contains( '?' ) ? '&' : '?'; + const std::string target_url = action + sep + param_name + "=" + encoded_query; + history.resize( history_idx + 1 ); history.push_back( target_url ); history_idx++; diff --git a/src/render/renderer.cc b/src/render/renderer.cc index 5f65ea7..8708206 100644 --- a/src/render/renderer.cc +++ b/src/render/renderer.cc @@ -69,19 +69,21 @@ std::string decode_entities( std::string text ) return text; } -std::string extract_href( const std::string& tag_name ) +std::string extract_attr( const std::string& tag_name, std::string_view attr_name ) { - std::size_t pos = tag_name.find( "href=\"" ); + std::string match = std::string( attr_name ) + "=\""; + std::size_t pos = tag_name.find( match ); if ( pos != std::string::npos ) { - pos += 6; - const std::size_t end = tag_name.find( '\"', pos ); + pos += match.length(); + const std::size_t end = tag_name.find( '"', pos ); if ( end != std::string::npos ) { return tag_name.substr( pos, end - pos ); } } - pos = tag_name.find( "href='" ); + match = std::string( attr_name ) + "='"; + pos = tag_name.find( match ); if ( pos != std::string::npos ) { - pos += 6; + pos += match.length(); const std::size_t end = tag_name.find( '\'', pos ); if ( end != std::string::npos ) { return tag_name.substr( pos, end - pos ); @@ -90,6 +92,58 @@ std::string extract_href( const std::string& tag_name ) return ""; } +void render_link_end( const parser::DomNode& node, RenderResult& result ) +{ + result.text += "\033[0m"; + const std::string href = extract_attr( node.tag_name, "href" ); + if ( !href.empty() && !href.starts_with( "javascript:" ) && !href.starts_with( "#" ) ) { + result.links.push_back( decode_entities( href ) ); + result.text += " \033[7m[" + std::to_string( result.links.size() ) + "]\033[0m"; + } +} + +void render_img( const parser::DomNode& node, RenderResult& result ) +{ + std::size_t alt_pos = node.tag_name.find( "alt=\"" ); + if ( alt_pos != std::string::npos ) { + alt_pos += 5; + const std::size_t alt_end = node.tag_name.find( '"', alt_pos ); + result.text += " \033[90m[Image: " + node.tag_name.substr( alt_pos, alt_end - alt_pos ) + "]\033[0m "; + } else { + result.text += " \033[90m[IMAGE]\033[0m "; + } +} + +std::string build_input_hint( const std::string& tag_name ) +{ + std::string placeholder = extract_attr( tag_name, "placeholder" ); + std::string type = extract_attr( tag_name, "type" ); + std::string val = extract_attr( tag_name, "value" ); + + if ( !placeholder.empty() ) { + return placeholder; + } + if ( type == "submit" || type == "button" ) { + return val.empty() ? "Submit" : val; + } + if ( type == "checkbox" || type == "radio" ) { + return type; + } + if ( type == "hidden" ) { + return "hidden"; + } + if ( !val.empty() ) { + return val; + } + return "text"; +} + +struct FormContext +{ + std::string action; + std::string method; +}; + bool is_ignored_tag( std::string_view tag ) { return tag == "head" || tag == "style" || tag == "script" || tag == "meta" || tag == "title" || tag == "link"; @@ -101,12 +155,10 @@ bool is_heading_tag( std::string_view tag ) } void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) - RenderResult& result ) // NOLINT(bugprone-easily-swappable-parameters) + RenderResult& result, + FormContext ctx ) { std::string& output = result.text; - std::vector<std::string>& links = result.links; - std::vector<std::string>& inputs = result.inputs; - const std::size_t space_pos = node.tag_name.find( ' ' ); const std::string base_tag = ( space_pos == std::string::npos ) ? node.tag_name : node.tag_name.substr( 0, space_pos ); @@ -114,12 +166,16 @@ void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) if ( is_ignored_tag( base_tag ) ) { return; } - if ( base_tag.empty() ) { output += decode_entities( node.text_content ); return; } + if ( base_tag == "form" ) { + ctx.action = extract_attr( node.tag_name, "action" ); + ctx.method = extract_attr( node.tag_name, "method" ); + } + const bool is_header = is_heading_tag( base_tag ); const bool is_link = ( base_tag == "a" ); const bool is_list_item = ( base_tag == "li" ); @@ -132,8 +188,12 @@ void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) } else if ( is_list_item ) { output += "\n • "; } else if ( is_input ) { - inputs.emplace_back( "input_field" ); - output += " \033[7;33m[I" + std::to_string( inputs.size() ) + "]\033[0m "; + result.inputs.push_back( { extract_attr( node.tag_name, "name" ), ctx.action, ctx.method } ); + const std::string hint = build_input_hint( node.tag_name ); + + if ( hint != "hidden" ) { + output += " \033[7;33m[I" + std::to_string( result.inputs.size() ) + ":" + hint + "]\033[0m "; + } } else if ( base_tag == "p" || base_tag == "div" || is_header ) { if ( !output.empty() && output.back() != '\n' ) { output += "\n"; @@ -141,33 +201,20 @@ void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) } for ( const auto& child : node.children ) { - render_node( child, result ); + render_node( child, result, ctx ); } if ( is_header ) { output += "\033[0m"; } else if ( is_link ) { - output += "\033[0m"; - const std::string href = extract_href( node.tag_name ); - if ( !href.empty() && !href.starts_with( "javascript:" ) && !href.starts_with( "#" ) ) { - links.push_back( decode_entities( href ) ); - output += " \033[7m[" + std::to_string( links.size() ) + "]\033[0m"; // 打印数字标签! - } + render_link_end( node, result ); } if ( base_tag == "h1" || base_tag == "p" || base_tag == "div" ) { output += "\n"; } - if ( base_tag == "img" ) { - std::size_t alt_pos = node.tag_name.find( "alt=\"" ); - if ( alt_pos != std::string::npos ) { - alt_pos += 5; - const std::size_t alt_end = node.tag_name.find( '"', alt_pos ); - output += " \033[90m[Image: " + node.tag_name.substr( alt_pos, alt_end - alt_pos ) + "]\033[0m "; - } else { - output += " \033[90m[IMAGE]\033[0m "; - } + render_img( node, result ); } } @@ -177,7 +224,7 @@ RenderResult Renderer::render( // NOLINT(readability-convert-member-functions-to const parser::DomNode& node ) const { RenderResult result; - render_node( node, result ); + render_node( node, result, FormContext {} ); return result; } |
