$include_dir="/home/hyper-archives/boost-commit/include"; include("$include_dir/msg-header.inc") ?>
Subject: [Boost-commit] svn:boost r63462 - in branches/release/tools/inspect: . build build/msvc
From: daniel_james_at_[hidden]
Date: 2010-06-30 19:33:34
Author: danieljames
Date: 2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
New Revision: 63462
URL: http://svn.boost.org/trac/boost/changeset/63462
Log:
Merge inspect from trunk.
Added:
   branches/release/tools/inspect/apple_macro_check.cpp
      - copied, changed from r57825, /trunk/tools/inspect/apple_macro_check.cpp
   branches/release/tools/inspect/apple_macro_check.hpp
      - copied, changed from r57825, /trunk/tools/inspect/apple_macro_check.hpp
   branches/release/tools/inspect/link_check_test.html
      - copied unchanged from r59102, /trunk/tools/inspect/link_check_test.html
Properties modified: 
   branches/release/tools/inspect/   (props changed)
Text files modified: 
   branches/release/tools/inspect/apple_macro_check.cpp           |    25 ++++-                                   
   branches/release/tools/inspect/apple_macro_check.hpp           |     2                                         
   branches/release/tools/inspect/ascii_check.cpp                 |     3                                         
   branches/release/tools/inspect/build/Jamfile.v2                |     2                                         
   branches/release/tools/inspect/build/msvc/boost_inspect.vcproj |    28 +----                                   
   branches/release/tools/inspect/build/msvc/readme.txt           |     2                                         
   branches/release/tools/inspect/inspect.cpp                     |    37 +++++++-                                
   branches/release/tools/inspect/inspector.hpp                   |     3                                         
   branches/release/tools/inspect/link_check.cpp                  |   177 ++++++++++++++++++++++++++++++++------- 
   branches/release/tools/inspect/link_check.hpp                  |    13 ++                                      
   branches/release/tools/inspect/unnamed_namespace_check.cpp     |     3                                         
   11 files changed, 222 insertions(+), 73 deletions(-)
Copied: branches/release/tools/inspect/apple_macro_check.cpp (from r57825, /trunk/tools/inspect/apple_macro_check.cpp)
==============================================================================
--- /trunk/tools/inspect/apple_macro_check.cpp	(original)
+++ branches/release/tools/inspect/apple_macro_check.cpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -11,13 +11,16 @@
 #include <functional>
 #include "boost/regex.hpp"
 #include "boost/lexical_cast.hpp"
+#include "boost/filesystem/operations.hpp"
+
+namespace fs = boost::filesystem;
 
 namespace
 {
   boost::regex apple_macro_regex(
     "("
     "^\\s*#\\s*undef\\s*" // # undef
-    "\\b(min|max)\\b"     // followed by min or max, whole word
+    "\\b(check|verify|require|check_error)\\b"     // followed by apple macro name, whole word
     ")"
     "|"                   // or (ignored)
     "("
@@ -29,7 +32,7 @@
     ")"
     "|"                   // or
     "("
-    "\\b(check|verify|require|check_error)\\b" // min or max, whole word
+    "\\b(check|verify|require|check_error)\\b" // apple macro name, whole word
     "\\s*\\("         // followed by 0 or more spaces and an opening paren
     ")"
     , boost::regex::normal);
@@ -59,8 +62,15 @@
     {
       if (contents.find( "boostinspect:" "naapple_macros" ) != string::npos) return;
 
+      // Only check files in the boost directory, as we can avoid including the
+      // apple test headers elsewhere.
+      path relative( relative_to( full_path, fs::initial_path() ), fs::no_check );
+      if ( relative.empty() || *relative.begin() != "boost") return;
+
       boost::sregex_iterator cur(contents.begin(), contents.end(), apple_macro_regex), end;
 
+      long errors = 0;
+
       for( ; cur != end; ++cur /*, ++m_files_with_errors*/ )
       {
 
@@ -79,12 +89,15 @@
               }
           }
 
-          ++m_files_with_errors;
-          error( library_name, full_path, string(name())
-              + " violation of Boost apple-macro guidelines on line "
-              + boost::lexical_cast<string>( line_number ) );
+          ++errors;
+          error( library_name, full_path, 
+            "Apple macro clash: " + std::string((*cur)[0].first, (*cur)[0].second-1),
+            line_number );
         }
       }
