0
votes

I am trying to replace nodes in one document by nodes selected from another document using the same XPath. I am using Saxon XPath selector to select nodes from both documents but I am not finding any clue about how can I replace the nodes.

Say my XPath is: /bookstore/book and following is a piece of code what I have written so far to access the nodes from source and target docs.

Processor SaxonProcessor = new Processor();
XPathCompiler Compiler = SaxonProcessor.NewXPathCompiler();

XmlDocument xmlDocumentTarget = new XmlDocument();
xmlDocumentTarget.LoadXml(targetXml);

DocumentBuilder sBuilder = SaxonProcessor.NewDocumentBuilder();
var sourceNode = sBuilder.Wrap(xmlDocumentSource);

XmlDocument xmlDocumentTarget = new XmlDocument();
xmlDocumentTarget.LoadXml(targetXml);

DocumentBuilder sBuilder = SaxonProcessor.NewDocumentBuilder();
var sourceDoc = sBuilder.Wrap(xmlDocumentSource);

DocumentBuilder tBuilder = SaxonProcessor.NewDocumentBuilder();
var targetDoc = tBuilder.Wrap(xmlDocumentTarget);

string xPath = @"/bookstore/book";
var sourceExp = Compiler.Compile(xPath).Load();
sourceExp.ContextItem = sourceDoc;

var targetExp = Compiler.Compile(xPath).Load();
targetExp.ContextItem = targetDoc;

var sourceXdmValue = sourceExp.Evaluate(); // Gives me source nodes
var targetXdmValue = targetExp.Evaluate(); // Gives me target nodes to replace

Now I want to replace the nodes in target document from the nodes in source document. How can I do this using Saxon API?

Please note in the target document, the book with "my" namespace is not included because that would not be selected from source based on the provided XPath.

Source XML

<bookstore specialty="novel">
  <book style="autobiography">
    <author>
      <first-name>Joe</first-name>
      <last-name>Bob</last-name>
      <award>Trenton Literary Review Honorable Mention</award>
    </author>
    <price>12</price>
  </book>
  <book style="textbook">
    <author>
      <first-name>Mary</first-name>
      <last-name>Bob</last-name>
      <publication>Selected Short Stories of
        <first-name>Mary</first-name>
        <last-name>Bob</last-name>
      </publication>
    </author>
    <editor>
      <first-name>Britney</first-name>
      <last-name>Bob</last-name>
    </editor>
    <price>55</price>
  </book>
  <book style="novel" id="myfave">
    <author>
      <first-name>Toni</first-name>
      <last-name>Bob</last-name>
      <degree from="Trenton U">B.A.</degree>
      <degree from="Harvard">Ph.D.</degree>
      <award>Pulitzer</award>
      <publication>Still in Trenton</publication>
      <publication>Trenton Forever</publication>
    </author>
    <price intl="Canada" exchange="0.7">6.50</price>
    <excerpt>
      <p>It was a dark and stormy night.</p>
      <p>But then all nights in Trenton seem dark and
      stormy to someone who has gone through what
      <emph>I</emph> have.</p>
      <definition-list>
        <term>Trenton</term>
        <definition>misery</definition>
      </definition-list>
    </excerpt>
  </book>
  <my:book xmlns:my="uri:mynamespace" style="leather" price="29.50">
    <my:title>Who's Who in Trenton</my:title>
    <my:author>Robert Bob</my:author>
  </my:book>
</bookstore>

Target XML

<bookstore specialty="novel">
  <book style="History">
    <author>
      <first-name>Joe</first-name>
      <last-name>Bob</last-name>
      <award>Trenton Literary Review Honorable Mention</award>
    </author>
    <price>12</price>
  </book>
</bookstore>

Result XML

