anchor.diff:  {$mode objfpc}{$H+}
   
   // NewView - a new OS/2 Help Viewer
  -// Copyright 2001 Aaron Lawrence (aaronl at consultant dot com)
  +// Copyright 2003 Aaron Lawrence (aaronl at consultant dot com)
   // This software is released under the Gnu Public License - see readme.txt
   
   Interface
  
positive.code: procedure SearchHelpFile( HelpFile: THelpFile;
                            Query: TTextSearchQuery;
                            Results: TList;
                            HighlightWords: UInt32ArrayPointer );
  var
    Topic: TTopic;
    TopicIndex: longint;
    TermIndex: longint;
    Term: TSearchTerm;
    TopicMatches: UInt32ArrayPointer;
    TopicRelevancesForTerm: UInt32ArrayPointer;
    TopicMatchedTerm: boolean;
  
    WordRelevance: longint;
    DictionaryRelevances: UInt32ArrayPointer;
    DictIndex: longint;
    TopicRelevanceForTerm: longint;
    TopicWordCount: longint;
  begin
    // Reset flags per topic
    for TopicIndex := 0 to HelpFile.TopicCount - 1 do
    begin
      Topic := HelpFile.Topics[ TopicIndex ];
      Topic.FoundInSearch := false;
      Topic.ExcludedInSearch := false;
      Topic.SearchRelevance := 0;
    end;
  
    if HighlightWords <> nil then
      // Clear the highlightwords array
      FillUInt32Array( HighlightWords, HelpFile.DictionaryCount, 0 );
  
    // Get memory for dictionary/topic relevance arrays
    GetMem( DictionaryRelevances, HelpFile.DictionaryCount * sizeof( longint ) );
    GetMem( TopicMatches, HelpFile.TopicCount * sizeof( longint ) );
    GetMem( TopicRelevancesForTerm, HelpFile.TopicCount * sizeof( longint ) );
  
    for TermIndex := 0 to Query.TermCount - 1 do
    begin
      Term := Query.Term[ TermIndex ];
  
      FillUInt32Array( TopicRelevancesForTerm, HelpFile.TopicCount, 0 );
  
      // Search the dictionary for matches.
      SearchDictionary( HelpFile, Term.Text, DictionaryRelevances );
  
      // Update the highlight words array.
      // (effectively an OR)
      if HighlightWords <> nil then
      begin
        if Term.CombineMethod in [ cmAnd, cmOr ] then
        begin
          for DictIndex := 0 to HelpFile.DictionaryCount - 1 do
            inc( HighlightWords^[ DictIndex ],  DictionaryRelevances^[ DictIndex ] );
        end;
      end;
  
      // For each word in the dictionary that matches
      // this search word, search topics/titles/index
      for DictIndex := 0 to HelpFile.DictionaryCount - 1 do
      begin
        WordRelevance := DictionaryRelevances^[ DictIndex ];
        if WordRelevance > 0 then
        begin
          // Search for occurrences of this word
          // within the text of topics
          HelpFile.SearchTable.Search( DictIndex, TopicMatches );
  
          // Work out total relevance for each topic found:
          for TopicIndex := 0 to HelpFile.TopicCount - 1 do
          begin
            if TopicMatches^[ TopicIndex ] > 0 then
            begin
              // Search table indicates word occurs in
              // this topic, so count number of
              // occurrences to get relevance
              Topic := HelpFile.Topics[ TopicIndex ];
  
              TopicWordCount := Topic.CountWord( DictIndex );
              TopicRelevancesForTerm^[ TopicIndex ] := TopicWordCount * WordRelevance;
            end;
          end;
        end;
      end;
  
      // Search titles and index
      SearchTopicTitles( HelpFile, Term.Text, TopicRelevancesForTerm );
      SearchIndex( HelpFile, Term.Text, TopicRelevancesForTerm );
  
      // Set match flags for each topic, marking
      // as found or excluded depending on combine
      // method
      for TopicIndex := 0 to HelpFile.TopicCount - 1 do
      begin
        Topic := HelpFile.Topics[ TopicIndex ];
        TopicRelevanceForTerm := TopicRelevancesForTerm^[ TopicIndex ];
        TopicMatchedTerm := TopicRelevanceForTerm > 0;
        case Term.CombineMethod of
         cmAnd:
           if not TopicMatchedTerm then
             Topic.ExcludedInSearch := true
           else
             Topic.FoundInSearch := true;
  
         cmNot:
           if TopicMatchedTerm then
             Topic.ExcludedInSearch := true;
  
         cmOr:
           if TopicMatchedTerm then
             Topic.FoundInSearch := true;
        end;
        if TopicMatchedTerm then
          inc( Topic.SearchRelevance, TopicRelevanceForTerm );
      end;
  
      // loop for next word...
    end;
  
    // Now find topics that DID have a match
    // and did NOT have an exclusion match
    // ... add the topic to result list
    for TopicIndex := 0 to HelpFile.TopicCount - 1 do
    begin
      Topic := HelpFile.Topics[ TopicIndex ];
      if Topic.FoundInSearch
         and ( not Topic.ExcludedInSearch ) then
      begin
        Results.Add( Topic );
      end;
    end;
  
    FreeMem( TopicRelevancesForTerm, HelpFile.TopicCount * sizeof( longint ) );
    FreeMem( TopicMatches, HelpFile.TopicCount * sizeof( longint ) );
    FreeMem( DictionaryRelevances, HelpFile.DictionaryCount * sizeof( longint ) );
  end;
  
  function ExtractNextIPFWordPart( var Word: string ): string;
  var
    CharIndex: longint;
  begin
    assert( Length( Word ) > 0 );
    CharIndex := 2;
    if IsDigit( Word[ 1 ] ) then
    begin
      // extract string of digits
      while CharIndex <= Length( Word ) do
      begin
        if not IsDigit( Word[ CharIndex ] ) then
          break;
        inc( CharIndex );
      end;
    end
    else if IsAlpha( Word[ 1 ] ) then
    begin
      // extract string of letters
      while CharIndex <= Length( Word ) do
      begin
        if not IsAlpha( Word[ CharIndex ] ) then
          break;
        inc( CharIndex );
      end;
    end
    else
    begin
      // extract single non-alphanumeric symbol
    end;
    assert( CharIndex > 1 );
    Result := Copy(Word, 0, CharIndex-1);
  //  Result := StrLeft( Word, CharIndex - 1 );
    Delete( Word, 1, CharIndex - 1 )
  end;
  
  Initialization
  End.
  
