2010-05-20 14 views
36

¿Cómo puedo establecer el espacio de nombres predeterminado de un XDocument existente (para poder deserializarlo con DataContractSerializer)? He intentado lo siguiente:Cómo configurar el espacio de nombres XML predeterminado para un XDocument

var doc = XDocument.Parse("<widget/>"); 
var attrib = new XAttribute("xmlns", 
          "http://schemas.datacontract.org/2004/07/Widgets"); 
doc.Root.Add(attrib); 

La excepción es que se ve es The prefix '' cannot be redefined from '' to 'http://schemas.datacontract.org/2004/07/Widgets' within the same start element tag.

¿Alguna idea?

+2

cómo es esto no es un defecto en LINQ to XML? – micahhoover

+1

Trate de usar un XElemenet en lugar de XDocument para ver si funciona (vea MSDN: http://msdn.microsoft.com/en-us/library/bb387069(v=vs.100).aspx) – juFo

+0

http: // www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx – KyleMit

Respuesta

41

Parece que Linq to XML no proporciona una API para este caso de uso (descargo de responsabilidad: no investigé mucho). Si cambia el espacio de nombre del elemento raíz, así:

XNamespace xmlns = "http://schemas.datacontract.org/2004/07/Widgets"; 
doc.Root.Name = xmlns + doc.Root.Name.LocalName; 

Solo se cambiará el espacio de nombres del elemento raíz. Todos los niños tendrán una etiqueta xmlns vacía explícita.

Una solución podría ser algo como esto:

public static void SetDefaultXmlNamespace(this XElement xelem, XNamespace xmlns) 
{ 
    if(xelem.Name.NamespaceName == string.Empty) 
     xelem.Name = xmlns + xelem.Name.LocalName; 
    foreach(var e in xelem.Elements()) 
     e.SetDefaultXmlNamespace(xmlns); 
} 

// ... 
doc.Root.SetDefaultXmlNamespace("http://schemas.datacontract.org/2004/07/Widgets"); 

O, si se prefiere una versión que no mutar el documento existente:

public XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns) 
{ 
    XName name; 
    if(xelem.Name.NamespaceName == string.Empty) 
     name = xmlns + xelem.Name.LocalName; 
    else 
     name = xelem.Name; 
    return new XElement(name, 
        from e in xelem.Elements() 
        select e.WithDefaultXmlNamespace(xmlns)); 
} 
+0

Gracias. Tu primera solución funciona para mí. +1 para crear un método de extensión. –

+0

Tiene el mismo error (el prefijo '' etc ...). – micahhoover

+3

En serio.¿Cómo es el caso de uso literalmente más común que no se admite? – aaronburro

43

No estoy seguro si esto ya trabajó en .net 3.5 o sólo en 4, pero esto funciona bien para mí:

XNamespace ns = @"http://mynamespace"; 
var result = new XDocument(
    new XElement(ns + "rootNode", 
     new XElement(ns + "child", 
      new XText("Hello World!") 
     ) 
    ) 
); 

produce este documento:

<rootNode xmlns="http://mynamespace"> 
    <child>Hello World!</child> 
</rootNode> 

Importante es utilizar siempre la sintaxis ns + "NodeName".

+1

Esta es básicamente la mejor respuesta. Tenga en cuenta que funciona por sobrecarga del operador en el 'XNamespace', por lo que (ns + name) devuelve un * XName * que apunta a _exact el mismo objeto XNamespace_. No se puede hacer esto usando 'XName.Get()', desafortunadamente. También hace intercesión de cadena en nombres locales, para que no termines con demasiadas cadenas en la memoria. –

+3

Uso una función como esta (donde xmlns está previamente configurado): 'Func ns = s => xmlns + s;' Entonces solo use 'ns (" MyNodeName ")' – joshcomley

6

que tenían el mismo requisito, pero se me ocurrió algo diferente de menor importancia:

/// <summary> 
/// Sets the default XML namespace of this System.Xml.Linq.XElement 
/// and all its descendants 
/// </summary> 
public static void SetDefaultNamespace(this XElement element, XNamespace newXmlns) 
{ 
    var currentXmlns = element.GetDefaultNamespace(); 
    if (currentXmlns == newXmlns) 
     return; 

    foreach (var descendant in element.DescendantsAndSelf() 
     .Where(e => e.Name.Namespace == currentXmlns)) //!important 
    { 
     descendant.Name = newXmlns.GetName(descendant.Name.LocalName); 
    } 
} 

Si desea hacerlo correctamente, usted tiene que considerar, que el elemento puede contener elementos de extensión de espacios de nombres diferentes. No desea cambiarlos todos, sino solo los elementos de espacio de nombres predeterminados.

3

R. Martinho Fernandes, la respuesta anterior, (eso no muta el documento existente) solo necesita una pequeña modificación para que los valores del elemento también se devuelvan. No he probado esto con angustia, solo estaba jugando con linqpad, lo siento, no se proporcionaron pruebas de unidad.

public static XElement SetNamespace(this XElement src, XNamespace ns) 
{ 
    var name = src.isEmptyNamespace() ? ns + src.Name.LocalName : src.Name; 
    var element = new XElement(name, src.Attributes(), 
      from e in src.Elements() select e.SetNamespace(ns)); 
    if (!src.HasElements) element.Value = src.Value; 
    return element; 
} 

public static bool isEmptyNamespace(this XElement src) 
{ 
    return (string.IsNullOrEmpty(src.Name.NamespaceName)); 
} 
2

método de extensión modificado para incluir XElement.Value (es decir, los nodos hoja):

public static XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns) 
{ 
    XName name; 
    if (xelem.Name.NamespaceName == string.Empty) 
     name = xmlns + xelem.Name.LocalName; 
    else 
     name = xelem.Name; 
    if (xelem.Elements().Count() == 0) 
    { 
     return new XElement(name, xelem.Value); 
    } 
    return new XElement(name, 
        from e in xelem.Elements() 
        select e.WithDefaultXmlNamespace(xmlns)); 
} 

Y ahora funciona para mí!

-1

no se olvide de copiar también el resto de atributos:

public static XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns) 
    { 
     XName name; 
     if (xelem.Name.NamespaceName == string.Empty) 
      name = xmlns + xelem.Name.LocalName; 
     else 
      name = xelem.Name; 


     XElement retelement; 
     if (!xelem.Elements().Any()) 
     { 
      retelement = new XElement(name, xelem.Value); 
     } 
     else 
     retelement= new XElement(name, 
      from e in xelem.Elements() 
      select e.WithDefaultXmlNamespace(xmlns)); 

     foreach (var at in xelem.Attributes()) 
     { 
      retelement.Add(at); 
     } 

     return retelement; 
    } 
Cuestiones relacionadas