| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.axiom.om.impl.serialize; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.activation.DataHandler; |
| import javax.xml.transform.sax.SAXSource; |
| |
| import org.apache.axiom.om.OMAttribute; |
| import org.apache.axiom.om.OMComment; |
| import org.apache.axiom.om.OMContainer; |
| import org.apache.axiom.om.OMDocType; |
| import org.apache.axiom.om.OMDocument; |
| import org.apache.axiom.om.OMElement; |
| import org.apache.axiom.om.OMEntityReference; |
| import org.apache.axiom.om.OMNamespace; |
| import org.apache.axiom.om.OMNode; |
| import org.apache.axiom.om.OMProcessingInstruction; |
| import org.apache.axiom.om.OMText; |
| import org.apache.axiom.om.impl.jaxp.OMSource; |
| import org.apache.axiom.util.base64.Base64EncodingWriterOutputStream; |
| import org.apache.axiom.util.sax.AbstractXMLReader; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.DTDHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.ext.DeclHandler; |
| import org.xml.sax.ext.LexicalHandler; |
| |
| /** |
| * SAX {@link XMLReader} implementation that traverses a given OM tree and invokes the |
| * callback methods on the configured {@link ContentHandler}. This can be used to |
| * serialize an Axiom tree to SAX. |
| * <p> |
| * Note that this class only supports {@link ContentHandler} and {@link LexicalHandler}. |
| * {@link DTDHandler} and {@link DeclHandler} are not supported. |
| * <p> |
| * This class can also generate SAX events for a subtree. This is the case if the |
| * element passed to the constructor is not the root element of the document. In this |
| * case, care is taken to properly generate <code>startPrefixMapping</code> and |
| * <code>endPrefixMapping</code> events also for namespace mappings declared on the ancestors |
| * of the element. |
| * <p> |
| * To understand why this is important, consider the following example: |
| * <pre><root xmlns:ns="urn:ns"><element attr="ns:someThing"/><root></pre> |
| * In that case, to correctly interpret the attribute value, the SAX content handler must be |
| * aware of the namespace mapping for the {@code ns} prefix, even if the serialization starts |
| * only at the child element. |
| * |
| * @deprecated This class is used internally by {@link OMSource}. Starting with Axiom 1.2.13, |
| * application code should use {@link OMContainer#getSAXSource(boolean)} to serialize an OM tree to |
| * SAX. If there is a need to obtain an {@link XMLReader} instance, use |
| * {@link SAXSource#getXMLReader()} on the {@link SAXSource} returned by |
| * {@link OMContainer#getSAXSource(boolean)}. |
| */ |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public class OMXMLReader extends AbstractXMLReader { |
| private final OMContainer root; |
| private final AttributesAdapter attributesAdapter = new AttributesAdapter(); |
| |
| public OMXMLReader(OMContainer root) { |
| this.root = root; |
| } |
| |
| @Override |
| public void parse(InputSource input) throws IOException, SAXException { |
| parse(); |
| } |
| |
| @Override |
| public void parse(String systemId) throws IOException, SAXException { |
| parse(); |
| } |
| |
| private void parse() throws SAXException { |
| if (root instanceof OMDocument) { |
| generateEvents((OMDocument)root); |
| } else { |
| OMElement element = (OMElement)root; |
| contentHandler.startDocument(); |
| generateParentPrefixMappingEvents(element, true); |
| generateEvents(element); |
| generateParentPrefixMappingEvents(element, false); |
| contentHandler.endDocument(); |
| } |
| } |
| |
| private void generateEvents(OMDocument document) throws SAXException { |
| contentHandler.startDocument(); |
| generateEventsForChildren(document); |
| contentHandler.endDocument(); |
| } |
| |
| private void generatePrefixMappingEvents(OMNamespace ns, boolean start) throws SAXException { |
| String prefix = ns.getPrefix(); |
| if (prefix != null) { |
| if (start) { |
| contentHandler.startPrefixMapping(prefix, ns.getNamespaceURI()); |
| } else { |
| contentHandler.endPrefixMapping(prefix); |
| } |
| } |
| } |
| |
| private void generatePrefixMappingEvents(OMElement omElement, boolean start) |
| throws SAXException { |
| |
| for (Iterator it = omElement.getAllDeclaredNamespaces(); it.hasNext(); ) { |
| generatePrefixMappingEvents((OMNamespace)it.next(), start); |
| } |
| } |
| |
| private void generateParentPrefixMappingEvents(OMElement omElement, boolean start) |
| throws SAXException { |
| |
| if (!(omElement.getParent() instanceof OMElement)) { |
| return; |
| } |
| // Maintain a set of the prefixes we have already seen. This is required to take into |
| // account that a namespace mapping declared on an element can hide another one declared |
| // for the same prefix on an ancestor of the element. |
| Set/*<String>*/ seenPrefixes = new HashSet(); |
| for (Iterator it = omElement.getAllDeclaredNamespaces(); it.hasNext(); ) { |
| seenPrefixes.add(((OMNamespace)it.next()).getPrefix()); |
| } |
| OMElement current = omElement; |
| while (true) { |
| OMContainer parent = current.getParent(); |
| if (!(parent instanceof OMElement)) { |
| return; |
| } |
| current = (OMElement)parent; |
| for (Iterator it = current.getAllDeclaredNamespaces(); it.hasNext(); ) { |
| OMNamespace ns = (OMNamespace)it.next(); |
| if (seenPrefixes.add(ns.getPrefix())) { |
| generatePrefixMappingEvents(ns, start); |
| } |
| } |
| } |
| } |
| |
| private void generateEvents(OMElement omElement) throws SAXException { |
| generatePrefixMappingEvents(omElement, true); |
| OMNamespace omNamespace = omElement.getNamespace(); |
| String uri; |
| String prefix; |
| if (omNamespace != null) { |
| uri = omNamespace.getNamespaceURI(); |
| prefix = omNamespace.getPrefix(); |
| } else { |
| uri = ""; |
| prefix = null; |
| } |
| String localName = omElement.getLocalName(); |
| String qName; |
| if (prefix == null || prefix.length() == 0) { |
| qName = localName; |
| } else { |
| qName = prefix + ":" + localName; |
| } |
| // For performance reasons, we always reuse the same instance of AttributesAdapter. |
| // This is explicitely allowed by the specification of the startElement method. |
| attributesAdapter.setAttributes(omElement); |
| contentHandler.startElement(uri, localName, qName, attributesAdapter); |
| generateEventsForChildren(omElement); |
| contentHandler.endElement(uri, localName, qName); |
| generatePrefixMappingEvents(omElement, false); |
| } |
| |
| private void generateEventsForChildren(OMContainer parent) throws SAXException { |
| for (Iterator it = parent.getChildren(); it.hasNext(); ) { |
| OMNode node = (OMNode)it.next(); |
| switch (node.getType()) { |
| case OMNode.DTD_NODE: |
| if (lexicalHandler != null) { |
| OMDocType doctype = (OMDocType)node; |
| lexicalHandler.startDTD(doctype.getRootName(), doctype.getPublicId(), doctype.getSystemId()); |
| lexicalHandler.endDTD(); |
| } |
| break; |
| case OMNode.ELEMENT_NODE: |
| generateEvents((OMElement)node); |
| break; |
| case OMNode.TEXT_NODE: |
| generateEvents((OMText)node, false); |
| break; |
| case OMNode.SPACE_NODE: |
| generateEvents((OMText)node, true); |
| break; |
| case OMNode.CDATA_SECTION_NODE: |
| if (lexicalHandler != null) { |
| lexicalHandler.startCDATA(); |
| } |
| generateEvents((OMText)node, false); |
| if (lexicalHandler != null) { |
| lexicalHandler.endCDATA(); |
| } |
| break; |
| case OMNode.COMMENT_NODE: |
| if (lexicalHandler != null) { |
| char[] ch = ((OMComment)node).getValue().toCharArray(); |
| lexicalHandler.comment(ch, 0, ch.length); |
| } |
| break; |
| case OMNode.PI_NODE: |
| OMProcessingInstruction pi = (OMProcessingInstruction)node; |
| contentHandler.processingInstruction(pi.getTarget(), pi.getValue()); |
| break; |
| case OMNode.ENTITY_REFERENCE_NODE: |
| contentHandler.skippedEntity(((OMEntityReference)node).getName()); |
| break; |
| default: |
| throw new IllegalStateException("Unrecognized node type " + node.getType()); |
| } |
| } |
| } |
| |
| private void generateEvents(OMText omText, boolean space) throws SAXException { |
| if (omText.isBinary()) { |
| // Stream the binary content |
| DataHandler dh = omText.getDataHandler(); |
| Base64EncodingWriterOutputStream out = new Base64EncodingWriterOutputStream(new ContentHandlerWriter(contentHandler)); |
| try { |
| dh.writeTo(out); |
| out.complete(); |
| } catch (IOException ex) { |
| Throwable cause = ex.getCause(); |
| if (cause instanceof SAXException) { |
| throw (SAXException)ex.getCause(); |
| } else { |
| throw new SAXException(ex); |
| } |
| } |
| } else { |
| char[] ch = omText.getTextCharacters(); |
| if (space) { |
| contentHandler.ignorableWhitespace(ch, 0, ch.length); |
| } else { |
| contentHandler.characters(ch, 0, ch.length); |
| } |
| } |
| } |
| |
| protected static class AttributesAdapter implements Attributes { |
| private List/*<OMAttribute>*/ attributes = new ArrayList(5); |
| |
| public void setAttributes(OMElement element) { |
| attributes.clear(); |
| for (Iterator it = element.getAllAttributes(); it.hasNext(); ) { |
| attributes.add(it.next()); |
| } |
| } |
| |
| @Override |
| public int getLength() { |
| return attributes.size(); |
| } |
| |
| @Override |
| public int getIndex(String qName) { |
| for (int i=0, len=attributes.size(); i<len; i++) { |
| if (getQName(i).equals(qName)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| @Override |
| public int getIndex(String uri, String localName) { |
| for (int i=0, len=attributes.size(); i<len; i++) { |
| if (getURI(i).equals(uri) && getLocalName(i).equals(localName)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| @Override |
| public String getLocalName(int index) { |
| return ((OMAttribute)attributes.get(index)).getLocalName(); |
| } |
| |
| @Override |
| public String getQName(int index) { |
| OMAttribute attribute = ((OMAttribute)attributes.get(index)); |
| OMNamespace ns = attribute.getNamespace(); |
| if (ns == null) { |
| return attribute.getLocalName(); |
| } else { |
| String prefix = ns.getPrefix(); |
| if (prefix == null || prefix.length() == 0) { |
| return attribute.getLocalName(); |
| } else { |
| return ns.getPrefix() + ":" + attribute.getLocalName(); |
| } |
| } |
| } |
| |
| @Override |
| public String getType(int index) { |
| return ((OMAttribute)attributes.get(index)).getAttributeType(); |
| } |
| |
| @Override |
| public String getType(String qName) { |
| int index = getIndex(qName); |
| return index == -1 ? null : getType(index); |
| } |
| |
| @Override |
| public String getType(String uri, String localName) { |
| int index = getIndex(uri, localName); |
| return index == -1 ? null : getType(index); |
| } |
| |
| @Override |
| public String getURI(int index) { |
| OMNamespace ns = ((OMAttribute)attributes.get(index)).getNamespace(); |
| return ns == null ? "" : ns.getNamespaceURI(); |
| } |
| |
| @Override |
| public String getValue(int index) { |
| return ((OMAttribute)attributes.get(index)).getAttributeValue(); |
| } |
| |
| @Override |
| public String getValue(String qName) { |
| int index = getIndex(qName); |
| return index == -1 ? null : getValue(index); |
| } |
| |
| @Override |
| public String getValue(String uri, String localName) { |
| int index = getIndex(uri, localName); |
| return index == -1 ? null : getValue(index); |
| } |
| } |
| } |