所以我的问题几乎与this previous StackOverflow question相同,但我正在重新提出这个问题,因为我不喜欢接受的答案。
我有一个连接的XML文档文件:
<?xml version="1.0" encoding="UTF-8"?>
<someData>...</someData>
<?xml version="1.0" encoding="UTF-8"?>
<someData>...</someData>
...
<?xml version="1.0" encoding="UTF-8"?>
<someData>...</someData>
我想解析每一个。
据我所知,我不能使用scala.xml.XML
,因为这取决于每个文件/字符串模型的一个文档。
我是否可以使用Parser
的子类来解析输入源中的XML文档?因为那时我可以做一些像many1 xmldoc
或类似的事情。
答案 0 :(得分:0)
如果您的顾虑是安全的,您可以使用独特的标签包裹您的块:
def mkTag = "block"+util.Random.alphanumeric.take(20).mkString
val reader = io.Source.fromFile("my.xml")
def mkChunk(it: Iterator[String], chunks: Vector[String] = Vector.empty): Vector[String] = {
val (chunk,extra) = it.span(s => !(s.startsWith("<?xml") && s.endsWith("?>"))
val tag = mkTag
def tagMe = "<"+tag+">"+chunk.mkString+"</"+tag+">"
if (!extra.hasNext) chunks :+ tagMe
else if (!chunk.hasNext) mkChunk(extra, chunks)
else mkChunk(extra, chunks :+ tagMe)
}
val chunks = mkChunk(reader.getLines())
reader.close
val answers = xml.XML.fromString("<everything>"+chunks.mkString+"</everything>")
// Now take apart the resulting parse
由于您提供了唯一的封闭标记,如果有人在中间某处嵌入了文字XML标记,则可能会出现解析错误,但不会意外地出错解析次数。
(警告:代码输入但根本没有检查 - 这是为了提出想法,而不是完全正确的行为。)
答案 1 :(得分:0)
好的,我想出了一个我更满意的答案。
基本上我尝试使用SAXParser
来解析XML,就像scala.xml.XML.load
一样,但要注意SAXParseException
,表明解析器遇到了<?xml
错误的地方。
然后,我抓取已经解析过的任何根元素,将输入放回到足够的位置,然后从那里重新启动解析。
// An input stream that can recover from a SAXParseException
object ConcatenatedXML {
// A reader that can be rolled back to the location of an exception
class Relocator(val re : java.io.Reader) extends java.io.Reader {
var marked = 0
var firstLine : Int = 1
var lineStarts : IndexedSeq[Int] = Vector(0)
override def read(arr : Array[Char], off : Int, len : Int) = {
// forget everything but the start of the last line in the
// previously marked area
val pos = lineStarts(lineStarts.length - 1) - marked
firstLine += lineStarts.length - 1
// read the next chunk of data into the given array
re.mark(len)
marked = re.read(arr,off,len)
// find the line starts for the lines in the array
lineStarts = pos +: (for (i <- 0 until marked if arr(i+off) == '\n') yield (i+1))
marked
}
override def close { re.close }
override def markSupported = false
def relocate(line : Int, col : Int , off : Int) {
re.reset
val skip = lineStarts( line - firstLine ) + col + off
re.skip(skip)
marked = 0
firstLine = 1
lineStarts = Vector(0)
}
}
def parse( str : String ) : List[scala.xml.Node] = parse(new java.io.StringReader(str))
def parse( re : java.io.Reader ) : List[scala.xml.Node] = parse(new Relocator(re))
// parse all the concatenated XML docs out of a file
def parse( src : Relocator ) : List[scala.xml.Node] = {
val parser = javax.xml.parsers.SAXParserFactory.newInstance.newSAXParser
val adapter = new scala.xml.parsing.NoBindingFactoryAdapter
adapter.scopeStack.push(scala.xml.TopScope)
try {
// parse this, assuming it's the last XML doc in the string
parser.parse( new org.xml.sax.InputSource(src), adapter )
adapter.scopeStack.pop
adapter.rootElem.asInstanceOf[scala.xml.Node] :: Nil
} catch {
case (e : org.xml.sax.SAXParseException) => {
// we found the start of another xmldoc
if (e.getMessage != """The processing instruction target matching "[xX][mM][lL]" is not allowed."""
|| adapter.hStack.length != 1 || adapter.hStack(0) == null){
throw(e)
}
// tell the adapter we reached the end of a document
adapter.endDocument
// grab the current root node
adapter.scopeStack.pop
val node = adapter.rootElem.asInstanceOf[scala.xml.Node]
// reset to the start of this doc
src.relocate(e.getLineNumber, e.getColumnNumber, -6)
// and parse the next doc
node :: parse( src )
}
}
}
}
println(ConcatenatedXML.parse(new java.io.BufferedReader(
new java.io.FileReader("temp.xml")
)))
println(ConcatenatedXML.parse(
"""|<?xml version="1.0" encoding="UTF-8"?>
|<firstDoc><inner><innerer><innermost></innermost></innerer></inner></firstDoc>
|<?xml version="1.0" encoding="UTF-8"?>
|<secondDoc></secondDoc>
|<?xml version="1.0" encoding="UTF-8"?>
|<thirdDoc>...</thirdDoc>
|<?xml version="1.0" encoding="UTF-8"?>
|<lastDoc>...</lastDoc>""".stripMargin
))
try {
ConcatenatedXML.parse(
"""|<?xml version="1.0" encoding="UTF-8"?>
|<firstDoc>
|<?xml version="1.0" encoding="UTF-8"?>
|</firstDoc>""".stripMargin
)
throw(new Exception("That should have failed"))
} catch {
case _ => println("catches really incomplete docs")
}