negative.code: //  ACLUtility, ACLStringUtility,
    HelpTopic, CompareWordUnit, nvUtilities;
  
  type
    TSearchType = ( stGeneral, stStarts, stExactMatch, stEnds );
  
  procedure ClearWordSequence( WordSequence: TList;
                               DictionaryCount: longint );
  var
    StepIndex: longint;
    DictionaryRelevances: UInt32ArrayPointer;
  begin
    for StepIndex := 0 to WordSequence.Count - 1 do
    begin
      DictionaryRelevances := WordSequence[ StepIndex ];
      FreeUInt32Array( DictionaryRelevances, DictionaryCount );
    end;
    WordSequence.Clear;
  end;
  
  procedure ClearWordSequences( WordSequence: TList;
                                DictionaryCount: longint );
  var
    SequenceIndex: longint;
    WordSequence: TList;
  begin
    for SequenceIndex := 0 to WordSequences.Count - 1 do
    begin
      WordSequence := WordSequences[ SequenceIndex ];
      ClearWordSequence( WordSequence, DictionaryCount );
      WordSequence.Destroy;
    end;
    WordSequences.Clear;
  end;
  
  // given a search word which is known to matche Reference word,
  // return the relevance
  function MatchedWordRelevance( const SearchWord: string;
                                 const ReferenceWord: string ): longint;
  begin
    Result := mwExactWord
              * Length( SearchWord )
              div Length( ReferenceWord );
    if Result = 0 then
      Result := 1;
  end;
  
  // Compares the given search word against the given
  // reference word. Returns a value indicating how well the
  // search word matches, 0 = not at all.
  function CompareWord( const SearchWord: string;
                        const ReferenceWord: string ): longint;
  var
    OccurrencePos: longint;
  begin
    Result := 0;
    OccurrencePos := CaseInsensitivePos( SearchWord, ReferenceWord );
    if OccurrencePos = 0 then
    begin
      // no match
      exit;
    end;
  
    Result := MatchedWordRelevance( SearchWord, ReferenceWord );
  end;
  
  // Search the help file dictionary for words that match
  // the given search word. Partial matches are considered.
  // Results returns the matching word indexes.
  procedure SearchDictionary( HelpFile: THelpFile;
                              SearchWord: string;
                              Results: UInt32ArrayPointer );
  var
    DictIndex: integer;
    pDictWord: pstring;
  begin
    for DictIndex := 0 to HelpFile.DictionaryCount - 1 do
    begin
      pDictWord := HelpFile.DictionaryWordPtrs[ DictIndex ];
      Results[ DictIndex ] := CompareWord( SearchWord,
                                           pDictWord^ );
    end;
  end;
  
  // Search the help file dictionary for words that
  // match the given search word exactly (except for case-insensitive)
  procedure SearchDictionaryExact( HelpFile: THelpFile;
                                   SearchWord: string;
                                   Results: UInt32ArrayPointer );
  var
    DictIndex: integer;
    pDictWord: pstring;
  begin
    FillUInt32Array( Results, HelpFile.DictionaryCount, 0 );
  
    for DictIndex := 0 to HelpFile.DictionaryCount - 1 do
    begin
      pDictWord := HelpFile.DictionaryWordPtrs[ DictIndex ];
      if StrEqualIgnoringCase( SearchWord, pDictWord^ ) then
        Results[ DictIndex ] := mwExactWord;
    end;
  end;
  
  // Search the help file dictionary for words that
  // start with the given word
  procedure SearchDictionaryStarts( HelpFile: THelpFile;
                                    SearchWord: string;
                                    Results: UInt32ArrayPointer );
  var
    DictIndex: integer;
    DictWord: string;
  begin
    FillUInt32Array( Results, HelpFile.DictionaryCount, 0 );
  
    for DictIndex := 0 to HelpFile.DictionaryCount - 1 do
    begin
      DictWord := HelpFile.DictionaryWords[ DictIndex ];
      if StrStartsWithIgnoringCase(DictWord, SearchWord) then
        Results[ DictIndex ] := MatchedWordRelevance( SearchWord, DictWord );
    end;
  end;
  
  // Search the help file dictionary for words that
  // end with the given word
  procedure SearchDictionaryEnds( HelpFile: THelpFile;
                                  SearchWord: string;
                                  Results: UInt32ArrayPointer );
  var
    DictIndex: integer;
    DictWord: string;
  begin
    FillUInt32Array( Results, HelpFile.DictionaryCount, 0 );
  
    for DictIndex := 0 to HelpFile.DictionaryCount - 1 do
    begin
      DictWord := HelpFile.DictionaryWords[ DictIndex ];
      if StrEndsWithIgnoringCase( SearchWord, DictWord ) then
        Results^[ DictIndex ] := MatchedWordRelevance( SearchWord, DictWord );
    end;
  end;
  
  