/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk
2008 Didier Briel
Home page: http://www.omegat.org/
Support center: http://groups.yahoo.com/group/OmegaT/
This file is part of OmegaT.
OmegaT is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmegaT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
package org.omegat.filters3.xml;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import org.omegat.util.Log;
import org.omegat.util.PatternConsts;
/**
* This class writes out the XML files, intercepting the output. First it
* collects all the output inside itself in a string. Then it adds the specified
* encoding declaration (or replaces the encoding) and writes out the file in
* the encoding.
*
* @author Maxym Mykhalchuk
* @author Didier Briel
*/
public class XMLWriter extends Writer {
/** Internal Buffer to collect the output */
private StringWriter writer;
/** real writer to a file */
private BufferedWriter realWriter;
/** Replacement string for XML header */
private String xmlHeader;
/** Detected EOL chars. */
private String eol;
/**
* Creates new XMLWriter.
*
* @param fileName
* file name to write to
* @param encoding
* encoding to write a file in
*/
public XMLWriter(File file, String encoding, String eol)
throws FileNotFoundException, UnsupportedEncodingException {
if (encoding == null) {
xmlHeader = "<?xml version=\"1.0\"?>";
} else {
xmlHeader = "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>";
}
writer = new StringWriter();
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw;
if (encoding == null) {
osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
} else {
osw = new OutputStreamWriter(fos, encoding);
}
realWriter = new BufferedWriter(osw);
this.eol = eol;
}
/** The minimal size of already written HTML that will be appended headers */
private static final int MIN_HEADERED_BUFFER_SIZE = 4096;
/** The maximal size of a buffer before flush */
private static final int MAX_BUFFERED_SIZE = 65536;
/**
* Signals that the writer is being closed, hence it needs to write any
* (little) buffer out.
*/
private boolean signalClosing = false;
/**
* Signals that the writer was already flushed, i.e. already wrote out the
* headers stuff.
*/
private boolean signalAlreadyFlushed = false;
/**
* Flushes the writer (which does the real write-out of data) and closes the
* real writer.
*/
public void close() throws IOException {
signalClosing = true;
flush();
realWriter.close();
}
/**
* Does the real write-out of the data, first adding/replacing encoding
* statement.
*/
public void flush() throws IOException {
StringBuffer buffer = writer.getBuffer();
if (signalAlreadyFlushed) {
// already flushed, i.e. already wrote out the headers stuff
realWriter.write(fixEOL(buffer).toString());
buffer.setLength(0);
} else if (signalClosing || buffer.length() >= MIN_HEADERED_BUFFER_SIZE) {
// else if we're closing or the buffer is big enough
// to (hopefully) contain all the existing headers
signalAlreadyFlushed = true;
String contents;
Matcher matcherHeader = PatternConsts.XML_HEADER.matcher(buffer);
if (matcherHeader.find()) {
contents = fixEOL(new StringBuffer(matcherHeader.replaceFirst(xmlHeader))).toString();
} else {
Log.log("Shouldn't happen! " + "XMLWriter: XML File does not contain XML header:\n"
+ buffer.substring(0, Math.min(buffer.length(), 80)));
realWriter.write(xmlHeader);
contents = fixEOL(buffer).toString();
}
realWriter.write(contents);
buffer.setLength(0);
}
}
/**
* We assume that EOL is always '\n' after XML processing.
*/
StringBuffer fixEOL(StringBuffer out) {
for (int cp, i = 0; i < out.length(); i += Character.charCount(cp)) {
cp = out.codePointAt(i);
if (cp == '\n') {
// EOL
out.setCharAt(i, eol.charAt(0));
if (eol.length() > 1) {
// EOL is more than 1 char - need to insert remaining.
// Intentionally iterating by chars instead of code points.
for (int j = 1; j < eol.length(); j++) {
out.insert(i + 1, eol.charAt(j));
i++;
}
}
}
}
return out;
}
/**
* Write a portion of an array of characters. Simply calls
* <code>write(char[], int, int)</code> of the internal
* <code>StringWriter</code>.
*
* @param cbuf
* - Array of characters
* @param off
* - Offset from which to start writing characters
* @param len
* - Number of characters to write
* @throws IOException
* - If an I/O error occurs
*/
public void write(char[] cbuf, int off, int len) throws IOException {
writer.write(cbuf, off, len);
if (writer.getBuffer().length() >= MAX_BUFFERED_SIZE) {
flush();
}
}
}