@@ -116,9 +116,12 @@ def run_internal_link_checker(links)
116116 return true if blank? ( href_hash )
117117 return true unless @runner . options [ :check_internal_hash ]
118118
119- # prevents searching files we didn't ask about
120- return false unless url . known_extension?
121- return false unless url . has_hash?
119+ # For hash links, we need to defer to file-based checking
120+ # (even if URL doesn't have extension, it may resolve to index.html)
121+ return nil unless url . has_hash?
122+
123+ # If URL has no known extension, defer to file-based checking
124+ return nil unless url . known_extension?
122125
123126 decoded_href_hash = Addressable ::URI . unescape ( href_hash )
124127 fragment_ids = [ href_hash , decoded_href_hash ]
@@ -134,10 +137,18 @@ def run_internal_link_checker(links)
134137
135138 private def find_fragments ( fragment_ids , html )
136139 xpaths = fragment_ids . uniq . flat_map do |frag_id |
137- escaped_frag_id = "'#{ frag_id . split ( "'" ) . join ( "', \" '\" , '" ) } ', ''"
140+ # Build XPath string argument, handling single quotes
141+ if frag_id . include? ( "'" )
142+ # Use concat() to handle single quotes: concat('part1', "'", 'part2')
143+ escaped_frag_id = frag_id . split ( "'" ) . map { |part | "'#{ part } '" } . join ( ", \" '\" , " )
144+ xpath_arg = "concat(#{ escaped_frag_id } )"
145+ else
146+ # No single quotes, just use quoted string
147+ xpath_arg = "'#{ frag_id } '"
148+ end
138149 [
139- "//*[case_sensitive_equals(@id, concat( #{ escaped_frag_id } ) )]" ,
140- "//*[case_sensitive_equals(@name, concat( #{ escaped_frag_id } ) )]" ,
150+ "//*[case_sensitive_equals(@id, #{ xpath_arg } )]" ,
151+ "//*[case_sensitive_equals(@name, #{ xpath_arg } )]" ,
141152 ]
142153 end
143154 xpaths << XpathFunctions . new
0 commit comments