A typical Java application is a domain-specific XML editor:
- nobody wants to write the markup by hand
- general-purpose XML editors are too clunky
We generalize the business card language to allow collections of business cards:
<cards> <card> <name>John Doe</name> <title>CEO, Widget Inc.</title> <email>john.doe@widget.com</email> <phone>(202) 456-1414</phone> <logo url="widget.gif" /> </card> <card> <name>Michael Schwartzbach</name> <title>Associate Professor</title> <email>mis@brics.dk</email> <phone>+45 8610 8790</phone> <logo url="http://www.brics.dk/~mis/portrait.gif" /> </card> <card> <name>Anders Møller</name> <title>Research Assistant Professor</title> <email>amoeller@brics.dk</email> <phone>+45 8942 3475</phone> <logo url="http://www.brics.dk/~amoeller/am.jpg"/> </card> </cards> |
We then write a Java program to edit such collections.
First, we need a high-level representation of a business card:
class Card { public String name, title, email, phone, logo; public Card(String name, String title, String email, String phone, String logo) { this.name = name; this.title = title; this.email = email; this.phone = phone; this.logo = logo; } } |
An XML document must then be translated into a vector of such objects:
Vector doc2vector(Document d) { Vector v = new Vector(); Iterator i = d.getRootElement().getChildren().iterator(); while (i.hasNext()) { Element e = (Element)i.next(); String phone = e.getChildText("phone"); if (phone==null) phone=""; Element logo = e.getChild("logo"); String url; if (logo==null) url = ""; else url = logo.getAttributeValue("url"); Card c = new Card(e.getChildText("name"), // exploit schema, e.getChildText("title"), // assume validity e.getChildText("email"), phone, url); v.add(c); } return v; } |
And back into an XML document:
Document vector2doc() { Element cards = new Element("cards"); for (int i=0; i<cardvector.size(); i++) { Card c = (Card)cardvector.elementAt(i); if (c!=null) { Element card = new Element("card"); Element name = new Element("name"); name.addContent(c.name); card.addContent(name); Element title = new Element("title"); title.addContent(c.title); card.addContent(title); Element email = new Element("email"); email.addContent(c.email); card.addContent(email); if (!c.phone.equals("")) { Element phone = new Element("phone"); phone.addContent(c.phone); card.addContent(phone); } if (!c.logo.equals("")) { Element logo = new Element("logo"); logo.setAttribute("url",c.logo); card.addContent(logo); } cards.addContent(card); } } return new Document(cards); } |
A little logic and some GUI then completes the editor:
Compile with: javac -classpath xerces.jar:jdom.jar BCedit.java
This example contains some general observations:
- XML documents are parsed via JDOM into domain-specific data structures
- if the input is known to validate according to some schema, then many runtime errors can be assumed never to occur
- how do we ensure that the output of vector2doc is valid according to the schema? (well-formedness is for free)
- that's a current research challenge! import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
class Card {
public String name, title, email, phone, logo;
public Card(String name, String title, String email, String phone, String logo) {
this.name = name;
this.title = title;
this.email = email;
this.phone = phone;
this.logo = logo;
}
}
public class BCedit extends Frame implements ActionListener {
Button ok = new Button("ok");
Button delete = new Button("delete");
Button clear = new Button("clear");
Button save = new Button("save");
Button quit = new Button("quit");
TextField name = new TextField(20);
TextField title = new TextField(20);
TextField email = new TextField(20);
TextField phone = new TextField(20);
TextField logo = new TextField(20);
Panel cardpanel = new Panel(new GridLayout(0,1));
String cardfile;
Vector cardvector;
int current = -1;
public static void main(String[] args) { new BCedit(args[0]); }
Vector doc2vector(Document d) {
Vector v = new Vector();
Iterator i = d.getRootElement().getChildren().iterator();
while (i.hasNext()) {
Element e = (Element)i.next();
String phone = e.getChildText("phone");
if (phone==null) phone="";
Element logo = e.getChild("logo");
String url;
if (logo==null) url=""; else url=logo.getAttributeValue("url");
Card c = new Card(e.getChildText("name"),
e.getChildText("title"),
e.getChildText("email"),
phone,
url);
v.add(c);
}
return v;
}
Document vector2doc() {
Element cards = new Element("cards");
for (int i=0; i<cardvector.size(); i++) {
Card c = (Card)cardvector.elementAt(i);
if (c!=null) {
Element card = new Element("card");
Element name = new Element("name");
name.addContent(c.name);
card.addContent(name);
Element title = new Element("title");
title.addContent(c.title);
card.addContent(title);
Element email = new Element("email");
email.addContent(c.email);
card.addContent(email);
if (!c.phone.equals("")) {
Element phone = new Element("phone");
phone.addContent(c.phone);
card.addContent(phone);
}
if (!c.logo.equals("")) {
Element logo = new Element("logo");
logo.setAttribute("url",c.logo);
card.addContent(logo);
}
cards.addContent(card);
}
}
return new Document(cards);
}
void addCards() {
cardpanel.removeAll();
for (int i=0; i<cardvector.size(); i++) {
Card c = (Card)cardvector.elementAt(i);
if (c!=null) {
Button b = new Button(c.name);
b.setActionCommand(String.valueOf(i));
b.addActionListener(this);
cardpanel.add(b);
}
}
this.pack();
}
public BCedit(String cardfile) {
super("BCedit");
this.cardfile=cardfile;
try {
cardvector = doc2vector(new SAXBuilder().build(new File(cardfile)));
} catch (Exception e) {e.printStackTrace();}
this.setLayout(new BorderLayout());
ScrollPane s = new ScrollPane();
s.setSize(200,0);
s.add(cardpanel);
this.add(s,BorderLayout.WEST);
Panel l = new Panel(new GridLayout(5,1));
l.add(new Label("Name"));
l.add(new Label("Title"));
l.add(new Label("Email"));
l.add(new Label("Phone"));
l.add(new Label("Logo"));
this.add(l,BorderLayout.CENTER);
Panel f = new Panel(new GridLayout(5,1));
f.add(name);
f.add(title);
f.add(email);
f.add(phone);
f.add(logo);
this.add(f,BorderLayout.EAST);
Panel p = new Panel();
ok.addActionListener(this);
p.add(ok);
delete.addActionListener(this);
p.add(delete);
clear.addActionListener(this);
p.add(clear);
save.addActionListener(this);
p.add(save);
quit.addActionListener(this);
p.add(quit);
this.add(p,BorderLayout.SOUTH);
addCards();
this.show();
}
public void actionPerformed(ActionEvent event) {
Card c;
String command = event.getActionCommand();
if (command.equals("ok")) {
c = new Card(name.getText(),
title.getText(),
email.getText(),
phone.getText(),
logo.getText());
if (current==-1) {
cardvector.add(c);
} else {
cardvector.setElementAt(c,current);
}
addCards();
} else if (command.equals("delete")) {
if (current!=-1) {
cardvector.setElementAt(null,current);
addCards();
}
} else if (command.equals("clear")) {
current = -1;
name.setText("");
title.setText("");
email.setText("");
phone.setText("");
logo.setText("");
} else if (command.equals("save")) {
try {
new XMLOutputter().output(vector2doc(),new FileOutputStream(cardfile));
} catch (Exception e) {e.printStackTrace();}
} else if (command.equals("quit")) {
System.exit(0);
} else {
current = Integer.parseInt(command);
c = (Card)cardvector.elementAt(current);
name.setText(c.name);
title.setText(c.title);
email.setText(c.email);
phone.setText(c.phone);
logo.setText(c.logo);
}
}
}