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-diffx Show documentation
Show all versions of docx4j-diffx Show documentation
differencing of docx files
/*
* 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();
}
}
}