LibItemSearch-1.0.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. --[[
  2. ItemSearch
  3. An item text search engine of some sort
  4. Grammar:
  5. <search> := <intersect search>
  6. <intersect search> := <union search> & <union search> ; <union search>
  7. <union search> := <negatable search> | <negatable search> ; <negatable search>
  8. <negatable search> := !<primitive search> ; <primitive search>
  9. <primitive search> := <tooltip search> ; <quality search> ; <type search> ; <text search>
  10. <tooltip search> := bop ; boa ; bou ; boe ; quest
  11. <quality search> := q<op><text> ; q<op><digit>
  12. <ilvl search> := ilvl<op><number>
  13. <type search> := t:<text>
  14. <text search> := <text>
  15. <op> := : | = | == | != | ~= | < | > | <= | >=
  16. I kindof half want to make a full parser for this
  17. --]]
  18. -- local MAJOR, MINOR = "LibItemSearch-1.0", 3
  19. -- local ItemSearch = LibStub:NewLibrary(MAJOR, MINOR)
  20. -- if not ItemSearch then return end
  21. local myname, ns = ...
  22. ns.LibItemSearch = {}
  23. local ItemSearch = ns.LibItemSearch
  24. --[[ general search ]]--
  25. function ItemSearch:Find(itemLink, search)
  26. if not search or search == "" then
  27. return true
  28. end
  29. if not itemLink then
  30. return false
  31. end
  32. local search = search:lower()
  33. if search:match('\124') then
  34. return self:FindUnionSearch(itemLink, strsplit('\124', search))
  35. end
  36. return self:FindUnionSearch(itemLink, search)
  37. end
  38. --[[ union search: <search>&<search> ]]--
  39. function ItemSearch:FindUnionSearch(itemLink, ...)
  40. for i = 1, select('#', ...) do
  41. local search = select(i, ...)
  42. if search and search ~= '' then
  43. if search:match('\038') then
  44. if self:FindIntersectSearch(itemLink, strsplit('\038', search)) then
  45. return true
  46. end
  47. else
  48. if self:FindIntersectSearch(itemLink, search) then
  49. return true
  50. end
  51. end
  52. end
  53. end
  54. return false
  55. end
  56. --[[ intersect search: <search>|<search> ]]--
  57. function ItemSearch:FindIntersectSearch(itemLink, ...)
  58. for i = 1, select('#', ...) do
  59. local search = select(i, ...)
  60. if search and search ~= '' then
  61. if not self:FindNegatableSearch(itemLink, search) then
  62. return false
  63. end
  64. end
  65. end
  66. return true
  67. end
  68. --[[ negated search: !<search> ]]--
  69. function ItemSearch:FindNegatableSearch(itemLink, search)
  70. local negatedSearch = search:match('^\033(.+)$')
  71. if negatedSearch then
  72. return not self:FindTypedSearch(itemLink, negatedSearch)
  73. end
  74. return self:FindTypedSearch(itemLink, search)
  75. end
  76. --[[
  77. typed search:
  78. user defined search types
  79. A typed search object should look like the following:
  80. {
  81. string id
  82. unique identifier for the search type,
  83. string searchCapture = function isSearch(self, search)
  84. returns a capture if the given search matches this typed search
  85. returns nil if the search is not a match for this type
  86. bool isMatch = function findItem(self, itemLink, searchCapture)
  87. returns true if <itemLink> is in the search defined by <searchCapture>
  88. }
  89. --]]
  90. local typedSearches = {}
  91. function ItemSearch:RegisterTypedSearch(typedSearchObj)
  92. typedSearches[typedSearchObj.id] = typedSearchObj
  93. end
  94. function ItemSearch:GetTypedSearches()
  95. return pairs(typedSearches)
  96. end
  97. function ItemSearch:GetTypedSearch(id)
  98. return typedSearches[id]
  99. end
  100. function ItemSearch:FindTypedSearch(itemLink, search)
  101. if not search then
  102. return false
  103. end
  104. for id, searchInfo in self:GetTypedSearches() do
  105. local capture1, capture2, capture3 = searchInfo:isSearch(search)
  106. if capture1 then
  107. return searchInfo:findItem(itemLink, capture1, capture2, capture3)
  108. end
  109. end
  110. return self:GetTypedSearch('itemTypeGeneric'):findItem(itemLink, search) or self:GetTypedSearch('itemName'):findItem(itemLink, search)
  111. end
  112. --[[
  113. Basic typed searches
  114. --]]
  115. function ItemSearch:Compare(op, lhs, rhs)
  116. --ugly, but it works
  117. if op == ':' or op == '=' or op == '==' then
  118. return lhs == rhs
  119. end
  120. if op == '!=' or op == '~=' then
  121. return lhs ~= rhs
  122. end
  123. if op == '<=' then
  124. return lhs <= rhs
  125. end
  126. if op == '<' then
  127. return lhs < rhs
  128. end
  129. if op == '>' then
  130. return lhs > rhs
  131. end
  132. if op == '>=' then
  133. return lhs >= rhs
  134. end
  135. return false
  136. end
  137. --[[ basic text search n:(.+) ]]--
  138. local function search_IsInText(search, ...)
  139. for i = 1, select('#', ...) do
  140. local text = select(i, ...)
  141. text = text and tostring(text):lower()
  142. if text and (text == search or text:match(search)) then
  143. return true
  144. end
  145. end
  146. return false
  147. end
  148. ItemSearch:RegisterTypedSearch{
  149. id = 'itemName',
  150. isSearch = function(self, search)
  151. return search and search:match('^n:(.+)$')
  152. end,
  153. findItem = function(self, itemLink, search)
  154. local itemName = (GetItemInfo(itemLink))
  155. return search_IsInText(search, itemName)
  156. end
  157. }
  158. --[[ item type,subtype,equip loc search t:(.+) ]]--
  159. ItemSearch:RegisterTypedSearch{
  160. id = 'itemTypeGeneric',
  161. isSearch = function(self, search)
  162. return search and search:match('^t:(.+)$')
  163. end,
  164. findItem = function(self, itemLink, search)
  165. local name, link, quality, iLevel, reqLevel, type, subType, maxStack, equipSlot = GetItemInfo(itemLink)
  166. if not name then
  167. return false
  168. end
  169. return search_IsInText(search, type, subType, _G[equipSlot])
  170. end
  171. }
  172. --[[ item quality search: q(sign)(%d+) | q:(qualityName) ]]--
  173. ItemSearch:RegisterTypedSearch{
  174. id = 'itemQuality',
  175. isSearch = function(self, search)
  176. if search then
  177. return search:match('^q([%~%:%<%>%=%!]+)(%w+)$')
  178. end
  179. end,
  180. descToQuality = function(self, desc)
  181. local q = 0
  182. local quality = _G['ITEM_QUALITY' .. q .. '_DESC']
  183. while quality and quality:lower() ~= desc do
  184. q = q + 1
  185. quality = _G['ITEM_QUALITY' .. q .. '_DESC']
  186. end
  187. if quality then
  188. return q
  189. end
  190. end,
  191. findItem = function(self, itemLink, op, search)
  192. local name, link, quality = GetItemInfo(itemLink)
  193. if not name then
  194. return false
  195. end
  196. local num = tonumber(search) or self:descToQuality(search)
  197. return num and ItemSearch:Compare(op, quality, num) or false
  198. end,
  199. }
  200. --[[ item level search: lvl(sign)(%d+) ]]--
  201. ItemSearch:RegisterTypedSearch{
  202. id = 'itemLevel',
  203. isSearch = function(self, search)
  204. if search then
  205. return search:match('^ilvl([:<>=!]+)(%d+)$')
  206. end
  207. end,
  208. findItem = function(self, itemLink, op, search)
  209. local name, link, quality, iLvl = GetItemInfo(itemLink)
  210. if not iLvl then
  211. return false
  212. end
  213. local num = tonumber(search)
  214. return num and ItemSearch:Compare(op, iLvl, num) or false
  215. end,
  216. }
  217. --[[ tooltip keyword search ]]--
  218. local tooltipCache = setmetatable({}, {__index = function(t, k) local v = {} t[k] = v return v end})
  219. local tooltipScanner = _G['LibItemSearchTooltipScanner'] or CreateFrame('GameTooltip', 'LibItemSearchTooltipScanner', UIParent, 'GameTooltipTemplate')
  220. local function link_FindSearchInTooltip(itemLink, search)
  221. --look in the cache for the result
  222. local itemID = itemLink:match('item:(%d+)')
  223. local cachedResult = tooltipCache[search][itemID]
  224. if cachedResult ~= nil then
  225. return cachedResult
  226. end
  227. --no match?, pull in the resut from tooltip parsing
  228. tooltipScanner:SetOwner(UIParent, 'ANCHOR_NONE')
  229. tooltipScanner:SetHyperlink(itemLink)
  230. local result = false
  231. if tooltipScanner:NumLines() > 1 and _G[tooltipScanner:GetName() .. 'TextLeft2']:GetText() == search then
  232. result = true
  233. elseif tooltipScanner:NumLines() > 2 and _G[tooltipScanner:GetName() .. 'TextLeft3']:GetText() == search then
  234. result = true
  235. end
  236. tooltipScanner:Hide()
  237. tooltipCache[search][itemID] = result
  238. return result
  239. end
  240. ItemSearch:RegisterTypedSearch{
  241. id = 'tooltip',
  242. isSearch = function(self, search)
  243. return self.keywords[search]
  244. end,
  245. findItem = function(self, itemLink, search)
  246. return search and link_FindSearchInTooltip(itemLink, search)
  247. end,
  248. keywords = {
  249. ['boe'] = ITEM_BIND_ON_EQUIP,
  250. ['bop'] = ITEM_BIND_ON_PICKUP,
  251. ['bou'] = ITEM_BIND_ON_USE,
  252. ['quest'] = ITEM_BIND_QUEST,
  253. ['boa'] = ITEM_BIND_TO_ACCOUNT
  254. }
  255. }
  256. ItemSearch:RegisterTypedSearch{
  257. id = 'tooltipDesc',
  258. isSearch = function(self, search)
  259. return search and search:match('^tt:(.+)$')
  260. end,
  261. findItem = function(self, itemLink, search)
  262. --no match?, pull in the resut from tooltip parsing
  263. tooltipScanner:SetOwner(UIParent, 'ANCHOR_NONE')
  264. tooltipScanner:SetHyperlink(itemLink)
  265. local i = 1
  266. while i <= tooltipScanner:NumLines() do
  267. local text = _G[tooltipScanner:GetName() .. 'TextLeft' .. i]:GetText():lower()
  268. if text and text:match(search) then
  269. tooltipScanner:Hide()
  270. return true
  271. end
  272. i = i + 1
  273. end
  274. tooltipScanner:Hide()
  275. return false
  276. end,
  277. }
  278. --[[ equipment set search ]]--
  279. local function IsWardrobeLoaded()
  280. local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo('Wardrobe')
  281. return enabled
  282. end
  283. local function findEquipmentSetByName(search)
  284. local startsWithSearch = '^' .. search
  285. local partialMatch = nil
  286. for i = 1, GetNumEquipmentSets() do
  287. local setName = (GetEquipmentSetInfo(i))
  288. local lSetName = setName:lower()
  289. if lSetName == search then
  290. return setName
  291. end
  292. if lSetName:match(startsWithSearch) then
  293. partialMatch = setName
  294. end
  295. end
  296. -- Wardrobe Support
  297. if Wardrobe then
  298. for i, outfit in ipairs( Wardrobe.CurrentConfig.Outfit) do
  299. local setName = outfit.OutfitName
  300. local lSetName = setName:lower()
  301. if lSetName == search then
  302. return setName
  303. end
  304. if lSetName:match(startsWithSearch) then
  305. partialMatch = setName
  306. end
  307. end
  308. end
  309. return partialMatch
  310. end
  311. local function isItemInEquipmentSet(itemLink, setName)
  312. if not setName then
  313. return false
  314. end
  315. local itemIDs = GetEquipmentSetItemIDs(setName)
  316. if not itemIDs then
  317. return false
  318. end
  319. local itemID = tonumber(itemLink:match('item:(%d+)'))
  320. for inventoryID, setItemID in pairs(itemIDs) do
  321. if itemID == setItemID then
  322. return true
  323. end
  324. end
  325. return false
  326. end
  327. local function isItemInWardrobeSet(itemLink, setName)
  328. if not Wardrobe then return false end
  329. local itemName = (GetItemInfo(itemLink))
  330. for i, outfit in ipairs(Wardrobe.CurrentConfig.Outfit) do
  331. if outfit.OutfitName == setName then
  332. for j, item in pairs(outfit.Item) do
  333. if item and (item.IsSlotUsed == 1) and (item.Name == itemName) then
  334. return true
  335. end
  336. end
  337. end
  338. end
  339. return false
  340. end
  341. ItemSearch:RegisterTypedSearch{
  342. id = 'equipmentSet',
  343. isSearch = function(self, search)
  344. return search and search:match('^s:(.+)$')
  345. end,
  346. findItem = function(self, itemLink, search)
  347. local setName = findEquipmentSetByName(search)
  348. if not setName then
  349. return false
  350. end
  351. return isItemInEquipmentSet(itemLink, setName)
  352. or isItemInWardrobeSet(itemLink, setName)
  353. end,
  354. }