From 19af37a1431a65ecef723d5027455ad9260bd615 Mon Sep 17 00:00:00 2001 From: Alex Shvartzkop Date: Wed, 30 Oct 2024 08:22:33 +0300 Subject: [PATCH] Fix/improve HTML unescape function. Also adds some tests. (cherry picked from commit 6ae334a7517f41498f395004444b8fbb5423ce17) --- common/string_utils.cpp | 54 ++++++++++++++++++++------- qa/tests/common/test_kicad_string.cpp | 30 ++++++++++++++- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/common/string_utils.cpp b/common/string_utils.cpp index c70680c7b3..77c2c0881d 100644 --- a/common/string_utils.cpp +++ b/common/string_utils.cpp @@ -28,6 +28,8 @@ #include #include +#include +#include #include #include #include // StrPrintf @@ -568,22 +570,32 @@ wxString UnescapeHTML( const wxString& aString ) { wxString converted = aString; - converted.Replace( wxS( """ ), wxS( "\"" ) ); - converted.Replace( wxS( "'" ), wxS( "'" ) ); - converted.Replace( wxS( "&" ), wxS( "&" ) ); - converted.Replace( wxS( "<" ), wxS( "<" ) ); - converted.Replace( wxS( ">" ), wxS( ">" ) ); + // clang-format off + static const std::map c_replacements = { + { wxS( "quot" ), wxS( "\"" ) }, + { wxS( "apos" ), wxS( "'" ) }, + { wxS( "amp" ), wxS( "&" ) }, + { wxS( "lt" ), wxS( "<" ) }, + { wxS( "gt" ), wxS( ">" ) } + }; + // clang-format on - // Yes, &#123; is going to give unexpected results. + // Construct regex + wxString regexStr = "&(#(\\d*)|#x([a-zA-Z0-9]{4})"; - wxString result; + for( auto& [key, value] : c_replacements ) + regexStr << '|' << key; - wxRegEx regex( "&#(\\d*);" ); + regexStr << ");"; + wxRegEx regex( regexStr ); + + // Process matches size_t start = 0; size_t len = 0; - wxString str = aString; + wxString result; + wxString str = converted; while( regex.Matches( str ) ) { @@ -592,12 +604,26 @@ wxString UnescapeHTML( const wxString& aString ) result << str.Left( start ); - unsigned long codeVal = 0; - wxString code = regex.GetMatch( str, 1 ); - code.ToCULong( &codeVal ); + wxString code = regex.GetMatch( str, 1 ); + wxString codeDec = regex.GetMatch( str, 2 ); + wxString codeHex = regex.GetMatch( str, 3 ); - if( codeVal != 0 ) - result << wxUniChar( codeVal ); + if( !codeDec.IsEmpty() || !codeHex.IsEmpty() ) + { + unsigned long codeVal = 0; + + if( !codeDec.IsEmpty() ) + codeDec.ToCULong( &codeVal ); + else if( !codeHex.IsEmpty() ) + codeHex.ToCULong( &codeVal, 16 ); + + if( codeVal != 0 ) + result << wxUniChar( codeVal ); + } + else if( auto val = get_opt( c_replacements, code ) ) + { + result << *val; + } str = str.Mid( start + len ); } diff --git a/qa/tests/common/test_kicad_string.cpp b/qa/tests/common/test_kicad_string.cpp index 148bb37750..1510a261eb 100644 --- a/qa/tests/common/test_kicad_string.cpp +++ b/qa/tests/common/test_kicad_string.cpp @@ -1,4 +1,4 @@ -/* +/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 KiCad Developers, see AUTHORS.TXT for contributors. @@ -141,4 +141,32 @@ BOOST_AUTO_TEST_CASE( Double2Str ) } } + +/** + * Test #EscapeHTML and #UnescapeHTML methods. + */ +BOOST_AUTO_TEST_CASE( HTMLEscape ) +{ + using CASE = std::pair; + + // conceptually a little quirky because doubles do have all those pesky additional values + const std::vector cases = { + { "I will display € €", "I will display € €" }, + { "<", "&lt;" }, + { "Don't Ω", "Don't Ω" }, + }; + + for( const auto& c : cases ) + { + wxString original( c.first ); + wxString escaped = EscapeHTML( original ); + wxString unescaped = UnescapeHTML( escaped ); + + wxString unescapedTest = UnescapeHTML( c.second ); + + BOOST_CHECK( original == unescaped ); + BOOST_CHECK( original == unescapedTest ); + } +} + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file