Dépôt officiel du code source de l'ERP OpenConcerto
Blame | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils.doml;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class Document {
private Element root;
private byte[] buffer = new byte[64];
public Document() {
}
public Document(String rootName) {
this.root = new Element(rootName);
}
public Document(Element root) {
this.root = root;
}
public void loadFrom(BufferedReader br) throws IOException {
this.root = readElement(new DOMLStreamReader(br), 0);
}
private Element readElement(DOMLStreamReader r, int depth) throws IOException {
int t = r.readNextToken();
if (t == DOMLStreamReader.EOF || t == DOMLStreamReader.NO_TAG_CHARACTER) {
return null;
}
if (t != DOMLStreamReader.TAG_START) {
throw new IOException("not element start found (" + t + ")");
}
final String tagName = r.readTagName();
final Element current = new Element(tagName);
t = r.readNextToken();
if (t == DOMLStreamReader.TAG_END_COMPACT) {
return current;
} else if (t == DOMLStreamReader.TAG_END) {
readChildren(r, depth, tagName, current);
return current;
} else if (t == DOMLStreamReader.NO_TAG_CHARACTER) {
while (t == DOMLStreamReader.NO_TAG_CHARACTER) {
final String name = r.readAttributeName();
final String value = r.readAttributeValue();
current.setAttribute(name, value);
t = r.readNextToken();
}
if (t == DOMLStreamReader.TAG_END) {
readChildren(r, depth, tagName, current);
}
return current;
}
return null;
}
private void readChildren(DOMLStreamReader r, int depth, String tagName, Element current) throws IOException {
int t;
boolean tryReadNextChild = false;
do {
char n1 = r.readNextChar();
while (n1 < 33) {
// Optimization for : n1 == ' ' || n1 == '\n' || n1 == '\r' || n1 == '\t'
n1 = r.readNextChar();
}
if (n1 == 65535) {
// EOF
return;
}
tryReadNextChild = false;
if (n1 == '<') {
char n2 = r.readNextChar();
if (n2 != '/') {
tryReadNextChild = true;
r.unread(n2);
r.unread(n1);
Element e = readElement(r, depth + 1);
if (e != null) {
current.addChild(e);
}
} else {
r.unread(n2);
r.unread(n1);
}
}
} while (tryReadNextChild);
// end tag
t = r.readNextToken();
if (t != DOMLStreamReader.TAG_START) {
throw new IOException("not closing element start found for " + tagName + " (" + t + ") : '" + r.readNextChar());
}
final String ctagName = r.readTagName();
t = r.readNextToken();
if (t != DOMLStreamReader.TAG_END) {
throw new IOException("closing element end tag for '" + ctagName + "' is missing " + t);
}
}
public void writeTo(BufferedWriter writer) throws IOException {
if (this.root == null)
return;
writeTo(writer, 0, this.root);
writer.flush();
}
private Element readElement(BufferedInputStream in) throws IOException {
final String name = readUTF(in);
final int attributeCount = readInt(in);
final int childrenCount = readInt(in);
final Element e = new Element(name, attributeCount, childrenCount);
for (int i = 0; i < attributeCount; i++) {
final String n = readUTF(in);
final String v = readUTF(in);
e.addAttributeNoCheck(n, v);
}
for (int i = 0; i < childrenCount; i++) {
e.addChildNoCheck(readElement(in));
}
return e;
}
public void loadFromBinary(BufferedInputStream in) throws IOException {
this.root = readElement(in);
}
public void loadFromBinary(File file) throws IOException {
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
this.root = readElement(in);
}
}
public void loadFromBinary(byte[] bytes) throws IOException {
try (BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(bytes))) {
this.root = readElement(in);
}
}
public void writeToBinary(BufferedOutputStream out) throws IOException {
if (this.root == null) {
return;
}
writeTo(out, this.root);
out.flush();
}
public void writeToBinary(File file) throws IOException {
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
this.writeToBinary(out);
}
}
private void writeTo(BufferedOutputStream out, Element e) throws IOException {
// ObjectStream are not used because readUTF allocates too much char[]
writeUTF(out, e.getName());
final int attributeCount = e.getAttributeCount();
writeInt(out, attributeCount);
writeInt(out, e.getChildrenCount());
// write attributes
if (attributeCount > 0) {
e.writeAttributes(out);
}
// write children
for (Element c : e.getChildren()) {
writeTo(out, c);
}
}
public byte[] toByteArray() throws IOException {
if (this.root == null) {
return new byte[0];
}
final ByteArrayOutputStream out2 = new ByteArrayOutputStream(1024 + this.root.getChildrenCount() * 32);
try (BufferedOutputStream out = new BufferedOutputStream(out2)) {
this.writeToBinary(out);
}
return out2.toByteArray();
}
private static final void writeInt(BufferedOutputStream out, int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v) & 0xFF);
}
static final void writeUTF(BufferedOutputStream out, String str) throws IOException {
final byte[] buf = str.getBytes(StandardCharsets.UTF_8);
writeInt(out, buf.length);
out.write(buf);
}
private final int readInt(BufferedInputStream in) throws IOException {
final int ch1 = in.read();
final int ch2 = in.read();
final int ch3 = in.read();
final int ch4 = in.read();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
}
private final String readUTF(BufferedInputStream in) throws IOException {
final int size = readInt(in);
if (this.buffer.length < size) {
this.buffer = new byte[size + 32];
}
final int s = in.read(this.buffer, 0, size);
return new String(this.buffer, 0, s, StandardCharsets.UTF_8);
}
@Override
public String toString() {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
writeTo(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
return out.toString(StandardCharsets.UTF_8.name());
} catch (IOException e) {
return e.getMessage();
}
}
private void writeTo(final BufferedWriter writer, int depth, Element e) throws IOException {
if (e.getChildrenCount() == 0) {
for (int i = 0; i < depth; i++) {
writer.append(' ');
}
writer.append('<');
writer.append(e.getName());
e.appendAttributes(writer);
writer.append(" />\n");
} else {
for (int i = 0; i < depth; i++) {
writer.append(' ');
}
writer.append('<');
writer.append(e.getName());
e.appendAttributes(writer);
writer.append(">\n");
int d = depth + 1;
for (Element child : e.getChildren()) {
writeTo(writer, d, child);
}
for (int i = 0; i < depth; i++) {
writer.append(' ');
}
writer.append("</");
writer.append(e.getName());
writer.append(">\n");
}
}
public Element getRoot() {
return this.root;
}
}