From 78ba8e5ced82d8dcbdbb795f190c43b85d096eaa Mon Sep 17 00:00:00 2001 From: Beman Dawes Date: Wed, 7 Sep 2005 21:01:25 +0000 Subject: [PATCH] small fixes, changes to allow path_table.cpp to support both Windows and Posix in same program (for testing purposes) [SVN r30861] --- example/path_table.cpp | 264 ++++++++++++++++++++++++ include/boost/filesystem/cerrno.hpp | 2 +- include/boost/filesystem/config.hpp | 7 +- include/boost/filesystem/operations.hpp | 2 +- include/boost/filesystem/path.hpp | 174 ++++++++++------ src/operations.cpp | 8 +- test/path_test.cpp | 31 ++- 7 files changed, 413 insertions(+), 75 deletions(-) create mode 100644 example/path_table.cpp diff --git a/example/path_table.cpp b/example/path_table.cpp new file mode 100644 index 0000000..b175873 --- /dev/null +++ b/example/path_table.cpp @@ -0,0 +1,264 @@ +// Generate an HTML table showing path decomposition -----------------------// + +// Copyright Beman Dawes 2005. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/filesystem for documentation. + +// For purposes of generating the table, support both POSIX and Windows paths +#define BOOST_FILESYSTEM_NAMESPACE posix +#define BOOST_POSIX_PATH +#include "boost/filesystem/path.hpp" + +#undef BOOST_FILESYSTEM_PATH_HPP +#undef BOOST_FILESYSTEM_NAMESPACE +#define BOOST_FILESYSTEM_NAMESPACE windows +#define BOOST_WINDOWS_PATH +#include "boost/filesystem/path.hpp" + +namespace pos = boost::posix; +namespace win = boost::windows; + +#include +#include + +using std::string; +using std::cout; + +namespace +{ + std::ifstream infile; + std::ofstream outfile; + + const string empty_string; + + struct column_abc + { + virtual string heading() const = 0; + virtual string cell_value( const pos::path & p ) const = 0; + virtual string cell_value( const win::path & p ) const = 0; + }; + + struct c0 : public column_abc + { + string heading() const { return string("string()"); } + string cell_value( const pos::path & p ) const + { + return p.string(); + } + string cell_value( const win::path & p ) const + { + return p.string(); + } + } o0; + + struct c1 : public column_abc + { + string heading() const { return string("file_
string()"); } + string cell_value( const pos::path & p ) const { return p.file_string(); } + string cell_value( const win::path & p ) const { return p.file_string(); } + } o1; + + struct c2 : public column_abc + { + string heading() const { return string("Elements found
by iteration"); } + string cell_value( const pos::path & p ) const + { + string s; + for( pos::path::iterator i(p.begin()); i != p.end(); ++i ) + { + if ( i != p.begin() ) s += ','; + s += '\"'; + s += *i; + s += '\"'; + } + if ( s.empty() ) s += "\"\""; + return s; + } + string cell_value( const win::path & p ) const + { + string s; + for( win::path::iterator i(p.begin()); i != p.end(); ++i ) + { + if ( i != p.begin() ) s += ','; + s += '\"'; + s += *i; + s += '\"'; + } + if ( s.empty() ) s += "\"\""; + return s; + } + } o2; + + struct c3 : public column_abc + { + string heading() const { return string("root_
path()
.string()
"); } + string cell_value( const pos::path & p ) const { return p.root_path().string(); } + string cell_value( const win::path & p ) const { return p.root_path().string(); } + } o3; + + struct c4 : public column_abc + { + string heading() const { return string("root_
name()
"); } + string cell_value( const pos::path & p ) const { return p.root_name(); } + string cell_value( const win::path & p ) const { return p.root_name(); } + } o4; + + struct c5 : public column_abc + { + string heading() const { return string("root_
directory()
"); } + string cell_value( const pos::path & p ) const { return p.root_directory(); } + string cell_value( const win::path & p ) const { return p.root_directory(); } + } o5; + + struct c6 : public column_abc + { + string heading() const { return string("relative_
path()
.string()
"); } + string cell_value( const pos::path & p ) const { return p.relative_path().string(); } + string cell_value( const win::path & p ) const { return p.relative_path().string(); } + } o6; + + struct c7 : public column_abc + { + string heading() const { return string("branch_
path()
.string()
"); } + string cell_value( const pos::path & p ) const { return p.branch_path().string(); } + string cell_value( const win::path & p ) const { return p.branch_path().string(); } + } o7; + + struct c8 : public column_abc + { + string heading() const { return string("leaf()"); } + string cell_value( const pos::path & p ) const { return p.leaf(); } + string cell_value( const win::path & p ) const { return p.leaf(); } + } o8; + + const column_abc * column[] = { &o2, &o0, &o1, &o3, &o4, &o5, &o6, &o7, &o8 }; + + // do_cell ---------------------------------------------------------------// + + void do_cell( const string & test_case, int i ) + { + + string posix_result( column[i]->cell_value( pos::path(test_case) ) ); + string windows_result( column[i]->cell_value( win::path(test_case) ) ); + + outfile << + (posix_result != windows_result + ? "" + : ""); + + if ( posix_result.empty() || posix_result[0] != '\"' ) + outfile << '\"' << posix_result << '\"'; + else + outfile << posix_result; + + if ( posix_result != windows_result ) + { + outfile << "
"; + if ( windows_result.empty() || windows_result[0] != '\"' ) + outfile << '\"' << windows_result << '\"'; + else + outfile << windows_result; + } + + outfile << "
\n"; + } + +// do_row ------------------------------------------------------------------// + + void do_row( const string & test_case ) + { + outfile << "\n\"" << test_case << "\"\n"; + + for ( int i = 0; i < sizeof(column)/sizeof(column_abc&); ++i ) + { + do_cell( test_case, i ); + } + + outfile << "\n"; + } + +// do_table ----------------------------------------------------------------// + + void do_table() + { + outfile << + "

Path Decomposition Table for POSIX and Windows

\n" + "

Shaded entries indicate cases where POSIX and Windows\n" + "implementations yield different results. The top value is the\n" + "POSIX result and the bottom value is the Windows result.\n" + "\n" + "

\n" + ; + + // generate the column headings + + outfile << "

\n"; + + for ( int i = 0; i < sizeof(column)/sizeof(column_abc&); ++i ) + { + outfile << "\n"; + } + + outfile << "\n"; + + // generate a row for each input line + + string test_case; + while ( std::getline( infile, test_case ) ) + { + do_row( test_case ); + } + + outfile << "
Constructor
argument
" << column[i]->heading() << "
\n"; + } + +} // unnamed namespace + +// main --------------------------------------------------------------------// + +#define BOOST_NO_CPP_MAIN_SUCCESS_MESSAGE +#include + +int cpp_main( int argc, char * argv[] ) // note name! +{ + if ( argc != 3 ) + { + std::cerr << + "Usage: path_table input-file output-file\n" + " input-file contains the paths to appear in the table.\n" + " output-file will contain the generated HTML.\n" + ; + return 1; + } + + infile.open( argv[1] ); + if ( !infile ) + { + std::cerr << "Could not open input file: " << argv[1] << std::endl; + return 1; + } + + outfile.open( argv[2] ); + if ( !outfile ) + { + std::cerr << "Could not open output file: " << argv[2] << std::endl; + return 1; + } + + outfile << "\n" + "\n" + "Path Decomposition Table\n" + "\n" + "\n" + ; + + do_table(); + + outfile << "\n" + "\n" + ; + + return 0; +} diff --git a/include/boost/filesystem/cerrno.hpp b/include/boost/filesystem/cerrno.hpp index d3b8358..5a3f082 100644 --- a/include/boost/filesystem/cerrno.hpp +++ b/include/boost/filesystem/cerrno.hpp @@ -17,7 +17,7 @@ #endif #define EBADHANDLE 9998 // bad handle -#define EOTHERERR 9999 // Other operating system error not translatable +#define EOTHER 9999 // Other error not translatable // to a POSIX errno value #endif // include guard diff --git a/include/boost/filesystem/config.hpp b/include/boost/filesystem/config.hpp index ebf58c0..3fa395f 100644 --- a/include/boost/filesystem/config.hpp +++ b/include/boost/filesystem/config.hpp @@ -14,6 +14,11 @@ #define BOOST_FILESYSTEM_I18N // aid users wishing to compile several versions +// ability to change namespace aids path_table.cpp ------------------------// +#ifndef BOOST_FILESYSTEM_NAMESPACE +# define BOOST_FILESYSTEM_NAMESPACE filesystem +#endif + // This header implements separate compilation features as described in // http://www.boost.org/more/separate_compilation.html @@ -35,7 +40,7 @@ // BOOST_WINDOWS_PATH enables Windows path syntax recognition -# if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__) +# if !defined(BOOST_POSIX_PATH) && (defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__)) # define BOOST_WINDOWS_PATH # endif diff --git a/include/boost/filesystem/operations.hpp b/include/boost/filesystem/operations.hpp index deb1c05..db0bff8 100644 --- a/include/boost/filesystem/operations.hpp +++ b/include/boost/filesystem/operations.hpp @@ -70,7 +70,7 @@ namespace boost static const status_flags symlink_flag = 32; struct symlink_t{}; - extern symlink_t symlink; + BOOST_FILESYSTEM_DECL extern symlink_t symlink; template class basic_directory_entry; diff --git a/include/boost/filesystem/path.hpp b/include/boost/filesystem/path.hpp index ed28995..bcabcd3 100644 --- a/include/boost/filesystem/path.hpp +++ b/include/boost/filesystem/path.hpp @@ -33,7 +33,7 @@ namespace boost { - namespace filesystem + namespace BOOST_FILESYSTEM_NAMESPACE { struct path_traits; @@ -233,11 +233,14 @@ namespace boost ~basic_path() {} // append operations: + // overloads are not required to support use cases (automatic conversions + // take care of that), but are provided because they allow implementations + // to avoid construction of otherwise unneeded temporaries. basic_path & operator /=( const value_type * rhs ); basic_path & operator /=( const basic_path & rhs ) { return operator /=( rhs.string().c_str() ); } basic_path & operator /=( const string_type & rhs ) - { return operator /=( rhs.c_str() ); } + { return operator /=( rhs.c_str() ); } basic_path operator /( const basic_path & rhs ) const { return basic_path( *this ) /= rhs; } @@ -293,23 +296,23 @@ namespace boost { private: friend class boost::iterator_core_access; - friend class boost::filesystem::basic_path; + friend class boost::BOOST_FILESYSTEM_NAMESPACE::basic_path; const string_type & dereference() const { return m_name; } bool equal( const iterator & rhs ) const { return m_path_ptr == rhs.m_path_ptr && m_pos == rhs.m_pos; } - friend class boost::filesystem::detail::iterator_helper; + friend class boost::BOOST_FILESYSTEM_NAMESPACE::detail::iterator_helper; void increment() { - boost::filesystem::detail::iterator_helper::do_increment( + boost::BOOST_FILESYSTEM_NAMESPACE::detail::iterator_helper::do_increment( *this ); } void decrement() { - boost::filesystem::detail::iterator_helper::do_decrement( + boost::BOOST_FILESYSTEM_NAMESPACE::detail::iterator_helper::do_decrement( *this ); } @@ -326,8 +329,11 @@ namespace boost iterator begin() const; iterator end() const; -// TODO: should these definitions follow the std::basic_string practice? // relational operators + // overloads for string_type and value_type are not provided because + // they are not needed to compile use cases (automatic conversions + // take care of that), and there is no efficiency gain (lexicographical + // comparison requires basic_paths). bool operator<( const basic_path & that ) const { return std::lexicographical_compare( @@ -349,17 +355,18 @@ namespace boost // Note: This is an implementation for POSIX and Windows, where there // are only minor differences between generic and native path grammars. // Private members might be quite different in other implementations, - // particularly where there were wide differences between generic and + // particularly where there were wide differences between portable and // native path formats, or between file_string() and - // directory_string() formats, or simply that the implementer - // was willing expend additional memory to achieve greater speed. - - string_type m_path; // invariant: portable generic path grammar + // directory_string() formats, or simply that the implementation + // was willing expend additional memory to achieve greater speed for + // some operations at the expense of other operations. + string_type m_path; // invariant: portable path grammar + // on Windows, backslashes converted to slashes // Was qualified; como433beta8 reports: // warning #427-D: qualified name is not allowed in member declaration friend class iterator; - friend class boost::filesystem::detail::iterator_helper; + friend class boost::BOOST_FILESYSTEM_NAMESPACE::detail::iterator_helper; // Deprecated features ease transition for existing code. Don't use these // in new code. @@ -427,7 +434,8 @@ namespace boost typename String::size_type end_pos ) // end_pos is past-the-end position // return 0 if str itself is leaf (or empty) { - typedef typename boost::filesystem::basic_path path_type; + typedef typename + boost::BOOST_FILESYSTEM_NAMESPACE::basic_path path_type; // case: "//" if ( end_pos == 2 @@ -471,7 +479,7 @@ namespace boost element_size = 0; if ( src.empty() ) return; - typedef typename boost::filesystem::basic_path path_type; + typedef typename boost::BOOST_FILESYSTEM_NAMESPACE::basic_path path_type; typename String::size_type cur(0); @@ -531,7 +539,7 @@ namespace boost typename String::size_type size ) // return npos if no root_directory found { - typedef typename boost::filesystem::basic_path path_type; + typedef typename boost::BOOST_FILESYSTEM_NAMESPACE::basic_path path_type; # ifdef BOOST_WINDOWS_PATH // case "c:/" @@ -560,7 +568,32 @@ namespace boost if ( size > 0 && s[0] == path_separator::value ) return 0; return String::npos; + } + // is_non_root_slash helper -------------------------------------------// + + template + bool is_non_root_slash( const String & str, + typename String::size_type pos ) // pos is position of the slash + { + typedef typename + boost::BOOST_FILESYSTEM_NAMESPACE::basic_path + path_type; + + assert( !str.empty() && str[pos] == path_separator::value + && "precondition violation" ); + + // subsequent logic expects pos to be for leftmost slash of a set + while ( pos > 0 && str[pos-1] == path_separator::value ) + --pos; + + return pos != 0 + && (pos <= 2 || str[1] != path_separator::value + || str.find( path_separator::value, 2 ) != pos) +# ifdef BOOST_WINDOWS_PATH + && (pos !=2 || str[1] != path_device_delim::value) +# endif + ; } } // namespace detail @@ -569,8 +602,14 @@ namespace boost template String basic_path::leaf() const { - return m_path.substr( + typename String::size_type end_pos( detail::leaf_pos( m_path, m_path.size() ) ); + return (m_path.size() + && end_pos + && m_path[end_pos] == path_separator::value + && detail::is_non_root_slash< String, Traits >(m_path, end_pos)) + ? String( 1, path_relative::value ) + : m_path.substr( end_pos ); } template @@ -646,12 +685,8 @@ namespace boost template basic_path basic_path::root_path() const { - return basic_path( -# ifdef BOOST_WINDOWS_PATH - root_name() ) /= root_directory(); -# else - root_directory() ); -# endif + // even on POSIX, root_name() is non-empty() on network paths + return basic_path( root_name() ) /= root_directory(); } // path query functions -------------------------------------------------// @@ -902,65 +937,71 @@ namespace boost // do_increment ------------------------------------------------------// template - void iterator_helper::do_increment( iterator & ph ) + void iterator_helper::do_increment( iterator & itr ) { - assert( ph.m_pos < ph.m_path_ptr->m_path.size() && "basic_path::iterator increment past end()" ); + typedef typename Path::string_type string_type; + typedef typename Path::traits_type traits_type; - bool was_net( ph.m_name.size() > 2 - && ph.m_name[0] == path_separator::value - && ph.m_name[1] == path_separator::value - && ph.m_name[2] != path_separator::value ); + assert( itr.m_pos < itr.m_path_ptr->m_path.size() && "basic_path::iterator increment past end()" ); - ph.m_pos += ph.m_name.size(); - if ( ph.m_pos == ph.m_path_ptr->m_path.size() ) // end + bool was_net( itr.m_name.size() > 2 + && itr.m_name[0] == path_separator::value + && itr.m_name[1] == path_separator::value + && itr.m_name[2] != path_separator::value ); + + // increment to position past current element + itr.m_pos += itr.m_name.size(); + + // if end reached, create end iterator + if ( itr.m_pos == itr.m_path_ptr->m_path.size() ) { - ph.m_name.erase( ph.m_name.begin(), ph.m_name.end() ); // VC++ 6.0 lib didn't supply clear() + itr.m_name.erase( itr.m_name.begin(), itr.m_name.end() ); // VC++ 6.0 lib didn't supply clear() return; } - if ( ph.m_path_ptr->m_path[ph.m_pos] == path_separator::value ) + // process separator (Windows drive spec is only case not a separator) + if ( itr.m_path_ptr->m_path[itr.m_pos] == path_separator::value ) { + // detect root directory if ( was_net # ifdef BOOST_WINDOWS_PATH // case "c:/" - || ph.m_name[ph.m_name.size()-1] == path_device_delim::value + || itr.m_name[itr.m_name.size()-1] == path_device_delim::value # endif ) { - ph.m_name = path_separator::value; + itr.m_name = path_separator::value; return; } - while ( ph.m_pos != ph.m_path_ptr->m_path.size() - && ph.m_path_ptr->m_path[ph.m_pos] == path_separator::value ) - { ++ph.m_pos; } + // bypass separators + while ( itr.m_pos != itr.m_path_ptr->m_path.size() + && itr.m_path_ptr->m_path[itr.m_pos] == path_separator::value ) + { ++itr.m_pos; } - // treat trailing "/" as if "/.", per POSIX spec - if ( ph.m_pos == ph.m_path_ptr->m_path.size() ) + // detect trailing separator, and treat it as ".", per POSIX spec + if ( itr.m_pos == itr.m_path_ptr->m_path.size() + && detail::is_non_root_slash< string_type, traits_type >( + itr.m_path_ptr->m_path, itr.m_pos-1 ) ) { - --ph.m_pos; - ph.m_name = path_relative::value; + --itr.m_pos; + itr.m_name = path_relative::value; return; } } - typedef typename Path::string_type string_type; + // get next element typename string_type::size_type end_pos( - ph.m_path_ptr->m_path.find( path_separator::value, ph.m_pos ) ); -# ifdef BOOST_WINDOWS_PATH - if ( end_pos == string_type::npos ) - end_pos = - ph.m_path_ptr->m_path.find( path_alt_separator::value, ph.m_pos ); -# endif - ph.m_name = ph.m_path_ptr->m_path.substr( ph.m_pos, end_pos - ph.m_pos ); + itr.m_path_ptr->m_path.find( path_separator::value, itr.m_pos ) ); + itr.m_name = itr.m_path_ptr->m_path.substr( itr.m_pos, end_pos - itr.m_pos ); } // do_decrement ------------------------------------------------------// template - void iterator_helper::do_decrement( iterator & ph ) + void iterator_helper::do_decrement( iterator & itr ) { - assert( ph.m_pos && "basic_path::iterator decrement past begin()" ); + assert( itr.m_pos && "basic_path::iterator decrement past begin()" ); typedef typename Path::string_type string_type; typedef typename Path::traits_type traits_type; @@ -972,23 +1013,22 @@ namespace boost # endif 0 }; - typename string_type::size_type end_pos( ph.m_pos ); + typename string_type::size_type end_pos( itr.m_pos ); typename string_type::size_type root_dir_pos( detail::root_directory_start( - ph.m_path_ptr->m_path, end_pos ) ); + itr.m_path_ptr->m_path, end_pos ) ); // if at end and there was a trailing non-root '/', return "." - if ( ph.m_pos == ph.m_path_ptr->m_path.size() - && ph.m_path_ptr->m_path.size() > 1 - && ph.m_path_ptr->m_path[ph.m_pos-1] == path_separator::value - && (root_dir_pos == string_type::npos // there is no root - || ph.m_path_ptr->m_path.find_first_not_of( separators, - root_dir_pos ) != string_type::npos) // this is not the root + if ( itr.m_pos == itr.m_path_ptr->m_path.size() + && itr.m_path_ptr->m_path.size() > 1 + && itr.m_path_ptr->m_path[itr.m_pos-1] == path_separator::value + && detail::is_non_root_slash< string_type, traits_type >( + itr.m_path_ptr->m_path, itr.m_pos-1 ) ) { - --ph.m_pos; - ph.m_name = path_relative::value; + --itr.m_pos; + itr.m_name = path_relative::value; return; } @@ -997,13 +1037,13 @@ namespace boost ; end_pos > 0 && (end_pos-1) != root_dir_pos - && ph.m_path_ptr->m_path[end_pos-1] == path_separator::value + && itr.m_path_ptr->m_path[end_pos-1] == path_separator::value ; --end_pos ) {} - ph.m_pos = detail::leaf_pos - ( ph.m_path_ptr->m_path, end_pos ); - ph.m_name = ph.m_path_ptr->m_path.substr( ph.m_pos, end_pos - ph.m_pos ); + itr.m_pos = detail::leaf_pos + ( itr.m_path_ptr->m_path, end_pos ); + itr.m_name = itr.m_path_ptr->m_path.substr( itr.m_pos, end_pos - itr.m_pos ); } } // namespace detail @@ -1081,7 +1121,7 @@ namespace boost return m_imp_ptr->m_what.c_str(); } - } // namespace filesystem + } // namespace BOOST_FILESYSTEM_NAMESPACE } // namespace boost #include // pops abi_prefix.hpp pragmas diff --git a/src/operations.cpp b/src/operations.cpp index 77e2b52..a11f08f 100644 --- a/src/operations.cpp +++ b/src/operations.cpp @@ -872,8 +872,8 @@ namespace boost struct stat path_stat; if ( ::stat( ph.c_str(), &path_stat ) != 0 ) return std::make_pair( errno, 0 ); - if ( S_ISDIR( path_stat.st_mode ) ) - return std::make_pair( EISDIR, 0 ); + if ( !S_IFREG( path_stat.st_mode ) ) + return std::make_pair( EPERM, 0 ); return std::make_pair( 0, static_cast(path_stat.st_size) ); } @@ -939,8 +939,8 @@ namespace boost const std::string & new_ph ) { // we don't allow hard links to directories; too non-portable - if ( (status_api( existing_ph ) & fs::directory_flag) != 0 ) - return EISDIR; + if ( (status_api( existing_ph ) & fs::file_flag) == 0 ) + return EPERM; return ::link( existing_ph.c_str(), new_ph.c_str() ) == 0 ? 0 : errno; } diff --git a/test/path_test.cpp b/test/path_test.cpp index f0802d2..b1128a3 100644 --- a/test/path_test.cpp +++ b/test/path_test.cpp @@ -215,12 +215,23 @@ int test_main( int, char*[] ) BOOST_CHECK( p4.string() == "foobar" ); // These verify various overloads don't cause compiler errors + + fs::exists( p1 ); fs::exists( "foo" ); fs::exists( std::string( "foo" ) ); - fs::exists( p1 ); + + fs::exists( p1 / path( "foo" ) ); + fs::exists( p1 / "foo" ); + fs::exists( p1 / std::string( "foo" ) ); + fs::exists( "foo" / p1 ); fs::exists( std::string( "foo" ) / p1 ); + p4 /= path( "foo" ); + p4 /= "foo"; + p4 /= std::string( "foo" ); + + try { fs::is_directory("should-throw-exception"); } catch ( const fs::filesystem_error & ex ) { @@ -671,6 +682,24 @@ int test_main( int, char*[] ) else BOOST_CHECK( !p.is_complete() ); + p = "/foo/"; + CHECK_EQUAL( p.relative_path().string(), "foo/" ); + CHECK_EQUAL( p.branch_path().string(), "/foo" ); + CHECK_EQUAL( p.leaf(), "." ); + CHECK_EQUAL( p.root_name(), "" ); + CHECK_EQUAL( p.root_directory(), "/" ); + CHECK_EQUAL( p.root_path().string(), "/" ); + BOOST_CHECK( p.has_root_path() ); + BOOST_CHECK( !p.has_root_name() ); + BOOST_CHECK( p.has_root_directory() ); + BOOST_CHECK( p.has_relative_path() ); + BOOST_CHECK( p.has_leaf() ); + BOOST_CHECK( p.has_branch_path() ); + if ( platform == "POSIX" ) + BOOST_CHECK( p.is_complete() ); + else + BOOST_CHECK( !p.is_complete() ); + p = "///foo"; CHECK_EQUAL( p.relative_path().string(), "foo" ); CHECK_EQUAL( p.branch_path().string(), "/" );