using System.Collections.Generic;
using System.IO;
using System.Text;
namespace SabreTools.Matching
{
///
/// Helper class for matching
///
public static class MatchUtil
{
#region Array Content Matching
///
/// Get all content matches for a given list of matchers
///
/// File to check for matches
/// Array to search
/// List of ContentMatchSets to be run on the file
/// True if any content match is a success, false if all have to match
/// True to include positional data, false otherwise
/// List of strings representing the matches, null or empty otherwise
public static List GetAllMatches(string file,
byte[]? stack,
List matchSets,
bool any = false,
bool includeDebug = false)
=> FindAllMatches(file, stack, matchSets, any, includeDebug, false);
///
/// Get first content match for a given list of matchers
///
/// File to check for matches
/// Array to search
/// List of ContentMatchSets to be run on the file
/// True if any content match is a success, false if all have to match
/// True to include positional data, false otherwise
/// String representing the match, null otherwise
public static string? GetFirstMatch(string file,
byte[]? stack,
List matchSets,
bool any = false,
bool includeDebug = false)
{
var contentMatches = FindAllMatches(file, stack, matchSets, any, includeDebug, true);
if (contentMatches is null || contentMatches.Count == 0)
return null;
return contentMatches[0];
}
///
/// Get the required set of content matches on a per Matcher basis
///
/// File to check for matches
/// Array to search
/// List of ContentMatchSets to be run on the file
/// True if any content match is a success, false if all have to match
/// True to include positional data, false otherwise
/// True to stop after the first match, false otherwise
/// List of strings representing the matches, empty otherwise
private static List FindAllMatches(string file,
byte[]? stack,
List matchSets,
bool any,
bool includeDebug,
bool stopAfterFirst)
{
// If either set is null or empty
if (stack is null || stack.Length == 0 || matchSets.Count == 0)
return [];
// Initialize the list of matches
var matchesList = new List();
// Loop through and try everything otherwise
foreach (var matcher in matchSets)
{
// Determine if the matcher passes
List positions = any
? [matcher.MatchesAny(stack)]
: matcher.MatchesAll(stack);
// If we don't have a pass, just continue
if (positions.Count == 0 || positions[0] == -1)
continue;
// Build the output string
var matchString = new StringBuilder();
matchString.Append(matcher.SetName);
// Invoke the version delegate, if it exists
if (matcher.GetArrayVersion is not null)
{
// A null version returned means the check didn't pass at the version step
var version = matcher.GetArrayVersion(file, stack, positions);
if (version is null)
continue;
// Trim and add the version
version = version.Trim();
if (version.Length > 0)
matchString.Append($" {version}");
}
// Append the positional data if required
if (includeDebug)
{
string positionsString = string.Join(", ", [.. positions.ConvertAll(p => p.ToString())]);
matchString.Append($" (Index {positionsString})");
}
// Append the match to the list
matchesList.Add(matchString.ToString());
// If we're stopping after the first match, bail out here
if (stopAfterFirst)
return matchesList;
}
return matchesList;
}
#endregion
#region Stream Content Matching
///
/// Get all content matches for a given list of matchers
///
/// File to check for matches
/// Stream to search
/// List of ContentMatchSets to be run on the file
/// True if any content match is a success, false if all have to match
/// True to include positional data, false otherwise
/// List of strings representing the matches, null or empty otherwise
public static List GetAllMatches(string file,
Stream? stack,
List matchSets,
bool any = false,
bool includeDebug = false)
=> FindAllMatches(file, stack, matchSets, any, includeDebug, false);
///
/// Get first content match for a given list of matchers
///
/// File to check for matches
/// Stream to search
/// List of ContentMatchSets to be run on the file
/// True if any content match is a success, false if all have to match
/// True to include positional data, false otherwise
/// String representing the match, null otherwise
public static string? GetFirstMatch(string file,
Stream? stack,
List matchSets,
bool any = false,
bool includeDebug = false)
{
var contentMatches = FindAllMatches(file, stack, matchSets, any, includeDebug, true);
if (contentMatches is null || contentMatches.Count == 0)
return null;
return contentMatches[0];
}
///
/// Get the required set of content matches on a per Matcher basis
///
/// File to check for matches
/// Stream to search
/// List of ContentMatchSets to be run on the file
/// True if any content match is a success, false if all have to match
/// True to include positional data, false otherwise
/// True to stop after the first match, false otherwise
/// List of strings representing the matches, empty otherwise
private static List FindAllMatches(string file,
Stream? stack,
List matchSets,
bool any,
bool includeDebug,
bool stopAfterFirst)
{
// If either set is null or empty
if (stack is null || stack.Length == 0 || matchSets.Count == 0)
return [];
// Initialize the list of matches
var matchesList = new List();
// Loop through and try everything otherwise
foreach (var matcher in matchSets)
{
// Determine if the matcher passes
List positions = any
? [matcher.MatchesAny(stack)]
: matcher.MatchesAll(stack);
// If we don't have a pass, just continue
if (positions.Count == 0 || positions[0] == -1)
continue;
// Build the output string
var matchString = new StringBuilder();
matchString.Append(matcher.SetName);
// Invoke the version delegate, if it exists
if (matcher.GetStreamVersion is not null)
{
// A null version returned means the check didn't pass at the version step
var version = matcher.GetStreamVersion(file, stack, positions);
if (version is null)
continue;
// Trim and add the version
version = version.Trim();
if (version.Length > 0)
matchString.Append($" {version}");
}
// Append the positional data if required
if (includeDebug)
{
string positionsString = string.Join(", ", [.. positions.ConvertAll(p => p.ToString())]);
matchString.Append($" (Index {positionsString})");
}
// Append the match to the list
matchesList.Add(matchString.ToString());
// If we're stopping after the first match, bail out here
if (stopAfterFirst)
return matchesList;
}
return matchesList;
}
#endregion
#region Path Matching
///
/// Get all path matches for a given list of matchers
///
/// File path to check for matches
/// List of PathMatchSets to be run on the file
/// True if any path match is a success, false if all have to match
/// List of strings representing the matches, null or empty otherwise
public static List GetAllMatches(string stack, List matchSets, bool any = false)
=> FindAllMatches([stack], matchSets, any, false);
///
/// Get all path matches for a given list of matchers
///
/// File paths to check for matches
/// List of PathMatchSets to be run on the file
/// True if any path match is a success, false if all have to match
/// List of strings representing the matches, null or empty otherwise
public static List GetAllMatches(List? stack, List matchSets, bool any = false)
=> FindAllMatches(stack, matchSets, any, false);
///
/// Get first path match for a given list of matchers
///
/// File path to check for matches
/// List of PathMatchSets to be run on the file
/// True if any path match is a success, false if all have to match
/// String representing the match, null otherwise
public static string? GetFirstMatch(string stack, List matchSets, bool any = false)
{
var contentMatches = FindAllMatches([stack], matchSets, any, true);
if (contentMatches is null || contentMatches.Count == 0)
return null;
return contentMatches[0];
}
///
/// Get first path match for a given list of matchers
///
/// File paths to check for matches
/// List of PathMatchSets to be run on the file
/// True if any path match is a success, false if all have to match
/// String representing the match, null otherwise
public static string? GetFirstMatch(List stack, List matchSets, bool any = false)
{
var contentMatches = FindAllMatches(stack, matchSets, any, true);
if (contentMatches is null || contentMatches.Count == 0)
return null;
return contentMatches[0];
}
///
/// Get the required set of path matches on a per Matcher basis
///
/// File paths to check for matches
/// List of PathMatchSets to be run on the file
/// True if any path match is a success, false if all have to match
/// True to stop after the first match, false otherwise
/// List of strings representing the matches, null or empty otherwise
private static List FindAllMatches(List? stack, List matchSets, bool any, bool stopAfterFirst)
{
// If either set is null or empty
if (stack is null || stack.Count == 0 || matchSets.Count == 0)
return [];
// Initialize the list of matches
var matchesList = new List();
// Loop through and try everything otherwise
foreach (var matcher in matchSets)
{
// Determine if the matcher passes
List matches = [];
if (any)
{
string? anyMatch = matcher.MatchesAny(stack);
if (anyMatch is not null)
matches = [anyMatch];
}
else
{
matches = matcher.MatchesAll(stack);
}
// If we don't have a pass, just continue
if (matches.Count == 0)
continue;
// Build the output string
var matchString = new StringBuilder();
matchString.Append(matcher.SetName);
// Invoke the version delegate, if it exists
if (matcher.GetVersion is not null)
{
// A null version returned means the check didn't pass at the version step
var version = matcher.GetVersion(matches[0], stack);
if (version is null)
continue;
// Trim and add the version
version = version.Trim();
if (version.Length > 0)
matchString.Append($" {version}");
}
// Append the match to the list
matchesList.Add(matchString.ToString());
// If we're stopping after the first match, bail out here
if (stopAfterFirst)
return matchesList;
}
return matchesList;
}
#endregion
}
}