com.topologi.diffx.format.BasicXMLFormatter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of docx4j Show documentation
Show all versions of docx4j Show documentation
docx4j is a library which helps you to work with the Office Open
XML file format as used in docx
documents, pptx presentations, and xlsx spreadsheets.
The newest version!
/*
* This file is part of the DiffX library.
*
* For licensing information please see the file license.txt included in the release.
* A copy of this licence can also be found at
* http://www.opensource.org/licenses/artistic-license-2.0.php
*/
package com.topologi.diffx.format;
import java.io.IOException;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Stack;
import com.topologi.diffx.config.DiffXConfig;
import com.topologi.diffx.event.AttributeEvent;
import com.topologi.diffx.event.CloseElementEvent;
import com.topologi.diffx.event.DiffXEvent;
import com.topologi.diffx.event.OpenElementEvent;
import com.topologi.diffx.event.TextEvent;
import com.topologi.diffx.event.impl.ProcessingInstructionEvent;
import com.topologi.diffx.sequence.PrefixMapping;
import com.topologi.diffx.util.Constants;
import com.topologi.diffx.xml.XMLWriter;
import com.topologi.diffx.xml.XMLWriterNSImpl;
/**
* A XML formatter that simply uses a different namespace for any inserted or modified
* node.
*
* Nodes that have not changed are kept the way they are.
*
*
Nodes that have been modified will always be in a different namespace and will be
* reported as follows:
*
Elements:
*
* <mod:element name="elt.getName()" uri="elt.getURI()">
* ...
* </mod:element>
*
*
* Attributes:
*
* <mod:attribute name="att.getName()" uri="att.getURI()" value="att.getValue()"/>
*
*
* Texts:
*
* <mod:text>text.getCharacters()</mod:text>
*
*
* @author Christophe Lauret
* @version 17 May 2005
*/
public final class BasicXMLFormatter implements XMLDiffXFormatter {
// class attributes ---------------------------------------------------------------------------
/**
* The output goes here.
*/
private final XMLWriter xml;
/**
* The DiffX configuration to use
*/
private DiffXConfig config = new DiffXConfig();
// state variables ----------------------------------------------------------------------------
/**
* Set to true
to include the XML declaration. This attribute is
* set to false
when the {@link #setWriteXMLDeclaration(boolean)}
* is called with false
or once the XML declaration has been written.
*/
private transient boolean writeXMLDeclaration = true;
/**
* Indicates whether the XML writer has been setup already.
*/
private transient boolean isSetup = false;
/**
* Indicates whether some text is being inserted or removed.
*
* 0 = indicate format or no open text element.
* +1 = indicates an insert open text element.
* -1 = indicates an delete open text element.
*/
private transient short textFormat = 0;
/**
* A stack of attributes to insert.
*/
private transient Stack insAttributes = new Stack();
/**
* A stack of attributes to delete.
*/
private transient Stack delAttributes = new Stack();
// constructors -------------------------------------------------------------------------------
/**
* Creates a new formatter using the specified writer.
*
* @param w The writer to use.
*
* @throws NullPointerException If the specified writer is null
.
*/
public BasicXMLFormatter(Writer w) throws NullPointerException {
if (w == null)
throw new NullPointerException("The XML formatter requires a writer");
this.xml = new XMLWriterNSImpl(w, false);
}
// methods ------------------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public void format(DiffXEvent e) throws IOException {
if (!this.isSetup) {
setUpXML();
}
endTextChange();
if (!(e instanceof AttributeEvent)) {
flushAttributes();
}
e.toXML(this.xml);
if (e instanceof TextEvent)
if (this.config.isIgnoreWhiteSpace() && !this.config.isPreserveWhiteSpace()) {
this.xml.writeXML(" ");
}
this.xml.flush();
}
/**
* {@inheritDoc}
*/
@Override
public void insert(DiffXEvent e) throws IOException {
change(e, +1);
}
/**
* {@inheritDoc}
*/
@Override
public void delete(DiffXEvent e) throws IOException {
change(e, -1);
}
/**
* Reports a change in XML.
*
* @param e The diff-x event that has been inserted or deleted.
* @param mod The modification flag (positive for inserts, negative for deletes).
*
* @throws IOException an I/O exception if an error occurs.
*/
private void change(DiffXEvent e, int mod) throws IOException {
if (!this.isSetup) {
setUpXML();
}
// change in element
if (e instanceof OpenElementEvent) {
flushAttributes();
endTextChange();
this.xml.openElement(mod > 0? Constants.INSERT_NS_URI : Constants.DELETE_NS_URI, "element", false);
this.xml.attribute("name", ((OpenElementEvent)e).getName());
this.xml.attribute("ns-uri", ((OpenElementEvent)e).getURI());
// change in element
} else if (e instanceof CloseElementEvent) {
flushAttributes();
endTextChange();
this.xml.closeElement();
// change in text
} else if (e instanceof TextEvent) {
flushAttributes();
switchTextChange(mod);
e.toXML(this.xml);
if (this.config.isIgnoreWhiteSpace() && !this.config.isPreserveWhiteSpace()) {
this.xml.writeXML(" ");
}
// put the attribute as part of the 'delete' namespace
} else if (e instanceof AttributeEvent) {
if (mod > 0) {
this.insAttributes.push((AttributeEvent)e);
} else {
this.delAttributes.push((AttributeEvent)e);
}
// put the attribute as part of the 'delete' namespace
} else if (e instanceof ProcessingInstructionEvent) {
flushAttributes();
endTextChange();
this.xml.openElement(mod > 0? Constants.INSERT_NS_URI : Constants.DELETE_NS_URI, "processing-instruction", false);
this.xml.attribute("data", ((ProcessingInstructionEvent)e).getData());
this.xml.attribute("target", ((ProcessingInstructionEvent)e).getTarget());
// just format naturally
} else {
flushAttributes();
endTextChange();
e.toXML(this.xml);
}
this.xml.flush();
}
@Override
public void setConfig(DiffXConfig config) {
this.config = config;
}
@Override
public void setWriteXMLDeclaration(boolean show) {
this.writeXMLDeclaration = show;
}
/**
* Adds the prefix mapping to this class.
*
* @param mapping The prefix mapping to add.
*/
@Override
public void declarePrefixMapping(PrefixMapping mapping) {
for (Enumeration uris = mapping.getURIs(); uris.hasMoreElements();) {
String uri = uris.nextElement();
this.xml.setPrefixMapping(uri, mapping.getPrefix(uri));
}
}
// private helpers ----------------------------------------------------------------------------
/**
* Set up the XML.
*
* @throws IOException an I/O exception if an error occurs.
*/
private void setUpXML() throws IOException {
if (this.writeXMLDeclaration) {
this.xml.xmlDecl();
}
this.xml.setPrefixMapping(Constants.DELETE_NS_URI, "del");
this.xml.setPrefixMapping(Constants.INSERT_NS_URI, "ins");
this.writeXMLDeclaration = false;
this.isSetup = true;
}
/**
* Formats the end of a text change.
*
* @throws IOException If throws by XMl writer.
*/
private void endTextChange() throws IOException {
if (this.textFormat != 0) {
this.xml.closeElement();
this.textFormat = 0;
}
}
/**
* Switch between text changes.
*
* @param mod The modification flag (positive for inserts, negative for deletes).
*
* @throws IOException If throws by XMl writer.
*/
private void switchTextChange(int mod) throws IOException {
// insert
if (mod > 0) {
if (this.textFormat < 0) {
this.xml.closeElement();
}
if (this.textFormat <= 0) {
this.xml.openElement(Constants.INSERT_NS_URI, "text", false);
this.textFormat = +1;
}
// delete
} else {
if (this.textFormat > 0) {
this.xml.closeElement();
}
if (this.textFormat >= 0) {
this.xml.openElement(Constants.DELETE_NS_URI, "text", false);
this.textFormat = -1;
}
}
}
/**
* Writes any attribute that has not be written.
*
* @throws IOException Should an I/O error occur.
*/
private void flushAttributes() throws IOException {
flushAttributes(this.insAttributes, Constants.INSERT_NS_URI);
flushAttributes(this.delAttributes, Constants.DELETE_NS_URI);
}
/**
* Writes any attribute that has not be written.
*
* @param atts The attribute stack.
* @param uri The Namespace URI required
*
* @throws IOException Should an I/O error occur.
*/
private void flushAttributes(Stack atts, String uri) throws IOException {
while (!atts.empty()) {
AttributeEvent att = atts.pop();
this.xml.openElement(uri, "attribute", false);
this.xml.attribute("name", att.getName());
if (att.getURI() != null) {
this.xml.attribute("ns-uri", att.getURI());
}
this.xml.attribute("value", att.getValue());
this.xml.closeElement();
}
}
}