Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright (C) 2006-2014 phloc systems
* http://www.phloc.com
* office[at]phloc[dot]com
*
* Licensed 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 com.phloc.commons.xml.serialize;
import java.io.OutputStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.WillNotClose;
import javax.xml.namespace.NamespaceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.Nonempty;
import com.phloc.commons.annotations.OverrideOnDemand;
import com.phloc.commons.io.streams.NonBlockingBufferedWriter;
import com.phloc.commons.io.streams.StreamUtils;
import com.phloc.commons.string.StringHelper;
import com.phloc.commons.string.ToStringGenerator;
import com.phloc.commons.xml.CXML;
import com.phloc.commons.xml.IXMLIterationHandler;
import com.phloc.commons.xml.XMLHelper;
import com.phloc.commons.xml.namespace.IIterableNamespaceContext;
/**
* Abstract XML serializer implementation that works with IMicroNode and
* org.w3c.dom.Node objects.
*
* @author Philip Helger
* @param
* The DOM node type to use
*/
public abstract class AbstractSerializerPhloc implements IXMLSerializer
{
/**
* The prefix to be used for created namespace prefixes :) (e.g. for "ns0" or
* "ns1")
*/
public static final String DEFAULT_NAMESPACE_PREFIX_PREFIX = "ns";
private static final Logger s_aLogger = LoggerFactory.getLogger (AbstractSerializerPhloc.class);
/**
* Contains the XML namespace definitions for a single element.
*
* @author Philip Helger
*/
protected static final class NamespaceLevel
{
@SuppressWarnings ("hiding")
private static final Logger s_aLogger = LoggerFactory.getLogger (NamespaceLevel.class);
private String m_sDefaultNamespaceURI;
private Map m_aURL2PrefixMap;
/**
* Extract all attribute starting with "xmlns" and create the appropriate
* prefix mapping.
*
* @param aAttrs
* Attribute map to check. May be null.
*/
public NamespaceLevel (@Nullable final Map aAttrs)
{
// check all attributes
if (aAttrs != null)
for (final Map.Entry aEntry : aAttrs.entrySet ())
{
final String sAttrName = aEntry.getKey ();
if (sAttrName.equals (CXML.XML_ATTR_XMLNS))
{
// set default namespace (xmlns)
final String sNamespaceURI = aEntry.getValue ();
addPrefixNamespaceMapping (null, sNamespaceURI);
// Happens quite often when using regular DOM serialization
if (s_aLogger.isDebugEnabled ())
s_aLogger.debug ("Found default namespace '" + sNamespaceURI + "' in attribute!");
}
else
if (sAttrName.startsWith (CXML.XML_ATTR_XMLNS_WITH_SEP))
{
// prefixed namespace (xmlns:...)
final String sPrefix = sAttrName.substring (CXML.XML_ATTR_XMLNS_WITH_SEP.length ());
final String sNamespaceURI = aEntry.getValue ();
addPrefixNamespaceMapping (sPrefix, sNamespaceURI);
// Happens quite often when using regular DOM serialization
if (s_aLogger.isDebugEnabled ())
s_aLogger.debug ("Found namespace prefix '" +
sPrefix +
"' (with URL '" +
sNamespaceURI +
"') in attribute!");
}
}
}
/**
* Get the URL matching a given namespace prefix in this level.
*
* @param sPrefix
* The prefix to be searched. If it is null the default
* namespace URL is returned.
* @return null if the namespace mapping is not used or the URL
* otherwise.
*/
@Nullable
public String getNamespaceURIOfPrefix (@Nullable final String sPrefix)
{
if (StringHelper.hasNoText (sPrefix))
return m_sDefaultNamespaceURI;
if (m_aURL2PrefixMap != null)
for (final Map.Entry aEntry : m_aURL2PrefixMap.entrySet ())
if (aEntry.getValue ().equals (sPrefix))
return aEntry.getKey ();
return null;
}
void addPrefixNamespaceMapping (@Nullable final String sPrefix, @Nonnull @Nonempty final String sNamespaceURI)
{
if (s_aLogger.isTraceEnabled ())
s_aLogger.trace ("Adding namespace mapping " + sPrefix + ":" + sNamespaceURI);
// namespace prefix uniqueness check
final String sExistingNamespaceURI = getNamespaceURIOfPrefix (sPrefix);
if (sExistingNamespaceURI != null && !sExistingNamespaceURI.equals (sNamespaceURI))
s_aLogger.warn ("Overwriting namespace prefix '" +
sPrefix +
"' to use URL '" +
sNamespaceURI +
"' instead of '" +
sExistingNamespaceURI +
"'");
if (StringHelper.hasNoText (sPrefix))
{
if (m_sDefaultNamespaceURI != null)
s_aLogger.warn ("Overwriting default namespace '" +
m_sDefaultNamespaceURI +
"' with namespace '" +
sNamespaceURI +
"'");
m_sDefaultNamespaceURI = sNamespaceURI;
}
else
{
if (m_aURL2PrefixMap == null)
m_aURL2PrefixMap = new HashMap ();
m_aURL2PrefixMap.put (sNamespaceURI, sPrefix);
}
}
@Nullable
public String getDefaultNamespaceURI ()
{
return m_sDefaultNamespaceURI;
}
@Nullable
public String getPrefixOfNamespaceURI (@Nonnull final String sNamespaceURI)
{
// is it the default?
if (sNamespaceURI.equals (m_sDefaultNamespaceURI))
return null;
// Check in the map
return m_aURL2PrefixMap == null ? null : m_aURL2PrefixMap.get (sNamespaceURI);
}
@Nonnegative
int getNamespaceCount ()
{
return (m_sDefaultNamespaceURI == null ? 0 : 1) + (m_aURL2PrefixMap == null ? 0 : m_aURL2PrefixMap.size ());
}
boolean hasAnyNamespace ()
{
return m_sDefaultNamespaceURI != null || (m_aURL2PrefixMap != null && !m_aURL2PrefixMap.isEmpty ());
}
@Override
public String toString ()
{
return new ToStringGenerator (this).append ("defaultNSURI", m_sDefaultNamespaceURI)
.append ("url2prefix", m_aURL2PrefixMap)
.toString ();
}
}
/**
* Contains the hierarchy of XML namespaces within a document structure.
* Important: null namespace URIs are different from empty namespace URIs!
*
* @author Philip Helger
*/
protected static final class NamespaceStack
{
private final List m_aStack = new ArrayList ();
private final NamespaceContext m_aNamespaceCtx;
public NamespaceStack (@Nullable final NamespaceContext aNamespaceCtx)
{
m_aNamespaceCtx = aNamespaceCtx;
}
/**
* Start a new namespace level.
*
* @param aAttrs
* Existing attributes from which XML namespace prefix mappings
* should be extracted. May be null.
*/
public void push (@Nullable final Map aAttrs)
{
final NamespaceLevel aNSL = new NamespaceLevel (aAttrs);
// add at front
m_aStack.add (0, aNSL);
}
public void addNamespaceMapping (@Nullable final String sPrefix, @Nonnull @Nonempty final String sNamespaceURI)
{
// Add the namespace to the current level
m_aStack.get (0).addPrefixNamespaceMapping (sPrefix, sNamespaceURI);
}
public void pop ()
{
// remove at front
m_aStack.remove (0);
}
@Nonnegative
public int size ()
{
return m_aStack.size ();
}
/**
* @return The namespace URI that is currently active in the stack. May be
* null for no specific namespace.
*/
@Nullable
public String getDefaultNamespaceURI ()
{
// iterate from front to end
for (final NamespaceLevel aNSLevel : m_aStack)
{
final String sDefaultNamespaceURI = aNSLevel.getDefaultNamespaceURI ();
if (StringHelper.hasText (sDefaultNamespaceURI))
return sDefaultNamespaceURI;
}
// no default namespace
return null;
}
/**
* Resolve the given namespace URI to a prefix.
*
* @param sNamespaceURI
* The namespace URI to resolve. May not be null. Pass
* in an empty string for an empty namespace URI!
* @return null if no namespace prefix is required.
*/
@Nullable
public String getUsedPrefixOfNamespace (@Nonnull final String sNamespaceURI)
{
ValueEnforcer.notNull (sNamespaceURI, "NamespaceURI");
// find existing prefix (iterate current to root)
for (final NamespaceLevel aNSLevel : m_aStack)
{
final String sPrefix = aNSLevel.getPrefixOfNamespaceURI (sNamespaceURI);
if (sPrefix != null)
return sPrefix;
}
// no matching prefix found
return null;
}
private boolean _containsNoNamespace ()
{
for (final NamespaceLevel aNSLevel : m_aStack)
if (aNSLevel.hasAnyNamespace ())
return false;
return true;
}
/**
* Check if the whole prefix is used somewhere in the stack.
*
* @param sPrefix
* The prefix to be checked
* @return true if somewhere in the stack, the specified prefix
* is already used
*/
private boolean _containsNoPrefix (@Nonnull final String sPrefix)
{
// find existing prefix (iterate current to root)
for (final NamespaceLevel aNSLevel : m_aStack)
if (aNSLevel.getNamespaceURIOfPrefix (sPrefix) != null)
return false;
return true;
}
/**
* Check if the passed namespace URI is mapped in the namespace context.
*
* @param sNamespaceURI
* The namespace URI to check. May not be null.
* @return null if no namespace context mapping is present
*/
@Nullable
public String getMappedPrefix (@Nonnull final String sNamespaceURI)
{
ValueEnforcer.notNull (sNamespaceURI, "NamespaceURI");
// If a mapping is defined, it always takes precedence over the default
// namespace
if (m_aNamespaceCtx != null)
{
// Is a mapping defined?
final String sPrefix = m_aNamespaceCtx.getPrefix (sNamespaceURI);
if (sPrefix != null)
return sPrefix;
}
return null;
}
/**
* Create a new unique namespace prefix.
*
* @return null or empty if the default namespace is available,
* the prefix otherwise.
*/
@Nullable
public String createUniquePrefix ()
{
// Is the default namespace available?
if (_containsNoNamespace ())
{
// Use the default namespace
return null;
}
// find a unique prefix
int nCount = 0;
do
{
final String sNSPrefix = DEFAULT_NAMESPACE_PREFIX_PREFIX + nCount;
if (_containsNoPrefix (sNSPrefix))
return sNSPrefix;
++nCount;
} while (true);
}
}
/**
* The serialization settings
*/
protected final IXMLWriterSettings m_aSettings;
/**
* helper variable: current indentation.
*/
protected final StringBuilder m_aIndent = new StringBuilder (32);
/**
* Current stack of namespaces.
*/
protected final NamespaceStack m_aNSStack;
public AbstractSerializerPhloc (@Nonnull final IXMLWriterSettings aSettings)
{
m_aSettings = ValueEnforcer.notNull (aSettings, "Settings");
m_aNSStack = new NamespaceStack (aSettings.getNamespaceContext ());
}
@Nonnull
public final IXMLWriterSettings getSettings ()
{
return m_aSettings;
}
protected final void handlePutNamespaceContextPrefixInRoot (@Nonnull final Map aAttrMap)
{
if (m_aNSStack.size () == 1 &&
m_aSettings.isPutNamespaceContextPrefixesInRoot () &&
m_aSettings.isEmitNamespaces ())
{
// The only place where the namespace context prefixes are added to the
// root element
final NamespaceContext aNC = m_aSettings.getNamespaceContext ();
if (aNC != null)
{
if (aNC instanceof IIterableNamespaceContext)
{
for (final Map.Entry aEntry : ((IIterableNamespaceContext) aNC).getPrefixToNamespaceURIMap ()
.entrySet ())
{
final String sNSPrefix = aEntry.getKey ();
final String sNamespaceURI = aEntry.getValue ();
aAttrMap.put (XMLHelper.getXMLNSAttrName (sNSPrefix), sNamespaceURI);
m_aNSStack.addNamespaceMapping (sNSPrefix, sNamespaceURI);
}
}
else
s_aLogger.error ("XMLWriter settings has putNamespaceContextPrefixesInRoot set, but the NamespaceContext does not implement the IIterableNamespaceContext interface!");
}
}
}
protected abstract void emitNode (@Nonnull final IXMLIterationHandler aXMLWriter,
@Nullable final NODETYPE aPrevSibling,
@Nonnull final NODETYPE aNode,
@Nullable final NODETYPE aNextSibling);
@Nonnull
@OverrideOnDemand
protected XMLEmitterPhloc createXMLEmitter (@Nonnull @WillNotClose final Writer aWriter,
@Nonnull final IXMLWriterSettings aSettings)
{
return new XMLEmitterPhloc (aWriter, aSettings);
}
public final void write (@Nonnull final NODETYPE aNode, @Nonnull @WillNotClose final Writer aWriter)
{
final XMLEmitterPhloc aXMLWriter = createXMLEmitter (aWriter, m_aSettings);
// No previous and no next sibling
emitNode (aXMLWriter, null, aNode, null);
// Flush is important for Writer!
StreamUtils.flush (aWriter);
}
public final void write (@Nonnull final NODETYPE aNode, @Nonnull final IXMLIterationHandler aXMLEmitter)
{
// No previous and no next sibling
emitNode (aXMLEmitter, null, aNode, null);
}
public final void write (@Nonnull final NODETYPE aNode, @Nonnull @WillNotClose final OutputStream aOS)
{
ValueEnforcer.notNull (aNode, "Node");
ValueEnforcer.notNull (aOS, "OutputStream");
// Create a writer for the passed output stream
final NonBlockingBufferedWriter aWriter = new NonBlockingBufferedWriter (StreamUtils.createWriter (aOS,
m_aSettings.getCharsetObj ()));
// Inside the other write method, the writer must be flushed!
write (aNode, aWriter);
// Do not close the writer!
}
@Override
public String toString ()
{
return new ToStringGenerator (this).append ("settings", m_aSettings)
.append ("sIndent", m_aIndent.toString ())
.append ("namespaceStack", m_aNSStack)
.toString ();
}
}