From 10c3746a6df45dd154955a9e91e662260f36363f Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Tue, 14 Apr 2026 14:38:42 +0800 Subject: refactor: adjust code structure --- src/render/renderer.cc | 303 +------------------------------------------------ 1 file changed, 6 insertions(+), 297 deletions(-) (limited to 'src/render/renderer.cc') diff --git a/src/render/renderer.cc b/src/render/renderer.cc index 53eca1b..7fb665f 100644 --- a/src/render/renderer.cc +++ b/src/render/renderer.cc @@ -17,92 +17,15 @@ */ #include "ricochet/render/renderer.hh" -#include -#include -#include -#include +#include "ricochet/render/input_renderer.hh" +#include "ricochet/render/internal.hh" +#include "ricochet/render/table_renderer.hh" +#include "ricochet/render/utils.hh" namespace ricochet::render { namespace { -std::string decode_entities( std::string text ) -{ - constexpr std::array, 25> entities - = { { { " ", " " }, { "<", "<" }, { ">", ">" }, { "&", "&" }, { """, "\"" }, - { "'", "'" }, { "©", "©" }, { "®", "®" }, { "™", "™" }, { "€", "€" }, - { "£", "£" }, { "¥", "¥" }, { "¢", "¢" }, { "§", "§" }, { "¶", "¶" }, - { "…", "…" }, { "†", "†" }, { "‡", "‡" }, { "•", "•" }, { "′", "′" }, - { "″", "″" }, { "‹", "‹" }, { "›", "›" }, { "‾", "‾" }, { "⁄", "⁄" } } }; - - for ( const auto& [entity, replacement] : entities ) { - std::size_t pos = 0; - while ( ( pos = text.find( entity, pos ) ) != std::string::npos ) { - text.replace( pos, entity.length(), replacement ); - pos += replacement.length(); - } - } - - std::size_t pos = 0; - while ( ( pos = text.find( "&#", pos ) ) != std::string::npos ) { - const std::size_t end = text.find( ';', pos ); - if ( end != std::string::npos && end - pos < 10 ) { - const std::string code = text.substr( pos + 2, end - pos - 2 ); - try { - std::uint32_t cp = 0; - if ( !code.empty() && ( code[0] == 'x' || code[0] == 'X' ) ) { - cp = std::stoul( code.substr( 1 ), nullptr, 16 ); - } else { - cp = std::stoul( code ); - } - if ( cp < 128 ) { - text.replace( pos, end - pos + 1, 1, static_cast( cp ) ); - continue; - } - } catch ( const std::invalid_argument& ) { - (void)0; // Not a valid number, leave as-is - } catch ( const std::out_of_range& ) { - (void)0; // Value too large, leave as-is - } - } - pos++; - } - return text; -} - -std::string extract_attr( const std::string& tag_name, std::string_view attr_name ) -{ - std::string match = " " + std::string( attr_name ) + "="; - std::size_t pos = tag_name.find( match ); - - if ( pos == std::string::npos ) { - match = std::string( attr_name ) + "="; - if ( !tag_name.starts_with( match ) ) { - return ""; - } - pos = 0; - } else { - pos += 1; - } - - pos += attr_name.length() + 1; - if ( pos >= tag_name.length() ) { - return ""; - } - - const char quote = tag_name[pos]; - if ( quote == '"' || quote == '\'' ) { - const std::size_t end = tag_name.find( quote, pos + 1 ); - if ( end != std::string::npos ) { - return tag_name.substr( pos + 1, end - pos - 1 ); - } - return ""; - } - - const std::size_t end = tag_name.find_first_of( " >/", pos ); - return tag_name.substr( pos, ( end == std::string::npos ) ? std::string::npos : end - pos ); -} - void render_link_end( const parser::DomNode& node, RenderResult& result ) { result.text += "\033[0m"; @@ -125,221 +48,9 @@ void render_img( const parser::DomNode& node, RenderResult& result ) } } -std::string build_input_hint( const std::string& tag_name, bool checked ) -{ - std::string placeholder = extract_attr( tag_name, "placeholder" ); - const 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" ) { - return checked ? "[X] " + val : "[ ] " + val; - } - if ( type == "radio" ) { - return checked ? "(X) " + val : "( ) " + val; - } - 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"; -} - -bool is_heading_tag( std::string_view tag ) -{ - return tag == "h1" || tag == "h2" || tag == "h3" || tag == "h4"; -} - -void render_input_node( const parser::DomNode& node, - RenderResult& result, - const FormContext& ctx, - std::string& output ) -{ - std::string type = extract_attr( node.tag_name, "type" ); - 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 ); - if ( base_tag == "button" && type.empty() ) { - type = "submit"; - } - const std::string default_val = extract_attr( node.tag_name, "value" ); - const bool is_checked = node.tag_name.contains( "checked" ); - result.inputs.push_back( - { extract_attr( node.tag_name, "name" ), ctx.action, ctx.method, default_val, type, is_checked } ); - - std::string hint = build_input_hint( node.tag_name, is_checked ); - if ( base_tag == "button" ) { - hint = "Submit"; - } - if ( hint != "hidden" ) { - output += " \033[7;33m[I" + std::to_string( result.inputs.size() ) + ":" + hint + "]\033[0m "; - } -} - -void render_node( const parser::DomNode& node, RenderResult& result, FormContext ctx ); - -std::size_t get_visible_length( std::string_view str ) -{ - std::size_t len = 0; - bool in_ansi = false; - for ( std::size_t i = 0; i < str.length(); ++i ) { - const auto c = static_cast( str[i] ); - if ( c == 0x1B ) { - in_ansi = true; - } else if ( in_ansi && c == 'm' ) { - in_ansi = false; - } else if ( !in_ansi ) { - if ( ( c & 0x80 ) == 0 || ( c & 0xE0 ) == 0xC0 ) { - len += 1; - } // ASCII (width 1) Latin/Greek (width 1) - else if ( ( c & 0xF0 ) == 0xE0 || ( c & 0xF8 ) == 0xF0 ) { - len += 2; - } // CJK (width 2) Emoji/Rare (width 2) - } - } - return len; -} - -std::size_t get_node_text_len( const parser::DomNode& node ) // NOLINT(misc-no-recursion) -{ - std::size_t len = get_visible_length( decode_entities( node.text_content ) ); - for ( const auto& child : node.children ) { - len += get_node_text_len( child ); - } - return len; -} - -void process_table_row_widths( const parser::DomNode& row, std::vector& widths ) -{ - std::size_t col_idx = 0; - for ( const auto& cell : row.children ) { - const std::size_t space_pos = cell.tag_name.find( ' ' ); - const std::string tag - = ( space_pos == std::string::npos ) ? cell.tag_name : cell.tag_name.substr( 0, space_pos ); - if ( tag == "td" || tag == "th" ) { - const std::size_t len = get_node_text_len( cell ) + 2; - if ( col_idx >= widths.size() ) { - widths.push_back( len ); - } else if ( len > widths[col_idx] ) { - widths[col_idx] = len; - } - col_idx++; - } - } -} - -std::vector calc_table_widths( const parser::DomNode& table_node ) -{ - std::vector widths; - for ( const auto& child : table_node.children ) { - const std::size_t space_pos = child.tag_name.find( ' ' ); - const std::string tag - = ( space_pos == std::string::npos ) ? child.tag_name : child.tag_name.substr( 0, space_pos ); - if ( tag == "tr" ) { - process_table_row_widths( child, widths ); - } else if ( tag == "tbody" || tag == "thead" ) { - for ( const auto& row : child.children ) { - if ( row.tag_name.starts_with( "tr" ) ) { - process_table_row_widths( row, widths ); - } - } - } - } - for ( auto& w : widths ) { - w = std::min( w, 30 ); - } - return widths; -} - -void render_table_row( const parser::DomNode& row, // NOLINT(misc-no-recursion) - RenderResult& result, - const FormContext& ctx, - const std::vector& widths ) -{ - std::string& output = result.text; - output += "\033[90m|\033[0m "; - std::size_t col_idx = 0; - for ( const auto& cell : row.children ) { - const std::size_t cell_space = cell.tag_name.find( ' ' ); - const std::string c_tag - = ( cell_space == std::string::npos ) ? cell.tag_name : cell.tag_name.substr( 0, cell_space ); - if ( c_tag != "td" && c_tag != "th" ) { - continue; - } - - const std::size_t text_start = output.size(); - if ( c_tag == "th" ) { - output += "\033[1;36m"; - } - - for ( const auto& cell_child : cell.children ) { - render_node( cell_child, result, ctx ); - } - - if ( c_tag == "th" ) { - output += "\033[0m"; - } - - const std::string added = output.substr( text_start ); - const std::size_t vis_len = get_visible_length( added ); - const std::size_t tw = ( col_idx < widths.size() ) ? widths[col_idx] : 10; - - if ( vis_len < tw ) { - output.append( tw - vis_len, ' ' ); - } - output += " \033[90m|\033[0m "; - col_idx++; - } - output += "\n"; -} - -void render_table( const parser::DomNode& node, // NOLINT(misc-no-recursion) - RenderResult& result, - const FormContext& ctx ) -{ - std::string& output = result.text; - const std::vector widths = calc_table_widths( node ); - - output += "\n\033[90m+" + std::string( 78, '-' ) + "+\033[0m\n"; - - for ( const auto& child : node.children ) { - const std::size_t child_space = child.tag_name.find( ' ' ); - const std::string child_tag - = ( child_space == std::string::npos ) ? child.tag_name : child.tag_name.substr( 0, child_space ); - if ( child_tag == "tr" ) { - render_table_row( child, result, ctx, widths ); - } else if ( child_tag == "tbody" || child_tag == "thead" ) { - for ( const auto& row : child.children ) { - if ( row.tag_name.starts_with( "tr" ) ) { - render_table_row( row, result, ctx, widths ); - } - } - } - } - output += "\033[90m+" + std::string( 78, '-' ) + "+\033[0m\n"; -} +} // namespace -void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) - RenderResult& result, - FormContext ctx ) +void render_node( const parser::DomNode& node, RenderResult& result, FormContext ctx ) // NOLINT(misc-no-recursion) { std::string& output = result.text; const std::size_t space_pos = node.tag_name.find( ' ' ); @@ -401,8 +112,6 @@ void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) } } -} // namespace - RenderResult Renderer::render( // NOLINT(readability-convert-member-functions-to-static) const parser::DomNode& node ) const { -- cgit v1.2.3