+      if(errors > 0) {
+        ++m_files_with_errors;
+      }
     }
   } // namespace inspect
 } // namespace boost
Copied: branches/release/tools/inspect/apple_macro_check.hpp (from r57825, /trunk/tools/inspect/apple_macro_check.hpp)
==============================================================================
--- /trunk/tools/inspect/apple_macro_check.hpp	(original)
+++ branches/release/tools/inspect/apple_macro_check.hpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -31,7 +31,7 @@
         const std::string & contents );
 
       virtual ~apple_macro_check()
-        { std::cout << "  " << m_files_with_errors << " files with apple macros" << line_break(); }
+        { std::cout << "  " << m_files_with_errors << " files with Apple macros" << line_break(); }
     };
   }
 }
Modified: branches/release/tools/inspect/ascii_check.cpp
==============================================================================
--- branches/release/tools/inspect/ascii_check.cpp	(original)
+++ branches/release/tools/inspect/ascii_check.cpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -91,8 +91,9 @@
       if ( bad_char != contents.end ())
       {
         ++m_files_with_errors;
+        int ln = std::count( contents.begin(), bad_char, '\n' ) + 1;
         string the_line = find_line ( contents, bad_char );
-        error( library_name, full_path, string ( name()) + " non-ASCII: " + the_line );
+        error( library_name, full_path, "Non-ASCII: " + the_line, ln );
       }
     }
   } // namespace inspect
Modified: branches/release/tools/inspect/build/Jamfile.v2
==============================================================================
--- branches/release/tools/inspect/build/Jamfile.v2	(original)
+++ branches/release/tools/inspect/build/Jamfile.v2	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -15,7 +15,7 @@
 exe inspect
     :
     inspect.cpp license_check.cpp link_check.cpp path_name_check.cpp tab_check.cpp crlf_check.cpp end_check.cpp unnamed_namespace_check.cpp ascii_check.cpp
-    copyright_check.cpp minmax_check.cpp
+    copyright_check.cpp minmax_check.cpp apple_macro_check.cpp
     /boost//filesystem/<link>static
     /boost//regex/<link>static
     :
Modified: branches/release/tools/inspect/build/msvc/boost_inspect.vcproj
==============================================================================
--- branches/release/tools/inspect/build/msvc/boost_inspect.vcproj	(original)
+++ branches/release/tools/inspect/build/msvc/boost_inspect.vcproj	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -2,7 +2,7 @@
 <VisualStudioProject
         ProjectType="Visual C++"
         Version="9.00"
-	Name="boost_inspect"
+	Name="inspect"
         ProjectGUID="{0EC8AC1C-6D1F-47FC-A06A-9CC3F924BD82}"
         RootNamespace="boost_inspect"
         Keyword="Win32Proj"
@@ -42,7 +42,7 @@
                                 Name="VCCLCompilerTool"
                                 Optimization="0"
                                 AdditionalIncludeDirectories="..\..\..\.."
-				PreprocessorDefinitions="BOOST_SYSTEM_NO_LIB;BOOST_FILESYSTEM_NO_LIB;WIN32;_DEBUG;_CONSOLE"
+				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
                                 MinimalRebuild="true"
                                 ExceptionHandling="2"
                                 BasicRuntimeChecks="3"
@@ -118,7 +118,7 @@
                                 Optimization="2"
                                 EnableIntrinsicFunctions="true"
                                 AdditionalIncludeDirectories="..\..\..\.."
