none
XmlDocument, XmlDocumentFragment und der Namespace RRS feed

  • Frage

  • Hallo,

    für die Erstellung eines XML-Dokuments habe ich eine Grundstruktur in die ein Fragment eingefügt werden soll. In dem Fragment sollen vor dem einfügen noch einzelne Attribute angepasst werden.

    Die Grundstruktur schaut etwa so aus:

    <?xml version="1.0" encoding="UTF-8"?>
    <Some xmlns="einNamespace">
    	<Element1
    			AttribEins="yes"
    			AnderesAttribut="Bla"
    			NochEinAttribut="Irgendwas"
    			>
    
    		<EinKnoten Id="Node2"/>
    	</Element1>
    </Some>
    

     

    Das Fragment hat folgende Teile:

    <Fragment Id="123" Name="name_1">
      <Sub Id="456"/>
    </Fragment>
    

     

    Das Fragment sollte dann in ein XmlDocumentFragment eingelesen werden und mittels XmlNode.PrependChild, unter Element1, eingefügt werden. Nach dem einfügen wird dem Fragment ein leeres xmlns Attribut hinzugefügt, dabei ist der Wunsch dieses Fragment unter dem gleichen Namespace wie die Grundstruktur zu bekommen.

    Im zweiten Versuch habe ich dann dem Fragment ein xmlns Attribut hinzugefügt, der eingelesene string sieht dann so aus:

    <Fragment Id="123" Name="name_1" xmlns="einNamespace">
      <Sub Id="456"/>
    </Fragment>
    

     

    Nach dem einlesen in ein  XmlDocumentFragment  lassen sich die Attribute nicht mehr bearbeiten, weil man den XmlNode, aufgrund der nicht zum Schema passenden (unvollständigen) Struktur, nicht findet.

    Im letzten Schritt habe ich mich dann entschieden das Problem mit string.Format zu lösen und die Attribute so zu setzen.

    Nach dieser Erfahrung finde ich es doch einigermaßen unbefriedigend, dass man keine Möglichkeit hat den Namespace beim Einfügen von Fragmenten passend anzugeben und stattdessen auf einen Workarround zurückgreifen muss. Dazu kommt noch, dass es auch nicht möglich ist sein Dokument erst ohne Namespace zu erzeugen, alles zusammen zu bauen und zum Schluss den richtigen Namespace hinzuzufügen.

    Gruß


    - Florian
    Freitag, 30. September 2011 08:30

Antworten

  • Sicher wird das XmlDocumentFragment zu einem XmlDocument erzeugt, aber Namensräume werden ja nicht für ein Dokument definiert, sondern für Elementknoten und sind dann für das Element, seine Attribute und auch für die Kinder und Nachfahren gültig, so sie nicht überschrieben werden.

    Es ist ja durchaus möglich, dass in einem Dokument mehrere Namensräume verwendete werden, etwa wie in

    <root xmlns="http://example.com/ns1">
      <element xmlns="http://example.com/ns2">
         <element2 xmlns="http://example.com/ns3"></element2>
      </element>
    </root>
    


    und wenn man dann ein Fragment in ein bestimmtes Element einfügen will und dabei den Namensraum des Elementes berücksichtigen will, dann reicht das Dokument selbst nicht aus.

    Ansonsten stimme ich dir zu, dass der von mir gepostete Code etwas länglich ist, aber deshalb der Vorschlag, diesen in eine Methode zu kapseln und dann bei Bedarf aufzurufen.

    Es gibt aber auch noch folgende Möglichkeit mit XPathNavigator.PrependChild:

                XmlDocument doc = new XmlDocument();
                doc.Load("../../XMLFile1.xml");
    
                XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
    
                mgr.AddNamespace("df", doc.DocumentElement.NamespaceURI);
    
                string fragment = @"<Fragment Id=""123"" Name=""name_1"">
      <Sub Id=""456""/>
    </Fragment>";
    
                XmlNode e1 = doc.SelectSingleNode("df:Some/df:Element1", mgr);
                if (e1 != null)
                {
                    e1.CreateNavigator().PrependChild(fragment);
                }
    
                doc.Save(Console.Out);
    


    dabei kommt dann folgendes heraus:

     

    <Some xmlns="einNamespace">
      <Element1 AttribEins="yes" AnderesAttribut="Bla" NochEinAttribut="Irgendwas">
        <Fragment Id="123" Name="name_1">
          <Sub Id="456" />
        </Fragment>
        <EinKnoten Id="Node2" />
      </Element1>
    </Some>
    
    


     

     

    Was das SelectSingleNode mit XmlDocumentFragment angeht, so kann ich das mit den bisherigen Angaben nicht nachvollziehen, wenn ich etwa den Code

                XmlDocument doc = new XmlDocument();
                string fragment = @"<Fragment Id=""123"" Name=""name_1"" xmlns=""einNamespace"">
      <Sub Id=""456""/>
    </Fragment>";
    
                XmlDocumentFragment fragNode = doc.CreateDocumentFragment();
                fragNode.InnerXml = fragment;
    
                XmlNamespaceManager mgr = new XmlNamespaceManager(fragNode.OwnerDocument.NameTable);
                mgr.AddNamespace("df", "einNamespace");
    
                XmlNode sub = fragNode.SelectSingleNode("/df:Fragment/df:Sub", mgr);
                Console.WriteLine("sub: {0}; OuterXml: {1}", sub, sub.OuterXml);
    


    ausführe, so ist das Resultat

    sub: System.Xml.XmlElement; OuterXml: <Sub Id="456" xmlns="einNamespace" />
    

    es lassen sich also durchaus Knoten im Fragment finden.

     

     

     

     

     

     

     


    MVP Data Platform Development My blog
    Freitag, 30. September 2011 12:27

