- nobody wants to write the markup by hand- general-purpose XML editors are too clunky
<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; |
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); } } }