blob: 54d420246dd8a9aa5752eee781c9023860445fe6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.vinci.transport.document;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.vinci.debug.Debug;
import org.apache.vinci.debug.FatalException;
import org.apache.vinci.transport.ServiceException;
import org.apache.vinci.transport.XTalkTransporter;
import org.apache.vinci.transport.util.XMLConverter;
/**
* Class for parsing an XML document and converting directly to XTalk.
*/
public class XMLToXTalk {
/**
* Utility class not intended to be instantiated.
*/
private XMLToXTalk() {
}
private static class StackEntry {
int childCount;
}
static String convert(String s) {
return XMLConverter.simpleConvertStringToXMLString(s);
}
/**
* This is a SAX document handler to parse XML into VinciFrames.
*/
private static class XTalkHandler extends DefaultHandler {
protected StackEntry top = null;
int depth = 0;
StackEntry[] childrenCount = new StackEntry[6969];
ArrayList countList = new ArrayList();
OutputStream os;
Writer xml_os;
boolean purgeWhitespace;
XTalkHandler(OutputStream os, boolean purgeWhitespace, Writer xml_os) {
this.os = os;
this.xml_os = xml_os;
childrenCount[0] = new StackEntry();
this.purgeWhitespace = purgeWhitespace;
}
public void startElement(String uri, String name, String qName, org.xml.sax.Attributes a) {
flushString();
try {
if (xml_os != null) {
xml_os.write('<');
xml_os.write(convert(qName));
}
os.write(XTalkTransporter.ELEMENT_MARKER);
childrenCount[depth].childCount++;
depth++;
childrenCount[depth] = new StackEntry();
countList.add(childrenCount[depth]);
// Debug.p("Attributes: " + qName + " : " + atts.getLength());
XTalkTransporter.stringToBin(qName, os);
XTalkTransporter.writeInt(a.getLength(), os);
for (int i = 0; i < a.getLength(); i++) {
XTalkTransporter.stringToBin(a.getQName(i), os);
XTalkTransporter.stringToBin(a.getValue(i), os);
if (xml_os != null) {
xml_os.write(' ');
xml_os.write(convert(a.getQName(i)));
xml_os.write("=\"");
xml_os.write(convert(a.getValue(i)));
xml_os.write('\"');
}
}
XTalkTransporter.writeInt(69, os); // placeholder for # of children, which is unknown
if (xml_os != null) {
xml_os.write('>');
}
// at this point.
} catch (IOException e) {
throw new FatalException(e);
}
}
public void endElement(String uri, String name, String qName) {
flushString();
depth--;
if (xml_os != null) {
try {
xml_os.write("</");
xml_os.write(convert(qName));
xml_os.write('>');
} catch (IOException e) {
throw new FatalException(e);
}
}
}
/**
* @pre ch != null
* @pre start &lt; ch.length
* @pre length &ge; start
* @pre top != null
* @pre top.sub_entries != null
*/
StringBuffer buf = new StringBuffer();
public void characters(char[] ch, int start, int length) {
buf.append(ch, start, length);
}
public void flushString() {
if (buf.length() > 0) {
String s = buf.toString();
buf.setLength(0);
if (purgeWhitespace) {
boolean all_whitespace = true;
int length = s.length();
for (int i = 0; i < length; i++) {
if (!Character.isWhitespace(s.charAt(i))) {
all_whitespace = false;
break;
}
}
if (all_whitespace) {
// Debug.p("Purging whitespace: " + new String(ch, start, length));
return;
}
}
childrenCount[depth].childCount++;
try {
os.write(XTalkTransporter.STRING_MARKER);
XTalkTransporter.stringToBin(s, os);
if (xml_os != null) {
xml_os.write(convert(s));
}
} catch (IOException e) {
throw new FatalException(e);
}
}
}
}
/**
* Right now we assume there are NO processing instructions. Given an XML file, create an XTalk
* representation of that data. If xml_filename is non-null, then this method will also create a
* UTF-8 representation of the xml file, exactly mimicing the XTalk encoding (e.g. removing
* irrelevant whitespace, expanding entity refs, etc).
* @param r -
* @param filename -
* @param purgeWhitespace -
* @param xml_filename -
* @throws ServiceException -
* @throws IOException -
*/
public static void xmlToXTalk(Reader r, String filename, boolean purgeWhitespace,
String xml_filename) throws ServiceException, IOException {
Writer xml_os = null;
if (xml_filename != null) {
xml_os = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(xml_filename),
"UTF-8"));
}
File file = new File(filename);
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
XTalkHandler handler = null;
try {
os.write(XTalkTransporter.DOCUMENT_MARKER);
os.write(XTalkTransporter.VERSION_CODE);
XTalkTransporter.writeInt(1, os);
XMLReader xr;
try {
xr = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
} catch (SAXException e) {
throw new ServiceException("Error creating SAX Parser: " + e);
} catch (ParserConfigurationException e) {
throw new ServiceException("Error creating SAX Parser: " + e);
}
if (xml_os != null) {
xml_os.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
}
// prevent undeclared namespace warnings.
try {
xr.setFeature("http://xml.org/sax/features/namespaces", false);
handler = new XTalkHandler(os, purgeWhitespace, xml_os);
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
xr.parse(new InputSource(r));
Debug.p("Final depth: " + handler.depth);
Debug.p("Final child list size: " + handler.countList.size());
} catch (SAXException e) {
Debug.reportException(e);
throw new ServiceException("XML Parse error: " + e);
}
} finally {
os.close();
if (xml_os != null) {
xml_os.close();
}
}
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
try {
raf.skipBytes(7);
// int return_val =
updateElement(raf, handler.countList, 0);
// Debug.p("Return val: " + return_val);
} finally {
raf.close();
}
}
static private int updateElement(RandomAccessFile raf, ArrayList counts, int index)
throws IOException {
skipString(raf);
int skipCount = raf.readInt();
// Debug.p("Skip count: " + skipCount);
for (int i = 0; i < skipCount; i++) {
skipString(raf);
skipString(raf);
}
int childCount = ((StackEntry) counts.get(index)).childCount;
index++;
raf.writeInt(childCount);
// Debug.p("Wrote: " + childCount);
for (int i = 0; i < childCount; i++) {
int marker = raf.read();
switch ((byte) marker) {
case XTalkTransporter.ELEMENT_MARKER:
index = updateElement(raf, counts, index);
break;
case XTalkTransporter.STRING_MARKER:
skipString(raf);
break;
default:
throw new IOException("Unexepcted marker: " + marker);
}
}
return index;
}
static private void skipString(RandomAccessFile raf) throws IOException {
final int count = raf.readInt();
// Debug.p("Skipping string of size: " + count);
final int skipped = raf.skipBytes(count);
if (count != skipped) {
throw new RuntimeException(String.format("%d bytes skipped when %d was requested, while reading from stream %s",
skipped, count, raf));
}
}
public static void main(String[] args) throws Exception {
if (args[0].endsWith(".xtalk")) {
InputStream is = new FileInputStream(args[0]);
AFrame af = new AFrame();
af.fromStream(is);
Debug.p(af.toXML());
} else if (args[0].endsWith(".xml")) {
Reader r = new FileReader(args[0]);
xmlToXTalk(r, "tmp.xtalk", args.length > 1, "tmp.xml");
} else {
throw new Exception("Unexpected filename suffix. Provide only .xml or .xtalk");
}
}
}