//------------------------------------------------------------------------------
// FUNCTION:  biblioFilter
// PURPOSE:   To filter the bibliographic entries in pub.htm according to a
//            user-specified pattern.  This sets the CSS display attribute for
//            each entry to "block" or "none" depending on whether or not its
//            text matches the pattern.
//
// ARGS:      pattern   (in) The pattern string to filter for.
//
//            Examples:
//
//               Pattern                    Some Posssible Matches
//               -----------------------   -----------------------------------
//               hab                        hab; Hab; HAB;
//               hab*                       HAB; habitat; Habot;
//               *hab                       HAB; TransHab;
//               *hab*                      HAB; habitat; Habot; TransHab;
//               hab*t                      habit; Habot;
//               test-bed                   test-bed; test bed;
//               luna*|moon                 Luna; lunar; moon;
//               moon man                   moon man;
//               moon & man                 moon man; man on the moon;
//               smith|jones & luna*|moon   [ Any item that matches both
//                                            smith|jones and luna*|moon]
//
//            This script tests the pattern against each bibliography item
//            according to the following rules:
//
//            .  "Words" are strings of contiguous letters and digits with no
//               white space, punctuation, or other characters.
//
//            .  Digits must match exactly.
//
//            .  Letter matching is insensitive to case and diacritical marks.
//
//            .  Pattern words must match whole item words (not word
//               fragments).
//
//            .  Adjacent pattern words must match adjacent item words.
//               Adjacent words comprise a phrase.  Phrases do not need to be
//               quoted.  Quotes are ignored.
//
//            .  The asterisk * is a wildcard that matches any substring of
//               zero or more letters or digits.  It may appear anywhere in a
//               pattern word.
//
//            .  The pipe | implies an "or" between the subpatterns on its
//               left and right.  The pattern matches if either subpattern
//               matches part of the item.
//
//            .  The ampersand & implies an "and" between the subpatterns on
//               its left and right.  The pattern matches if both subpatterns
//               match parts of the item.
//
//            .  The pipe | has higher precedence than the ampersand &.
//
//            .  All other characters are equivalent to blanks.
//
//            .  All strings of contiguous blanks are equivalent to a single
//               blank.
//
// DATE:      2007/02/16  T.W.H.
//                        JavaScript 1.5, ECMA 262 Edition 3,
//                        HTML 4.01, CSS 2, DOM 2
//
//            2009/12/02  T.W.H.  changed to two-pass and added count
//------------------------------------------------------------------------------
   function biblioFilter (pattern)
   {
      var re, nodelist, i, n, section, count ;

   // Convert the filter pattern to a set of regular expressions.

      re = biblioMakeRegExp (pattern) ;
      
   // Get the list of <P> elements.
   
      nodelist = document.getElementsByTagName ("P") ;
      n        = nodelist.length ;
      
   // Set the display property of <P class="biblio"> elements to "block"
   // or "none" according to whether the text matches the pattern.
   
      section = -1 ;
      count   = [] ;
      for (i = 0 ; i < n ; ++i)
      {
         switch (nodelist[i].className)
         {
            case "filtermsg" :
               count[++section] = 0 ;
               break ;
               
            case "biblio" :
               if (biblioMatchRegExp (nodelist[i], re))
                  ++count[section] ;
               break ;

            default :
               break ;
         }
      }

   // Set the content of <P class="filtermsg"> elements.

      section = -1 ;
      for (i = 0 ; i < n ; ++i)
      {
         switch (nodelist[i].className)
         {
            case "filtermsg" :
               biblioEchoPattern (nodelist[i], count[++section], pattern) ;
               break ;

            default :
               break ;
         }
      }
   }

//------------------------------------------------------------------------------
// FUNCTION:  biblioMakeReqExp
// PURPOSE:   To return an array of RegExp objects to implement the filter
//            pattern.
//
// ARGS:      pattern   (in) The filter pattern string.
// RETURN:              The array of RegExp objects.
//
// REMARKS:   The user interface filter pattern is simpler than a full-blown
//            regular expression.  This builds an equivalent list of regular
//            expressions.
//
//            The number of regular expressions is one greater than the number
//            of ampersands in the pattern.  The ampersand & means "and".  For
//            the pattern to match some text, each of the regular expressions
//            must match parts of it.
//
//            The pipe | means "or".  It may appear in any of the regular
//            expressions.  The JavaScript regular expression code handles it.
//
// DATE:      2007/02/16  T.W.H.
//------------------------------------------------------------------------------
   function biblioMakeRegExp (pattern)
   {
      var re ;

   // If there's no pattern, then there are no regular expressions.

      if (pattern.length < 1)
         re = [] ;

   // Else:  Desensitize the pattern to ignore case and diacritical marks.
   // Delimit all words (including the first and last) with blanks at both
   // edges to simplify whole-word matching.  Replace all non-letter non-digit
   // characters with blanks.  Replace * with [^ ]*.  Insert blanks around
   // | and & operators.  Collapse all strings of adjacent blanks to a single
   // blank.  Parse the pattern into separate regular expressions at &.  This
   // removes all occurrances of & from the regular expressions.

      else
      {
         re = (" " + biblioDesensitizeText (pattern) + " ")
            . replace (/[^0-9a-z&*|]+/g, " ")
            . replace (/\*+/g,           "[^ ]*")
            . replace (/(\| *)+/g,       " | ")
            . replace (/(& *)+/g,        " & ")
            . replace (/ +/g,            " ")
            . split ("&") ;
         n = re.length ;
         for (i = 0 ; i < n ; ++i)
            re[i] = RegExp (re[i]) ;
      }
      return re ;
   }

