Skip to content

Commit

Permalink
Handle very naive text search with support of AND and OR
Browse files Browse the repository at this point in the history
  • Loading branch information
axelberardino committed Aug 21, 2019
1 parent 75af4cf commit dba9afb
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 16 deletions.
93 changes: 93 additions & 0 deletions data/template/parts/helpers.js.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,99 @@
}
}

// Will highlight matching items.
function searchItem() {
// Disable search button.
var searchbutton = document.getElementById('searchbutton');
searchbutton.disabled = true;

// Get search query.
var search = document.getElementById('searchbar');
var query = search.value;

// Reset "not price" highlighting, as we will highlight differently.
var search = document.getElementById('highlight');
search.selectedIndex = 0;

// Split and sort search query.
var tempQueries = query.split('|');
var queries = new Array(0);
for (var i = 0; i < tempQueries.length; i++) {
var rawSplitQ = tempQueries[i].split(' ');
var splitQ = new Array(0);
// Remove whitespaces.
for (var j = 0; j < rawSplitQ.length; j++) {
if (' \t\n\r\v'.indexOf(rawSplitQ[j]) < 0) {
splitQ.push(rawSplitQ[j]);
}
}
splitQ.sort();
queries.push(splitQ);
}

// Get all indexed items.
var indexItem = document.getElementsByClassName('index-item');

// Highlight all matching items.
for (var i = 0; i < indexItem.length; i++) {
var id = indexItem[i].getAttribute('data-id');
var item = document.getElementById(id);
// Some item description don't have a real item (like divination
// in divination stash tabs).
if (item != undefined) {
var text = indexItem[i].value;
var desc = text.split(' ').sort();
if (query == "" || queriesMatch(desc, queries)) {
item.style.opacity = 1.0;
} else {
item.style.opacity = 0.3;
}
}
}

// Restore search button.
searchbutton.disabled = false;
}

// Check if any queries match the given description array.
function queriesMatch(desc, queries) {
for (var i = 0; i < queries.length; i++) {
if (queryMatch(desc, queries[i])) {
return true;
}
}
return false;
}

var myDesc = [ "a", "m", "x", "z" ];
var myQuery = [ "m", "z" ];

// Check if a given query match the given description array.
// All term of the query has to find a match in the description.
// It's a "AND" behavior.
function queryMatch(desc, query) {
var i = 0;
var j = 0;
while (i < desc.length && j < query.length) {
// The term match, let's check the next one.
if (desc[i] == query[j]) {
i++;
j++;
}
// No match, let's check the next term.
else if (desc[i] < query[j]) {
i++;
}
// No match, and it's seems there will be no match.
else {
return false;
}
}
// If we arrived here, then check that all terms have found a match.
// ie, j has advanced to the end.
return j >= query.length;
}

initImport();
initModal();
{{ if not .Demo }}
Expand Down
1 change: 1 addition & 0 deletions data/template/parts/itemdesc.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{{ define "itemdesc" }}
<div id="item-{{ .Id }}-tooltip" style="display: none;">
<div class="itemPopupContainer newItemPopup {{ ItemRarityType .FrameType }}">
<input data-id="item-{{ .Id }}" class="index-item" type="hidden" value="{{ GenNaiveSearchIndex . }}" />