-				PreprocessorDefinitions="BOOST_SYSTEM_NO_LIB;BOOST_FILESYSTEM_NO_LIB;WIN32;NDEBUG;_CONSOLE"
+				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
                                 ExceptionHandling="2"
                                 RuntimeLibrary="2"
                                 EnableFunctionLevelLinking="true"
@@ -177,6 +177,10 @@
                         UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
 			>
                         <File
+				RelativePath="..\..\apple_macro_check.cpp"
+				>
+			</File>
+			<File
                                 RelativePath="..\..\ascii_check.cpp"
 				>
                         </File>
@@ -189,7 +193,7 @@
 				>
                         </File>
                         <File
-				RelativePath="..\..\..\..\libs\system\src\error_code.cpp"
+				RelativePath="..\..\end_check.cpp"
 				>
                         </File>
                         <File
@@ -209,22 +213,10 @@
 				>
                         </File>
                         <File
-				RelativePath="..\..\..\..\libs\filesystem\src\operations.cpp"
-				>
-			</File>
-			<File
-				RelativePath="..\..\..\..\libs\filesystem\src\path.cpp"
-				>
-			</File>
-			<File
                                 RelativePath="..\..\path_name_check.cpp"
 				>
                         </File>
                         <File
-				RelativePath="..\..\..\..\libs\filesystem\src\portability.cpp"
-				>
-			</File>
-			<File
                                 RelativePath="..\..\tab_check.cpp"
 				>
                         </File>
@@ -232,10 +224,6 @@
                                 RelativePath="..\..\unnamed_namespace_check.cpp"
 				>
                         </File>
-			<File
-				RelativePath="..\..\..\..\libs\filesystem\src\utf8_codecvt_facet.cpp"
-				>
-			</File>
                 </Filter>
                 <Filter
                         Name="Header Files"
Modified: branches/release/tools/inspect/build/msvc/readme.txt
==============================================================================
--- branches/release/tools/inspect/build/msvc/readme.txt	(original)
+++ branches/release/tools/inspect/build/msvc/readme.txt	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -1,3 +1,3 @@
 The provided Microsoft VC++ solution assumes the following has been run in the root directory"
 
-     bjam --toolset=msvc-9.0express --build-type=complete --with-regex stage
\ No newline at end of file
+     bjam --toolset=msvc-9.0express --build-type=complete --with-filesystem,regex stage
\ No newline at end of file
Modified: branches/release/tools/inspect/inspect.cpp
==============================================================================
--- branches/release/tools/inspect/inspect.cpp	(original)
+++ branches/release/tools/inspect/inspect.cpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -22,6 +22,7 @@
 #include <cstring>
 
 #include "boost/shared_ptr.hpp"
+#include "boost/lexical_cast.hpp"
 #include "boost/filesystem/operations.hpp"
 #include "boost/filesystem/fstream.hpp"
 
@@ -38,6 +39,7 @@
 #include "path_name_check.hpp"
 #include "tab_check.hpp"
 #include "ascii_check.hpp"
+#include "apple_macro_check.hpp"
 #include "minmax_check.hpp"
 #include "unnamed_namespace_check.hpp"
 
@@ -75,6 +77,7 @@
     string library;
     string rel_path;
     string msg;
+    int    line_number;
 
     bool operator<( const error_msg & rhs ) const
     {
@@ -82,6 +85,8 @@
       if ( library > rhs.library ) return false;
       if ( rel_path < rhs.rel_path ) return true;
       if ( rel_path > rhs.rel_path ) return false;
+      if ( line_number < rhs.line_number ) return true;
+      if ( line_number > rhs.line_number ) return false;
       return msg < rhs.msg;
     }
   };
@@ -399,11 +404,11 @@
       }
       std::cout << "\n";
     }