//------------------------------------------------------------------------------
// FUNCTION:  biblioDesensitizeText
// PURPOSE:   To remove case and diacritical sensitivity from text before
//            applying it in pattern matching.
//
// ARGS:      text   (in) The original string.
// RETURN:           The desensitized string.
//
// REMARKS:   This converts the text to lower case.  For western European
//            Latin letters with diacritical marks (as in ISO-8859-1) this
//            substitutes the most similar lowercase English letter without
//            diacritical marks.
//
// DATE:      2007/02/15  T.W.H.
//------------------------------------------------------------------------------
   function biblioDesensitizeText (text)
   {

   // Test and work-around for bug in Safari and OmniWeb:

      if ("A" . replace (/\u0041/g, "a") != "a")
         return biblioDesensitizeText_dbg1 (text) ;

   // Normal behavior, works in Mozilla (Firefox, SeaMonkey) and MSIE.

      return text
         . toLowerCase ()

      // sup1, sup2, sup3, frac14, frac12, frac34:

         . replace (/\u00B9/g, "1")
         . replace (/\u00B2/g, "2")
         . replace (/\u00B3/g, "3")
         . replace (/\u00BC/g, "1/4")
         . replace (/\u00BD/g, "1/2")
         . replace (/\u00BE/g, "3/4")

      // Agrave, Aacute, Acirc, Atilde, Auml, Aring,
      // agrave, aacute, acirc, atilde, auml, aring:

         . replace (/[\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5]/g, "a")

      // AElig, aelig:

         . replace (/[\u00C6\u00E6]/g, "ae")

      // Ccedil, ccedil:

         . replace (/[\u00C7\u00E7]/g, "c")

      // Egrave, Eacute, Ecirc, Euml, egrave, eacute, ecirc, euml:

         . replace (/[\u00C8\u00C9\u00CA\u00CB\u00E8\u00E9\u00EA\u00EB]/g, "e")

      // Igrave, Iacute, Icirc, Iuml, igrave, iacute, icirc, iuml:

         . replace (/[\u00CC\u00CD\u00CE\u00CF\u00EC\u00ED\u00EE\u00EF]/g, "i")

      // ETH, eth:  (looks like a D or d with a cross stroke)

         . replace (/[\u00D0\u00F0]/g, "qd")

      // Ntilde, ntilde:

         . replace (/[\u00D1\u00F1]/g, "n")

      // Ograve, Oacute, Ocirc, Otilde, Ouml, Oslash,
      // ograve, ocaute, ocirc, otilde, ouml, oslash:

         . replace (/[\u00D2\u00D3\u00D4\u00D5\u00D6\u00D8\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8]/g, "o")

      // Ugrave, Uacute, Ucirc, Uuml, ugrave, uacute, ucirc, uuml:

         . replace (/[\u00D9\u00DA\u00DB\u00DC\u00F9\u00FA\u00FB\u00FC]/g, "u")

      // Yacute, Yuml, yacute, yuml:

         . replace (/[\u00DD\u0178\u00FD\u00FF]/g, "y")

      // THORN, thorn:  (looks like a combination of b and p)

         . replace (/[\u00DE\u00FE]/g, "qp")

      // szlig:

         . replace (/\u00DF/g, "ss")

      // OElig, oelig:

         . replace (/[\u0152\u0153]/g, "oe")

      // Scaron, scaron:

         . replace (/[\u0160\u0161]/g, "s") ;
   }


