package scala.xml
package include.sax
import scala.xml.include._
import org.xml.sax.{ Attributes, XMLReader, Locator }
import org.xml.sax.helpers.{ XMLReaderFactory, XMLFilterImpl, NamespaceSupport, AttributesImpl }
import java.io.{ InputStream, BufferedInputStream, InputStreamReader, IOException, UnsupportedEncodingException }
import java.util.Stack
import java.net.{ URL, MalformedURLException }
class XIncludeFilter extends XMLFilterImpl {
final val XINCLUDE_NAMESPACE = "http://www.w3.org/2001/XInclude";
private val bases = new Stack[URL]();
private val locators = new Stack[Locator]();
override def setDocumentLocator(locator: Locator) {
locators.push(locator)
val base = locator.getSystemId()
try {
bases.push(new URL(base))
}
catch {
case e:MalformedURLException =>
throw new UnsupportedOperationException("Unrecognized SYSTEM ID: " + base)
}
super.setDocumentLocator(locator)
}
private var level = 0
def insideIncludeElement(): Boolean = level != 0
override def startElement(uri: String, localName: String, qName: String, atts1: Attributes) {
var atts = atts1
if (level == 0) {
val base = atts.getValue(NamespaceSupport.XMLNS, "base")
val parentBase = bases.peek().asInstanceOf[URL]
var currentBase = parentBase
if (base != null) {
try {
currentBase = new URL(parentBase, base)
}
catch {
case e: MalformedURLException =>
throw new SAXException("Malformed base URL: "
+ currentBase, e)
}
}
bases.push(currentBase);
if (uri.equals(XINCLUDE_NAMESPACE) && localName.equals("include")) {
val href = atts.getValue("href")
if (href==null) {
throw new SAXException("Missing href attribute")
}
var parse = atts.getValue("parse")
if (parse == null) parse = "xml"
if (parse.equals("text")) {
val encoding = atts.getValue("encoding");
includeTextDocument(href, encoding);
}
else if (parse.equals("xml")) {
includeXMLDocument(href);
}
else {
throw new SAXException(
"Illegal value for parse attribute: " + parse);
}
level += 1
}
else {
if (atRoot) {
val attsImpl = new AttributesImpl(atts)
attsImpl.addAttribute(NamespaceSupport.XMLNS, "base",
"xml:base", "CDATA", currentBase.toExternalForm())
atts = attsImpl
atRoot = false
}
super.startElement(uri, localName, qName, atts)
}
}
}
override def endElement(uri: String, localName: String, qName: String) {
if (uri.equals(XINCLUDE_NAMESPACE)
&& localName.equals("include")) {
level -= 1;
}
else if (level == 0) {
bases.pop()
super.endElement(uri, localName, qName)
}
}
private var depth = 0;
override def startDocument() {
level = 0
if (depth == 0) super.startDocument()
depth += 1
}
override def endDocument() {
locators.pop()
bases.pop();
depth -= 1
if (depth == 0) super.endDocument()
}
override def startPrefixMapping(prefix: String , uri: String) {
if (level == 0) super.startPrefixMapping(prefix, uri)
}
override def endPrefixMapping(prefix: String) {
if (level == 0) super.endPrefixMapping(prefix)
}
override def characters(ch: Array[Char], start: Int, length: Int) {
if (level == 0) super.characters(ch, start, length)
}
override def ignorableWhitespace(ch: Array[Char], start: Int, length: Int) {
if (level == 0) super.ignorableWhitespace(ch, start, length)
}
override def processingInstruction(target: String, data: String) {
if (level == 0) super.processingInstruction(target, data)
}
override def skippedEntity(name: String) {
if (level == 0) super.skippedEntity(name)
}
private def getLocation(): String = {
var locationString = ""
val locator = locators.peek().asInstanceOf[Locator]
var publicID = ""
var systemID = ""
var column = -1
var line = -1
if (locator != null) {
publicID = locator.getPublicId()
systemID = locator.getSystemId()
line = locator.getLineNumber()
column = locator.getColumnNumber()
}
locationString = (" in document included from " + publicID
+ " at " + systemID
+ " at line " + line + ", column " + column);
locationString
}
private def includeTextDocument(url: String, encoding1: String) {
var encoding = encoding1
if (encoding == null || encoding.trim().equals("")) encoding = "UTF-8";
var source: URL = null
try {
val base = bases.peek().asInstanceOf[URL]
source = new URL(base, url)
}
catch {
case e: MalformedURLException =>
val ex = new UnavailableResourceException("Unresolvable URL " + url
+ getLocation());
ex.setRootCause(e);
throw new SAXException("Unresolvable URL " + url + getLocation(), ex);
}
try {
val uc = source.openConnection()
val in = new BufferedInputStream(uc.getInputStream())
var encodingFromHeader = uc.getContentEncoding()
var contentType = uc.getContentType()
if (encodingFromHeader != null)
encoding = encodingFromHeader
else {
if (contentType != null) {
contentType = contentType.toLowerCase();
if (contentType.equals("text/xml")
|| contentType.equals("application/xml")
|| (contentType.startsWith("text/") && contentType.endsWith("+xml") )
|| (contentType.startsWith("application/") && contentType.endsWith("+xml"))) {
encoding = EncodingHeuristics.readEncodingFromStream(in);
}
}
}
val reader = new InputStreamReader(in, encoding)
val c = new Array[Char](1024)
var charsRead: Int = 0
do {
charsRead = reader.read(c, 0, 1024);
if (charsRead > 0) this.characters(c, 0, charsRead);
} while (charsRead != -1) ;
}
catch {
case e: UnsupportedEncodingException =>
throw new SAXException("Unsupported encoding: "
+ encoding + getLocation(), e);
case e: IOException =>
throw new SAXException("Document not found: "
+ source.toExternalForm() + getLocation(), e);
}
}
private var atRoot = false
private def includeXMLDocument(url: String) {
val source =
try new URL(bases.peek(), url)
catch {
case e: MalformedURLException =>
val ex = new UnavailableResourceException("Unresolvable URL " + url + getLocation())
ex setRootCause e
throw new SAXException("Unresolvable URL " + url + getLocation(), ex)
}
try {
val parser: XMLReader =
try XMLReaderFactory.createXMLReader()
catch {
case e: SAXException =>
try XMLReaderFactory.createXMLReader(XercesClassName)
catch { case _: SAXException => return System.err.println("Could not find an XML parser") }
}
parser setContentHandler this
val resolver = this.getEntityResolver()
if (resolver != null)
parser setEntityResolver resolver
val previousLevel = level
this.level = 0
if (bases contains source)
throw new SAXException(
"Circular XInclude Reference",
new CircularIncludeException("Circular XInclude Reference to " + source + getLocation())
)
bases push source
atRoot = true
parser parse source.toExternalForm()
this.level = previousLevel
bases.pop()
}
catch {
case e: IOException =>
throw new SAXException("Document not found: " + source.toExternalForm() + getLocation(), e)
}
}
}