3 # FILE: SearchParameterSet.php 5 # Part of the ScoutLib application support library 6 # Copyright 2015-2016 Edward Almasy and Internet Scout Research Group 7 # http://scout.wisc.edu 16 # ---- SETUP / CONFIGURATION --------------------------------------------- 28 # if set data supplied 31 # set internal values from data 32 $this->LoadFromData($Data);
42 foreach ($this->Subgroups as &$Group)
44 $Group = clone $Group;
57 if (is_callable($Func))
59 self::$CanonicalFieldFunction = $Func;
63 throw new InvalidArgumentException(
"Invalid function supplied.");
76 if (is_callable($Func))
78 self::$PrintableFieldFunction = $Func;
82 throw new InvalidArgumentException(
"Invalid function supplied.");
92 # ---- SET CONSTRUCTION --------------------------------------------------- 107 # normalize field value if supplied 108 $Field = self::NormalizeField($Field);
110 # make sure search strings are an array 111 if (!is_array($SearchStrings))
112 { $SearchStrings = array($SearchStrings); }
114 # for each search string 115 foreach ($SearchStrings as $String)
120 # add strings to search values for field 121 $this->SearchStrings[$Field][] = $String;
125 # add strings to keyword search values 126 $this->KeywordSearchStrings[] = $String;
141 # normalize field value if supplied 142 $Field = self::NormalizeField($Field);
144 # if search strings specified 145 if ($SearchStrings != NULL)
147 # make sure search strings are an array 148 if (!is_array($SearchStrings))
149 { $SearchStrings = array($SearchStrings); }
151 # for each search string 152 foreach ($SearchStrings as $String)
157 # if there are search parameters for this field 158 if (isset($this->SearchStrings[$Field]))
160 # remove any matching search parameters 161 $NewSearchStrings = array();
162 foreach ($this->SearchStrings[$Field] as $Value)
164 if ($Value != $String)
166 $NewSearchStrings[] = $Value;
169 if (count($NewSearchStrings))
171 $this->SearchStrings[$Field] = $NewSearchStrings;
175 unset($this->SearchStrings[$Field]);
181 # remove any matching keyword search parameters 182 $NewSearchStrings = array();
183 foreach ($this->KeywordSearchStrings as $Value)
185 if ($Value != $String)
187 $NewSearchStrings[] = $Value;
190 $this->KeywordSearchStrings = $NewSearchStrings;
199 # clear any search strings for this field 200 if (isset($this->SearchStrings[$Field]))
202 unset($this->SearchStrings[$Field]);
207 # clear all keyword search parameters 208 $this->KeywordSearchStrings = array();
212 # for each parameter subgroup 213 $NewSubgroups = array();
214 foreach ($this->Subgroups as $Group)
216 # remove parameter from subgroup 217 $Group->RemoveParameter($SearchStrings, $Field);
219 # if the subgroup is not empty 220 if ($Group->ParameterCount())
223 $NewSubgroups[] = $Group;
226 $this->Subgroups = $NewSubgroups;
236 public function Logic($NewValue = NULL)
238 # if new value supplied 239 if ($NewValue !== NULL)
246 : strtoupper($NewValue));
248 # error out if value appears invalid 249 if (($NormValue !==
"AND") && ($NormValue !==
"OR"))
251 throw new InvalidArgumentException(
"New logic setting" 252 .
" is invalid (".$NewValue.
").");
256 $this->
Logic = $NormValue;
259 # return current logic setting to caller 273 if ($ItemTypes !== FALSE)
275 if (!is_array($ItemTypes))
277 $ItemTypes = array($ItemTypes);
281 return $this->ItemTypes;
292 throw new Exception(
"Attempt to add empty subgroup");
295 # add subgroup to privilege set 296 $this->Subgroups[] = $Set;
301 # ---- DATA RETRIEVAL ----------------------------------------------------- 310 $Count = count($this->KeywordSearchStrings);
311 foreach ($this->SearchStrings as $Field => $Strings)
313 $Count += count($Strings);
315 foreach ($this->Subgroups as $Group)
317 $Count += $Group->ParameterCount();
331 $SearchStrings = $this->SearchStrings;
332 if ($IncludeSubgroups)
334 foreach ($this->Subgroups as $Group)
336 $SubStrings = $Group->GetSearchStrings(TRUE);
337 foreach ($SubStrings as $Field => $Strings)
339 if (isset($SearchStrings[$Field]))
341 $SearchStrings[$Field] = array_merge(
342 $SearchStrings[$Field], $Strings);
346 $SearchStrings[$Field] = $Strings;
351 return $SearchStrings;
364 # normalize field value 365 $Field = self::NormalizeField($Field);
367 # start with our string values 368 $Strings = isset($this->SearchStrings[$Field])
369 ? $this->SearchStrings[$Field] : array();
371 # if strings from subgroups should also be returned 372 if ($IncludeSubgroups)
375 foreach ($this->Subgroups as $Group)
377 # add any strings from that subgroup 378 $Strings = array_merge($Strings,
379 $Group->GetSearchStringsForField($Field));
383 # return all strings found to caller 393 return $this->KeywordSearchStrings;
402 return $this->Subgroups;
411 # retrieve our fields 412 $Fields = array_keys($this->SearchStrings);
415 foreach ($this->Subgroups as $Group)
417 # add fields from subgroup to the list 418 $Fields = array_merge($Fields, $Group->GetFields());
421 # filter out duplicates and sort to ensure consistency 422 $Fields = array_unique($Fields);
425 # return list of field identifiers to caller 431 # ---- DATA TRANSLATION --------------------------------------------------- 444 public function Data($NewValue = NULL)
446 # if new data supplied 447 if ($NewValue !== NULL)
449 # unpack set data and load 450 $this->LoadFromData($NewValue);
453 # serialize current data and return to caller 455 if ($this->
Logic !==
"AND") { $Data[
"Logic"] = $this->Logic; }
456 if (count($this->SearchStrings))
457 { $Data[
"SearchStrings"] = $this->SearchStrings; }
458 if (count($this->KeywordSearchStrings))
460 $Data[
"KeywordSearchStrings"] = $this->KeywordSearchStrings;
462 if (count($this->Subgroups))
464 foreach ($this->Subgroups as $Subgroup)
466 $Data[
"Subgroups"][] = $Subgroup->Data();
469 return serialize($Data);
480 # if new value supplied 481 if ($NewValue !== NULL)
484 $this->SetFromUrlParameters($NewValue);
487 # get existing search parameters as URL parameters 488 $Params = $this->GetAsUrlParameters();
490 # sort parameters by parameter name to normalize result 493 # return parameters to caller 508 # combine values into string 511 foreach ($Params as $Index => $Value)
513 $ParamString .= $Separator.$Index.
"=".urlencode($Value);
517 # return string to caller 533 $TruncateLongWordsTo = 0, $Indent =
"")
535 # define list of phrases used to represent logical operators 536 $OperatorPhrases = array(
539 ">" =>
"is greater than",
540 "<" =>
"is less than",
541 ">=" =>
"is at least",
542 "<=" =>
"is no more than",
545 "^" =>
"begins with",
547 "@" =>
"was last modified on or after",
548 "@>" =>
"was last modified after",
549 "@>=" =>
"was last modified on or after",
550 "@<" =>
"was last modified before",
551 "@<=" =>
"was last modified on or before",
553 $AgoOperatorPhrases = array(
554 "@>" =>
"was last modified more than",
555 "@>=" =>
"was last modified at least or more than",
556 "@<" =>
"was last modified less than",
557 "@<=" =>
"was last modified at most or less than",
560 # set characters used to indicate literal strings 561 $LiteralStart = $IncludeHtml ?
"<i>" :
"\"";
562 $LiteralEnd = $IncludeHtml ?
"</i>" :
"\"";
563 $LiteralBreak = $IncludeHtml ?
"<br>\n" :
"\n";
564 $Indent .= $IncludeHtml ?
" " :
" ";
566 # for each keyword search string 567 $Descriptions = array();
568 foreach ($this->KeywordSearchStrings as $SearchString)
570 # escape search string if appropriate 573 $SearchString = defaulthtmlentities($SearchString);
576 # add string to list of descriptions 577 $Descriptions[] = $LiteralStart.$SearchString.$LiteralEnd;
580 # for each field with search strings 581 foreach ($this->SearchStrings as $FieldId => $SearchStrings)
583 # retrieve field name 584 $FieldName = call_user_func(self::$PrintableFieldFunction, $FieldId);
586 # for each search string 587 foreach ($SearchStrings as $SearchString)
589 # extract operator from search string 590 $MatchResult = preg_match(
'/^([=><!^$@]+)(.+)/',
591 $SearchString, $Matches);
593 # determine operator phrase 594 if (($MatchResult == 1) && isset($OperatorPhrases[$Matches[1]]))
596 if (isset($AgoOperatorPhrases[$Matches[1]])
597 && strstr($SearchString,
"ago"))
599 $OpPhrase = $AgoOperatorPhrases[$Matches[1]];
603 $OpPhrase = $OperatorPhrases[$Matches[1]];
605 $SearchString = $Matches[2];
609 $OpPhrase =
"contains";
612 # escape field name and search string if appropriate 615 $FieldName = defaulthtmlentities($FieldName);
616 $SearchString = defaulthtmlentities($SearchString);
619 # assemble field and operator and value into description 620 $Descriptions[] = $FieldName.
" ".$OpPhrase.
" " 621 .$LiteralStart.$SearchString.$LiteralEnd;
626 foreach ($this->Subgroups as $Subgroup)
628 # retrieve description for subgroup 629 if ($Subgroup->ParameterCount() == 1)
631 $Descriptions[] = $Subgroup->TextDescription($IncludeHtml,
632 $StartWithBreak, $TruncateLongWordsTo, $Indent);
634 elseif ($Subgroup->ParameterCount() > 1)
636 $Descriptions[] =
"(".$Subgroup->TextDescription($IncludeHtml,
637 $StartWithBreak, $TruncateLongWordsTo, $Indent).
")";
641 # join descriptions with appropriate conjunction 642 $Descrip = join($LiteralBreak.$Indent.
" ".strtolower($this->
Logic).
" ",
645 # if caller requested that long words be truncated 646 if ($TruncateLongWordsTo > 4)
648 # break description into words 649 $Words = explode(
" ", $Descrip);
653 foreach ($Words as $Word)
655 # if word is longer than specified length 656 if (strlen(strip_tags($Word)) > $TruncateLongWordsTo)
658 # truncate word and add ellipsis 659 $Word = NeatlyTruncateString($Word, $TruncateLongWordsTo - 3);
662 # add word to new description 663 $NewDescrip .=
" ".$Word;
666 # set description to new description 667 $Descrip = $NewDescrip;
670 # return description to caller 676 # ---- UTILITY METHODS ---------------------------------------------------- 686 # modify our fielded search strings 687 foreach ($this->SearchStrings as $Field => $Strings)
689 $this->SearchStrings[$Field] =
690 preg_replace($Pattern, $Replacement, $Strings);
693 # modify our keyword search strings 694 if (count($this->KeywordSearchStrings))
696 $this->KeywordSearchStrings =
697 preg_replace($Pattern, $Replacement, $this->KeywordSearchStrings);
700 # modify any subgroups 701 foreach ($this->Subgroups as $Group)
703 $Group->ReplaceSearchString($Pattern, $Replacement);
709 # ---- BACKWARD COMPATIBILITY --------------------------------------------- 724 $Group = $this->ConvertToLegacyGroup();
727 $Legacy[
"MAIN"] = $Group;
731 foreach ($this->Subgroups as $Subgroup)
733 # skip empty search groups 734 if (count($Subgroup->SearchStrings)==0)
739 $SubLegacy = $Subgroup->ConvertToLegacyGroup();
741 # give an index based on the FieldId of the first 742 # element in the SearchStrings 743 $FieldId = call_user_func(
744 self::$CanonicalFieldFunction,
745 current(array_keys($SubLegacy[
"SearchStrings"])) );
747 # add groups from legacy array to our array 748 if (!isset($Legacy[$FieldId]))
750 $Legacy[$FieldId] = $SubLegacy;
754 $Num = count($Legacy[$FieldId]);
755 $Legacy[$FieldId.
"-".$Num] = $SubLegacy;
758 if (count($Subgroup->Subgroups))
761 "Attempt to convert SearchParameterSet containing nested subgroups " 762 .
"to legacy format");
766 # return array to caller 776 # clear current settings 777 $this->KeywordSearchStrings = array();
778 $this->SearchStrings = array();
779 $this->Subgroups = array();
781 # iterate over legacy search groups 782 foreach ($SearchGroups as $GroupId => $SearchGroup)
784 if ($GroupId ==
"MAIN")
786 # add terms from the main search group to ourself 787 $this->LoadFromLegacyGroup($SearchGroup);
791 # create subgroups for other groups 793 $Subgroup->LoadFromLegacyGroup($SearchGroup);
795 # add any non-empty groups 796 if ($Subgroup->ParameterCount())
810 # clear current settings 811 $this->KeywordSearchStrings = array();
812 $this->SearchStrings = array();
813 $this->Subgroups = array();
815 # extact array of parameters from passed string 816 $GetVars = ParseQueryString($ParameterString);
818 # iterate over the provided parameters 819 foreach ($GetVars as $Key => $Val)
821 # if this param gives search information 822 if (preg_match(
"/^([FGH])(K|[0-9]+)$/", $Key, $Matches))
824 # extract what kind of search it was which field 826 $FieldId = $Matches[2];
828 # for 'contains' searches 831 # add this to our search strings 833 ($FieldId ==
"K" ? NULL : $FieldId) );
837 # otherwise, create a subgroup for this parameter 840 # set logic based on the search type 841 $Subgroup->Logic($Type==
"H" ?
"AND" :
"OR");
843 # extract the values and add them to a subgroup 844 $Values = explode(
"-", $Val);
845 $Subgroup->AddParameter(
846 self::TranslateLegacySearchValues($FieldId, $Values),
849 # if subgroup was non-empty, slurp it up 850 if ($Subgroup->ParameterCount())
866 $QueryVars = ParseQueryString($ParameterString);
868 return (array_key_exists(
"Q", $QueryVars) &&
869 $QueryVars[
"Q"] ==
"Y") ? TRUE : FALSE ;
880 $SearchParams->SetFromLegacyUrl($ParameterString);
882 return $SearchParams->UrlParameterString();
893 if (is_callable($Func))
895 self::$LegacyUrlTranslationFunction = $Func;
899 throw new InvalidArgumentException(
"Invalid function supplied.");
911 if (($Field !== NULL) && isset(self::$LegacyUrlTranslationFunction))
913 $Values = call_user_func(self::$LegacyUrlTranslationFunction,
920 # ---- PRIVATE INTERFACE ------------------------------------------------- 922 private $KeywordSearchStrings = array();
923 private $ItemTypes = NULL;
924 private $Logic = self::DEFAULT_LOGIC;
925 private $SearchStrings = array();
926 private $Subgroups = array();
928 static private $CanonicalFieldFunction;
929 static private $PrintableFieldFunction;
930 static private $LegacyUrlTranslationFunction;
931 static private $UrlParameterPrefix =
"F";
943 private function LoadFromData($Serialized)
946 $Data = unserialize($Serialized);
947 if (!is_array($Data))
949 throw new InvalidArgumentException(
"Incoming set data" 950 .
" appears invalid.");
954 $this->
Logic = isset($Data[
"Logic"]) ? $Data[
"Logic"] :
"AND";
956 # load search strings 957 $this->SearchStrings = isset($Data[
"SearchStrings"])
958 ? $Data[
"SearchStrings"] : array();
959 $this->KeywordSearchStrings = isset($Data[
"KeywordSearchStrings"])
960 ? $Data[
"KeywordSearchStrings"] : array();
963 $this->Subgroups = array();
964 if (isset($Data[
"Subgroups"]))
966 foreach ($Data[
"Subgroups"] as $SubgroupData)
977 private function ConvertToLegacyGroup()
980 # for each set of search strings 981 foreach ($this->SearchStrings as $Field => $Strings)
983 # get text name of field 984 $FieldName = call_user_func(self::$PrintableFieldFunction, $Field);
987 $Group[
"SearchStrings"][$FieldName] = $Strings;
990 # for each keyword search string 991 foreach ($this->KeywordSearchStrings as $String)
993 # add string to keyword entry in group 994 $Group[
"SearchStrings"][
"XXXKeywordXXX"][] = $String;
997 # if we had any search terms 1000 # smash single-value arrays to a scalar 1001 foreach ($Group[
"SearchStrings"] as &$Tgt)
1003 if (count($Tgt) == 1)
1005 $Tgt = current($Tgt);
1009 # set logic for search group 1010 $Group[
"Logic"] = ($this->
Logic ==
"OR")
1021 private function LoadFromLegacyGroup($Group)
1023 # set logic appropriately 1028 # if this group had no search strings, we're done 1029 if (!isset($Group[
"SearchStrings"]))
1034 # otherwise, load the search strings 1035 foreach ($Group[
"SearchStrings"] as $Field => $Params)
1038 if (count($Params)==0)
1044 ($Field ==
"XXXKeywordXXX" ? NULL : $Field) );
1054 private function GetAsUrlParameters($SetPrefix =
"")
1056 # for each search string group in set 1058 foreach ($this->SearchStrings as $FieldId => $Values)
1060 # get numeric version of field ID if not already numeric 1061 if (!is_numeric($FieldId))
1063 $FieldId = call_user_func(self::$CanonicalFieldFunction, $FieldId);
1066 # for each search string in group 1068 foreach ($Values as $Value)
1070 # check for too many search strings for this field 1071 if ($ParamSuffix ==
"Z")
1073 throw new Exception(
"Maximum search parameter complexity" 1074 .
" exceeded: more than 26 search parameters for" 1075 .
" field ID ".$FieldId.
".");
1078 # add search string to URL 1079 $Params[self::$UrlParameterPrefix.$SetPrefix
1080 .$FieldId.$ParamSuffix] = $Value;
1081 $ParamSuffix = ($ParamSuffix ==
"") ?
"A" 1082 : chr(ord($ParamSuffix) + 1);
1086 # for each keyword search string 1088 foreach ($this->KeywordSearchStrings as $Value)
1090 # check for too many keyword search strings 1091 if ($ParamSuffix ==
"Z")
1093 throw new Exception(
"Maximum search parameter complexity" 1094 .
" exceeded: more than 26 keyword search parameters.");
1097 # add search string to URL 1098 $Params[self::$UrlParameterPrefix.$SetPrefix
1099 .self::URL_KEYWORD_INDICATOR.$ParamSuffix] = $Value;
1100 $ParamSuffix = ($ParamSuffix ==
"") ?
"A" 1101 : chr(ord($ParamSuffix) + 1);
1104 # add logic if not default 1105 if ($this->
Logic != self::DEFAULT_LOGIC)
1107 $Params[self::$UrlParameterPrefix.$SetPrefix
1108 .self::URL_LOGIC_INDICATOR] = $this->Logic;
1111 # for each search parameter subgroup 1113 foreach ($this->Subgroups as $Subgroup)
1115 # check for too many subgroups 1116 if ($SetLetter ==
"Z")
1118 throw new Exception(
"Maximum search parameter complexity" 1119 .
" exceeded: more than 24 search parameter subgroups.");
1122 # retrieve URL string for subgroup and add it to URL 1123 $Params = array_merge($Params, $Subgroup->GetAsUrlParameters(
1124 $SetPrefix.$SetLetter));
1126 # move to next set letter 1127 $SetLetter = ($SetLetter == chr(ord(self::URL_KEYWORD_INDICATOR) - 1))
1128 ? chr(ord(self::URL_KEYWORD_INDICATOR) + 1)
1129 : chr(ord($SetLetter) + 1);
1132 # return constructed URL parameter string to caller 1142 private function SetFromUrlParameters($UrlParameters)
1144 # if string was passed in 1145 if (is_string($UrlParameters))
1147 # split string into parameter array 1148 $Params = explode(
"&", $UrlParameters);
1150 # pare down parameter array to search parameter elements 1151 # and strip off search parameter prefix 1152 $NewUrlParameters = array();
1153 foreach ($Params as $Param)
1155 if (strpos($Param, self::$UrlParameterPrefix) === 0)
1157 list($Index, $Value) = explode(
"=", $Param);
1158 $NewUrlParameters[$Index] = urldecode($Value);
1161 $UrlParameters = $NewUrlParameters;
1164 # for each search parameter 1165 foreach ($UrlParameters as $ParamName => $SearchString)
1167 # strip off standard search parameter prefix 1168 $ParamName = substr($ParamName, strlen(self::$UrlParameterPrefix));
1170 # split parameter into component parts 1171 $SplitResult = preg_match(
"/^([".self::URL_KEYWORDFREE_RANGE.
"]*)" 1172 .
"([0-9".self::URL_KEYWORD_INDICATOR.
"]+)([A-Z]*)$/",
1173 $ParamName, $Matches);
1175 # if split was successful 1176 if ($SplitResult === 1)
1178 # pull components from split pieces 1179 $SetPrefix = $Matches[1];
1180 $FieldId = $Matches[2];
1181 $ParamSuffix = $Matches[3];
1183 # if set prefix indicates parameter is part of our set 1184 if ($SetPrefix ==
"")
1188 case self::URL_LOGIC_INDICATOR:
1190 $this->
Logic($SearchString);
1193 case self::URL_KEYWORD_INDICATOR:
1194 # add string to keyword searches 1195 $this->KeywordSearchStrings[] = $SearchString;
1199 # add string to searches for appropriate field 1200 $this->SearchStrings[$FieldId][] = $SearchString;
1206 # add parameter to array for subgroup 1207 $SubgroupIndex = $SetPrefix[0];
1208 $SubgroupPrefix = (strlen($SetPrefix) > 1)
1209 ? substr($SetPrefix, 1) :
"";
1210 $SubgroupParamIndex = self::$UrlParameterPrefix
1211 .$SubgroupPrefix.$FieldId.$ParamSuffix;
1212 $SubgroupParameters[$SubgroupIndex][$SubgroupParamIndex]
1218 # if subgroups were found 1219 if (isset($SubgroupParameters))
1221 # for each identified subgroup 1222 foreach ($SubgroupParameters as $SubgroupIndex => $Parameters)
1224 # create subgroup and set parameters 1226 $Subgroup->SetFromUrlParameters($Parameters);
1228 # add subgroup to our set 1229 $this->Subgroups[] = $Subgroup;
1239 private static function NormalizeField($Field)
1241 if (($Field !== NULL) && isset(self::$CanonicalFieldFunction))
1243 $Field = call_user_func(self::$CanonicalFieldFunction, $Field);
TextDescription($IncludeHtml=TRUE, $StartWithBreak=TRUE, $TruncateLongWordsTo=0, $Indent="")
Get text description of search parameter set.
Data($NewValue=NULL)
Get/set search parameter set data, in the form of an opaque string.
static TranslateLegacySearchValues($Field, $Values)
Translate legacy search values to modern equivalents if possible.
SetFromLegacyArray($SearchGroups)
Set search parameters from legacy array format.
static SetCanonicalFieldFunction($Func)
Register function used to retrieve a canonical value for a field.
Set of parameters used to perform a search.
AddSet(SearchParameterSet $Set)
Add subgroup of search parameters to set.
UrlParameterString($NewValue=NULL)
Get/set search parameter set, in the form of an URL parameter string.
UrlParameters($NewValue=NULL)
Get/set search parameter set, in the form of URL parameters.
const URL_KEYWORD_INDICATOR
const URL_KEYWORDFREE_RANGE
Logic($NewValue=NULL)
Get/set logic for set.
GetFields()
Get fields used in search parameters (including subgroups).
static SetLegacyUrlTranslationFunction($Func)
Register function used to converrt values from the format used in a Legacy URL string to the modern v...
GetSearchStringsForField($Field, $IncludeSubgroups=TRUE)
Get search strings for specified field.
static SetPrintableFieldFunction($Func)
Register function used to retrieve a printable value for a field.
ItemTypes($ItemTypes=FALSE)
Get/set allowed item types.
SetFromLegacyUrl($ParameterString)
Set search parameters from legacy URL string.
__clone()
Class clone handler, implemented so that clones will be deep copies rather than PHP5's default shallo...
AddParameter($SearchStrings, $Field=NULL)
Add search parameter to set.
static ConvertLegacyUrl($ParameterString)
Convert legacy URL to the current URL format.
GetAsLegacyArray()
Retrieve search parameters in legacy array format.
GetSearchStrings($IncludeSubgroups=FALSE)
Get search strings in set.
__construct($Data=NULL)
Class constructor, used to create a new set or reload an existing set from previously-constructed dat...
RemoveParameter($SearchStrings, $Field=NULL)
Remove search parameter from set.
static IsLegacyUrl($ParameterString)
Determine if a URL uses legacy format.
GetKeywordSearchStrings()
Get keyword search strings in set.
ReplaceSearchString($Pattern, $Replacement)
Modify all search strings with the specified regular expression.
const URL_LOGIC_INDICATOR
GetSubgroups()
Get parameter subgroups.
ParameterCount()
Get number of search parameters in set, including those in subgroups.