//------------------------------------------------------------------------------
// FUNCTION:  biblioDesensitizeText_dbg1
// PURPOSE:   A tweaked version of biblioDesensitizeText to work around a bug
//            in Safari 2.0.4 and OmniWeb 5.5.4
//
// ARGS:      text   (in) The original string.
// RETURN:           The desensitized string.
//
// REMARKS:   This is for browsers that don't support UnicodeEscapeSequences
//            in Regular Expression Literals.  The work-around is to pass
//            UnicodeEscapeSequences in String Literals to the RegExp
//            constructor.
//
//            See ECMAScript Language Specification, ECMA 262 Edition 3,
//            sections 7.8.4 and 7.8.5
//
// DATE:      2007/02/19  T.W.H.
//------------------------------------------------------------------------------
   function biblioDesensitizeText_dbg1 (text)
   {
      return text
         . toLowerCase ()

      // sup1, sup2, sup3, frac14, frac12, frac34:

         . replace (RegExp ("\u00B9", "g"), "1")
         . replace (RegExp ("\u00B2", "g"), "2")
         . replace (RegExp ("\u00B3", "g"), "3")
         . replace (RegExp ("\u00BC", "g"), "1/4")
         . replace (RegExp ("\u00BD", "g"), "1/2")
         . replace (RegExp ("\u00BE", "g"), "3/4")

      // Agrave, Aacute, Acirc, Atilde, Auml, Aring,
      // agrave, aacute, acirc, atilde, auml, aring:

         . replace (RegExp ("[\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5]", "g"), "a")

      // AElig, aelig:

         . replace (RegExp ("[\u00C6\u00E6]", "g"), "ae")

      // Ccedil, ccedil:

         . replace (RegExp ("[\u00C7\u00E7]", "g"), "c")

      // Egrave, Eacute, Ecirc, Euml, egrave, eacute, ecirc, euml:

         . replace (RegExp ("[\u00C8\u00C9\u00CA\u00CB\u00E8\u00E9\u00EA\u00EB]", "g"), "e")

      // Igrave, Iacute, Icirc, Iuml, igrave, iacute, icirc, iuml:

         . replace (RegExp ("[\u00CC\u00CD\u00CE\u00CF\u00EC\u00ED\u00EE\u00EF]", "g"), "i")

      // ETH, eth:  (looks like a D or d with a cross stroke)

         . replace (RegExp ("[\u00D0\u00F0]", "g"), "qd")

      // Ntilde, ntilde:

         . replace (RegExp ("[\u00D1\u00F1]", "g"), "n")

      // Ograve, Oacute, Ocirc, Otilde, Ouml, Oslash,
      // ograve, ocaute, ocirc, otilde, ouml, oslash:

         . replace (RegExp ("[\u00D2\u00D3\u00D4\u00D5\u00D6\u00D8\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8]", "g"), "o")

      // Ugrave, Uacute, Ucirc, Uuml, ugrave, uacute, ucirc, uuml:

         . replace (RegExp ("[\u00D9\u00DA\u00DB\u00DC\u00F9\u00FA\u00FB\u00FC]", "g"), "u")

      // Yacute, Yuml, yacute, yuml:

         . replace (RegExp ("[\u00DD\u0178\u00FD\u00FF]", "g"), "y")

      // THORN, thorn:  (looks like a combination of b and p)

         . replace (RegExp ("[\u00DE\u00FE]", "g"), "qp")

      // szlig:

         . replace (RegExp ("\u00DF", "g"), "ss")

      // OElig, oelig:

         . replace (RegExp ("[\u0152\u0153]", "g"), "oe")

      // Scaron, scaron:

         . replace (RegExp ("[\u0160\u0161]", "g"), "s") ;
   }

//------------------------------------------------------------------------------
// FUNCTION:  biblioMatchRegExp
// PURPOSE:   To match a <P class="biblio"> element against a array of regular
//            expressions and set the element's display property according to
//            the result.
//
// ARGS:      node   (in) The DOM node for the element.
//            re     (in) The array of regular expressions.
//
// RETURN:           true if the element matches, else false.
//
// DATE:      2007/02/16  T.W.H.  assume CSS2Properties interface
//            2009/12/02  T.W.H.  change return from void to boolean
//------------------------------------------------------------------------------
   function biblioMatchRegExp (node, re)
   {
      var text, i, n, result ;

   // If there are no regular expressions to match, then there's nothing to
   // filter out the entry.

      n = re.length ;
      if (n < 1)
         result = true ;

   // Else, filter it.

      else
      {

      // Extract the text from the node.  Desensitize the text to ignore case
      // and diacritical marks.  Delimit all words (including the first and
      // last) with blanks at both edges to simplify whole-word matching.
      // Replace all non-letter non-digit characters with blanks.

         text = (" " + biblioDesensitizeText (biblioExtractText (node)) + " ")
            . replace (/[^0-9a-z]+/g, " ") ;

      // Search for the regular expressions in the transformed text.  For the
      // match to succeed, all of the regular expressions must succeed.

         for (i = 0 ; i < n ; ++i)
            if (text.search (re[i]) < 0)
               break ;
         result = (i == n) ;
      }
      node.style.display = result ? "block" : "none" ;
      return result ;
   }