-    else
+    else  // html
     {
       // display error messages with group indication
       error_msg current;
-      string sep;
+      bool first_sep = true;
       bool first = true;
       for ( error_msg_vector::iterator itr ( msgs.begin() );
         itr != msgs.end(); ++itr )
@@ -419,14 +424,26 @@
         {
           std::cout << "\n";
           std::cout << itr->rel_path;
-          sep = ": ";
+          first_sep = true;
         }
         if ( current.library != itr->library
           || current.rel_path != itr->rel_path
           || current.msg != itr->msg )
         {
-          std::cout << sep << itr->msg;
-          sep = ", ";
+          std::string sep;
+          if (first_sep)
+            if (itr->line_number) sep = ":<br>    ";
+            else sep = ": ";
+          else
+            if (itr->line_number) sep = "<br>    ";
+            else sep = ", ";
+
+          // print the message
+          if (itr->line_number)
+            std::cout << sep << "(line " << itr->line_number << ") " << itr->msg;
+          else std::cout << sep << itr->msg;
+
+          first_sep = false;
         }
         current.library = itr->library;
         current.rel_path = itr->rel_path;
@@ -529,6 +546,7 @@
          "  -path_name\n"
          "  -tab\n"
          "  -ascii\n"
+         "  -apple_macro\n"
          "  -minmax\n"
          "  -unnamed\n"
          " default is all checks on; otherwise options specify desired checks"
@@ -579,13 +597,14 @@
 //  error  -------------------------------------------------------------------//
 
     void inspector::error( const string & library_name,
-      const path & full_path, const string & msg )
+      const path & full_path, const string & msg, int line_number )
     {
       ++error_count;
       error_msg err_msg;
       err_msg.library = library_name;
       err_msg.rel_path = relative_to( full_path, fs::initial_path() );
       err_msg.msg = msg;
+      err_msg.line_number = line_number;
       msgs.push_back( err_msg );
 
 //     std::cout << library_name << ": "
@@ -699,6 +718,7 @@
   bool path_name_ck = true;
   bool tab_ck = true;
   bool ascii_ck = true;
+  bool apple_ok = true;
   bool minmax_ck = true;
   bool unnamed_ck = true;
   bool cvs = false;
@@ -731,6 +751,7 @@
     path_name_ck = false;
     tab_ck = false;
     ascii_ck = false;
+    apple_ok = false;
     minmax_ck = false;
     unnamed_ck = false;
   }
@@ -754,6 +775,8 @@
       tab_ck = true;
     else if ( std::strcmp( argv[1], "-ascii" ) == 0 )
       ascii_ck = true;
+    else if ( std::strcmp( argv[1], "-apple_macro" ) == 0 )
+      apple_ok = true;
     else if ( std::strcmp( argv[1], "-minmax" ) == 0 )
         minmax_ck = true;
     else if ( std::strcmp( argv[1], "-unnamed" ) == 0 )
@@ -797,6 +820,8 @@
       inspectors.push_back( inspector_element( new boost::inspect::tab_check ) );
   if ( ascii_ck )
       inspectors.push_back( inspector_element( new boost::inspect::ascii_check ) );
+  if ( apple_ok )
+      inspectors.push_back( inspector_element( new boost::inspect::apple_macro_check ) );
   if ( minmax_ck )
       inspectors.push_back( inspector_element( new boost::inspect::minmax_check ) );
   if ( unnamed_ck )
Modified: branches/release/tools/inspect/inspector.hpp
==============================================================================
--- branches/release/tools/inspect/inspector.hpp	(original)
+++ branches/release/tools/inspect/inspector.hpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -64,7 +64,8 @@
       void error(
         const string & library_name,
         const path & full_path,
-        const string & msg );
+        const string & msg,
+        int line_number =0 );  // 0 if not available or not applicable
 
     private:
       string_set m_signatures;
Modified: branches/release/tools/inspect/link_check.cpp
==============================================================================
--- branches/release/tools/inspect/link_check.cpp	(original)
+++ branches/release/tools/inspect/link_check.cpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -11,17 +11,26 @@
 #include "boost/filesystem/operations.hpp"
 #include <boost/algorithm/string/case_conv.hpp>
 #include <cstdlib>
