views:

4338

answers:

3

I'm trying to use System.Xml.Linq to create XHTML documents. Thus, the vast majority of the nodes in my trees ought to use this namespace:

http://www.w3.org/1999/xhtml

I can create XElement nodes scoped to this namespace easily enough, using an XNamespace, like this:

XNamespace xhtml = "http://www.w3.org/1999/xhtml";
// ...
new XElement(xhtml + "html", // ...

However, I don't want to have to make an XNamespace available throughout all the code that creates HTML nodes, and have to prefix every single XElement (and XAttribute) name I create accordingly.

The XML text format itself takes this requirement into account, and permits setting a default namespace in an ancestor which is inherited by descendants, using the reserved xmlns attribute. I'd like to do something similar using System.Xml.Linq.

Is this possible?

A: 

The problem is the the XName used to create the XElement needs to specify the correct namespace. What I would be tempted to do is create a static class like this:-

public static class XHtml
{
 public static readonly XNamespace Namespace = "http://www.w3.org/1999/xhtml";
 public static XName Html { get { return Namespace + "html"; } }
 public static XName Body { get { return Namespace + "body"; } }
              //.. other element types
}

Now you can build a xhtml doc like this:-

XDocument doc = new XDocument(
 new XElement(XHtml.Html,
  new XElement(XHtml.Body)
 )
);

An alternative approach to that static class would be:-

static class XHtml
{
 public static readonly XNamespace Namespace = "http://www.w3.org/1999/xhtml";
 public static readonly XName Html = Namespace + "html";
 public static readonly XName Body = Namespace + "body";
}

This has the downside of instancing all the possible XName regardless of whether you use them but the upside is the conversion of Namespace + "tagname" only happens once. I'm not sure this conversion would be optimised out otherwise. I am sure that XNames are only instanced once:-

XNamepace n = "http://www.w3.org/1999/xhtml";
XNames x = n + "A";
XName y = n + "A";
Object.ReferenceEquals(x, y) //is true.
AnthonyWJones
A third approach is to rewrite the subtree, html-node down, before adding to the XDocument at the root. I'm aware of these other ways to solve the problem, I was wondering if there was a way around it. I guess probably not.
Barry Kelly
Umm... huh? html-node down? I don't get that. You'd still need to use XNames that have the xhtml namespace.
AnthonyWJones
Anthony - rewriting the tree to include the XHTML namespace in the names that aren't scoped by a namespace, i.e. checking XName.Namespace is XNamespace.None; by html node, I mean the root node of XHTML documents.
Barry Kelly
Rewriting = recursively reconstructing the entire tree, with new XName values in the constructors, composed out of the old node name with the XHTML namespace.
Barry Kelly
+3  A: 

I've decided to use a static class called XHtml, that looks like this:

public static class XHtml
{
    static XHtml()
    {
        Namespace = "http://www.w3.org/1999/xhtml";
    }

    public static XNamespace Namespace { get; private set; }

    public static XElement Element(string name)
    {
        return new XElement(Namespace + name);
    }

    public static XElement Element(string name, params object[] content)
    {
        return new XElement(Namespace + name, content);
    }

    public static XElement Element(string name, object content)
    {
        return new XElement(Namespace + name, content);
    }

    public static XAttribute Attribute(string name, object value)
    {
        return new XAttribute(/* Namespace + */ name, value);
    }

    public static XText Text(string text)
    {
        return new XText(text);
    }

    public static XElement A(string url, params object[] content)
    {
        XElement result = Element("a", content);
        result.Add(Attribute("href", url));
        return result;
    }
}

This seems to be the cleanest way of doing things, particularly as I can then add in convenience routines, such as the XHtml.A method (not all of my class is shown here).

Barry Kelly
A: 

I took the recursively rewriting path. You do not really have to 'reconstruct' the tree. You can just swap out the node names (XName).

    private static void ApplyNamespace(XElement parent, XNamespace nameSpace)
    {
        if(DetermineIfNameSpaceShouldBeApplied(parent, nameSpace))
        {
            parent.Name = nameSpace + parent.Name.LocalName;
        }
        foreach (XElement child in parent.Elements())
        {
            ApplyNamespace(child, nameSpace);
        }
    }
Julian Lettner