Alle Antworten

  • Du musst dir beim DOM-Modell klar machen, dass der Namensraum eines Knoten bei der Erzeugung des Knoten festgelegt wird, also entweder bei der Benutzung von Methoden wie CreateElement/CreateAttribute/CreateNode oder beim Parsen des Markups. Ich nehme also an, dass du das Fragment

    <Fragment Id="123" Name="name_1">
      <Sub Id="456"/>
    </Fragment>
    


    mit Code wie

      XmlDocumentFragment frag = doc.CreateXmlDocumentFragment();

      frag.InnerXml = stringMitXmlFragment;

    parst. Da in dem Fragment keinerlei Namensraum definiert ist, werden die Elementknoten auch so erzeugt, dass sie in keinem Namensraum sind. Und der Namensraum lässt sich dann nicht mehr ändern, auch wenn es für deinen Zweck wünschenswert wäre, den Namensraum z.B. anzupassen, wenn die Knoten anderswo eingefügt werden.

    Es ist aber mittels XmlParserContext möglich, dein Fragment ohne Namensraumdefinitionen im Kontext z.b. des DocumentElement eines XmlDocument zu parsen. Hier ist ein Beispiel:

                XmlDocument doc = new XmlDocument();
                doc.Load("../../XMLFile1.xml");
    
                XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
                foreach (var ns in doc.DocumentElement.CreateNavigator().GetNamespacesInScope(XmlNamespaceScope.All))
                {
                    mgr.AddNamespace(ns.Key, ns.Value);
                }
    
                XmlParserContext pc = new XmlParserContext(
                    doc.NameTable,
                    mgr,
                    null,
                    XmlSpace.Default
                );
    
                string fragment = @"<Fragment Id=""123"" Name=""name_1"">
      <Sub Id=""456""/>
    </Fragment>";
    
                XmlNode parsedFragment;
    
                using (XmlReader xr = XmlReader.Create(new StringReader(fragment), null, pc))
                {
                    parsedFragment = doc.ReadNode(xr);
                }
    
                doc.DocumentElement.PrependChild(parsedFragment);
    
                doc.Save(Console.Out);
    
    


    So XMLFile1.xml dein Dokument

    <?xml version="1.0" encoding="UTF-8"?>
    <Some xmlns="einNamespace">
      <Element1
    			AttribEins="yes"
    			AnderesAttribut="Bla"
    			NochEinAttribut="Irgendwas"
    			>
    
        <EinKnoten Id="Node2"/>
      </Element1>
    </Some>
    
    


    ist, wird das per doc.Save() ausgegebene  Dokument folgendermaßen aussehen:

    <Some xmlns="einNamespace">
      <Fragment Id="123" Name="name_1">
      <Sub Id="456" />
    </Fragment>
      <Element1 AttribEins="yes" AnderesAttribut="Bla" NochEinAttribut="Irgendwas">
        <EinKnoten Id="Node2" />
      </Element1>
    </Some>
    
    


    Hilft das? 

    Was deine Aussage "Nach dem einlesen in ein  XmlDocumentFragment  lassen sich die Attribute nicht mehr bearbeiten, weil man den XmlNode, aufgrund der nicht zum Schema passenden (unvollständigen) Struktur, nicht findet." angeht, musst du mal an einem Beispiel mit XML und C# erklären, welchen Knoten du suchst aber nicht findest, auch das sollte sich lösen lassen.

    Erwähnt sei auch noch, dass es seit .NET 3.5 LINQ to XML (System.Xml.Linq.XDocument/XElement usw.) gibt, dieses Modell ist etwas flexibler, was die nachträgliche Änderung des Namensraum von Elementknoten angeht.

     

     

     

     


    MVP Data Platform Development My blog
    Freitag, 30. September 2011 11:02
  • Hallo Martin,

    ja, das hilft, zeigt aber auch wie umständlich das gemacht werden muss. Ich hätte erwartet, das bei der Verwendung von XmlDocumentFragment frag = doc.CreateXmlDocumentFragment(); der Namespace zum "doc" passend für das Fragment gesetzt ist, schließlich wird es für dieses erzeugt oder zumindest die Möglichkeit besteht diesen anzugeben.

    "SelectSingleNode("//Some:Fragment", xmlnsManager)" und  "SelectSingleNode("//Fragment")"  sind zwei der Varianten mit denen ich versucht habe den XmlNode zu erhalten. Die zweite Variante funktionierte wenn keine Angabe in Form von xmlns vorhanden ist, die erste dann dementsprechend nicht, was mir richtig erscheint. Wenn eine Angabe von xmlns im Fragment vorliegt, funktionieren beide Varianten nicht auf dem Fragment, die erste vermutlich nicht, weil die Struktur unvollständig ist (nicht zum Namespace passt) und die zweite nicht, weil der Namespace ein anderer ist. Ich habe keine Ahnung mit welcher Variante es klappen könnte. Dabei beachten, ich möchte diese Änderungen ausgeführt haben, bevor das Fragment eingefügt wird.

    Noch eine Kleinigkeit, das Fragment sollte unter "Element1" stehen nicht unter "Some", sollte nicht viel ändern.

    Vielen Dank für Dein Interesse an dem Thema.


    - Florian
    Freitag, 30. September 2011 11:43
  • Sicher wird das XmlDocumentFragment zu einem XmlDocument erzeugt, aber Namensräume werden ja nicht für ein Dokument definiert, sondern für Elementknoten und sind dann für das Element, seine Attribute und auch für die Kinder und Nachfahren gültig, so sie nicht überschrieben werden.

    Es ist ja durchaus möglich, dass in einem Dokument mehrere Namensräume verwendete werden, etwa wie in

    <root xmlns="http://example.com/ns1">
      <element xmlns="http://example.com/ns2">
         <element2 xmlns="http://example.com/ns3"></element2>
      </element>
    </root>
    


    und wenn man dann ein Fragment in ein bestimmtes Element einfügen will und dabei den Namensraum des Elementes berücksichtigen will, dann reicht das Dokument selbst nicht aus.

    Ansonsten stimme ich dir zu, dass der von mir gepostete Code etwas länglich ist, aber deshalb der Vorschlag, diesen in eine Methode zu kapseln und dann bei Bedarf aufzurufen.

    Es gibt aber auch noch folgende Möglichkeit mit XPathNavigator.PrependChild:

                XmlDocument doc = new XmlDocument();
                doc.Load("../../XMLFile1.xml");
    
                XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
    
                mgr.AddNamespace("df", doc.DocumentElement.NamespaceURI);
    
                string fragment = @"<Fragment Id=""123"" Name=""name_1"">
      <Sub Id=""456""/>
    </Fragment>";
    
                XmlNode e1 = doc.SelectSingleNode("df:Some/df:Element1", mgr);
                if (e1 != null)
                {
                    e1.CreateNavigator().PrependChild(fragment);
                }
    
                doc.Save(Console.Out);
    


    dabei kommt dann folgendes heraus:

     

    <Some xmlns="einNamespace">
      <Element1 AttribEins="yes" AnderesAttribut="Bla" NochEinAttribut="Irgendwas">
        <Fragment Id="123" Name="name_1">
          <Sub Id="456" />
        </Fragment>
        <EinKnoten Id="Node2" />
      </Element1>
    </Some>
    
    


     

     

    Was das SelectSingleNode mit XmlDocumentFragment angeht, so kann ich das mit den bisherigen Angaben nicht nachvollziehen, wenn ich etwa den Code

                XmlDocument doc = new XmlDocument();
                string fragment = @"<Fragment Id=""123"" Name=""name_1"" xmlns=""einNamespace"">
      <Sub Id=""456""/>
    </Fragment>";
    
                XmlDocumentFragment fragNode = doc.CreateDocumentFragment();
                fragNode.InnerXml = fragment;
    
                XmlNamespaceManager mgr = new XmlNamespaceManager(fragNode.OwnerDocument.NameTable);
                mgr.AddNamespace("df", "einNamespace");
    
                XmlNode sub = fragNode.SelectSingleNode("/df:Fragment/df:Sub", mgr);
                Console.WriteLine("sub: {0}; OuterXml: {1}", sub, sub.OuterXml);
    


    ausführe, so ist das Resultat

    sub: System.Xml.XmlElement; OuterXml: <Sub Id="456" xmlns="einNamespace" />
    

    es lassen sich also durchaus Knoten im Fragment finden.

     

     

     

     

     

     

     


    MVP Data Platform Development My blog
    Freitag, 30. September 2011 12:27
  • Hmm, das mit dem XmlDocumentFragment ist für mich jetzt auch nicht mehr Nachvollziehbar. Ich dachte ich hätte es genauso stehen gehabt, wahrscheinlich war es ein Tippfehler den ich einfach nicht mehr gesehen habe, Tschuldigung.

    Die Variante mit dem XPathNavigator.PrependChild gefällt mir gut, Ich denke das passt, Danke.


    - Florian
    Freitag, 30. September 2011 12:56