+#include <set>
+
+// #include <iostream>
 
 namespace fs = boost::filesystem;
 
 namespace
 {
+  boost::regex html_bookmark_regex(
+    "<([^\\s<>]*)\\s*[^<>]*\\s+(NAME|ID)\\s*=\\s*(['\"])(.*?)\\3"
+    "|<!--.*?-->",
+    boost::regbase::normal | boost::regbase::icase);
   boost::regex html_url_regex(
     "<([^\\s<>]*)\\s*[^<>]*\\s+(?:HREF|SRC)" // HREF or SRC
-    "\\s*=\\s*(['\"])(.*?)\\2",
+    "\\s*=\\s*(['\"])\\s*(.*?)\\s*\\2"
+    "|<!--.*?-->",
     boost::regbase::normal | boost::regbase::icase);
   boost::regex css_url_regex(
-    "(\\@import\\s*[\"']|url\\s*\\(\\s*[\"']?)([^\"')]*)",
+    "(\\@import\\s*[\"']|url\\s*\\(\\s*[\"']?)([^\"')]*)"
+    "|/\\*.*?\\*/",
     boost::regbase::normal | boost::regbase::icase);
 
   // Regular expression for parsing URLS from:
@@ -30,6 +39,10 @@
     "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$",
     boost::regbase::normal);
 
