4

I'd like to retrieve each undeclared namespaces' prefix in a Xml file on load using (where msCurrentContent is a memorystream) :

xmlCurrentDoc = new XmlDocument();
xmlCurrentDoc.Load(msCurrentContent);

For example, when loading a Xml file with the following declaration : <Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="mondoc" xls:schemaLocation="mondoc.xsd">

It must retrieve the undeclared prefix xls without throwing an exception (as it does know).

What is the best way to do this ?

Thanks for your help !

SeyoS
  • 661
  • 5
  • 22
  • `catch` the exception and ignore it? – nvoigt Dec 16 '14 at 09:30
  • Lol ! That would be a solution but the exception throw a basic XmlException and as the software is used in different languages, the error message is not always the same. Moreover, this exception will stop the loading of my document. – SeyoS Dec 16 '14 at 09:39
  • I think you can try parsing the xml string, because what you need to do is finding those undeclared prefixes, don't need to use XmlDocument/XDocument or XmlReader since they require well-formed xmls. Find the colons (`ns:`) in the document, get the prefix before it, and then searching the head of the document for its declaration (`xmlns:ns`). – kennyzx Dec 16 '14 at 09:57
  • If the document contains undeclared namespace prefixes, then by definition it is not namespace-well-formed, so a parser that requires namespace-well-formedness will be of no use to you. There are XML parsers that don't require namespace-well-formedness, but I don't know what the situation is on .NET. – Michael Kay Dec 16 '14 at 10:21
  • Thanks @kennyzx, that would be my last resort as I'll prefer to continue working with my `XmlDocument` even if there is this error. But if I don't have any solutions, I would do this and ask for a correction by the user before going further. – SeyoS Dec 16 '14 at 10:21

1 Answers1

3

This is really hacky, but you could subclass XmlNamespaceManager and add fake namespaces as you encounter unknown prefixes:

public class MyXmlNamespaceManager : XmlNamespaceManager
{
    const string DefaultMissingNamespacePrefix = "http://missing.namespace.prefix.net/2014/";

    private string MissingNamespacePrefix { get; set; }
    private int NextMissingNamespaceIndex { get; set; }

    // The dictionary consists of a collection of namespace names keyed by prefix.
    public Dictionary<string, List<string>> MissingNamespaces { get; private set; }

    public MyXmlNamespaceManager(XmlNameTable nameTable)
        : this(nameTable, null) { }

    public MyXmlNamespaceManager(XmlNameTable nameTable, string missingNamespacePrefix)
        : base(nameTable)
    {
        this.MissingNamespacePrefix = (string.IsNullOrEmpty(missingNamespacePrefix) ? DefaultMissingNamespacePrefix : missingNamespacePrefix);
        this.MissingNamespaces = new Dictionary<string, List<string>>();
    }

    void AddMissingNamespace(string prefix)
    {
        if (string.IsNullOrEmpty(prefix))
            return;

        string uri;
        do
        {
            int index = NextMissingNamespaceIndex++;
            uri = MissingNamespacePrefix + index.ToString();
        }
        while (LookupPrefix(uri) != null); // Just in case.

        Debug.WriteLine(string.Format("Missing namespace \"{0}\" found, added fake namespace \"{1}\"", prefix, uri));
        AddNamespace(prefix, uri);
        MissingNamespaces.Add(prefix, uri);
    }

    public override bool HasNamespace(string prefix)
    {
        var result = base.HasNamespace(prefix);
        if (!result)
            AddMissingNamespace(prefix);
        result = base.HasNamespace(prefix);
        return result;
    }

    public override string LookupNamespace(string prefix)
    {
        var result = base.LookupNamespace(prefix);
        if (result == null)
            AddMissingNamespace(prefix);
        result = base.LookupNamespace(prefix);
        return result;
    }
}

public static class DictionaryExtensions
{
    public static void Add<TKey, TValue>(this IDictionary<TKey, List<TValue>> listDictionary, TKey key, TValue value)
    {
        if (listDictionary == null)
            throw new ArgumentNullException();
        List<TValue> values;
        if (!listDictionary.TryGetValue(key, out values))
        {
            listDictionary[key] = values = new List<TValue>();
        }
        values.Add(value);
    }
}

And then, to test:

        string xml = @"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""no""?>
<Document xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""mondoc"" xls:schemaLocation=""mondoc.xsd"">
</Document>
";
        XmlDocument xmlDoc;

        using (var stream = new StringReader(xml))
        {
            var settings = new XmlReaderSettings();
            settings.NameTable = new NameTable();
            var manager = new MyXmlNamespaceManager(settings.NameTable);
            XmlParserContext context = new XmlParserContext(null, manager, null, XmlSpace.Default);
            using (var xmlReader = XmlReader.Create(stream, settings, context))
            {
                xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlReader);
            }
        }

        string newXml;
        using (var writer = new StringWriter())
        {
            xmlDoc.Save(writer);
            newXml = writer.ToString();
        }

        Debug.WriteLine(newXml);

Which produces the following result:

<?xml version="1.0" encoding="utf-16" standalone="no"?>
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="mondoc" xls:schemaLocation="mondoc.xsd" xmlns:xls="http://missing.namespace.prefix.net/2014/0">
</Document>

At least, it's not an exception. Note - only partially tested.

dbc
  • 104,963
  • 20
  • 228
  • 340