{{ if .ArtFilename }}
{{ template "divinationcard" dict "Item" . "Small" false }}
Expand Down
4 changes: 4 additions & 0 deletions data/template/parts/style.css.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -421,5 +421,9 @@
overflow-x: hidden !important;
overflow-y: scroll !important;
}
#searchbar {
width: 85%;
margin-top: 5px;
}
</style>
{{ end }}
38 changes: 22 additions & 16 deletions data/template/profile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,29 @@
<img class="thumbnail" src="https://web.poecdn.com/image/Art/2DItems/Currency/CurrencyUpgradeToRare.png?scale=1&w=1&h=1&v=89c110be97333995522c7b2c29cae728"><small>x</small>{{ .Wealth.NbAlch }})
</div>
<div id="shop" class="box">
<button class="button" onclick="generateShop()">Generate items shop</button>
<button class="button" onclick="exportShop()">Export shop</button>
<input type="file" name="files[]" id="shop-file" class="inputfile inputfile-1" accept=".txt" multiple="">
<label for="shop-file">
<span class="button button-label" >
Import shop
<div>
<button class="button" onclick="generateShop()">Generate items shop</button>
<button class="button" onclick="exportShop()">Export shop</button>
<input type="file" name="files[]" id="shop-file" class="inputfile inputfile-1" accept=".txt" multiple="">
<label for="shop-file">
<span class="button button-label" >
Import shop
</span>
</label>
<span id="merge-box">
<input type="checkbox" id="merge" name="merge" checked="checked" onclick="onMergeChange(this)">
<label for="merge">Merge</label>
</span>
</label>
<span id="merge-box">
<input type="checkbox" id="merge" name="merge" checked="checked" onclick="onMergeChange(this)">
<label for="merge">Merge</label>
</span>
<select id="highlight" class="dropdown" onchange="highlightItems(this.value)">
<option value="all" selected="selected">Show all</option>
<option value="sell">Show price items</option>
<option value="noprice">Show no price set</option>
</select>
<select id="highlight" class="dropdown" onchange="highlightItems(this.value)">
<option value="all" selected="selected">Show all</option>
<option value="sell">Show price items</option>
<option value="noprice">Show no price set</option>
</select>
</div>
<div>
<input id="searchbar" class="input-text" type="text" name="searchbar" value="" placeholder="Example: ring | vix lunaris | increased damage" />
<button id="searchbutton" class="button" onclick="searchItem()">Search</button>
</div>
</div>

<div class="clear"><!-- --></div>
Expand Down
61 changes: 61 additions & 0 deletions generate/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -69,6 +70,7 @@ func LoadAllTemplates() (*template.Template, error) {
"ContainsPattern": ContainsPattern,
"GenProperties": GenProperties,
"SearchItem": SearchItem,
"GenNaiveSearchIndex": GenNaiveSearchIndex,
"Version": func() string {
return misc.Version
},
Expand Down Expand Up @@ -601,3 +603,62 @@ func SearchItem(items []models.Item, name string) models.Item {
}
return models.Item{}
}

// extractWords extracts relevant words from a sentence.
func extractWords(line string) []string {
line = strings.ToLower(line)
line = strings.ReplaceAll(line, "'", " ")
line = strings.ReplaceAll(line, ":", " ")
return strings.Split(line, " ")
}

// GenNaiveSearchIndex generates very naive indexing for an item description.
// It's just a list of selected unique sorted words.
func GenNaiveSearchIndex(item models.Item) string {
words := make(map[string]struct{}, 0)

// Extract name.
for _, v := range extractWords(item.Name) {
words[v] = struct{}{}
}

// Extract type of item.
for _, v := range extractWords(item.Type) {
words[v] = struct{}{}
}

// Extract properties.
for _, mod := range item.ExplicitMods {
for _, v := range extractWords(mod) {
words[v] = struct{}{}
}
}
for _, mod := range item.ImplicitMods {
for _, v := range extractWords(mod) {
words[v] = struct{}{}
}
}
for _, mod := range item.UtilityMods {
for _, v := range extractWords(mod) {
words[v] = struct{}{}
}
}
for _, mod := range item.EnchantMods {
for _, v := range extractWords(mod) {
words[v] = struct{}{}
}
}
for _, mod := range item.CraftedMods {
for _, v := range extractWords(mod) {
words[v] = struct{}{}
}
}

// Construct final string with sorted keywords.
keys := make([]string, 0, len(words))
for key := range words {
keys = append(keys, key)
}
sort.Strings(keys)
return strings.Join(keys, " ")
}
29 changes: 29 additions & 0 deletions generate/generator_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package generate

import (
"reflect"
"testing"
)

Expand Down Expand Up @@ -92,3 +93,31 @@ func TestPoEMarkup(t *testing.T) {
}
}
}

// TestExtractWords tests that creating an index using item description works.
func TestExtractWords(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{
input: "An Item To LowerCase",
expected: []string{"an", "item", "to", "lowercase"},
},
{
input: "remove single letter like a b c",
expected: []string{"remove", "single", "letter", "like"},
},
{
input: "remove some useless char like ' or :",
expected: []string{"remove", "some", "useless", "char", "like", "or"},
},
}

for _, current := range tests {
res := extractWords(current.input)
if !reflect.DeepEqual(res, current.expected) {
t.Errorf("\n\texpected:\n%v\n\tbut got:\n%v\n", current.expected, res)
}
}
}

0 comments on commit dba9afb

Please sign in to comment.