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
02:00
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:

Posted by MiddleTommy @ 29 Dec 2009 08:19
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 16:19
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.

Post a comment:

Name  

E-mail (never shared)

URL

Comments  

Captcha ImageRefresh Image
What's this?
Enter code above