2015-09-02 33 views
1

我有一個文件有效地包含多個相同格式的XML文件,因此該文件本身不是有效的XML;例如:將包含多個XML文件的單個大文件讀入C#中的多個xml記錄

<?xml version='1.0' encoding='UTF-8'?> 
<Proposal xmlns="a namespace"> 
    <ASubnode>Text</ASubNode> 
    <LotsOfOtherNodes /> 
</Proposal> 
<?xml version='1.0' encoding='UTF-8'?> 
<Proposal xmlns="a namespace"> 
    <ASubnode>Text</ASubNode> 
    <LotsOfOtherNodes /> 
</Proposal> 
.... 

我想處理所有建議節點,一次一個;例如:

foreach (var proposal in file) 
    do something 

我不能使用XmlReader,因爲它在到達中間XML聲明節點時引發異常。我可能會將整個文件讀入一個字符串,然後使用Split方法,但這些文件的大小是千兆字節,因此作爲選項並不是特別有吸引力。看起來,我可以一次讀取一行文件,通過正則表達式搜索適當的節點,但這些文件不是像上面那樣以每行一個節點進行行格式化,而是包含非常長的多行節點,節點文本中出現隨機換行符。

有沒有手動實現文本解析器的方法?

+0

ReadAllText,斯普利特,序列化 –

+0

你可以使用'String.IndexOf',並從一個終端節點「跳」到接下來,處理它們之間的文本。 – germi

+0

xml聲明是從新行開始的,還是可以從同一行開始? –

回答

0

您可以通過閱讀文本行因爲xml文檔的標題是相同的:

IEnumerable<XDocument> GetDocuments(Stream bulkStream) 
{ 
    var reader = new StreamReader(bulkStream); 
    var sb = new StringBuilder(); 
    var firstLine = reader.ReadLine(); 
    string line = firstLine;  
    while(line != null) 
    { 
     sb.Clear(); 
     sb.Append(firstLine); 
     while((line = reader.ReadLine()) != null && line != firstLine) 
     { 
      sb.Append(line); 
     } 

     yield return XDocument.Parse(sb.ToString()); 
    } 
} 

UPDATE: 下面的工作,即使聲明可以開始行的在兩者之間:

IEnumerable<XDocument> GetDocuments(Stream bulkStream) 
{ 
    const string decl = @"<?xml version='1.0' encoding='UTF-8'?>"; 
    var sb = new StringBuilder(); 

    bool start = true; 
    foreach(var line in GetLines(bulkStream).Where(l => !string.IsNullOrWhiteSpace(l))) 
    { 
     if (start) 
     { 
      if (line == decl) 
       start = false; 
      sb.AppendLine(line); 
     } 
     else 
     { 
      if (line == decl) 
      { 
       sb.ToString().Dump(); 
       yield return XDocument.Parse(sb.ToString()); 

       sb.Clear(); 
       start = true; 
       sb.AppendLine(line); 
      } 
      else 
       sb.AppendLine(line); 
     } 
    } 

    sb.ToString().Dump(); 
    yield return XDocument.Parse(sb.ToString()); 
} 

IEnumerable<string> GetLines(Stream bulkStream) 
{ 
    const string decl = @"<?xml version='1.0' encoding='UTF-8'?>"; 
    var reader = new StreamReader(bulkStream); 
    string line; 
    while((line = reader.ReadLine()) != null) 
    { 
     if (line.Contains(decl)) 
     { 
      var declIndex = line.IndexOf(decl); 
      yield return line.Substring(0, declIndex); 
      yield return decl; 
      yield return line.Substring(declIndex + decl.Length); 
     } 
     else 
     { 
      yield return line; 
     } 
    } 
} 
+0

如果你修復錯誤,這將解析你的輸入:ASubnode - ASubNode –

+0

這幾乎可以工作。不幸的是,XML聲明並不總是單獨存在於一行上;例如,一些行如下所示: –

+0

然後,如果該行包含xml聲明,則可以擴展此方法來分割行,並根據需要追加到前一個字符串構建器或下一個字符串構建器。我希望XML聲明本身不會被拆分:) –

2

你有兩個選擇:

  1. 告訴XmlReader中不那麼挑剔。將XmlReaderSettings.ConformanceLevel設置爲ConformanceLevel.Fragment。這將使解析器忽略沒有根節點的事實。

    var settings = new XmlReaderSettings(); 
    settings.ConformanceLevel = ConformanceLevel.Fragment; 
    using (var reader = XmlReader.Create(textReader, settings)) 
    { 
        ... 
    } 
    
  2. 與「根」元素包裝你的XML文件,這樣你的文件將只有一個根節點

<?xml version='1.0' encoding='UTF-8'?> 
<root> 
    <Proposal xmlns="a namespace"> 
     <ASubnode>Text</ASubNode> 
     <LotsOfOtherNodes /> 
    </Proposal> 
    <?xml version='1.0' encoding='UTF-8'?> 
    <Proposal xmlns="a namespace"> 
     <ASubnode>Text</ASubNode> 
     <LotsOfOtherNodes /> 
    </Proposal> 
.... 
</root> 
+0

關於你的第二個建議:它甚至可以和那些'<?xml version ...?>'節點一起工作嗎?剛剛在Linqpad用'XDocument'測試了它,並且我得到了'意外的XML聲明'[...]。 – germi

+0

@germi,我沒有意識到每個節點都有自己的<?xml version ...?>。在這種情況下,可以做的是在裝入元素之前可以刪除那些節點,或者使用選項1。 –

+0

刪除XML聲明是顯而易見的事情。不幸的是,這需要讀取整個文件並將其作爲字符串處理。這些文件的大小是幾千兆字節,因此不太實用。我想我可以按塊讀取文件,解析輸入並隨時寫出新的臨時文件,但這需要編寫一個解析器。識別XML聲明不是直截了當的,因爲它們並不一定就是一條線。 –