blob: edcd3a04378be0e9a39ee769f14063dcf26e910c [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.oxm.record;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Stack;
import javax.xml.namespace.QName;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.core.helper.CoreField;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.Marshaller;
import org.eclipse.persistence.internal.oxm.Namespace;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.ObjectBuilder;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.XMLBinaryDataHelper;
import org.eclipse.persistence.internal.oxm.XPathPredicate;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.XPathNode;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.Login;
import org.eclipse.persistence.internal.oxm.record.AbstractMarshalRecordImpl;
import org.eclipse.persistence.oxm.record.ValidatingMarshalRecord.MarshalSAXParseException;
import org.eclipse.persistence.core.queries.CoreAttributeGroup;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
/**
* <p>A MarshalRecord encapsulates the marshal target.</p>
*
* <p>MarshalRecords are stateful and state changes are triggered by different
* event notifications, therefore this class is not thread safe.</p>
*
* <p>XML document creation will differ depending on the subclass of MarshalRecord
* used. For example when NodeRecord is used a child element is created on the
* openStartElement event, and when the ContentHandlerRecord is used a child
* element is not created until the closeStartMethod event.</p>
*
* @see org.eclipse.persistence.oxm.XMLMarshaller
*/
public abstract class MarshalRecord<MARSHALLER extends Marshaller> extends AbstractMarshalRecordImpl<CoreAbstractSession, CoreField, MARSHALLER, NamespaceResolver> implements org.eclipse.persistence.internal.oxm.record.MarshalRecord<CoreAbstractSession, CoreField, MARSHALLER, NamespaceResolver> {
private ArrayList<XPathNode> groupingElements;
private HashMap positionalNodes;
protected XPathFragment textWrapperFragment;
private CycleDetectionStack<Object> cycleDetectionStack = new CycleDetectionStack<>();
private Stack<CoreAttributeGroup> attributeGroupStack;
protected static final String COLON_W_SCHEMA_NIL_ATTRIBUTE = Constants.COLON + Constants.SCHEMA_NIL_ATTRIBUTE;
protected static final String TRUE = "true";
protected MarshalRecord() {
super(null);
namespaceResolver = new NamespaceResolver();
}
@Override
public void forceValueWrapper(){}
public HashMap getPositionalNodes() {
if (positionalNodes == null) {
positionalNodes = new HashMap();
}
return positionalNodes;
}
public String getLocalName() {
throw XMLMarshalException.operationNotSupported("getLocalName");
}
public String getNamespaceURI() {
throw XMLMarshalException.operationNotSupported("getNamespaceURI");
}
public void clear() {
throw XMLMarshalException.operationNotSupported("clear");
}
public Document getDocument() {
throw XMLMarshalException.operationNotSupported("getDocument");
}
@Override
public Node getDOM() {
return null;
}
/**
* INTERNAL:
* If an XPathNode does not have an associated NodeValue then add it to the
* MarshalRecord as a grouping element.
*/
@Override
public void addGroupingElement(XPathNode xPathNode) {
if (null == groupingElements) {
groupingElements = new ArrayList(2);
}
groupingElements.add(xPathNode);
}
/**
*
* INTERNAL:
*/
@Override
public void removeGroupingElement(XPathNode xPathNode) {
if (null != groupingElements) {
groupingElements.remove(xPathNode);
}
}
public String transformToXML() {
return null;
}
@Override
public void setSession(CoreAbstractSession session) {
super.setSession(session);
if (session != null && session.getDatasourceLogin() instanceof Login) {
this.equalNamespaceResolvers = ((Login) session.getDatasourceLogin()).hasEqualNamespaceResolvers();
}
}
/**
* INTERNAL:
* Add the field-value pair to the document.
*/
@Override
public void add(CoreField key, Object value) {
if (null == value) {
return;
}
Field xmlField = convertToXMLField(key);
XPathFragment lastFragment = xmlField.getLastXPathFragment();
if (lastFragment.nameIsText()) {
characters(xmlField.getSchemaType(), value, null, xmlField.isCDATA());
} else if (lastFragment.isAttribute()) {
attribute(lastFragment, xmlField.getNamespaceResolver(), value, xmlField.getSchemaType());
} else {
element(lastFragment);
}
}
/**
* INTERNAL:
* Add the field-value pair to the document.
*/
@Override
public Object put(CoreField key, Object value) {
add(key, value);
return null;
}
/**
* INTERNAL:
* Add the namespace declarations to the XML document.
* @param namespaceResolver The NamespaceResolver contains the namespace
* prefix and URI pairings that need to be declared.
*/
public void namespaceDeclarations(NamespaceResolver namespaceResolver) {
if (namespaceResolver == null) {
return;
}
String namespaceURI = namespaceResolver.getDefaultNamespaceURI();
if(null != namespaceURI) {
defaultNamespaceDeclaration(namespaceURI);
}
if(namespaceResolver.hasPrefixesToNamespaces()) {
for(Entry<String, String> entry: namespaceResolver.getPrefixesToNamespaces().entrySet()) {
String namespacePrefix = entry.getKey();
namespaceDeclaration(namespacePrefix, entry.getValue());
}
}
}
/**
* Handle marshal of an empty collection.
* @param openGrouping if grouping elements should be marshalled for empty collections
*/
@Override
public boolean emptyCollection(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, boolean openGrouping) {
if (openGrouping) {
XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver);
closeStartGroupingElements(groupingFragment);
return true;
} else {
return false;
}
}
/**
* Add the specified namespace declaration
*/
@Override
public void namespaceDeclaration(String prefix, String namespaceURI){
attribute(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, prefix, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON + prefix, namespaceURI);
}
/**
* Add the defaultNamespace declaration
*/
public void defaultNamespaceDeclaration(String defaultNamespace){
attribute(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE, javax.xml.XMLConstants.XMLNS_ATTRIBUTE, defaultNamespace);
}
/**
* Receive notification that a document is being started.
* @param encoding The XML document will be encoded using this encoding.
* @param version This specifies the version of XML.
*/
public abstract void startDocument(String encoding, String version);
/**
* INTERNAL
* Writes the header, if appropriate.
*/
public void writeHeader() {
}
/**
* Recieve notification that a document is being ended.
*/
public abstract void endDocument();
/**
* INTERNAL
*/
public void marshalWithoutRootElement(ObjectBuilder treeObjectBuilder, Object object, Descriptor descriptor, Root root, boolean isXMLRoot){
}
/**
* Receive notification that a namespace has been declared.
* @param prefix The namespace prefix.
* @param namespaceURI The namespace URI.
*/
@Override
public void startPrefixMapping(String prefix, String namespaceURI) {
}
public void startPrefixMappings(NamespaceResolver namespaceResolver) {
if (namespaceResolver != null && namespaceResolver.hasPrefixesToNamespaces()) {
for(Entry<String, String> entry: namespaceResolver.getPrefixesToNamespaces().entrySet()) {
startPrefixMapping(entry.getKey(), entry.getValue());
}
}
}
/**
* Receive notification that the scope of this namespace declaration has
* ended.
* @param prefix The namespace prefix.
*/
@Override
public void endPrefixMapping(String prefix) {
}
public void endPrefixMappings(NamespaceResolver namespaceResolver) {
}
/**
* Receive notification that an element is being started.
* @param xPathFragment The XPathFragment contains the name and prefix
* information about the XML element being ended.
* @param namespaceResolver The NamespaceResolver can be used to resolve the
* namespace URI for the namespace prefix held by the XPathFragment (if
* required).
*/
@Override
public void openStartElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
this.addPositionalNodes(xPathFragment, namespaceResolver);
}
/**
* Receive notification of an element.
* @param frag The XPathFragment of the element
*/
public abstract void element(XPathFragment frag);
/**
* Receive notification of an attribute.
* @param xPathFragment The XPathFragment contains the name and prefix
* information about the XML element being ended.
* @param namespaceResolver The NamespaceResolver can be used to resolve the
* namespace URI for the namespace prefix held by the XPathFragment (if
* required).
* @param value This is the complete value for the attribute.
*/
@Override
public abstract void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, String value);
/**
* Receive notification of an attribute.
* @param namespaceURI The namespace URI, if the attribute is not namespace
* qualified the value of this parameter wil be null.
* @param localName The local name of the attribute.
* @param qName The qualified name of the attribute.
* @param value This is the complete value for the attribute.
*/
@Override
public abstract void attribute(String namespaceURI, String localName, String qName, String value);
/**
* Receive notification that all of the attribute events have occurred for
* the most recent element that has been started.
*/
@Override
public abstract void closeStartElement();
/**
* Receive notification that an element is being ended.
* @param xPathFragment The XPathFragment contains the name and prefix
* information about the XML element being ended.
* @param namespaceResolver The NamespaceResolver can be used to resolve the
* namespace URI for the namespace prefix held by the XPathFragment (if
* required).
*/
@Override
public abstract void endElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver);
/**
* Receive notification of character data.
* @param value This is the entire value of the text node.
*/
@Override
public abstract void characters(String value);
/**
* Convert the value if necessary and write out the attribute and converted value.
* @since EclipseLink 2.4
*/
@Override
public void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, Object value, QName schemaType){
if(schemaType != null && Constants.QNAME_QNAME.equals(schemaType)){
String convertedValue = getStringForQName((QName)value);
attribute(xPathFragment, namespaceResolver, convertedValue);
} else{
String convertedValue = ((String) ((ConversionManager) session.getDatasourcePlatform().getConversionManager()).convertObject(value, CoreClassConstants.STRING, schemaType));
attribute(xPathFragment, namespaceResolver, convertedValue);
}
}
/**
* Convert the value if necessary and write out the converted value.
* @since EclipseLink 2.4
*/
@Override
public void characters(QName schemaType, Object value, String mimeType, boolean isCDATA){
if(mimeType != null) {
if(value instanceof List){
value = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesListForBinaryValues(//
(List)value, marshaller, mimeType);
}else{
value = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesForBinaryValue(//
value, marshaller, mimeType).getData();
}
}
if(schemaType != null && Constants.QNAME_QNAME.equals(schemaType)){
String convertedValue = getStringForQName((QName)value);
characters(convertedValue);
}else{
String convertedValue = ((String) ((ConversionManager) session.getDatasourcePlatform().getConversionManager()).convertObject(value, CoreClassConstants.STRING, schemaType));
if(isCDATA){
cdata(convertedValue);
}else{
characters(convertedValue);
}
}
}
@Override
public String getValueToWrite(QName schemaType, Object value, ConversionManager conversionManager) {
if(value == null){
return null;
}
if(schemaType != null && Constants.QNAME_QNAME.equals(schemaType)){
return getStringForQName((QName)value);
}else if(value.getClass() == String.class){
return (String) value;
}
return (String) conversionManager.convertObject(value, CoreClassConstants.STRING, schemaType);
}
protected String getStringForQName(QName qName){
if(null == qName) {
return null;
}
String namespaceURI = qName.getNamespaceURI();
if(null == namespaceURI || 0 == namespaceURI.length()) {
if(getNamespaceResolver() != null && getNamespaceResolver().getDefaultNamespaceURI() != null) {
//need to add a default namespace declaration.
defaultNamespaceDeclaration(namespaceURI);
}
return qName.getLocalPart();
} else {
NamespaceResolver namespaceResolver = getNamespaceResolver();
if(namespaceResolver == null){
throw XMLMarshalException.namespaceResolverNotSpecified(namespaceURI);
}
if(namespaceURI.equals(namespaceResolver.getDefaultNamespaceURI())) {
return qName.getLocalPart();
}
String prefix = namespaceResolver.resolveNamespaceURI(namespaceURI);
if(null == prefix) {
if(namespaceURI.equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)){
prefix = namespaceResolver.generatePrefix(Constants.SCHEMA_PREFIX);
}else{
prefix = namespaceResolver.generatePrefix();
}
namespaceDeclaration(prefix, namespaceURI);
}
if(Constants.EMPTY_STRING.equals(prefix)){
return qName.getLocalPart();
}
return prefix + Constants.COLON + qName.getLocalPart();
}
}
/**
* Receive notification of character data to be wrapped in a CDATA node.
* @param value This is the value of the text to be wrapped
*/
@Override
public abstract void cdata(String value);
/**
* Receive notification of a node.
* @param node The Node to be added to the document
* @param resolver The NamespaceResolver can be used to resolve the
* namespace URI/prefix of the node
*/
@Override
public void node(Node node, NamespaceResolver resolver ){
node(node, resolver, null, null);
}
/**
* Receive notification of a node.
* @param node The Node to be added to the document
* @param resolver The NamespaceResolver can be used to resolve the
* @param qualifiedName replacement root name for the node
* @param rootUri replacement root namespace for the node
* namespace URI/prefix of the node
*/
public abstract void node(Node node, NamespaceResolver resolver, String qualifiedName, String rootUri);
/**
* INTERNAL:
* Trigger that the grouping elements should be written. This is normally
* done when something like a mapping has a non-null value that is
* marshalled.
* @param namespaceResolver The NamespaceResolver can be used to resolve the
* namespace URI for the namespace prefix held by the XPathFragment (if
* required).
*/
@Override
public XPathFragment openStartGroupingElements(NamespaceResolver namespaceResolver) {
if (null == groupingElements) {
return null;
}
XPathFragment xPathFragment = null;
for (int x = 0, groupingElementsSize = groupingElements.size(); x < groupingElementsSize; x++) {
XPathNode xPathNode = groupingElements.get(x);
xPathFragment = xPathNode.getXPathFragment();
openStartElement(xPathFragment, namespaceResolver);
predicateAttribute(xPathFragment, namespaceResolver);
if (x != (groupingElementsSize - 1)) {
closeStartElement();
}
}
groupingElements = null;
return xPathFragment;
}
@Override
public void closeStartGroupingElements(XPathFragment groupingFragment) {
if (null != groupingFragment) {
this.closeStartElement();
}
}
protected void addPositionalNodes(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
if (xPathFragment.containsIndex()) {
String shortName = xPathFragment.getShortName();
Integer index = (Integer)getPositionalNodes().get(shortName);
int start;
if (null == index) {
start = 1;
} else {
start = index;
}
for (int x = start; x < xPathFragment.getIndexValue(); x++) {
element(xPathFragment);
}
getPositionalNodes().put(shortName, xPathFragment.getIndexValue() + 1);
}
}
@Override
public void beforeContainmentMarshal(Object child) {
if(null != marshaller) {
Marshaller.Listener marshalListener = marshaller.getMarshalListener();
if(null != marshalListener) {
try {
marshalListener.beforeMarshal(child);
} catch(EclipseLinkException e) {
ErrorHandler errorHandler = marshaller.getErrorHandler();
if(null == errorHandler) {
throw e;
} else {
try {
MarshalSAXParseException saxParseException = new MarshalSAXParseException(null, null, null, -1, -1, e, child);
errorHandler.error(saxParseException);
} catch(SAXException saxParseException) {
throw e;
}
}
}
}
}
setOwningObject(child);
}
@Override
public void afterContainmentMarshal(Object parent, Object child) {
if(null != marshaller) {
Marshaller.Listener marshalListener = marshaller.getMarshalListener();
if(null != marshalListener) {
try {
marshalListener.afterMarshal(child);
} catch(EclipseLinkException e) {
ErrorHandler errorHandler = marshaller.getErrorHandler();
if(null == errorHandler) {
throw e;
} else {
try {
MarshalSAXParseException saxParseException = new MarshalSAXParseException(null, null, null, -1, -1, e, child);
errorHandler.error(saxParseException);
} catch(SAXException saxParseException) {
throw e;
}
}
}
}
}
setOwningObject(parent);
}
/**
* INTERNAL:
* Returns the list of grouping elements currently stored on the MarshalRecord
*/
@Override
public ArrayList<XPathNode> getGroupingElements() {
return this.groupingElements;
}
/**
* INTERNAL:
* Sets the list of grouping elements to be marshalled on this record.
*/
@Override
public void setGroupingElement(ArrayList<XPathNode> elements) {
this.groupingElements = elements;
}
/**
* Marshal the attribute for the predicate if one was specified.
*/
@Override
public void predicateAttribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
if(null != xPathFragment) {
XPathPredicate predicate = xPathFragment.getPredicate();
if(null != predicate) {
XPathFragment predicateXPathFragment = predicate.getXPathFragment();
if(predicateXPathFragment.isAttribute()) {
attribute(predicateXPathFragment, namespaceResolver, predicate.getValue());
}
}
}
}
/**
* This method is used to inform the MarshalRecord that the element events
* it is about to receive are part of a collection.
* @since EclipseLink 2.4
* @see #endCollection
*/
@Override
public void startCollection() {
}
/**
* Used when an nil attribute should be written
* @since EclipseLink 2.4
*/
@Override
public void emptyAttribute(XPathFragment xPathFragment,NamespaceResolver namespaceResolver){
XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver);
// We mutate the null into an empty string
attribute(xPathFragment, namespaceResolver, Constants.EMPTY_STRING);
closeStartGroupingElements(groupingFragment);
}
/**
* Used when an nil attribute should be written
* @since EclipseLink 2.4
*/
@Override
public void emptyComplex(XPathFragment xPathFragment, NamespaceResolver namespaceResolver){
XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver);
closeStartGroupingElements(groupingFragment);
openStartElement(xPathFragment, namespaceResolver);
closeStartElement();
endElement(xPathFragment, namespaceResolver);
}
/**
* Used when an nil attribute should be written
* @since EclipseLink 2.4
*/
@Override
public void emptySimple(NamespaceResolver namespaceResolver){
XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver);
closeStartGroupingElements(groupingFragment);
}
/**
* Used when an nil attribute should be written
* @since EclipseLink 2.4
*/
@Override
public void nilSimple(NamespaceResolver namespaceResolver){
XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver);
String xsiPrefix = processNamespaceResolverForXSIPrefix(namespaceResolver);
StringBuilder qName = new StringBuilder(Constants.ATTRIBUTE); // Unsynchronized
qName.append(xsiPrefix).append(COLON_W_SCHEMA_NIL_ATTRIBUTE);
XPathFragment nilFragment = new XPathFragment(qName.toString());
nilFragment.setNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
attribute(nilFragment, namespaceResolver, TRUE);
closeStartGroupingElements(groupingFragment);
}
/**
* Used when an nil attribute should be written
* @since EclipseLink 2.4
*/
@Override
public void nilComplex(XPathFragment xPathFragment, NamespaceResolver namespaceResolver){
XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver);
closeStartGroupingElements(groupingFragment);
openStartElement(xPathFragment, namespaceResolver);
String xsiPrefix = processNamespaceResolverForXSIPrefix(namespaceResolver);
XPathFragment nilFragment = new XPathFragment(Constants.ATTRIBUTE + xsiPrefix + COLON_W_SCHEMA_NIL_ATTRIBUTE);
nilFragment.setNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
attribute(nilFragment, namespaceResolver, TRUE);
closeStartElement();
endElement(xPathFragment, namespaceResolver);
}
/**
* This method is used to inform the MarshalRecord that it is done receiving
* element events that are part of a collection.
* @since EclipseLink 2.4
* @see #startCollection
*/
@Override
public void endCollection() {
}
/**
* INTERNAL:
* Private function to process or create an entry in the NamespaceResolver for the xsi prefix.
* @return xsi prefix
* @since EclipseLink 2.4
*/
protected String processNamespaceResolverForXSIPrefix(NamespaceResolver namespaceResolver) {
String xsiPrefix;
if (null == namespaceResolver) {
// add new xsi entry into the properties map
xsiPrefix = Constants.SCHEMA_INSTANCE_PREFIX;
namespaceResolver = new NamespaceResolver();
namespaceResolver.put(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
namespaceDeclaration(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
} else {
// find an existing xsi entry in the map
xsiPrefix = namespaceResolver.resolveNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
if (null == xsiPrefix) {
xsiPrefix = namespaceResolver.generatePrefix(Constants.SCHEMA_INSTANCE_PREFIX);
namespaceDeclaration(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
}
}
return xsiPrefix;
}
/**
* INTERNAL:
* The optional fragment used to wrap the text() mappings
* @since 2.4
*/
@Override
public XPathFragment getTextWrapperFragment() {
//return null as this is not supported by default
//subclass records can return the fragment if supported.
return null;
}
protected String getNameForFragment(XPathFragment xPathFragment) {
if(!this.hasCustomNamespaceMapper()) {
return xPathFragment.getShortName();
}
if(xPathFragment.getNamespaceURI() != null && xPathFragment.getNamespaceURI().length() > 0) {
String prefix = this.getPrefixForFragment(xPathFragment);
if(prefix != null && prefix.length() > 0) {
return prefix + Constants.COLON + xPathFragment.getLocalName();
}
}
return xPathFragment.getLocalName();
}
protected byte[] getPrefixBytes(XPathFragment xPathFragment) {
if(!this.hasCustomNamespaceMapper()) {
return xPathFragment.getPrefixBytes();
}
String prefix = this.getPrefixForFragment(xPathFragment);
if(null == prefix || prefix.isEmpty()) {
return null;
}
return prefix.getBytes(Constants.DEFAULT_CHARSET);
}
protected String getPrefixForFragment(XPathFragment xPathFragment) {
if(!hasCustomNamespaceMapper) {
return xPathFragment.getPrefix();
}
String uri = xPathFragment.getNamespaceURI();
if(uri == null || uri.length() == 0) {
return Constants.EMPTY_STRING;
}
String defaultNamespace = getNamespaceResolver().getDefaultNamespaceURI();
if(defaultNamespace != null && defaultNamespace.equals(uri)) {
return Constants.EMPTY_STRING;
}
String prefix = this.getNamespaceResolver().resolveNamespaceURI(uri);
if(prefix != null) {
return prefix;
}
for(Object next:getNamespaceResolver().getNamespaces()) {
Namespace ns = (Namespace)next;
uri = ns.getNamespaceURI();
prefix = ns.getPrefix();
}
return xPathFragment.getPrefix();
}
/**
* INTERNAL
*/
@Override
public CycleDetectionStack<Object> getCycleDetectionStack() {
return this.cycleDetectionStack;
}
private Field convertToXMLField(CoreField field) {
return (Field) field;
}
@Override
public boolean isWrapperAsCollectionName() {
return false;
}
@Override
public CoreAttributeGroup getCurrentAttributeGroup() {
if(this.attributeGroupStack == null || this.attributeGroupStack.isEmpty()) {
return DEFAULT_ATTRIBUTE_GROUP;
}
return attributeGroupStack.peek();
}
@Override
public void pushAttributeGroup(CoreAttributeGroup group) {
if(group == DEFAULT_ATTRIBUTE_GROUP && this.attributeGroupStack == null) {
return;
}
if(this.attributeGroupStack == null) {
this.attributeGroupStack = new Stack<>();
}
this.attributeGroupStack.push(group);
}
@Override
public void popAttributeGroup() {
if(attributeGroupStack != null) {
attributeGroupStack.pop();
}
}
@Override
public void flush() {
}
/**
* INTERNAL:
* Convenience method for splitting CDATA content to parts
* so that {@code "]]>"} occurrences are eliminated.
* <p>
* For example {@code splitCData("a]]>b")} returns list of two elements,
* {@code "a]]"} and {@code ">b"}.
* <p>
* This method is intended for use in {@link #cdata(String)}
* implementations where appropriate.
*
* @param value for CDATA section possibly containing {@code "]]>"}
* @return list of CDATA-valid chunks of the input
*/
static List<String> splitCData(String value) {
if (value == null) {
return Collections.emptyList();
}
List<String> parts = new LinkedList<>();
int index = -1;
String remainder = value;
while (0 <= (index = remainder.indexOf("]]>"))) {
String part = remainder.substring(0, index + 2);
parts.add(part);
remainder = remainder.substring(index + 2);
}
parts.add(remainder);
return parts;
}
}