+    typedef std::set<std::string> bookmark_set;
+    bookmark_set bookmarks;
+    bookmark_set bookmarks_lowercase; // duplicate check needs case insensitive
+
   // Decode html escapsed ampersands, returns an empty string if there's an error.
   std::string decode_ampersands(std::string const& url_path) {
     std::string::size_type pos = 0, next;
@@ -95,7 +108,7 @@
 
    link_check::link_check()
      : m_broken_errors(0), m_unlinked_errors(0), m_invalid_errors(0),
-       m_bookmark_errors(0)
+       m_bookmark_errors(0), m_duplicate_bookmark_errors(0)
    {
        // HTML signatures are already registered by the base class,
        // 'hypertext_inspector' 
@@ -126,6 +139,65 @@
       bool no_link_errors =
           (contents.find( "boostinspect:" "nolink" ) != string::npos);
 
+      // build bookmarks databases
+      bookmarks.clear();
+      bookmarks_lowercase.clear();
+      string::const_iterator a_start( contents.begin() );
+      string::const_iterator a_end( contents.end() );
+      boost::match_results< string::const_iterator > a_what;
+      boost::match_flag_type a_flags = boost::match_default;
+
+      if(!is_css(full_path))
+      {
+        string previous_id;
+
+        while( boost::regex_search( a_start, a_end, a_what, html_bookmark_regex, a_flags) )
+        {
+          // a_what[0] contains the whole string iterators.
+          // a_what[1] contains the tag iterators.
+          // a_what[2] contains the attribute name.
+          // a_what[4] contains the bookmark iterators.
+
+          if (a_what[4].matched)
+          {
+            string tag( a_what[1].first, a_what[1].second );
+            boost::algorithm::to_lower(tag);
+            string attribute( a_what[2].first, a_what[2].second );
+            boost::algorithm::to_lower(attribute);
+            string bookmark( a_what[4].first, a_what[4].second );
+
+            bool name_following_id = ( attribute == "name" && previous_id == bookmark );
+            if ( tag != "meta" && attribute == "id" ) previous_id = bookmark;
+            else previous_id.clear();
+
+            if ( tag != "meta" && !name_following_id )
+            {
+              bookmarks.insert( bookmark );
+//              std::cout << "******************* " << bookmark << '\n';
+
+              // w3.org recommends case-insensitive checking for duplicate bookmarks
+              // since some browsers do a case-insensitive match.
+              string bookmark_lowercase( bookmark );
+              boost::algorithm::to_lower(bookmark_lowercase);
+
+              std::pair<bookmark_set::iterator, bool> result
+                = bookmarks_lowercase.insert( bookmark_lowercase );
+              if (!result.second)
+              {
+                ++m_duplicate_bookmark_errors;
+                int ln = std::count( contents.begin(), a_what[3].first, '\n' ) + 1;
+                error( library_name, full_path, "Duplicate bookmark: " + bookmark, ln );
+              }
+            }
+          }
+
+          a_start = a_what[0].second; // update search position
+          a_flags |= boost::match_prev_avail; // update flags
+          a_flags |= boost::match_not_bob;
+        }
+      }
+
+      // process urls
       string::const_iterator start( contents.begin() );
       string::const_iterator end( contents.end() );
       boost::match_results< string::const_iterator > what;
@@ -138,14 +210,17 @@
           // what[0] contains the whole string iterators.
           // what[1] contains the element type iterators.
           // what[3] contains the URL iterators.
+          
+          if(what[3].matched)
+          {
+            string type( what[1].first, what[1].second );
+            boost::algorithm::to_lower(type);
 
-          string type( what[1].first, what[1].second );
-          boost::algorithm::to_lower(type);
-
-          // TODO: Complain if 'link' tags use external stylesheets.
-          do_url( string( what[3].first, what[3].second ),
-            library_name, full_path, no_link_errors,
-            type == "a" || type == "link" );
+            // TODO: Complain if 'link' tags use external stylesheets.
+            do_url( string( what[3].first, what[3].second ),
+              library_name, full_path, no_link_errors,
+              type == "a" || type == "link", contents.begin(), what[3].first );
+          }
 
           start = what[0].second; // update search position
           flags |= boost::match_prev_avail; // update flags
@@ -157,8 +232,13 @@
       {
         // what[0] contains the whole string iterators.
         // what[2] contains the URL iterators.
-        do_url( string( what[2].first, what[2].second ),
-          library_name, full_path, no_link_errors, false );
+        
+        if(what[2].matched)
+        {
+          do_url( string( what[2].first, what[2].second ),
+            library_name, full_path, no_link_errors, false,
+            contents.begin(), what[3].first );
+        }
 
         start = what[0].second; // update search position
         flags |= boost::match_prev_avail; // update flags
@@ -169,12 +249,14 @@
 //  do_url  ------------------------------------------------------------------//
 
     void link_check::do_url( const string & url, const string & library_name,
-      const path & source_path, bool no_link_errors, bool allow_external_content )
+      const path & source_path, bool no_link_errors, bool allow_external_content,
+        std::string::const_iterator contents_begin, std::string::const_iterator url_start )
         // precondition: source_path.is_complete()
     {
       if(!no_link_errors && url.empty()) {
         ++m_invalid_errors;
-        error( library_name, source_path, string(name()) + " empty URL." );
+        int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+        error( library_name, source_path, "Empty URL.", ln );
         return;
       }
 
@@ -183,7 +265,9 @@
       if(decoded_url.empty()) {
         if(!no_link_errors) {
           ++m_invalid_errors;
-          error( library_name, source_path, string(name()) + " invalid URL (invalid ampersand encodings): " + url );
+          int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+          error( library_name, source_path,
+            "Invalid URL (invalid ampersand encodings): " + url, ln );
         }
         return;
       }
@@ -192,7 +276,8 @@
       if(!boost::regex_match(decoded_url, m, url_decompose_regex)) {
         if(!no_link_errors) {
           ++m_invalid_errors;
-          error( library_name, source_path, string(name()) + " invalid URL: " + decoded_url );
+          int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+          error( library_name, source_path, "Invalid URL: " + decoded_url, ln );
         }
         return;
       }
@@ -212,7 +297,8 @@
       if(!allow_external_content && (authority_matched || scheme_matched)) {
         if(!no_link_errors) {
           ++m_invalid_errors;
-          error( library_name, source_path, string(name()) + " external content: " + decoded_url );
+          int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+          error( library_name, source_path, "External content: " + decoded_url, ln );
         }
       }
 
@@ -225,7 +311,8 @@
           if(!authority_matched) {
             if(!no_link_errors) {
               ++m_invalid_errors;
-              error( library_name, source_path, string(name()) + " no hostname: " + decoded_url );
+              int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+              error( library_name, source_path, "No hostname: " + decoded_url, ln );
             }
           }
 
@@ -234,19 +321,24 @@
         else if(scheme == "file") {
           if(!no_link_errors) {
             ++m_invalid_errors;
-            error( library_name, source_path, string(name()) + " invalid URL (hardwired file): " + decoded_url );
+            int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+            error( library_name, source_path,
+              "Invalid URL (hardwired file): " + decoded_url, ln );
           }
         }
         else if(scheme == "mailto" || scheme == "ftp" || scheme == "news" || scheme == "javascript") {
           if ( !no_link_errors && is_css(source_path) ) {
             ++m_invalid_errors;
-            error( library_name, source_path, string(name()) + " invalid protocol for css: " + decoded_url );
+            int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+            error( library_name, source_path,
+              "Invalid protocol for css: " + decoded_url, ln );
           }
         }
         else {
           if(!no_link_errors) {
             ++m_invalid_errors;
-            error( library_name, source_path, string(name()) + " unknown protocol: " + decoded_url );
+            int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+            error( library_name, source_path, "Unknown protocol: '" + scheme + "' in url: " + decoded_url, ln );
           }
         }
 
@@ -257,7 +349,9 @@
       if(authority_matched) {
         if(!no_link_errors) {
           ++m_invalid_errors;
-          error( library_name, source_path, string(name()) + " invalid URL (hostname without protocol): " + decoded_url );
+          int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+          error( library_name, source_path,
+            "Invalid URL (hostname without protocol): " + decoded_url, ln );
         }
       }
 
@@ -266,14 +360,26 @@
         if ( is_css(source_path) ) {
             if ( !no_link_errors ) {
               ++m_invalid_errors;
-              error( library_name, source_path, string(name()) + " fragment link in CSS: " + decoded_url );
+              int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+              error( library_name, source_path,
+                "Fragment link in CSS: " + decoded_url, ln );
             }
         }
         else {
           if ( !no_link_errors && fragment.find( '#' ) != string::npos )
           {
             ++m_bookmark_errors;
-            error( library_name, source_path, string(name()) + " invalid bookmark: " + decoded_url );
+            int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+            error( library_name, source_path, "Invalid bookmark: " + decoded_url, ln );
+          }
+          else if ( !no_link_errors && url_path.empty() && !fragment.empty()
+            // w3.org recommends case-sensitive broken bookmark checking
+            // since some browsers do a case-sensitive match.
+            && bookmarks.find(decode_percents(fragment)) == bookmarks.end() )
+          {
+            ++m_broken_errors;
+            int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+            error( library_name, source_path, "Unknown bookmark: " + decoded_url, ln );
           }
         }
 
@@ -285,23 +391,29 @@
       if ( !no_link_errors && decoded_url.find_first_of( " <>\"{}|\\^[]'" ) != string::npos )
       {
         ++m_invalid_errors;
-        error( library_name, source_path, string(name()) + " invalid character in URL: " + decoded_url );
+        int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+        error( library_name, source_path,
+          "Invalid character in URL: " + decoded_url, ln );
       }
 
       // Check that we actually have a path.
       if(url_path.empty()) {
         if(!no_link_errors) {
           ++m_invalid_errors;
-          error( library_name, source_path, string(name()) + " invalid URL (empty path in relative url): " + decoded_url );
+          int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+          error( library_name, source_path,
+            "Invalid URL (empty path in relative url): " + decoded_url, ln );
         }
       }
 
-      // Decode percent and ampersand encoded characters.
+      // Decode percent encoded characters.
       string decoded_path = decode_percents(url_path);
       if(decoded_path.empty()) {
         if(!no_link_errors) {
           ++m_invalid_errors;
-          error( library_name, source_path, string(name()) + " invalid URL (invalid character encodings): " + decoded_url );
+          int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+          error( library_name, source_path,
+            "Invalid URL (invalid character encodings): " + decoded_url, ln );
         }
         return;
       }
@@ -316,8 +428,10 @@
       catch ( const fs::filesystem_error & )
       {
         if(!no_link_errors) {
+          int ln = std::count( contents_begin, url_start, '\n' ) + 1;
           ++m_invalid_errors;
-          error( library_name, source_path, string(name()) + " invalid URL (error resolving path): " + decoded_url );
+          error( library_name, source_path,
+            "Invalid URL (error resolving path): " + decoded_url, ln );
         }
         return;
       }
@@ -339,7 +453,8 @@
       if ( !no_link_errors && (itr->second & m_present) == 0 )
       {
         ++m_broken_errors;
-        error( library_name, source_path, string(name()) + " broken link: " + decoded_url );
+        int ln = std::count( contents_begin, url_start, '\n' ) + 1;
+        error( library_name, source_path, "Broken link: " + decoded_url, ln );
       }
     }
 
@@ -362,7 +477,7 @@
        {
          ++m_unlinked_errors;
          path full_path( fs::initial_path() / path(itr->first, fs::no_check) );
-         error( impute_library( full_path ), full_path, string(name()) + " unlinked file" );
+         error( impute_library( full_path ), full_path, "Unlinked file" );
        }
      }
    }
