improve search so that it works with strings

This commit is contained in:
Benjamin Popp 2018-12-18 22:02:05 -06:00
parent 258d5c4562
commit b079ca65ee
5 changed files with 97 additions and 11 deletions

View File

@ -46,6 +46,7 @@
<Compile Include="Models\FormattedRun.cs" />
<Compile Include="Models\DataFormatModel.cs" />
<Compile Include="Models\PCSString.cs" />
<Compile Include="Models\SearchByte.cs" />
<Compile Include="NumericExtensions.cs" />
<Compile Include="Models\Enums.cs" />
<Compile Include="Models\IFileSystem.cs" />

View File

@ -50,6 +50,7 @@ namespace HavenSoft.Gen3Hex.Core.Models {
}
public static List<byte> Convert(string input) {
if (input.StartsWith("\"")) input = input.Substring(1); // trim leading " at start of string
var result = new List<byte>();
int index = 0;

View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace HavenSoft.Gen3Hex.Core.Models {
public interface ISearchByte {
bool Match(byte value);
}
public class SearchByte : ISearchByte {
private readonly byte value;
public SearchByte(int value) => this.value = (byte)value;
public static explicit operator SearchByte (byte value) => new SearchByte(value);
public bool Match(byte value) => value == this.value;
}
public class PCSSearchByte : ISearchByte {
private readonly byte match1, match2;
public PCSSearchByte(int value) {
match1 = (byte)value;
match2 = match1;
if (PCSString.PCS[match1] == null) return;
var valueAsChar = PCSString.PCS[match1][0];
if (char.IsUpper(valueAsChar)) {
Debug.Assert(IndexOf(PCSString.PCS, "a") - IndexOf(PCSString.PCS, "A") == 0x1A);
match2 += 0x1A;
}
}
public bool Match(byte value) => value == match1 || value == match2;
private static int IndexOf(IReadOnlyList<string> pcs, string value) => Enumerable.Range(0, 0x100).Single(i => pcs[i] == value);
}
}

View File

@ -344,23 +344,40 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels {
public IReadOnlyList<int> Find(string rawSearch) {
var results = new List<int>();
var cleanedSearchString = rawSearch.Replace(" ", string.Empty).ToUpper();
var searchBytes = new List<byte>();
var cleanedSearchString = rawSearch.ToUpper();
var searchBytes = new List<ISearchByte>();
var hex = "0123456789ABCDEF";
// precheck: it might be a string with no quotes, we should check for matches for that.
if (cleanedSearchString.Length > 3 && !cleanedSearchString.Contains('"')) {
var pcsBytes = PCSString.Convert(cleanedSearchString);
searchBytes.AddRange(pcsBytes.Select(b => new PCSSearchByte(b)));
for (int i = 0; i < Model.Count - searchBytes.Count; i++) {
for (int j = 0; j < searchBytes.Count; j++) {
if (!searchBytes[j].Match(Model[i + j])) break;
if (j == searchBytes.Count - 1) results.Add(i);
}
}
searchBytes.Clear();
}
for (int i = 0; i < cleanedSearchString.Length;) {
if (cleanedSearchString[i] == ' ') {
i++;
continue;
}
if (cleanedSearchString[i] == '<') {
var pointerEnd = cleanedSearchString.IndexOf('>', i);
if (pointerEnd == -1) { OnError(this, "Search mismatch: no closing >"); return results; }
var pointerContents = cleanedSearchString.Substring(i + 1, pointerEnd - i - 2);
var address = Model.GetAddressFromAnchor(-1, pointerContents);
if (address != Pointer.NULL) {
searchBytes.Add((byte)(address >> 0));
searchBytes.Add((byte)(address >> 8));
searchBytes.Add((byte)(address >> 16));
searchBytes.Add(0x08);
searchBytes.Add((SearchByte)(address >> 0));
searchBytes.Add((SearchByte)(address >> 8));
searchBytes.Add((SearchByte)(address >> 16));
searchBytes.Add((SearchByte)0x08);
} else if (pointerContents.All(hex.Contains) && pointerContents.Length <= 6) {
searchBytes.AddRange(Parse(pointerContents).Reverse().Append((byte)0x08));
searchBytes.AddRange(Parse(pointerContents).Reverse().Append((byte)0x08).Select(b => (SearchByte)b));
} else {
OnError(this, $"Could not parse pointer <{pointerContents}>");
return results;
@ -368,18 +385,31 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels {
i = pointerEnd + 1;
continue;
}
if (cleanedSearchString[i] == '"') {
var endIndex = cleanedSearchString.IndexOf('"', i + 1);
while (endIndex > i && cleanedSearchString[endIndex - 1] == '\\') endIndex = cleanedSearchString.IndexOf('"', endIndex + 1);
if (endIndex > i) {
var pcsBytes = PCSString.Convert(cleanedSearchString.Substring(i, endIndex + 1 - i));
i = endIndex + 1;
if (i == cleanedSearchString.Length) pcsBytes.RemoveAt(pcsBytes.Count - 1);
searchBytes.AddRange(pcsBytes.Select(b => new PCSSearchByte(b)));
continue;
}
}
if (cleanedSearchString.Length >= i + 2 && cleanedSearchString.Substring(i, 2).All(hex.Contains)) {
searchBytes.AddRange(Parse(cleanedSearchString.Substring(i, 2)));
searchBytes.AddRange(Parse(cleanedSearchString.Substring(i, 2)).Select(b => (SearchByte)b));
i += 2;
continue;
}
OnError(this, $"Could not parse search term {cleanedSearchString.Substring(i)}");
if (results.Count == 0) {
OnError(this, $"Could not parse search term {cleanedSearchString.Substring(i)}");
}
return results;
}
for (int i = 0; i < Model.Count - searchBytes.Count; i++) {
for (int j = 0; j < searchBytes.Count; j++) {
if (Model[i + j] != searchBytes[j]) break;
if (!searchBytes[j].Match(Model[i + j])) break;
if (j == searchBytes.Count - 1) results.Add(i);
}
}

View File

@ -231,7 +231,31 @@ namespace HavenSoft.Gen3Hex.Tests {
Assert.Equal("^bob\"\" \"Hello World!\"", fileSystem.CopyText);
}
// TODO Find
[Fact]
public void FindForStringsIsNotCaseSensitive() {
var buffer = Enumerable.Repeat((byte)0xFF, 0x200).ToArray();
for (int i = 0; i < 0x10; i++) buffer[i] = 0x00;
var model = new PointerAndStringModel(buffer);
var viewPort = new ViewPort(new LoadedFile("test.txt", buffer), model) { Width = 0x10, Height = 0x10 };
viewPort.Edit("^bob\"\" \"Text and BULBASAUR!\"");
var results = viewPort.Find("\"bulbasaur\"");
Assert.Single(results);
Assert.Equal(9, results[0]);
}
[Fact]
public void FindForStringsWorksWithoutQuotes() {
var buffer = Enumerable.Repeat((byte)0xFF, 0x200).ToArray();
for (int i = 0; i < 0x10; i++) buffer[i] = 0x00;
var model = new PointerAndStringModel(buffer);
var viewPort = new ViewPort(new LoadedFile("test.txt", buffer), model) { Width = 0x10, Height = 0x10 };
viewPort.Edit("^bob\"\" \"Text and BULBASAUR!\"");
var results = viewPort.Find("bulbasaur");
Assert.Single(results);
Assert.Equal(9, results[0]);
}
// TODO undo/redo