package edu.ysu.itrace; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.ProgressEvent; import org.eclipse.swt.browser.ProgressListener; /** * Keeps updated information about the stack overflow document viewed in one Browser. */ public class SOManager { /** * Types of stack overflow entities. */ public enum SOEType { TEXT, CODE, COMMENT, TITLE, //for Question part only TAG, //for Question part only VOTE, IMAGE } public enum SOEPart { ANSWER, QUESTION } /** * Information extracted about a stack overflow entity. */ public class StackOverflowEntity { public SOEPart part; public SOEType type; public int partNum; //number of part (ie. Answer number 1, Answer number 2), Question part is always 1 public int typeNum; //number of type (ie. Comment number 1, Comment number 2), Vote, and Title will always be 1 //numbering is by the first element to be seen in the html code } private Browser browser; /** * Constructor. Sets up the Browser with a progress listener. * @param browser Browser to which this SO DOM pertains. */ public SOManager(Browser browser) { this.browser = browser; /* * add the progress listener with a JavaScript function * to find the SOE at a specified location */ addSOEFinder(); } /** * Returns a string representation of the URL to the * Stack Overflow question page associated with the current browser. */ public String getURL() { return browser.getUrl(); } /** * Returns a string representation of the title of the * Stack Overflow web page */ public String getTitle() { return (String) browser.evaluate("if (typeof findSOGaze == 'function') {" + "try {" + "var title = document.getElementsByTagName('title');" + "return title[0].textContent;" + "} catch(err) {" + "return err.message;" + "}" + "}"); } /** * Gets the stack overflow entity found at a location on the page. * @param relativeX x gaze coordinate relative to the control browser. * @param relativeY y gaze coordinate relative to the control browser. * @return the stack overflow entity */ public StackOverflowEntity getSOE(int relativeX, int relativeY) { StackOverflowEntity entity = new StackOverflowEntity(); //call JavaScript with relativeX and relativeY to map the x,y position to its SOE String soe = (String) browser.evaluate( "if (typeof findSOGaze == 'function') {" + "return findSOGaze(" + relativeX + "," + relativeY +");" + "}"); //create the soe based on the returned string soe if (soe != null) { if (soe.contains("question image")) { entity.part = SOEPart.QUESTION; entity.type = SOEType.IMAGE; entity.partNum = 1; entity.typeNum = Integer.parseInt(soe.split("-")[1])+1; return entity; } if (soe.contains("question text")) { entity.part = SOEPart.QUESTION; entity.type = SOEType.TEXT; entity.partNum = 1; entity.typeNum = Integer.parseInt(soe.split("-")[1])+1; return entity; } if (soe.contains("question code")) { entity.part = SOEPart.QUESTION; entity.type = SOEType.CODE; entity.partNum = 1; entity.typeNum = Integer.parseInt(soe.split("-")[1])+1; return entity; } if (soe.contains("question title")) { entity.part = SOEPart.QUESTION; entity.type = SOEType.TITLE; entity.partNum = 1; entity.typeNum = 1; return entity; } if (soe.contains("question tag")) { entity.part = SOEPart.QUESTION; entity.type = SOEType.TAG; entity.partNum = 1; entity.typeNum = Integer.parseInt(soe.split("-")[1])+1; return entity; } if (soe.contains("question vote")) { entity.part = SOEPart.QUESTION; entity.type = SOEType.VOTE; entity.partNum = 1; entity.typeNum = 1; return entity; } if (soe.contains("question comment")) { entity.part = SOEPart.QUESTION; entity.type = SOEType.COMMENT; entity.partNum = 1; entity.typeNum = Integer.parseInt(soe.split("-")[1])+1; return entity; } if (soe.contains("answer image")) { entity.part = SOEPart.ANSWER; entity.type = SOEType.IMAGE; entity.partNum = Integer.parseInt(soe.split("-")[1])+1; entity.typeNum = Integer.parseInt(soe.split("-")[2])+1; return entity; } if (soe.contains("answer text")) { entity.part = SOEPart.ANSWER; entity.type = SOEType.TEXT; entity.partNum = Integer.parseInt(soe.split("-")[1])+1; entity.typeNum = Integer.parseInt(soe.split("-")[2])+1; return entity; } if (soe.contains("answer code")) { entity.part = SOEPart.ANSWER; entity.type = SOEType.CODE; entity.partNum = Integer.parseInt(soe.split("-")[1])+1; entity.typeNum = Integer.parseInt(soe.split("-")[2])+1; return entity; } if (soe.contains("answer comment")) { entity.part = SOEPart.ANSWER; entity.type = SOEType.COMMENT; entity.partNum = Integer.parseInt(soe.split("-")[1])+1; entity.typeNum = Integer.parseInt(soe.split("-")[2])+1; return entity; } if (soe.contains("answer vote")) { entity.part = SOEPart.ANSWER; entity.type = SOEType.VOTE; entity.partNum = Integer.parseInt(soe.split("-")[1])+1; entity.typeNum = 1; return entity; } } return null; } /** * Set up a page load listener to declare a JavaScript function for finding the SOE * at a specified position on every new page load */ private void addSOEFinder() { browser.addProgressListener(new ProgressListener() { @Override public void changed(ProgressEvent event) { } @Override public void completed(ProgressEvent event) { browser.execute( "function foundSOGaze(x, y, bounds) {" + "return (y > bounds.bottom+10 || y < bounds.top-10 || x < bounds.left-10 || x > bounds.right+10) ? false:true;" + "}" + "function findSOGaze(x,y) {" + "try {" + "var question = document.getElementById('question');" + "var qPostText = question.getElementsByClassName('post-text');" + "var i;" + "var qCode = qPostText[0].getElementsByTagName('code');" + "for (i = 0; i < qCode.length; i++) {" + "var found = foundSOGaze(x, y, qCode[i].getBoundingClientRect());" + "if (found == true) return 'question code' + '-' + i;" + "}" + "var qImage = qPostText[0].getElementsByTagName('img');" + "for (i = 0; i < qImage.length; i++) {" + "var found = foundSOGaze(x, y, qImage[i].getBoundingClientRect());" + "if (found == true) return 'question image' + '-' + i;" + "}" + "var qText = qPostText[0].querySelectorAll('p, ol, ul, dl, h1, h2, h3, h4, h5, h6');" + "for (i = 0; i < qText.length; i++) {" + "var found = foundSOGaze(x, y, qText[i].getBoundingClientRect());" + "if (found == true) return 'question text' + '-' + i;" + "}" + "var qTags = question.getElementsByClassName('post-tag');" + "for (i = 0; i < qTags.length; i++) {" + "var found = foundSOGaze(x, y, qTags[i].getBoundingClientRect());" + "if (found == true) return 'question tag' + '-' + i;" + "}" + "var qVote = question.getElementsByClassName('vote');" + "found = foundSOGaze(x, y, qVote[0].getBoundingClientRect());" + "if (found == true) return 'question vote';" + "var qHeader = document.getElementById('question-header');" + "var qTitle = qHeader.getElementsByTagName('h1');" + "found = foundSOGaze(x, y, qTitle[0].getBoundingClientRect());" + "if (found == true) return 'question title';" + "var qComment = question.getElementsByClassName('comment-text');" + "for (i = 0; i < qComment.length; i++) {" + "var found = foundSOGaze(x, y, qComment[i].getBoundingClientRect());" + "if (found == true) return 'question comment' + '-' + i;" + "}" + "var answers = document.getElementById('answers');" + "if (answers == null) return null;" + "var aVotes = answers.getElementsByClassName('vote');" + "for (i = 0; i < aVotes.length; i++) {" + "var found = foundSOGaze(x, y, aVotes[i].getBoundingClientRect());" + "if (found == true) return 'answer vote' + '-' + i;" + "}" + "var aPostText = answers.getElementsByClassName('post-text');" + "for (i = 0; i < aPostText.length; i++) {" + "var aImage = aPostText[i].getElementsByTagName('img');" + "var aText = aPostText[i].querySelectorAll('p, ol, ul, dl, h1, h2, h3, h4, h5, h6');" + "var aCode = aPostText[i].getElementsByTagName('code');" + "var j;" + "for (j = 0; j < aCode.length; j++) {" + "var found = foundSOGaze(x, y, aCode[j].getBoundingClientRect());" + "if (found == true) return 'answer code' + '-' + i + '-' + j;" + "}" + "for (j = 0; j < aImage.length; j++) {" + "var found = foundSOGaze(x, y, aImage[j].getBoundingClientRect());" + "if (found == true) return 'answer image' + '-' + i + '-' + j;" + "}" + "for (j = 0; j < aText.length; j++) {" + "var found = foundSOGaze(x, y, aText[j].getBoundingClientRect());" + "if (found == true) return 'answer text' + '-' + i + '-' + j;" + "}" + "}" + "var answerComments = answers.getElementsByClassName('comments');" + "for (i = 0; i < answerComments.length; i++) {" + "var aComments = answerComments[i].getElementsByClassName('comment-text');" + "for (var j = 0; j < aComments.length; j++) {" + "var found = foundSOGaze(x, y, aComments[j].getBoundingClientRect());" + "if (found == true) return 'answer comment' + '-' + i + '-' + j;" + "}" + "}" + "} catch(err) {" + "return err.message;" + "}" + "}"); } }); } }