Skip Navigation LinksHome > View Post

Merging XPS Documents

This had me stumped for a while now. You may remember me playing with XPS documents and displaying them using WPF from this post: Cracking an XPS in WPF.

Since then I've been struggling some more with the XPS API; this time trying to merge two documents to create a single XPS. It turns out there are a number of ways to think about this as XPS documents can actually contain multiple documents (or DocumentReferences as they appear in the API). I decided to merge all pages from both XPS files into a single FixedDocument (and thus single DocumentReference).

Here's the code I used to do this - first we need to extract all the pages from the existing documents. We go for the PageContent type:

public static List<PageContent> GetAllPages(FixedDocumentSequence documentSequence)
{
    var docs = documentSequence.References.Select(r => r.GetDocument(true));
    List<PageContent> pages = new List<PageContent>();
    foreach (var doc in docs)
    {
        pages.AddRange(doc.Pages);
    }
    return pages;
}

You've probably noticed that this takes a FixedDocumentSequence. Getting one of those from an XPS is the easy bit:

XpsDocument doc = new XpsDocument(sourcePath, FileAccess.Read);
FixedDocumentSequence seq = doc.GetFixedDocumentSequence();

Now to take those pages and put them into a new FixedDocumentSequence:

public static FixedDocumentSequence CreateNewDocumentFromPages(IEnumerable<PageContent> pages)
{
    FixedDocumentSequence newSequence = new FixedDocumentSequence();
    DocumentReference newDocReference = new DocumentReference();
    FixedDocument newDoc = new FixedDocument();
    newDocReference.SetDocument(newDoc);

    foreach (PageContent page in pages)
    {
        PageContent newPage = new PageContent();
        newPage.Source = page.Source;
        (newPage as IUriContext).BaseUri = ((IUriContext)page).BaseUri;
        newPage.GetPageRoot(true);
        newDoc.Pages.Add(newPage);
    }

    // The order in which you do this is REALLY important
    newSequence.References.Add(newDocReference);

    return newSequence;
}

This returns a new FixedDocumentSequence that can be written to disk as easily as this:

using (XpsDocument xpsDocument = new XpsDocument(targetPath, System.IO.FileAccess.ReadWrite))
{
    XpsDocumentWriter xpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
    xpsDocumentWriter.Write(fixedDocumentSequence);
}

All sounds easy right? No - this has had me stumped for weeks on and off. A very helpful chap in Redmond put me on to this XPS document merge sample on MSDN. However, it didn't make for the easiest of reading (a lot of the code was WPF drag n' drop stuff and whole bunch of new XPS 'model'). However, I managed to distill out the bits I needed but my code was plagued with issues. The resulting document was invalid and the performance of the newDoc.Pages.Add(newPage) line grew exponentially worse with each addition. Weirdly, the resulting FixedDocumentSequence could be viewed using WPF's DocumentViewer (and the FixedDocumentSequence.DocumentPaginator.Source property) just fine.

All very odd and all because I was adding my DocumentReference to the FixedDocumentSequence too soon. You'll notice in the sample above this is almost the last thing to happen - and with this all my troubles went away.

Tags: XPS WPF

 
Josh Post By Josh Twist
2:00 AM
16 Jun 2009

» Next Post: A developer's roundabout way of fixing some issues with a wireless router
« Previous Post: Help! Why can't I use DataTriggers with controls in WPF?

Comments are closed for this post.

Posted by MiddleTommy @ 29 Dec 2009 8:19 AM
Thanks this helped me and saved lots of research.
Microsoft doesnt publish this kind of stuff easily to find

Posted by Jim Beam @ 16 Feb 2010 4:19 PM
Great code. But could you please extend it to show exactly how you "dissect" the XPS files you want to merge? I am trying to use your code to loop through a series of IDs, create the XPS file and merge them all into a single file. The end result is that I get the right page count but all the pages have the same data (the last record). So I'm either not looping properly or there is something amiss in the code. TIA.

Posted by Daniel @ 29 Apr 2011 3:20 PM
Thanks a lot for the code. Exactly what I'm was looking for. Works great.

Posted by Hrishi @ 24 Nov 2011 2:17 PM
Thanks for the inputs.
However, i have a XPS file which has searchable text along with image. its kind of a layered file with foreground image and text hidden in background. When i merge this file with another xps file to create a final consolidated XPS file, this file gets added with only images. the searchable text of the document is lost completely. All that i have is a image of the content.
Any idea?

© 2005 - 2014 Josh Twist - All Rights Reserved.