Senza ricorrere a una soluzione basata su XSLT, se vuoi pulito, elegante e intelligente, avresti bisogno di un supporto dal framework, in particolare, il pattern del visitatore potrebbe renderlo un gioco da ragazzi. Purtroppo non è disponibile qui.
L'ho implementato ispirato a LINQ ExpressionVisitor
per avere una struttura simile. In questo modo è possibile applicare il pattern del visitatore agli oggetti XML (LINQ-to-). (Ho fatto test limitati su questo ma funziona bene per quanto ne so)
public abstract class XObjectVisitor
{
public virtual XObject Visit(XObject node)
{
if (node != null)
return node.Accept(this);
return node;
}
public ReadOnlyCollection<XObject> Visit(IEnumerable<XObject> nodes)
{
return nodes.Select(node => Visit(node))
.Where(node => node != null)
.ToList()
.AsReadOnly();
}
public T VisitAndConvert<T>(T node) where T : XObject
{
if (node != null)
return Visit(node) as T;
return node;
}
public ReadOnlyCollection<T> VisitAndConvert<T>(IEnumerable<T> nodes) where T : XObject
{
return nodes.Select(node => VisitAndConvert(node))
.Where(node => node != null)
.ToList()
.AsReadOnly();
}
protected virtual XObject VisitAttribute(XAttribute node)
{
return node.Update(node.Name, node.Value);
}
protected virtual XObject VisitComment(XComment node)
{
return node.Update(node.Value);
}
protected virtual XObject VisitDocument(XDocument node)
{
return node.Update(
node.Declaration,
VisitAndConvert(node.Nodes())
);
}
protected virtual XObject VisitElement(XElement node)
{
return node.Update(
node.Name,
VisitAndConvert(node.Attributes()),
VisitAndConvert(node.Nodes())
);
}
protected virtual XObject VisitDocumentType(XDocumentType node)
{
return node.Update(
node.Name,
node.PublicId,
node.SystemId,
node.InternalSubset
);
}
protected virtual XObject VisitProcessingInstruction(XProcessingInstruction node)
{
return node.Update(
node.Target,
node.Data
);
}
protected virtual XObject VisitText(XText node)
{
return node.Update(node.Value);
}
protected virtual XObject VisitCData(XCData node)
{
return node.Update(node.Value);
}
#region Implementation details
internal InternalAccessor Accessor
{
get { return new InternalAccessor(this); }
}
internal class InternalAccessor
{
private XObjectVisitor visitor;
internal InternalAccessor(XObjectVisitor visitor) { this.visitor = visitor; }
internal XObject VisitAttribute(XAttribute node) { return visitor.VisitAttribute(node); }
internal XObject VisitComment(XComment node) { return visitor.VisitComment(node); }
internal XObject VisitDocument(XDocument node) { return visitor.VisitDocument(node); }
internal XObject VisitElement(XElement node) { return visitor.VisitElement(node); }
internal XObject VisitDocumentType(XDocumentType node) { return visitor.VisitDocumentType(node); }
internal XObject VisitProcessingInstruction(XProcessingInstruction node) { return visitor.VisitProcessingInstruction(node); }
internal XObject VisitText(XText node) { return visitor.VisitText(node); }
internal XObject VisitCData(XCData node) { return visitor.VisitCData(node); }
}
#endregion
}
public static class XObjectVisitorExtensions
{
#region XObject.Accept "instance" method
public static XObject Accept(this XObject node, XObjectVisitor visitor)
{
Validation.CheckNullReference(node);
Validation.CheckArgumentNull(visitor, "visitor");
// yay, easy dynamic dispatch
Acceptor acceptor = new Acceptor(node as dynamic);
return acceptor.Accept(visitor);
}
private class Acceptor
{
public Acceptor(XAttribute node) : this(v => v.Accessor.VisitAttribute(node)) { }
public Acceptor(XComment node) : this(v => v.Accessor.VisitComment(node)) { }
public Acceptor(XDocument node) : this(v => v.Accessor.VisitDocument(node)) { }
public Acceptor(XElement node) : this(v => v.Accessor.VisitElement(node)) { }
public Acceptor(XDocumentType node) : this(v => v.Accessor.VisitDocumentType(node)) { }
public Acceptor(XProcessingInstruction node) : this(v => v.Accessor.VisitProcessingInstruction(node)) { }
public Acceptor(XText node) : this(v => v.Accessor.VisitText(node)) { }
public Acceptor(XCData node) : this(v => v.Accessor.VisitCData(node)) { }
private Func<XObjectVisitor, XObject> accept;
private Acceptor(Func<XObjectVisitor, XObject> accept) { this.accept = accept; }
public XObject Accept(XObjectVisitor visitor) { return accept(visitor); }
}
#endregion
#region XObject.Update "instance" method
public static XObject Update(this XAttribute node, XName name, string value)
{
Validation.CheckNullReference(node);
Validation.CheckArgumentNull(name, "name");
Validation.CheckArgumentNull(value, "value");
return new XAttribute(name, value);
}
public static XObject Update(this XComment node, string value = null)
{
Validation.CheckNullReference(node);
return new XComment(value);
}
public static XObject Update(this XDocument node, XDeclaration declaration = null, params object[] content)
{
Validation.CheckNullReference(node);
return new XDocument(declaration, content);
}
public static XObject Update(this XElement node, XName name, params object[] content)
{
Validation.CheckNullReference(node);
Validation.CheckArgumentNull(name, "name");
return new XElement(name, content);
}
public static XObject Update(this XDocumentType node, string name, string publicId = null, string systemId = null, string internalSubset = null)
{
Validation.CheckNullReference(node);
Validation.CheckArgumentNull(name, "name");
return new XDocumentType(name, publicId, systemId, internalSubset);
}
public static XObject Update(this XProcessingInstruction node, string target, string data)
{
Validation.CheckNullReference(node);
Validation.CheckArgumentNull(target, "target");
Validation.CheckArgumentNull(data, "data");
return new XProcessingInstruction(target, data);
}
public static XObject Update(this XText node, string value = null)
{
Validation.CheckNullReference(node);
return new XText(value);
}
public static XObject Update(this XCData node, string value = null)
{
Validation.CheckNullReference(node);
return new XCData(value);
}
#endregion
}
public static class Validation
{
public static void CheckNullReference<T>(T obj) where T : class
{
if (obj == null)
throw new NullReferenceException();
}
public static void CheckArgumentNull<T>(T obj, string paramName) where T : class
{
if (obj == null)
throw new ArgumentNullException(paramName);
}
}
ps, questa particolare implementazione utilizza alcune funzionalità di .NET 4 per rendere l'implementazione un po 'più semplice / pulita (utilizzo di dynamic
e argomenti predefiniti). Non dovrebbe essere troppo difficile renderlo compatibile con .NET 3.5, forse anche compatibile con .NET 2.0.
Quindi per implementare il visitatore, eccone uno generalizzato che può cambiare più spazi dei nomi (e il prefisso utilizzato).
public class ChangeNamespaceVisitor : XObjectVisitor
{
private INamespaceMappingManager manager;
public ChangeNamespaceVisitor(INamespaceMappingManager manager)
{
Validation.CheckArgumentNull(manager, "manager");
this.manager = manager;
}
protected INamespaceMappingManager Manager { get { return manager; } }
private XName ChangeNamespace(XName name)
{
var mapping = Manager.GetMapping(name.Namespace);
return mapping.ChangeNamespace(name);
}
private XObject ChangeNamespaceDeclaration(XAttribute node)
{
var mapping = Manager.GetMapping(node.Value);
return mapping.ChangeNamespaceDeclaration(node);
}
protected override XObject VisitAttribute(XAttribute node)
{
if (node.IsNamespaceDeclaration)
return ChangeNamespaceDeclaration(node);
return node.Update(ChangeNamespace(node.Name), node.Value);
}
protected override XObject VisitElement(XElement node)
{
return node.Update(
ChangeNamespace(node.Name),
VisitAndConvert(node.Attributes()),
VisitAndConvert(node.Nodes())
);
}
}
// and all the gory implementation details
public class NamespaceMappingManager : INamespaceMappingManager
{
private Dictionary<XNamespace, INamespaceMapping> namespaces = new Dictionary<XNamespace, INamespaceMapping>();
public NamespaceMappingManager Add(XNamespace fromNs, XNamespace toNs, string toPrefix = null)
{
var item = new NamespaceMapping(fromNs, toNs, toPrefix);
namespaces.Add(item.FromNs, item);
return this;
}
public INamespaceMapping GetMapping(XNamespace fromNs)
{
INamespaceMapping mapping;
if (!namespaces.TryGetValue(fromNs, out mapping))
mapping = new NullMapping();
return mapping;
}
private class NullMapping : INamespaceMapping
{
public XName ChangeNamespace(XName name)
{
return name;
}
public XObject ChangeNamespaceDeclaration(XAttribute node)
{
return node.Update(node.Name, node.Value);
}
}
private class NamespaceMapping : INamespaceMapping
{
private XNamespace fromNs;
private XNamespace toNs;
private string toPrefix;
public NamespaceMapping(XNamespace fromNs, XNamespace toNs, string toPrefix = null)
{
this.fromNs = fromNs ?? "";
this.toNs = toNs ?? "";
this.toPrefix = toPrefix;
}
public XNamespace FromNs { get { return fromNs; } }
public XNamespace ToNs { get { return toNs; } }
public string ToPrefix { get { return toPrefix; } }
public XName ChangeNamespace(XName name)
{
return name.Namespace == fromNs
? toNs + name.LocalName
: name;
}
public XObject ChangeNamespaceDeclaration(XAttribute node)
{
if (node.Value == fromNs.NamespaceName)
{
if (toNs == XNamespace.None)
return null;
var xmlns = !String.IsNullOrWhiteSpace(toPrefix)
? (XNamespace.Xmlns + toPrefix)
: node.Name;
return node.Update(xmlns, toNs.NamespaceName);
}
return node.Update(node.Name, node.Value);
}
}
}
public interface INamespaceMappingManager
{
INamespaceMapping GetMapping(XNamespace fromNs);
}
public interface INamespaceMapping
{
XName ChangeNamespace(XName name);
XObject ChangeNamespaceDeclaration(XAttribute node);
}
E un piccolo metodo di aiuto per far rotolare la palla:
T ChangeNamespace<T>(T node, XNamespace fromNs, XNamespace toNs, string toPrefix = null) where T : XObject
{
return node.Accept(
new ChangeNamespaceVisitor(
new NamespaceMappingManager()
.Add(fromNs, toNs, toPrefix)
)
) as T;
}
Quindi per rimuovere uno spazio dei nomi, puoi chiamarlo in questo modo:
var doc = ChangeNamespace(XDocument.Load(pathToXml),
fromNs: "http://schema.peters.com/doc_353/1/Types",
toNs: null);
Utilizzando questo visitatore, puoi scrivere a INamespaceMappingManager
per rimuovere tutti gli spazi dei nomi.
T RemoveAllNamespaces<T>(T node) where T : XObject
{
return node.Accept(
new ChangeNamespaceVisitor(new RemoveNamespaceMappingManager())
) as T;
}
public class RemoveNamespaceMappingManager : INamespaceMappingManager
{
public INamespaceMapping GetMapping(XNamespace fromNs)
{
return new RemoveNamespaceMapping();
}
private class RemoveNamespaceMapping : INamespaceMapping
{
public XName ChangeNamespace(XName name)
{
return name.LocalName;
}
public XObject ChangeNamespaceDeclaration(XAttribute node)
{
return null;
}
}
}