2012-04-17 2 views
0

Так что моя проблема практически идентична this previous StackOverflow question, но я задаю вопрос, потому что мне не нравится принятый ответ.Scala: Анализ объединенных XML-документов

У меня есть файл каскадных документов 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

Этот вопрос является дубликатом, если вы не объясните _why_, что вам не нравятся другие ответы. Утверждение, что нет синтаксического анализа того типа, который вы предложили, недостаточно для ИМО для полного вопроса/ответа. –

+0

@RexKerr: Справедливая точка. Я считаю, что принятый ответ неприемлем, поскольку «ломать« rampion

ответ

0

Хорошо, я пришел с ответом я более доволен.

В основном я пытаюсь разобрать XML с помощью SAXParser, так же, как 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") 
} 
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-тег где-то посередине, но вы не будете случайно получите неправильное количество разборов.

(Внимание:. Код набран, но не проверил на всех - это дать идею, не совсем правильное поведение)