Here’s the User class and its collaborators as it is right now. It is a bit more evolved than its original form : when I first wrote it all of the logic was in the User itself as I had no need to evaluate Javascript outside of html, later I separated the two responsibilities (parsing xml/html and evaluating Javascript) since I had a need to evaluate Javascript no matter where.
public class User {
private final JavaScript javaScript = new JavaScript();
private final Result result = new Result();
public User() {
javaScript.evaluateFile("browser.js");
}
public User lookAt(String htmlPage) {
JavaScriptSource source = new XomJavaScriptSource(htmlPage);
source.evaluateWith(javaScript);
triggerOnLoad();
result.readOutput(javaScript);
return this;
}
public String currentSight() {
return result.nextValue();
}
private void triggerOnLoad() {
javaScript.evaluateScript("window.onload();", "onload");
}
}
And here’s “JavaScript”, which manages everything Rhino-related.
public class JavaScript {
private final Context context;
private final ScriptableObject scope;
public JavaScript() {
context = Context.enter();
scope = context.initStandardObjects();
}
public Object valueOf(String variableName) {
return scope.get(variableName);
}
public void evaluateScript(String script, String scriptName) {
context.evaluateString(scope, script, scriptName, 1, null);
}
public void evaluateScript(String script) {
evaluateScript(script, "script");
}
public void evaluateFile(String sourceFileName) {
try {
context.evaluateReader(scope, read(sourceFileName), sourceFileName, 1, null);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private InputStreamReader read(String sourceFileName) {
return new InputStreamReader(getClass().getClassLoader().getResourceAsStream(sourceFileName));
}
}
This is “Result”, which extracts values from the results array in Javascript.
public class Result {
private NativeArray output = new NativeArray(0);
private int current = 0;
public void readOutput(JavaScript javaScript) {
output = (NativeArray) javaScript.valueOf("output");
}
public String nextValue() {
return (String) output.get(current++);
}
}
Finally, this is the class that hides the fact that scripts are mixed within html.
public class XomJavaScriptSource implements JavaScriptSource {
private final Document document;
public XomJavaScriptSource(String htmlPage) {
document = parsePage(htmlPage);
}
@Override
public void evaluateWith(JavaScript javaScript) {
Nodes scriptNodes = document.query("//script");
for (int i = 0; i < scriptNodes.size(); i++) {
evaluateNode(scriptNodes.get(i), javaScript);
}
}
private final Document parsePage(String htmlPage) {
try {
return new Builder().build(htmlPage, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void evaluateNode(Node scriptNode, JavaScript javaScript) {
if (scriptNode instanceof Element) {
Attribute sourceAttribute = ((Element) scriptNode).getAttribute("src");
if (sourceAttribute != null) {
javaScript.evaluateFile(sourceAttribute.getValue());
return;
}
}
javaScript.evaluateScript(scriptNode.getValue());
}
}
Advertisement