Modified: branches/release/tools/inspect/link_check.hpp
==============================================================================
--- branches/release/tools/inspect/link_check.hpp	(original)
+++ branches/release/tools/inspect/link_check.hpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -27,18 +27,22 @@
       long m_unlinked_errors;
       long m_invalid_errors;
       long m_bookmark_errors;
+      long m_duplicate_bookmark_errors;
 
       typedef std::map< string, int > m_path_map;
       m_path_map m_paths; // first() is relative initial_path()
 
       void do_url( const string & url, const string & library_name,
         const path & full_source_path, bool no_link_errors,
-        bool allow_external_links );
+        bool allow_external_links,
+        std::string::const_iterator contents_begin, std::string::const_iterator url_start);
     public:
 
       link_check();
       virtual const char * name() const { return "*LINK*"; }
-      virtual const char * desc() const { return "invalid bookmarks, invalid urls, broken links, unlinked files"; }
+      virtual const char * desc() const
+      { return "invalid bookmarks, duplicate bookmarks,"
+               " invalid urls, broken links, unlinked files"; }
 
       virtual void inspect(
         const std::string & library_name,
@@ -53,7 +57,10 @@
 
       virtual ~link_check()
         {
-          std::cout << "  " << m_bookmark_errors << " bookmarks with invalid characters" << line_break();
+          std::cout << "  " << m_bookmark_errors
+                    << " bookmarks with invalid characters" << line_break();
+          std::cout << "  " << m_duplicate_bookmark_errors
+                    << " duplicate bookmarks" << line_break();
           std::cout << "  " << m_invalid_errors << " invalid urls" << line_break();
           std::cout << "  " << m_broken_errors << " broken links" << line_break();
           std::cout << "  " << m_unlinked_errors << " unlinked files" << line_break();
Modified: branches/release/tools/inspect/unnamed_namespace_check.cpp
==============================================================================
--- branches/release/tools/inspect/unnamed_namespace_check.cpp	(original)
+++ branches/release/tools/inspect/unnamed_namespace_check.cpp	2010-06-30 19:33:33 EDT (Wed, 30 Jun 2010)
@@ -51,8 +51,7 @@
         const string::size_type
          ln = std::count( contents.begin(), (*cur)[0].first, '\n' ) + 1;
 
-        error( library_name, full_path, string(name()) + " unnamed namespace at line "
-            + lexical_cast<string>(ln) );
+        error( library_name, full_path, "Unnamed namespace", ln );
       }