| /* |
| * 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.daffodil.processors |
| |
| import java.io.OutputStream |
| import java.io.OutputStreamWriter |
| |
| import scala.xml.NamespaceBinding |
| |
| import org.apache.daffodil.exceptions.Assert |
| import org.apache.daffodil.util.Indentable |
| import org.apache.daffodil.util.MStackOf |
| import org.apache.daffodil.util.MStackOfBoolean |
| import org.apache.daffodil.xml.XMLUtils |
| import org.xml.sax.Attributes |
| import org.xml.sax.ContentHandler |
| import org.xml.sax.Locator |
| |
| /** |
| * ContentHandler implementation that receives SAX events from DaffodilParseXMLReader to output |
| * XML to the specified outputStream. Depending on the features set in the XMLReader, it uses either |
| * prefixMappings or attributes to determine the prefix of the XML element. This means it will always |
| * try to find and print a prefix if an element has a URI. |
| * |
| * @param out outputStream object to write generated XML to |
| * @param pretty boolean to pretty print XML if true, or not if false |
| */ |
| class DaffodilParseOutputStreamContentHandler(out: OutputStream, pretty: Boolean = false) |
| extends ContentHandler with Indentable { |
| private val writer = new OutputStreamWriter(out) |
| /** |
| * represents the currently active prefix mappings (i.e all mappings include from parent element), |
| * which is usefully for doing lookups |
| */ |
| private var activePrefixMapping: NamespaceBinding = null |
| /** |
| * represents only the prefix mapping of the current element. We use this to generate the prefix mappings |
| * when outputting the element tag |
| */ |
| private var currentElementPrefixMapping: NamespaceBinding = null |
| /** |
| * used to maintain the correct scope of activePrefixMapping throughout processing. It is also used |
| * to reset the activePrefixMapping after processing each element. |
| */ |
| private lazy val activePrefixMappingContextStack = new MStackOf[NamespaceBinding] |
| private val outputNewlineStack: MStackOfBoolean = { |
| val s = MStackOfBoolean() |
| s.push(false) |
| s |
| } |
| |
| // if the top of the stack is true, we have guessed we should output a newline |
| private def outputNewline: Boolean = outputNewlineStack.top |
| |
| def reset(): Unit = { |
| resetIndentation() |
| writer.flush() |
| activePrefixMapping = null |
| currentElementPrefixMapping = null |
| activePrefixMappingContextStack.clear() |
| outputNewlineStack.clear() |
| outputNewlineStack.push(false) //to match initialization state |
| out.flush() |
| } |
| |
| override def setDocumentLocator(locator: Locator): Unit = { |
| // do nothing |
| } |
| |
| override def startDocument(): Unit = { |
| writer.write("""<?xml version="1.0" encoding="UTF-8"?>""") |
| } |
| |
| override def endDocument(): Unit = { |
| writer.write(System.lineSeparator()) |
| writer.flush() |
| } |
| |
| override def startPrefixMapping(prefix: String, uri: String): Unit = { |
| val _prefix = if (prefix == "") null else prefix |
| // only add this new prefix mapping to the currentElementMapping. The |
| // mappings in this variable will be added to the active mapping later. |
| // This is necessary because we essentially prepend NamespaceBindings when |
| // adding new ones, which effectively reverses the order. When we add these |
| // mappings to the activePrefixMapping, we'll undo that reversal so things |
| // are in the correct order |
| currentElementPrefixMapping = NamespaceBinding(_prefix, uri, currentElementPrefixMapping) |
| } |
| |
| override def endPrefixMapping(prefix: String): Unit = { |
| // do nothing |
| } |
| |
| /** |
| * Uses Attributes, which is passed in to the startElement callback, to |
| * gather prefix mappings in the case where namespacePrefixes is true. New |
| * prefix mappings are added to the currentElementPrefixMapping bindings |
| */ |
| def processAttributePrefixMappings(atts: Attributes): Unit = { |
| var i = 0 |
| while (i < atts.getLength) { |
| val qName = atts.getQName(i) |
| if (qName.nonEmpty) { |
| // if qName is populated that implies namespacePrefixes == true, and we |
| // might have prefix mappings if the qname is a special xmlns value |
| if (qName == "xmlns") { |
| val pre = null |
| val uri = atts.getValue(i) |
| currentElementPrefixMapping = NamespaceBinding(pre, uri, currentElementPrefixMapping) |
| } else if (qName.startsWith("xmlns:")) { |
| val pre = qName.substring(6) |
| val uri = atts.getValue(i) |
| currentElementPrefixMapping = NamespaceBinding(pre, uri, currentElementPrefixMapping) |
| } else { |
| // not a prefix mapping, ignore this attribute |
| } |
| } else { |
| // no qname, so namespacePrefixes == false, which means we get no |
| // prefix mappings in the Attributes, only regular attributes such as xsi:nil. |
| // This can't be a prefix mapping, so ignore this attribute |
| } |
| i += 1 |
| } |
| } |
| |
| /** |
| * Uses Attributes, which is passed in to the startElement callback, to write |
| * element attributes. Prefix mappings are ignored and assumed to be written |
| * elsewhere. Uses activePrefixMappings to map uri's to prefixes, so prefix |
| * mappings in the Attributes must have already been processed and added to |
| * activePrefixMappings |
| */ |
| def writeNonNamespaceAttributes(writer: OutputStreamWriter, atts: Attributes): Unit = { |
| var i = 0 |
| while (i < atts.getLength) { |
| val qName = atts.getQName(i) |
| if (qName.nonEmpty) { |
| if (qName.startsWith("xmlns:") || qName == "xmlns") { |
| // namespace mapping, ignore |
| } else { |
| // regular attribute with qname such as xsi:nil |
| val attrVal = atts.getValue(i) |
| val attr = s""" ${qName}="${attrVal}"""" |
| writer.write(attr) |
| } |
| } else { |
| // no qname, so namespacePrefixes == false, which means we get no |
| // prefix mappings in the attributes, only regular attributes such as xsi:nil |
| // though not in qname form |
| val uri = atts.getURI(i) |
| val localName = atts.getLocalName(i) |
| // prefixed attribute, not prefix mapping, as they only show up as qnames |
| if (uri.nonEmpty && localName.nonEmpty) { |
| val maybePrefix = XMLUtils.maybePrefix(activePrefixMapping, uri) |
| // found a prefix; add to attribute pairings |
| if (maybePrefix.isDefined) { |
| val prefix = maybePrefix.get |
| val attrVal = atts.getValue(i) |
| val attr = s""" $prefix:$localName="${attrVal}"""" |
| writer.write(attr) |
| } else { |
| // if an attribute has a URI, we must have a prefix, even if it is null |
| Assert.invariantFailed("Cannot have URI with no prefix mapping") |
| } |
| } else { |
| // non prefixed attribute don't exist in Daffodil |
| Assert.invariantFailed("Cannot have an attribute with no qname, uri or localname") |
| } |
| } |
| i += 1 |
| } |
| } |
| |
| override def startElement( |
| uri: String, localName: String, qName: String, atts: Attributes): Unit = { |
| // the pop/true removes whatever is on the stack which is our previous guess for whether we |
| // would need a newline after the previous end tag. As we are currently at the start of a new |
| // tag, we want to correct that assumption (in case it was false) |
| outputNewlineStack.pop() |
| outputNewlineStack.push(true) |
| if (pretty) { |
| writer.write(System.lineSeparator()) |
| outputIndentation(writer) |
| } |
| |
| // scan the attributes for any prefix mappings, which will update |
| // currentElementPrefixMappings |
| processAttributePrefixMappings(atts) |
| |
| // at this point, all prefix mappings specific to this element have been |
| // added to currentElementPrefixMapping (either from startPrefixMapping or |
| // processAttributePrefixMappings). Note that these new mappings are in the |
| // reverse order than they should be written because they were prepended to |
| // NamespaceBinding. We now add these new mappings to the activePrefixMapping, |
| // and in doing so re-reverses the order so that they are in the correct |
| // order in the activePrefixMaping. This ensures we get find the right |
| // prefix during lookups and write the mappings in the correct order. |
| val previousPrefixMapping = activePrefixMapping |
| while (currentElementPrefixMapping != null) { |
| val prefix = currentElementPrefixMapping.prefix |
| val uri = currentElementPrefixMapping.uri |
| |
| // check to see if the prefix is already mapped to the same URI. If it |
| // is, ignore this mapping since it adds nothing new |
| val maybeUri = XMLUtils.maybeURI(activePrefixMapping, prefix) |
| if (maybeUri.isEmpty || maybeUri.get != uri) { |
| activePrefixMapping = NamespaceBinding(prefix, uri, activePrefixMapping) |
| } |
| currentElementPrefixMapping = currentElementPrefixMapping.parent |
| } |
| |
| // we always push, but activePrefixMapping won't always be populated with new information |
| // from startPrefixMapping or processAttributes |
| activePrefixMappingContextStack.push(activePrefixMapping) |
| |
| // handle start of tag |
| writer.write("<") |
| outputTagName(uri, localName, qName) |
| |
| // write only the part of activePrefixMapping that is new for this element |
| if (activePrefixMapping ne previousPrefixMapping) { |
| val pm = activePrefixMapping.buildString(previousPrefixMapping) |
| writer.write(pm) |
| } |
| |
| // write the non-namespace attributes from the Attributes object. Example |
| // attributes is xsi:nil |
| writeNonNamespaceAttributes(writer, atts) |
| |
| // handle end of tag |
| writer.write(">") |
| incrementIndentation() |
| // this push makes the assumption that we would not need to output a newline after this end |
| // tag is complete |
| outputNewlineStack.push(false) |
| } |
| |
| private def outputTagName(uri: String, localName: String, qName: String): Unit = { |
| val tagName = { |
| if (qName.nonEmpty) { |
| // namespacePrefixes == true, so qName is populated |
| qName |
| } else { |
| // namespacePrefixes == false, so uri and localName are populated, but not qname |
| // and attributes don't have prefix mapping information |
| // if we have no qName, we need to use activePrefixMapping to get the prefix of the uri |
| // to build the qname |
| val sanitizedUri = if (uri.isEmpty) null else uri |
| val maybePrefix = XMLUtils.maybePrefix(activePrefixMapping, sanitizedUri) |
| if (maybePrefix.isDefined) { |
| val pre = maybePrefix.get |
| s"$pre:$localName" |
| } else { |
| localName |
| } |
| } |
| } |
| writer.write(tagName) |
| } |
| |
| override def endElement(uri: String, localName: String, qName: String): Unit = { |
| decrementIndentation() |
| if (outputNewline) { |
| if (pretty) { |
| writer.write(System.lineSeparator()) |
| outputIndentation(writer) |
| } |
| } |
| writer.write("</") |
| outputTagName(uri, localName, qName) |
| writer.write(">") |
| outputNewlineStack.pop() |
| |
| Assert.invariant(!activePrefixMappingContextStack.isEmpty) |
| // throw out current prefix mapping context as we're done with it |
| activePrefixMappingContextStack.pop |
| |
| // set the activePrefixMapping to the next mapping in the stack if the stack isn't empty |
| if (activePrefixMappingContextStack.isEmpty) { |
| activePrefixMapping = null |
| } else { |
| activePrefixMapping = activePrefixMappingContextStack.top |
| } |
| } |
| |
| override def characters(ch: Array[Char], start: Int, length: Int): Unit = { |
| writer.write(ch, start, length) |
| } |
| |
| override def ignorableWhitespace(ch: Array[Char], start: Int, length: Int): Unit = { |
| // do nothing |
| } |
| |
| override def processingInstruction(target: String, data: String): Unit = { |
| // do nothing |
| } |
| |
| override def skippedEntity(name: String): Unit = { |
| // do nothing |
| } |
| } |