--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/config.xml
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>PIMStuff</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.springframework.ide.eclipse.core.springbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.springframework.ide.eclipse.core.springnature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beansProjectDescription>
+ <version>1</version>
+ <pluginVersion><![CDATA[2.3.2.201003220227-RELEASE]]></pluginVersion>
+ <configSuffixes>
+ <configSuffix><![CDATA[xml]]></configSuffix>
+ </configSuffixes>
+ <enableImports><![CDATA[false]]></enableImports>
+ <configs>
+ </configs>
+ <configSets>
+ </configSets>
+</beansProjectDescription>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="PIMStuff" default="build" basedir=".">
+
+ <property name="src" location="src"/>
+ <property name="build" location="bin"/>
+ <property name="main" value="de.j32.pimstuff.Main"/>
+
+ <target name="makedirs">
+ <mkdir dir="${build}"/>
+ </target>
+
+ <target name="build" depends="makedirs"
+ description="Compile project to ${build} directory">
+ <javac srcdir="${src}" destdir="${build}"/>
+ </target>
+
+ <target name="clean"
+ description="Clean up ${build} directory">
+ <delete><fileset dir="${build}"/></delete>
+ </target>
+
+ <target name="run" depends="build"
+ description="Start main class">
+ <java fork="true" classpath="${build}" classname="${main}"/>
+ </target>
+
+</project>
--- /dev/null
+package de.j32.avmfritz;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.xml.sax.SAXException;
+
+import de.j32.httplib.HttpGETRequest;
+import de.j32.httplib.HttpPOSTRequest;
+import de.j32.httplib.HttpRequest;
+import de.j32.httplib.HttpResponse;
+import de.j32.util.Util;
+
+public class FritzBox
+{
+ String url_;
+ String sid_;
+
+ public FritzBox(String password, String url)
+ throws SAXException, IOException
+ {
+ url_ = url;
+
+ HttpResponse response = null;
+ try {
+ response = httpGet("cgi-bin/webcm")
+ .addParameter("getpage", "../html/login_sid.xml")
+ .execute();
+ LoginXML loginxml = new LoginXML(response);
+ response.close();
+ response = null;
+
+ if (loginxml.iswriteaccess()) {
+ sid_ = loginxml.sid();
+ return;
+ }
+
+ response = httpPost("cgi-bin/webcm")
+ .addParameter("getpage", "../html/login_sid.xml")
+ .addParameter("var:lang", "de")
+ .addParameter("login:command/response",
+ loginxml.response(password)).execute();
+ loginxml = new LoginXML(response);
+ response.close();
+ response = null;
+
+ if (!loginxml.iswriteaccess())
+ throw new RuntimeException("FritzBox login failed");
+
+ sid_ = loginxml.sid();
+ }
+ finally {
+ Util.nothrowClose(response);
+ }
+
+ }
+
+ public InputStream exportAddressbook()
+ throws IOException
+ {
+ return httpPostMultipart("cgi-bin/firmwarecfg")
+ .addParameter("sid", sid_)
+ .addParameter("PhonebookId", "0")
+ .addParameter("PhonebookExportName", "Telefonbuch")
+ .addParameter("PhonebookExport", "").execute();
+ }
+
+ public OutputStream importAddressbook()
+ throws IOException
+ {
+ return new ByteArrayOutputStream() {
+ public void close()
+ throws IOException
+ {
+ System.out.println("sending to fritzbox");
+ httpPostMultipart("cgi-bin/firmwarecfg")
+ .addParameter("sid", sid_)
+ .addParameter("PhonebookId", "0")
+ .addParameter("PhonebookImportFile", toByteArray(), "iso-8859-1")
+ .execute()
+ .close();
+ }
+ };
+ }
+
+ HttpRequest httpGet(String path)
+ {
+ return new HttpGETRequest(url_ + "/" + path);
+ }
+
+ HttpRequest httpPost(String path)
+ {
+ return new HttpPOSTRequest(url_ + "/" + path);
+ }
+
+ HttpRequest httpPostMultipart(String path)
+ {
+ return new HttpPOSTRequest(url_ + "/" + path).setMultipart(true);
+ }
+}
--- /dev/null
+package de.j32.avmfritz;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
+
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import de.j32.util.Util;
+import de.j32.util.XmlUtil;
+
+public class LoginXML
+{
+ Document xml_;
+
+ public LoginXML(InputStream is)
+ throws SAXException, IOException
+ {
+ xml_ = XmlUtil.parse(is);
+ }
+
+ public boolean iswriteaccess()
+ throws SAXException
+ {
+ try {
+ return xml_.getElementsByTagName("iswriteaccess").item(0).getTextContent().equals("1");
+ }
+ catch (NullPointerException e) {
+ throw new SAXException();
+ }
+ }
+
+ public String sid()
+ throws SAXException
+ {
+ try {
+ return Util.nonnull(xml_.getElementsByTagName("SID").item(0).getTextContent());
+ }
+ catch (NullPointerException e) {
+ throw new SAXException();
+ }
+ }
+
+ public String challenge()
+ throws SAXException
+ {
+ try {
+ return Util.nonnull(xml_.getElementsByTagName("Challenge").item(0).getTextContent());
+ }
+ catch (NullPointerException e) {
+ throw new SAXException();
+ }
+ }
+
+ public String response(String password)
+ throws SAXException
+ {
+ try {
+ String c = challenge();
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update((c + "-" + password).getBytes("UTF-16LE"));
+ return c + "-" + new HexBinaryAdapter().marshal(md.digest()).toLowerCase();
+ } catch (NoSuchAlgorithmException e) {
+ // Is it at all feasible for this to happen ?
+ throw new RuntimeException("missing MD5 implementation");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("missing UTF-16LE encoding");
+ }
+ }
+}
--- /dev/null
+package de.j32.httplib;
+
+public class HttpGETRequest
+ extends HttpRequest
+{
+ public HttpGETRequest(String url)
+ {
+ super(url,"GET");
+ }
+
+ @Override
+ public HttpGETRequest addParameter(String name, byte[] value, String encoding)
+ {
+ appendParameter(query(), query().length() == 0, name, value);
+ return this;
+ }
+
+}
--- /dev/null
+package de.j32.httplib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+public class HttpPOSTRequest
+ extends HttpRequest
+{
+ boolean multipart_ = false;
+ static final String separator_ =
+ "----MultiPartFormData--MultiPartFormData--MultiPartFormData----";
+
+ public HttpPOSTRequest(String url)
+ {
+ super(url, "POST");
+ setContentType("application/x-www-form-urlencoded; charset=utf-8");
+ }
+
+ public HttpPOSTRequest setMultipart(boolean flag)
+ {
+ multipart_ = flag;
+ if (multipart_) {
+ setContentType("multipart/form-data; boundary=" + separator_);
+ try {
+ OutputStream body = body();
+ body.write("--".getBytes());
+ body.write(separator_.getBytes());
+ body.write("\r\n".getBytes());
+ }
+ catch (IOException e) {
+ throw new AssertionError("ByteArrayOutputStream throwing IOExcpetion");
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public HttpRequest addParameter(String name, byte[] value, String encoding)
+ {
+ try {
+ if (multipart_) {
+ OutputStream body = body();
+ body.write("Content-Disposition: form-data; name=\"".getBytes());
+ body.write(name.getBytes());
+ body.write("\"\r\n".getBytes());
+ body.write("Content-Type: text/plain; charset=".getBytes());
+ body.write(encoding.getBytes());
+ body.write("\r\n".getBytes());
+ body.write(("Content-Length: " + value.length).getBytes());
+ body.write("\r\n\r\n".getBytes());
+ body.write(value);
+ body.write("\r\n--".getBytes());
+ body.write(separator_.getBytes());
+ body.write("\r\n".getBytes());
+ }
+ else {
+ // Encoding not really relevant here since url-encoding is plain ASCII
+ Writer writer = new OutputStreamWriter(body(),"ascii");
+ appendParameter(writer, body().size() == 0, name, value);
+ writer.flush();
+ }
+ }
+ catch (IOException e) {
+ throw new AssertionError("ByteArrayOutputStream throwing IOExcpetion");
+ }
+ return this;
+ }
+}
--- /dev/null
+package de.j32.httplib;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+
+public abstract class HttpRequest
+{
+ StringBuffer url_;
+ StringBuffer query_ = new StringBuffer();
+ String method_;
+ ByteArrayOutputStream body_ = new ByteArrayOutputStream();
+ String contentType_;
+
+ public HttpRequest(String url, String method)
+ {
+ url_ = new StringBuffer(url);
+ method_ = method;
+ }
+
+ protected StringBuffer query()
+ {
+ return query_;
+ }
+
+ protected ByteArrayOutputStream body()
+ {
+ return body_;
+ }
+
+ protected void setContentType(String c)
+ {
+ contentType_ = c;
+ }
+
+ protected static void appendParameter(Appendable buffer, boolean first, String name, byte[] value)
+ {
+ try {
+ if (! first)
+ buffer.append("&");
+ buffer.append(URLEncoder.encode(name,"utf-8"));
+ buffer.append("=");
+ // We really would need a URLEncoder for byte[] (pre-encoded or raw date)
+ buffer.append(URLEncoder.encode(new String(value,"ascii"),"ascii"));
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new AssertionError("Missing encoding");
+ }
+ catch (IOException e)
+ {
+ throw new AssertionError("IOException on buffer-based Appendable");
+ }
+ }
+
+ public HttpResponse execute()
+ throws MalformedURLException, IOException
+ {
+ if (query_.length() > 0)
+ url_.append("?");
+ url_.append(query_);
+ System.out.println("Opening: " + url_);
+ HttpURLConnection connection =
+ (HttpURLConnection) new URL(new String(url_)).openConnection();
+ System.out.println("Request method: " + method_);
+ connection.setRequestMethod(method_);
+ if (contentType_ != null) {
+ System.out.println("Content-Type: " + contentType_);
+ connection.setRequestProperty("Content-Type", contentType_);
+ connection.setDoOutput(true);
+ System.out.println("Body size: " + body_.size());
+ connection.setFixedLengthStreamingMode(body_.size());
+ System.out.println("Body:");
+ System.out.println(body_.toString());
+ connection.getOutputStream().write(body_.toByteArray());
+ System.out.println("done...");
+ }
+ if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
+ throw new IOException("HTTP request failed: "
+ + connection.getResponseCode() + " " + connection.getResponseMessage());
+ System.out.println("response ok");
+ return new HttpResponse(connection);
+ }
+
+ public HttpRequest addParameter(String name, String value)
+ throws UnsupportedEncodingException
+ {
+ return addParameter(name, value, "utf-8");
+ }
+
+ public HttpRequest addParameter(String name, String value, String encoding)
+ throws UnsupportedEncodingException
+ {
+ return addParameter(name, value.getBytes(encoding), encoding);
+ }
+
+ abstract public HttpRequest addParameter(String name, byte[] value, String encoding);
+}
--- /dev/null
+package de.j32.httplib;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+
+public class HttpResponse
+ extends InputStream
+{
+ HttpURLConnection connection_;
+ InputStream stream_;
+
+ public HttpResponse(HttpURLConnection connection)
+ throws IOException
+ {
+ connection_ = connection;
+ stream_ = connection_.getInputStream();
+ }
+
+ @Override
+ public int read()
+ throws IOException
+ {
+ return stream_.read();
+ }
+
+ @Override
+ public void close()
+ throws IOException
+ {
+ stream_.close();
+ }
+
+}
--- /dev/null
+package de.j32.pimstuff;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import org.xml.sax.SAXException;
+
+import de.j32.avmfritz.FritzBox;
+import de.j32.pimstuff.conduit.FritzAddressbookReader;
+import de.j32.pimstuff.conduit.FritzAddressbookWriter;
+import de.j32.pimstuff.data.Addressbook;
+import de.j32.util.Util;
+
+public class Main {
+
+ public static void main(String[] args)
+ {
+ try {
+
+ System.out.println("Launching pimstuff ...");
+ Properties config = new Properties();
+
+ try {
+ config.loadFromXML(new FileInputStream("config.xml"));
+ }
+ catch (FileNotFoundException e) {
+ config.setProperty("password", "password");
+ config.setProperty("url", "http://fritz.box");
+
+ config.storeToXML(new FileOutputStream("config.xml"), null, "UTF-8");
+ }
+
+ String pw, url;
+
+ try {
+ pw = Util.nonnull(config.getProperty("password"));
+ url = Util.nonnull(config.getProperty("url"));
+ }
+ catch (NullPointerException e) {
+ throw new RuntimeException("Missing configuration parameter");
+ }
+
+ Addressbook ab = new Addressbook();
+ FritzBox fb = new FritzBox(pw, url);
+
+ System.out.println("loading ...");
+ // Load Addressbook from FritzBox
+ InputStream is = null;
+ FritzAddressbookReader reader = null;
+ try {
+ ab = new Addressbook();
+ is = fb.exportAddressbook();
+ reader = new FritzAddressbookReader(is);
+ reader.sendTo(ab);
+ is.close();
+ is = null;
+ reader.close();
+ reader = null;
+ }
+ finally {
+ Util.nothrowClose(is);
+ }
+
+ System.out.println("saving ...");
+ // Save Addressbook back to FritzBox
+ FritzAddressbookWriter writer = null;
+ OutputStream os = null;
+ try {
+ os = fb.importAddressbook();
+ writer = new FritzAddressbookWriter(os);
+ ab.sendTo(writer);
+ writer.close();
+ writer = null;
+ os.close();
+ os = null;
+ }
+ finally {
+ Util.nothrowClose(writer);
+ Util.nothrowClose(os);
+ }
+
+ } catch (SAXException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+}
--- /dev/null
+package de.j32.pimstuff.conduit;
+
+import java.io.Closeable;
+
+import de.j32.pimstuff.data.EntryConsumer;
+
+public interface Exporter
+ extends Closeable, EntryConsumer
+{}
--- /dev/null
+package de.j32.pimstuff.conduit;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import de.j32.pimstuff.data.Entry;
+import de.j32.pimstuff.data.EntryConsumer;
+import de.j32.util.XmlUtil;
+
+public class FritzAddressbookReader
+ implements Importer
+{
+ Document xml_;
+
+ public FritzAddressbookReader(InputStream is)
+ throws SAXException, IOException
+ {
+ xml_ = XmlUtil.parse(is);
+ }
+
+ @Override
+ public void sendTo(EntryConsumer consumer)
+ {
+ for (Element node : XmlUtil.iterateElements(xml_.getElementsByTagName("contact"))) {
+ /* subnodes:
+ * category (unused, always 0)
+ * person/realName
+ * person/imageURL
+ * telephony/number (@prio, @type, @vanity)
+ * services/email
+ * mod_time
+ */
+ Entry entry = new Entry();
+
+ try {
+ entry.name(node.getElementsByTagName("realName").item(0).getTextContent());
+
+ for (Element phone : XmlUtil.iterateElements(node.getElementsByTagName("number"))) {
+ entry.attribute("phone", phone.getAttribute("type"), phone.getTextContent());
+ }
+
+ try {
+ entry.attribute("email", "",
+ node.getElementsByTagName("email").item(0).getTextContent());
+ }
+ catch (NullPointerException e) {} // ignore missing optional email
+ }
+ catch (NullPointerException e) {
+ // Ignore incomplete entries
+ entry = null;
+ }
+
+ if (entry != null)
+ consumer.consume(entry);
+ }
+ }
+
+ @Override
+ public void close()
+ throws IOException
+ {}
+
+}
--- /dev/null
+package de.j32.pimstuff.conduit;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.xml.sax.SAXException;
+
+import de.j32.pimstuff.data.Attribute;
+import de.j32.pimstuff.data.Entry;
+import de.j32.util.SimpleXmlGenerator;
+import de.j32.util.Util;
+
+public class FritzAddressbookWriter
+ implements Exporter
+{
+ OutputStream os_;
+ SimpleXmlGenerator gen_;
+
+ public FritzAddressbookWriter(OutputStream os)
+ {
+ try {
+ os_ = os;
+ gen_ = new SimpleXmlGenerator(os_, "iso-8859-1");
+ gen_.startDocument(); gen_.nl();
+ gen_.start("phonebooks"); gen_.nl();
+ gen_.start("phonebook"); gen_.nl();
+ }
+ catch (SAXException e)
+ {
+ throw new AssertionError("Invalid XML/SAX document generated.");
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError("Unsopported encoding iso-8859-1 ??");
+ }
+ }
+
+ @Override
+ public void consume(Entry entry)
+ {
+ try {
+ gen_.start("contact"); gen_.nl();
+
+ gen_.start("category"); gen_.text("0"); gen_.end(); gen_.nl();
+
+ gen_.start("person"); gen_.nl();
+ gen_.start("realName"); gen_.text(entry.name()); gen_.end(); gen_.nl();
+ gen_.empty("imageURL"); gen_.nl();
+ gen_.end(); gen_.nl();
+
+ gen_.start("telephony"); gen_.nl();
+ for (Attribute number : entry.attributes("phone")) {
+ gen_.start("number",
+ gen_.attribute("prio", number.index > 0 ? "0" : "1")
+ .attribute("type", number.rel)
+ .attribute("vanity", ""));
+ gen_.text(number.value); gen_.end(); gen_.nl();
+ }
+ gen_.end(); gen_.nl();
+
+ Attribute email = Util.first(entry.attributes("email"));
+ if (email != null) {
+ gen_.start("services"); gen_.nl();
+ gen_.start("email", gen_.attribute("classifier","private"));
+ gen_.text(email.value); gen_.end(); gen_.nl();
+ gen_.end(); gen_.nl();
+ }
+ else {
+ gen_.empty("services"); gen_.nl();
+ }
+
+ gen_.end(); gen_.nl();
+ }
+ catch (SAXException e)
+ {
+ throw new AssertionError("Invalid XML/SAX document generated.");
+ }
+ }
+
+ @Override
+ public void close()
+ throws IOException
+ {
+ try {
+ gen_.end(); gen_.nl();
+ gen_.end();
+ gen_.endDocument();
+ }
+ catch (SAXException e)
+ {
+ throw new AssertionError("Invalid XML/SAX document generated.");
+ }
+ }
+
+}
--- /dev/null
+package de.j32.pimstuff.conduit;
+
+import java.io.Closeable;
+
+import de.j32.pimstuff.data.EntryProducer;
+
+public interface Importer
+ extends Closeable, EntryProducer
+{}
--- /dev/null
+package de.j32.pimstuff.data;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+public class Addressbook
+ implements EntryConsumer, EntryProducer
+{
+ LinkedList<Entry> data_ = new LinkedList<Entry>();
+
+ public void add(Entry entry)
+ {
+ data_.add(entry);
+ }
+
+ public Iterator<Entry> entries()
+ {
+ return data_.iterator();
+ }
+
+ public void consume(Entry entry)
+ {
+ add(entry);
+ }
+
+ public void sendTo(EntryConsumer consumer)
+ {
+ for (Entry entry : data_)
+ consumer.consume(entry);
+ }
+}
--- /dev/null
+package de.j32.pimstuff.data;
+
+public class Attribute
+{
+ public String type;
+ public String rel;
+ public String value;
+ public int index;
+
+ public Attribute(String type_, String rel_, String value_, int index_)
+ {
+ type = type_;
+ rel = rel_;
+ value = value_;
+ index = index_;
+ }
+}
--- /dev/null
+package de.j32.pimstuff.data;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import de.j32.util.Filter;
+import de.j32.util.FilteredIterator;
+
+public class Entry
+{
+ long id_ = 0;
+ String name_ = "";
+ ArrayList<Attribute> attributes_ = new ArrayList<Attribute>();
+
+ public void name(String name)
+ {
+ name_ = name;
+ }
+
+ public String name()
+ {
+ return name_;
+ }
+
+ public void id(long id)
+ {
+ id_ = id;
+ }
+
+ public long id()
+ {
+ return id_;
+ }
+
+ public void attribute(String type, String rel, String value)
+ {
+ attributes_.add(new Attribute(type, rel, value,attributes_.size()));
+ }
+
+ public Iterable<Attribute> attributes()
+ {
+ return attributes_;
+ }
+
+ public Iterable<Attribute> attributes(final String type)
+ {
+ return new Iterable<Attribute>() {
+ public Iterator<Attribute> iterator() {
+ return new FilteredIterator<Attribute>(
+ attributes_.iterator(),
+ new Filter<Attribute>() {
+ public boolean match(Attribute element) {
+ return element.type == type;
+ }
+ });
+ }
+ };
+ }
+
+ public Iterable<Attribute> attributes(final String type, final String rel)
+ {
+ return new Iterable<Attribute>() {
+ public Iterator<Attribute> iterator() {
+ return new FilteredIterator<Attribute>(
+ attributes_.iterator(),
+ new Filter<Attribute>() {
+ public boolean match(Attribute element) {
+ return element.type == type && element.rel == rel;
+ }
+ });
+ }
+ };
+ }
+
+}
--- /dev/null
+package de.j32.pimstuff.data;
+
+public interface EntryConsumer
+{
+ public void consume(Entry entry);
+}
--- /dev/null
+package de.j32.pimstuff.data;
+
+public interface EntryProducer
+{
+ public void sendTo(EntryConsumer consumer);
+}
--- /dev/null
+package de.j32.util;
+
+public interface Filter<E>
+{
+ public boolean match(E element);
+}
--- /dev/null
+package de.j32.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+
+public class FilteredIterator<E>
+ implements Iterator<E>
+{
+ Iterator<E> base_;
+ Filter<E> filter_;
+ E next_;
+ boolean hasNext_ = true;
+
+ public FilteredIterator(Iterator<E> base, Filter<E> filter)
+ {
+ base_ = base;
+ filter_ = filter;
+ advance();
+ }
+
+ public E next()
+ {
+ if (hasNext_) {
+ E rv = next_;
+ advance();
+ return rv;
+ }
+ else
+ throw new NoSuchElementException();
+ }
+
+ public boolean hasNext()
+ {
+ return hasNext_;
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ void advance()
+ {
+ while (base_.hasNext()) {
+ next_ = base_.next();
+ if (filter_.match(next_))
+ return;
+ }
+ hasNext_ = false;
+ next_ = null;
+ }
+
+}
--- /dev/null
+package de.j32.util;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Stack;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+public class SimpleXmlGenerator
+{
+ TransformerHandler handler_;
+ Stack<String> openElements_ = new Stack<String>();
+
+ public SimpleXmlGenerator(OutputStream os, String encoding)
+ throws UnsupportedEncodingException
+ {
+ SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+ // factory.setAttribute("indent-number", new Integer(2));
+ try {
+ handler_ = factory.newTransformerHandler();
+ }
+ catch (TransformerConfigurationException e) {
+ throw new RuntimeException("XML/SAX transformer configuration error");
+ }
+ catch (TransformerFactoryConfigurationError e) {
+ throw new RuntimeException("XML/SAX transformer factory configuration error");
+ }
+ Transformer tf = handler_.getTransformer();
+ tf.setOutputProperty(OutputKeys.ENCODING,encoding);
+ // if (doctype != null)
+ // tf.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,doctype);
+ // tf.setOutputProperty(OutputKeys.INDENT,indent ? "yes" : "no");
+ handler_.setResult(new StreamResult(new OutputStreamWriter(os, encoding)));
+ }
+
+ public void startDocument()
+ throws SAXException
+ {
+ handler_.startDocument();
+ }
+
+ public void start(String name)
+ throws SAXException
+ {
+ handler_.startElement("","",name,null);
+ openElements_.add(name);
+ }
+
+ public static class Attributes
+ {
+ AttributesImpl attributes_ = new AttributesImpl();
+
+ public Attributes attribute(String name, String value)
+ {
+ attributes_.addAttribute("","",name,"CDATA",value);
+ return this;
+ }
+
+ AttributesImpl getAttributes()
+ {
+ return attributes_;
+ }
+
+ Attributes() {}
+ }
+
+ public Attributes attribute(String name, String value)
+ {
+ Attributes a = new Attributes();
+ return a.attribute(name, value);
+ }
+
+ public void start(String name, Attributes attrs)
+ throws SAXException
+ {
+ handler_.startElement("","",name, attrs.getAttributes());
+ openElements_.push(name);
+ }
+
+ public void end()
+ throws SAXException
+ {
+ handler_.endElement("","",openElements_.pop());
+ }
+
+ public void empty(String name)
+ throws SAXException
+ {
+ start(name);
+ end();
+ }
+
+ public void empty(String name, Attributes attrs)
+ throws SAXException
+ {
+ start(name, attrs);
+ end();
+ }
+
+ public void text(String text)
+ throws SAXException
+ {
+ handler_.characters(text.toCharArray(), 0, text.length());
+ }
+
+ public void nl()
+ throws SAXException
+ {
+ text("\n");
+ }
+
+ public void endDocument()
+ throws SAXException
+ {
+ handler_.endDocument();
+ }
+}
--- /dev/null
+package de.j32.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Iterator;
+
+public class Util {
+
+ public static <E> E nonnull(E ob) {
+ if (ob == null)
+ throw new NullPointerException();
+ return ob;
+ }
+
+ public static void transfer(InputStream is, OutputStream os)
+ throws IOException {
+ byte[] buffer = new byte[16384];
+ int len = -1;
+ while ((len = is.read(buffer)) != -1)
+ os.write(buffer, 0, len);
+ }
+
+ public static void nothrowClose(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (IOException e) {}
+ }
+ }
+
+ public static <E> E first(Iterable<E> i)
+ {
+ Iterator<E> it = i.iterator();
+ if (it.hasNext())
+ return it.next();
+ return null;
+ }
+}
--- /dev/null
+package de.j32.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.Iterator;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class XmlUtil
+{
+ public static Document parse(InputStream is)
+ throws SAXException, IOException
+ {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(false);
+ factory.setExpandEntityReferences(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.setEntityResolver(new EntityResolver() {
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException
+ {
+ return new InputSource(new StringReader(""));
+ }});
+ return builder.parse(is);
+ }
+ catch (ParserConfigurationException e) {
+ throw new RuntimeException("SAX/DOM parser configuration error");
+ }
+ }
+
+ public static Iterable<Element> iterateElements(final NodeList nodes)
+ {
+ return new Iterable<Element>() {
+ public Iterator<Element> iterator() {
+ return new NodeListIterator<Element>(nodes, Element.class);
+ }
+ };
+ }
+
+ static class NodeListIterator<E extends Node>
+ implements Iterator<E>
+ {
+ Class<?> nodeType_;
+ NodeList nodes_;
+ int i_ = 0;
+ E next_;
+
+ public NodeListIterator(NodeList nodes, Class<?> nodeType)
+ {
+ nodes_ = nodes;
+ nodeType_ = nodeType;
+ advance();
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return next_ != null;
+ }
+
+ @Override
+ public E next()
+ {
+ E rv = next_;
+ advance();
+ return rv;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ void advance()
+ {
+ while (i_ < nodes_.getLength()) {
+ Node n = nodes_.item(i_);
+ ++ i_;
+ if (nodeType_.isInstance(n)) {
+ next_ = (E) n;
+ return;
+ }
+ }
+ next_ = null;
+ }
+ }
+}