//------------------------------------------------------------------------------
// FUNCTION:  biblioExtractText
// PURPOSE:   To return the text of a node and all of its descendents as a
//            string.
//
// ARGS:      node   (in) The DOM Core Node from which to extract the text.
// RETURN:           The string (DOMString, UTF-16) containing the text.
//
// REMARKS:   The string contains only the renderable text, without HTML
//            tags or character entity tokens.
//
// DATE:      2007/02/09  T.W.H.
//------------------------------------------------------------------------------
   function biblioExtractText (node)
   {
      var TEXT_NODE = 3 ;   // DOM Level 2 Core interface Node
      var text      = "" ;
      var nodelist, i, n ;

   // if (node.nodeType == node.TEXT_NODE)   // Mozilla OK; Safari not;
      if (node.nodeType == TEXT_NODE)
         text += node.nodeValue ;
      else
      {
         nodelist = node.childNodes ;
         n        = nodelist.length ;
         for (i = 0 ; i < n ; ++i)
            text += biblioExtractText (nodelist[i]) ;
      }
      return (text) ;
   }

//------------------------------------------------------------------------------
// FUNCTION:  biblioEchoPattern
// PURPOSE:   To set the display and content of a <P class="filtermsg">
//            element to echo the filter pattern.
//
// ARGS:      node      (in) The DOM node for the element.
//            count     (in) The number of matching elements.
//            pattern   (in) The filter pattern string to echo.
//
// DATE:      2007/02/16  T.W.H.  assume CSS2Properties interface
//            2007/12/02  T.W.H.  added count and biblioInsertCount
//------------------------------------------------------------------------------
   function biblioEchoPattern (node, count, pattern)
   {
      var nodelist, i, n ;

      node.style.display = "block" ;
      nodelist = node.childNodes ;
      n        = nodelist.length ;
      for (i = 0 ; i < n ; ++i)
      {
         biblioInsertCount (node, count, pattern, false) ;
         biblioInsertPattern (node, pattern, false) ;
      }
   }

//------------------------------------------------------------------------------
// FUNCTION:  biblioInsertCount
// PURPOSE:   To insert the pattern text into the <SPAN class="filterpattern">
//            element in a <P class="filtermsg"> element.
//
// ARGS:      node      (in) The DOM node for the current element.
//            count     (in) The number of matching elements.
//            pattern   (in) The filter pattern string to insert.
//            flag      (in) A boolean flag that indicates whether the current
//                           node is under a "filterpattern" node.
//
// DATE:      2009/12/02  T.W.H.
//------------------------------------------------------------------------------
   function biblioInsertCount (node, count, pattern, flag)
   {
      var TEXT_NODE = 3 ;   // DOM Level 2 Core interface Node
      var nodelist, i, n, text ;

      if (! flag)
         if (node.className == "filtercount")
            flag = true ;
   // if (node.nodeType == node.TEXT_NODE)   // Mozilla OK; Safari not;
      if (node.nodeType == TEXT_NODE)
      {
         if (flag)
         {
            text = "" + count ;
            if (count == 1)
               text += " entry" ;
            else
               text += " entries" ;
            if (pattern.length > 0)
               text += " matching " ;
            node.nodeValue = text ;
         }
      }
      else
      {
         nodelist = node.childNodes ;
         n        = nodelist.length ;
         for (i = 0 ; i < n ; ++i)
            biblioInsertCount (nodelist[i], count, pattern, flag) ;
      }
   }

//------------------------------------------------------------------------------
// FUNCTION:  biblioInsertPattern
// PURPOSE:   To insert the pattern text into the <SPAN class="filterpattern">
//            element in a <P class="filtermsg"> element.
//
// ARGS:      node      (in) The DOM node for the current element.
//            pattern   (in) The filter pattern string to insert.
//            flag      (in) A boolean flag that indicates whether the current
//                           node is under a "filterpattern" node.
//
// DATE:      2007/02/16  T.W.H.
//------------------------------------------------------------------------------
   function biblioInsertPattern (node, pattern, flag)
   {
      var TEXT_NODE = 3 ;   // DOM Level 2 Core interface Node
      var nodelist, i, n ;

      if (! flag)
         if (node.className == "filterpattern")
            flag = true ;
   // if (node.nodeType == node.TEXT_NODE)   // Mozilla OK; Safari not;
      if (node.nodeType == TEXT_NODE)
      {
         if (flag)
            node.nodeValue = pattern ;
      }
      else
      {
         nodelist = node.childNodes ;
         n        = nodelist.length ;
         for (i = 0 ; i < n ; ++i)
            biblioInsertPattern (nodelist[i], pattern, flag) ;
      }
   }

