使用XmlDocument从带或不带命名空间的xml文件中读取内容

11

我有一些使用XmlDocument从带有命名空间的xml文件中读取的代码。 我的挑战在于,我当前硬编码了我正在读取的文件的命名空间,并将其传递给XmlNamespaceManager。 我希望我的方法更加灵活。 能够阅读任何类型的xml文件。 如果它具有命名空间,则使用命名空间管理器读取元素而无需硬编码命名空间。 如果文件没有命名空间,则继续解析它。 以下是我所做的。

xmldoc = new XmlDocument ();
xmldoc.Load (fileLocation);


XmlNamespaceManager nameSpaceManager = new XmlNamespaceManager(xmldoc.NameTable);

nameSpaceManager.AddNamespace ("ns","http://schemas.sample.data.org/2005");

XmlNodeList nodeList = xmldoc.SelectNodes("/ns:Demo/ns:Items",  nameSpaceManager);
if (nodeList != null) 
{
    foreach (XmlNode childNode in nodeList) 
    {
        string first = childNode.SelectSingleNode ("ns:First", nameSpaceManager).InnerText;
        string second= childNode.SelectSingleNode ("ns:Second", nameSpaceManager).InnerText;
        string third = childNode.SelectSingleNode ("ns:Third", nameSpaceManager).InnerText;
    }
}

这是我正在使用的示例 XML 文件

<Demo xmlns:i="http://www.justasample.com" xmlns="http://schemas.sample.data.org/2005">
 <Items>

  <First>first</First>
  <Second>second</Second>
  <Third>third</Third>

  </Items>

</Demo>

1
不使用XDocument的原因是什么?使用它可以使所有事情,特别是命名空间,变得更加容易。 - H H
2
好的,这并不是完全重复。但请更清楚地说明您当前方法的工作情况和问题。 - H H
@HenkHolterman,它不一定只是XmlDocument。我对任何其他建议都持开放态度。我认为我在问题中提到这一点是因为这是我实现的内容。为了澄清我的问题,我的当前方法效果不太好,因为命名空间是硬编码的。我需要摆脱它,并使其能够与任何xml文件一起使用。 - naffie
4个回答

20

您可以考虑以下选项:

  1. 确定文档是否包含命名空间,并根据情况构建XPath查询
  2. 使用不受命名空间限制的XPath,例如local-name(),它将忽略命名空间

选项1

var xmlDoc = new XmlDocument();
xmlDoc.Load(fileLocation);
//determine  whether document contains namespace
var namespaceName = "ns";
var namespacePrefix = string.Empty;
XmlNamespaceManager nameSpaceManager = null;
if (xmlDoc.FirstChild.Attributes != null)
{
    var xmlns = xmlDoc.FirstChild.Attributes["xmlns"];
    if (xmlns != null)
    {
          nameSpaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
          nameSpaceManager.AddNamespace(namespaceName, xmlns.Value);
          namespacePrefix = namespaceName + ":";
    }
}

XmlNodeList nodeList = xmlDoc.SelectNodes(string.Format("/{0}Demo/{0}Items",namespacePrefix), nameSpaceManager);
if (nodeList != null)
{
    foreach (XmlNode childNode in nodeList)
    {
       string first = childNode.SelectSingleNode(namespacePrefix + "First", nameSpaceManager).InnerText;
       string second = childNode.SelectSingleNode(namespacePrefix + "Second", nameSpaceManager).InnerText;
       string third = childNode.SelectSingleNode(namespacePrefix +  "Third", nameSpaceManager).InnerText;
     }
 }

方案二

XmlNodeList nodeList = xmlDoc.SelectNodes("/*[local-name() = 'Demo']/*[local-name() = 'Items']");
if (nodeList != null)
{
    foreach (XmlNode childNode in nodeList)
    {    
        string first = childNode.SelectSingleNode("*[local-name() = 'First']").InnerText;
        string second = childNode.SelectSingleNode("*[local-name() = 'Second']").InnerText;
        string third = childNode.SelectSingleNode("*[local-name() = 'Third']").InnerText;
     }
}

1
非常感谢您的回答。我会立即开始实施并告知您它的效果。谢谢。 - naffie
1
我尝试了两个选项,它们都很好用。非常感谢。我接受这个答案是因为它简单易懂。有一个快速的问题,在实现选项2中,是否有一种方法可以忽略节点"Demo",就像我们处理命名空间一样?假设"Demo"是自动生成的且不是固定的...下次它将具有另一个唯一名称。但内部节点将始终被称为"Items"。在实现中是否可能忽略'Demo'但仍然解析文件? - naffie
2
当然,xpath //*[local-name() = 'Items'] 可以解决问题,例如:XmlNodeList nodeList = xmlDoc.SelectNodes("//*[local-name() = 'Items']"); - Vadim Gremyachev
1
我也实现了最后一部分,并且它有效。这是我现在拥有的:xmlDoc.SelectNodes("//*[local-name() = 'Items']"); - naffie
1
选项2不错,这样你就可以使用多个命名空间来解析它,而且效果非常好。 - Sameers Javed
显示剩余2条评论

1

改进Vadim的Option1,但使用XDocument API(而不是XmlDocument)和F#代替C#:

let fileStream = File.Open(fileLocation, FileMode.Open)
let xDocument = XDocument.Load fileStream
let nsOpt =
    let nsString = xDocument.Root.Name.Namespace.ToString()
    if String.IsNullOrEmpty nsString then
        Console.Error.WriteLine "Warning: no namespace URL found in xmlns attrib"
        None
    else
        let nsManager = XmlNamespaceManager(NameTable())
        let nsPrefix = "x"
        nsManager.AddNamespace(nsPrefix, nsString)
        if nsString <> "http://schemas.sample.data.org/2005" then
            Console.Error.WriteLine "Warning: the namespace URL doesn't match expectations, query may result in no elements"
        Some(nsManager, sprintf "%s:" nsPrefix)

let query = "//{0}Demo/{0}Items"
let nodes =
    match nsOpt with
    | None ->
        let fixedQuery = String.Format(query, String.Empty)
        xDocument.XPathSelectElements fixedQuery
    | Some(nsManager, nsPrefix) ->
        let fixedQuery = String.Format(query, nsPrefix)
        xDocument.XPathSelectElements(fixedQuery, nsManager)
for node in nodes do
    ...

0

使用 Linq-to-XML 和我的 扩展库,特别是内部处理的 ToXName,您可以执行以下操作:

XElement root = XElement.Load(fileLocation);
var items = root.Descendants("Items")
                .Select(item => new
                {
                    First = item.Get("First", ""),
                    Second= item.Get("Second", ""),
                    Third = item.Get("Third", "")
                });

因此,每个FirstSecondThird元素都将使用Items元素的命名空间来确定自己的命名空间。


@Chuck Savage 谢谢您的回复。我很感激。 - naffie

0

您可以在XML文件中提供所有命名空间,并动态添加它们。

var document = new XmlDocument();
document.LoadXml(rawData);

var nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("dpx", document.DocumentElement.NamespaceURI); //default

var attributeCollection = document.DocumentElement.Attributes;
for (int i = 0; i < attributeCollection.Count; i++)
{
    var isAttribute = nsmgr.LookupNamespace(attributeCollection[i].LocalName) == null;     
    if (isAttribute)
        nsmgr.AddNamespace(attributeCollection[i].LocalName, attributeCollection[i].Value);
}

XmlElement xmlElem = document.DocumentElement;
var node = xmlElem.SelectSingleNode(xpath, nsmgr);

如果XML中的节点没有前缀,在给出路径时应该为它们指定默认前缀


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接