<bookstore specialty="novel">
  <book style="autobiography">
    <author>
      <first-name>Joe</first-name>
      <last-name>Bob</last-name>
      <award>Trenton Literary Review Honorable Mention</award>
    </author>
    <price>12</price>
  </book>
  <book style="textbook">
    <author>
      <first-name>Mary</first-name>
      <last-name>Bob</last-name>
      <publication>Selected Short Stories of
        <first-name>Mary</first-name>
        <last-name>Bob</last-name>
      </publication>
    </author>
    <editor>
      <first-name>Britney</first-name>
      <last-name>Bob</last-name>
    </editor>
    <price>55</price>
  </book>
  <book style="novel" id="myfave">
    <author>
      <first-name>Toni</first-name>
      <last-name>Bob</last-name>
      <degree from="Trenton U">B.A.</degree>
      <degree from="Harvard">Ph.D.</degree>
      <award>Pulitzer</award>
      <publication>Still in Trenton</publication>
      <publication>Trenton Forever</publication>
    </author>
    <price intl="Canada" exchange="0.7">6.50</price>
    <excerpt>
      <p>It was a dark and stormy night.</p>
      <p>But then all nights in Trenton seem dark and
      stormy to someone who has gone through what
      <emph>I</emph> have.</p>
      <definition-list>
        <term>Trenton</term>
        <definition>misery</definition>
      </definition-list>
    </excerpt>
  </book>
</bookstore>
2
I'm surprised to see the backslashes in your XPath expression. XPath always uses forward slash. Presumably this means that the code you are showing here is not actually executable. - Michael Kay
Yes, this sample code was written to explain the problem. I have replaced those slashes in the question - Jawwad Alam

2 Answers

1
votes

I am not sure you need Saxon and XPath 2.0 for your simple XPath expressions and I am not sure whether you really want to remove all elements selected in the target and then simply insert the elements selected in the source as children of the DocumentElement but a mixture of Saxon and .NET's DOM API would be along the lines of

        Processor SaxonProcessor = new Processor();
        XPathCompiler Compiler = SaxonProcessor.NewXPathCompiler();

        XmlDocument sourceDomDoc = new XmlDocument();
        sourceDomDoc.Load("../../XMLFile1.xml");

        DocumentBuilder sBuilder = SaxonProcessor.NewDocumentBuilder();
        XdmNode sourceNode = sBuilder.Wrap(sourceDomDoc);

        XmlDocument targetDomDoc = new XmlDocument();
        targetDomDoc.Load("../../XMLFile2.xml");

        XdmNode targetNode = sBuilder.Wrap(targetDomDoc);



        string xPath = @"/bookstore/book";
        var sourceExp = Compiler.Compile(xPath).Load();
        sourceExp.ContextItem = sourceNode;

        var targetExp = Compiler.Compile(xPath).Load();
        targetExp.ContextItem = targetNode;

        var sourceXdmValue = sourceExp.Evaluate(); // Gives me source nodes
        var targetXdmValue = targetExp.Evaluate(); // Gives me target nodes to replace
        foreach (XdmNode toRemove in targetXdmValue)
        {
            XmlNode domToRemove = toRemove.getUnderlyingXmlNode();
            domToRemove.ParentNode.RemoveChild(domToRemove);
        }

        foreach (XdmNode tobeCopied in sourceXdmValue)
        {
            XmlNode copy = targetDomDoc.ImportNode(tobeCopied.getUnderlyingXmlNode(), true);
            targetDomDoc.DocumentElement.AppendChild(copy);
        }

        targetDomDoc.Save(Console.Out); // for debugging

However, I agree that doing it in XSLT 2.0 seems like the better approach, as you already use Saxon.

0
votes

The basic answer is, "unwrap" the XdmNode values returned by the XPath expressions to get the underlying DOM nodes, and then use DOM interfaces to modify the underlying XmlDocument instances. So you don't actually use the Saxon API for this at all.

But personally, I would go about this quite differently: I would do the whole thing in an XSLT transformation.

I'm afraid I can't give you the actual code to do that because I don't understand your logic. Why does the result document include all the book and magazine elements from the "source", but not the my:book element, and nothing at all from the "target"?