In the
first part of this series an chart was made to show a graph on a grid.
In this part the content for the page is extracted from the page
www.europeantour.com.Before the tournament data (which we eventually want) can be fetched, first the schedule of a year is fetched from the homepage. JavaFX does have a class for getting http requests, but doesnt have one for the parsing of html, so the
htmlparser was used. In the end it was easier to do all the html work in Java and only the presentation in JavaFX.
A method that uses an xpath expression, a tagvalue and an attributename does the work of getting the url for the schedule page. The xpathexpression was used as a more general way of specifying which element would be needed, but in the end only the element name is used.
/**
* Returns the value of the requested attribute of the requested node.
*
* @param xpath The xPath expression to the requested node, including the nodename.
* @param tagvalue The value of the requested node.
* @param attributeName The attributename of the requested attribute of the requested node.
* @return The value of the requested attribute of the requested node.
* @throws javax.swing.text.BadLocationException
*/
public String getAttributeValue(String xpath, String tagvalue, String attributeName) throws BadLocationException {
String attributeValue = null;
try {
NodeList list = new NodeList ();
String tagName = xpath.substring(xpath.lastIndexOf("/") + 1);
// System.out.println("tagName = " + tagName);
NodeFilter filter = new AndFilter(new NodeFilter[] {
new TagNameFilter(tagName),
new HasChildFilter(new RegexFilter(tagvalue, RegexFilter.MATCH)),
new HasAttributeFilter(attributeName)
});
for (NodeIterator e = parser.elements (); e.hasMoreNodes ();) {
e.nextNode ().collectInto (list, filter);
}
// System.out.println("list size = " + list.size());
if(list.size() > 0) {
TagNode node = (TagNode)list.elementAt(0);
attributeValue = node.getAttribute(attributeName);
}
} catch(ParserException px) {
throw new BadLocationException(xpath, 0);
}
return attributeValue;
}
The AndFilter does all the hard work for us, it is a nice way of finding the right element. From that we get the value of the 'href' attribute.
The fetching of the schedule (list of tournaments) is mostly the same. Except that a list is needed and that it must be from a childelement of the elements we look for (A 'TD' element with css class 'tournNameCell' and child element 'a' which contains the name of the tournament)
/**
* Gets a list of values for all elements that have the css class type given and a child element
* with the specified name. The value is taken from the child element.
*
* @param xpath
* @param classvalue
* @param childElementName
* @return
* @throws javax.swing.text.BadLocationException
*/
public List getElementValuesForClass(String xpath, String classvalue, String childElementName) throws BadLocationException {
List tournamentNames = new ArrayList();
try {
NodeList list = new NodeList ();
String tagName = xpath.substring(xpath.lastIndexOf("/") + 1);
// System.out.println("tagName = " + tagName);
NodeFilter filter = new AndFilter(new NodeFilter[] {
new TagNameFilter(tagName),
new HasAttributeFilter("class", classvalue)
});
for (NodeIterator e = parser.elements (); e.hasMoreNodes ();) {
e.nextNode ().collectInto (list, filter);
}
// System.out.println("list size = " + list.size());
for (int i = 0; i < list.size(); i=i+2) {
TagNode node = (TagNode)list.elementAt(i);
NodeList childList = new NodeList();
NodeFilter childFilter = new TagNameFilter(childElementName);
for (NodeIterator e = node.getChildren().elements(); e.hasMoreNodes();) {
e.nextNode().collectInto(childList, childFilter);
}
TagNode childNode = (TagNode)childList.elementAt(0);
String tournamentLink = childNode.getText();
NodeList textChildList = new NodeList();
NodeFilter textChildFilter = new NodeClassFilter(TextNode.class);
for (NodeIterator e = childNode.getChildren().elements(); e.hasMoreNodes();) {
e.nextNode().collectInto(textChildList, textChildFilter);
}
String tournamentName = textChildList.elementAt(0).getText();
tournamentNames.add(tournamentName);
// System.out.println("list item = " + tournamentName);
}
} catch(ParserException px) {
throw new BadLocationException(xpath, 0);
}
return tournamentNames;
}
The method in the EuropeanTour class puts it all together.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
/**
*
* @author André Hogenkamp
*/
public class EuropeanTour {
private static final String EUROPEANTOUR_URL = "http://www.europeantour.com/";
/**
* returns a schedule from the european tour as a String array.
*
* @return
*/
public String[] getSchedule() {
List schedule = new ArrayList();
try {
HtmlParser parser = new HtmlParser(EUROPEANTOUR_URL);
parser.parse();
String scheduleUrl = parser.getAttributeValue("/html/body/table[1]/tr[2]/td/ul/li[6]/ul/li[4]/a", "Schedule", "href");
// System.out.println("scheduleUrl:" + scheduleUrl);
if(scheduleUrl != null) {
parser = new HtmlParser(scheduleUrl);
parser.parse();
schedule = parser.getElementValuesForClass("/html/body/table/tr[4]/td/table/tr/td[2]/table/tr/td/div/div/table/tr/td", "tournNameCell", "a");
// System.out.println("schedule length:" + schedule.size());
}
} catch (IOException ex) {
Logger.getLogger(HtmlParser.class.getName()).log(Level.SEVERE, null, ex);
} catch (BadLocationException blx) {
Logger.getLogger(HtmlParser.class.getName()).log(Level.SEVERE, null, blx);
}
String[] scheduleArray = new String[schedule.size()];
return (String[]) schedule.toArray(scheduleArray);
}
}
Now the presentation part.
The main page is just a simple one for now. It shows a list of years (the idea is to get the schedule for that year, but i don't know if that wil work, since the europeantour page does not have that functionality :-( ) and the schedule for that year (2009 for now)
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
/**
* @author André Hogenkamp
*/
var schedule = Schedule {
year: "2009";
};
Stage {
title: "Golf scores"
width: 500
height: 800
scene: Scene {
fill: Color.BLACK
content:
[
VBox {
spacing: 5
content:
[
Text {
content: "2009 2008"
fill: Color.WHITE
font: Font.font("Helvetica", FontWeight.BOLD, 16)
y: 16
}
HBox {
spacing: 5
translateY: 50
content: [
schedule
]
}
]
}
]
}
}
The VBox and HBox should space the containing nodes, but this does not seem to work. Maybe the used custom nodes are not complete yet.
The schedule class formats the schedule for printing on the screen. A Text node with 'Schedule' and the year followed by the list of tournaments. Content was a Text[] since the tournamet list contains only Text nodes, but was changed to a Node[] to make a group of the Text nodes and be able to change tre position.
import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.text.Text;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import nl.tikal.sport.golf.net.EuropeanTourFx;
/**
* @author André Hogenkamp
*/
public class Schedule extends CustomNode {
public var year: String = "2009";
var content: Node[];
def tour: EuropeanTourFx = EuropeanTourFx {
}
/**
* The grid contains lines to form a grid.
*/
var schedule : Group = Group {
content: bind [
Text {
content: "Schedule {year}"
fill: Color.WHITE
font: Font.font("Helvetica", FontWeight.BOLD, 16)
}
Group {
content: content
translateY: 20
}
]
}
public function getSchedule(year:String):Node[] {
content = tour.getTournaments();
}
override function create() : Node {
getSchedule(year);
schedule;
}
}
The list is filled from the EuropeanTourFx.getTournaments() method, which get its information from the java class EuropeanTour.
import javafx.scene.text.Text;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import java.lang.System;
/**
* @author André Hogenkamp
*/
public class EuropeanTourFx {
var tournaments: Text[];
var fontSize = 12;
var counter = -1;
public function getTournaments() :Text[] {
var europeanTour = new EuropeanTour();
if(tournaments == null) {
var schedule = europeanTour.getSchedule();
tournaments = [
for (tournament in schedule) {
counter++;
Text {
content: tournament;
fill: Color.YELLOW
font: Font {
size: fontSize
}
y: counter * fontSize
};
}
];
} else {
tournaments = [];
}
}
The for loop returns the last element therefore the counter++ is the first expression and the counter is initialised at -1.
When run it looks like

It is still basic, but it does what it should do.