NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name RetailPath product analysis // @namespace http://tampermonkey.net/ // @version 0.4.3 // @description Analyse the positions order and derive more information // @author fmm // @match https://metcash.retailpath.com.au/* // @license MIT // @updateURL https://openuserjs.org/meta/fmm/RetailPath_product_analysis.meta.js // ==/UserScript== function go() { 'use strict'; var aTags = document.getElementsByTagName("tr"); var searchText = "Positions Order"; for (var i = 0; i < aTags.length; i++) { if (aTags[i].textContent.includes(searchText)) { var found, index, description, foundIndex; var columnBeforeRaw = []; var columnAfterRaw = []; var columnBefore = []; var columnAfter = []; found = aTags[i]; var nextRow = found.nextElementSibling; var columns = nextRow.getElementsByClassName('textChange'); // Asterisks break the script so remove them from the raw data columnBeforeRaw = columns[0].innerHTML.replace(/<\/?span[^>]*>/g,"").replace(/\*+/g,"").trim().split(/\<br\>(?=\d+\s\-)/g); columnAfterRaw = columns[1].innerHTML.replace(/<\/?span[^>]*>/g,"").replace(/\*+/g,"").trim().split(/\<br\>(?=\d+\s\-)/g); // Replace the actual HTML columns[0].innerHTML = columns[0].innerHTML.replace(/<\/?span[^>]*>/g,"").replace(/\<br\>(?!\d+\s\-)/g,"").trim(); columns[1].innerHTML = columns[1].innerHTML.replace(/<\/?span[^>]*>/g,"").replace(/\<br\>(?!\d+\s\-)/g,"").trim(); // build arrays // Extra <br> and newlines cause null entries so remove those too for (var b = 0; b < columnBeforeRaw.length; b++) { index = columnBeforeRaw[b].match(/^\d\d?/g); description = columnBeforeRaw[b].replace(/\d\d? \- /g,""); description = escapeRegex(description); if (index !== null) { columnBefore.push({'index': index, 'description': description.replace(/\<br\>|\n|\r/g, " ").replace(/\s+$/g, "")}); // remove any inner <br> and newline chars, and trailing whitespace } } for (var a = 0; a < columnAfterRaw.length; a++) { index = columnAfterRaw[a].match(/^\d\d?/g); description = columnAfterRaw[a].replace(/\d\d? \- /g,""); description = escapeRegex(description); if (index !== null) { columnAfter.push({'index': index, 'description': description.replace(/\<br\>|\n|\r/g, " ").replace(/\s+$/g, "")}); // remove any inner <br> and newline chars, and trailing whitespace } } // find moved items for (var z = 0; z < columnBefore.length; z++) { for (var y = 0; y < columnAfter.length; y++) { if (columnBefore[z].description === columnAfter[y].description) { if (parseInt(columnBefore[z].index) !== parseInt(columnAfter[y].index)) { var re = new RegExp(columnAfter[y].description, "g"); columns[1].innerHTML = columns[1].innerHTML.replace(re, "<span style='letter-spacing: 0.05em; font-weight: bold; color: white; background-color: DodgerBlue; padding: 1px 3px;'>MOVED</span> $&"); columnBefore[z][status] = 'moved'; columnAfter[y][status] = 'moved'; } } } } // find edited items var re = new RegExp(/\sselected varieties/, "ig"); var testValue; for (var e = 0; e < columnAfter.length; e++) { for (var d = 0; d < columnBefore.length; d++) { console.log(columnAfter[e].description.match(re) === columnBefore[d].description.match(re)); if (columnAfter[e].description.match(re) === columnBefore[d].description.match(re)) { testValue = testCosineSimilarity(columnAfter[e].description.replace(re, ""), columnBefore[d].description.replace(re, "")); } else { testValue = testCosineSimilarity(columnAfter[e].description, columnBefore[d].description); } //var testValue = testCosineSimilarity(columnAfter[e].description.replace(/\sselected varieties/ig,""), columnBefore[d].description.replace(/\sselected varieties/ig,"")); //var testValue = JaroWrinker(columnAfter[e].description, columnBefore[d].description); //console.log(testValue); console.log(testValue + ": " + columnAfter[e].description + " --- " + columnBefore[d].description); // only mark as edited if high score AND items hasn't moved if (testValue >= 0.6 && testValue < 0.999 && columnAfter[e][status] !== 'moved' && parseInt(columnBefore[d].index) === parseInt(columnAfter[e].index)) { //console.log(testValue + ": " + columnAfter[e].description + " --- " + columnBefore[d].description); var re = new RegExp(columnAfter[e].description, "g"); columns[1].innerHTML = columns[1].innerHTML.replace(re, "<span style='letter-spacing: 0.05em; font-weight: bold; color: white; background-color: Darkorange; padding: 1px 3px;'>EDITED</span> $&"); columnBefore[d][status] = 'edited'; columnAfter[e][status] = 'edited'; //console.log(parseInt(columnBefore[d].index) === parseInt(columnAfter[e].index)); } } } // find new items foundIndex = []; for (var x = 0; x < columnAfter.length; x++) { foundIndex = columnBefore.findIndex(el => el.description === columnAfter[x].description); if (foundIndex == -1 && columnAfter[x][status] !== 'edited') { var re = new RegExp(columnAfter[x].description, "g"); columns[1].innerHTML = columns[1].innerHTML.replace(re, "<span style='letter-spacing: 0.05em; font-weight: bold; color: white; background-color: MediumSeaGreen; padding: 1px 3px;'>ADDED</span> $&"); } } // find deleted items foundIndex = []; for (var g = 0; g < columnBefore.length; g++) { foundIndex = columnAfter.findIndex(el => el.description === columnBefore[g].description); //console.log("found index: " + foundIndex); if (foundIndex === -1 && columnBefore[g][status] !== 'edited') { columns[1].innerHTML += "<br><span style='letter-spacing: 0.05em; font-weight: bold; color: white; background-color: red; padding: 1px 3px;'>DELETED</span> " + columnBefore[g].description; } } //break; } } } // UTILITY FUNCTIONS // Ensures symbols within descriptions wont break regex function escapeRegex(string) { return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); } // Cosine-Similarity.js // https://medium.com/@sumn2u/string-similarity-comparision-in-js-with-examples-4bae35f13968 function termFreqMap(str) { var words = str.split(' '); var termFreq = {}; words.forEach(function(w) { termFreq[w] = (termFreq[w] || 0) + 1; }); return termFreq; } function addKeysToDict(map, dict) { for (var key in map) { dict[key] = true; } } function termFreqMapToVector(map, dict) { var termFreqVector = []; for (var term in dict) { termFreqVector.push(map[term] || 0); } return termFreqVector; } function vecDotProduct(vecA, vecB) { var product = 0; for (var i = 0; i < vecA.length; i++) { product += vecA[i] * vecB[i]; } return product; } function vecMagnitude(vec) { var sum = 0; for (var i = 0; i < vec.length; i++) { sum += vec[i] * vec[i]; } return Math.sqrt(sum); } function cosineSimilarity(vecA, vecB) { return vecDotProduct(vecA, vecB) / (vecMagnitude(vecA) * vecMagnitude(vecB)); } testCosineSimilarity = function textCosineSimilarity(strA, strB) { var termFreqA = termFreqMap(strA); var termFreqB = termFreqMap(strB); var dict = {}; addKeysToDict(termFreqA, dict); addKeysToDict(termFreqB, dict); var termFreqVecA = termFreqMapToVector(termFreqA, dict); var termFreqVecB = termFreqMapToVector(termFreqB, dict); return cosineSimilarity(termFreqVecA, termFreqVecB); } // Jaro-Wrinker.js JaroWrinker = function (s1, s2) { var m = 0; // Exit early if either are empty. if ( s1.length === 0 || s2.length === 0 ) { return 0; } // Exit early if they're an exact match. if ( s1 === s2 ) { return 1; } var range = (Math.floor(Math.max(s1.length, s2.length) / 2)) - 1, s1Matches = new Array(s1.length), s2Matches = new Array(s2.length); for ( i = 0; i < s1.length; i++ ) { var low = (i >= range) ? i - range : 0, high = (i + range <= s2.length) ? (i + range) : (s2.length - 1); for ( j = low; j <= high; j++ ) { if ( s1Matches[i] !== true && s2Matches[j] !== true && s1[i] === s2[j] ) { ++m; s1Matches[i] = s2Matches[j] = true; break; } } } // Exit early if no matches were found. if ( m === 0 ) { return 0; } // Count the transpositions. var k = n_trans = 0; for ( i = 0; i < s1.length; i++ ) { if ( s1Matches[i] === true ) { for ( j = k; j < s2.length; j++ ) { if ( s2Matches[j] === true ) { k = j + 1; break; } } if ( s1[i] !== s2[j] ) { ++n_trans; } } } var weight = (m / s1.length + m / s2.length + (m - (n_trans / 2)) / m) / 3, l = 0, p = 0.1; if ( weight > 0.7 ) { while ( s1[l] === s2[l] && l < 4 ) { ++l; } weight = weight + l * p * (1 - weight); } return weight; } // GO go();