Combine document with different protection type

I’ve refactored this method and bundled the collection into the first item. This seems to work well but I’m having three problems now.

  1. These documents run right together without a page break in between.
  2. I’ve purposfully mixed together two types of protection in different documents so that I can see this will definitely work right. In one document I may be allowing “Only Changes” in a select area. In another document I’m using the legacy text form and then protecting the document to allow only “Filling Form”. Once merged I’m allowed to modify text in areas I had blocked off as “Everyone” and the rest of the document has been locked. But that includes my Text Forms… those shouldn’t have been locked.
  3. I now have no option to unlock anything in the entire document
    public static Stream MergeDocuments(IEnumerable<Stream> streams)
    {
        var documentStreams = streams.ToList();

        if (documentStreams.Count == 0)
            return null;

        var firstStream = documentStreams.ElementAt(0);
        firstStream.Position = 0;
        var mainDocument = XDocument.Load(firstStream);

        documentStreams.RemoveAt(0);

        using (documentStreams.WithReset())
        {
            foreach (var documentStream in documentStreams)
            {
                documentStream.Position = 0;
                var currentXDocument = XDocument.Load(documentStream);
                mainDocument.Root.Add(currentXDocument.Root.Elements());
            }
        }

        var mergedStream = new MemoryStream();

        mainDocument.Save(mergedStream);
        mergedStream.Position = 0;
        return mergedStream;
    }

@bsmithlti As I can see you do not use Aspose.Words in your code. In your case you can use Document.AppendDocument method to merge your documents together. Please see the documentation to learn more about appending and inserting documents.
Also, please attach your input documents and expected output. We will check the documents and provide you more information or a solution.

Hey there, I’m attaching a document with No Changes protection. The 2nd and 4th paragraphs were set to everyone. My main issues is that we’re unable to combine documents with protection into a single document but just to keep this simple I also lose the protection with just this one single document. This code was updated to create a main doc using the first stream. In my previous test you’re right I was using XDocument and I’ll stay away from that if we can get this working. Note: I’ve attached a .docx file, but this we originally saved this in a word xml format and I’m not sure if that should make a difference.

public static void MergeDocuments(IEnumerable<Stream> streams, ref Stream destination)
{
    if (destination == null)
        throw new ArgumentNullException(nameof(destination));

    var documentStreams = streams.ToList();

    if (documentStreams.Count == 0)
        new Document().Save(destination, null);

    var finalDocument = new Document(documentStreams.ElementAt(0));
    documentStreams.RemoveAt(0);

    using (documentStreams.WithReset())
    {
        foreach (var documentStream in documentStreams)
        {
            documentStream.Position = 0;
            var document = new Document(documentStream);

            // Each Document should start with a new page
            document.FirstSection.PageSetup.SectionStart = SectionStart.NewPage;

            document.FirstSection.PageSetup.RestartPageNumbering = true;
            document.FirstSection.PageSetup.PageStartingNumber = 1;
            document.FirstSection.HeadersFooters.LinkToPrevious(false);

            RemoveEmptyHeaderAndFooter(document);

            // Update each document with new pages
            document.UpdatePageLayout();
            finalDocument.AppendDocument(document, ImportFormatMode.KeepSourceFormatting);
        }
    }

    // refer to Aspose api docs → https://docs.aspose.com/words/net/join-and-append-documents/#controlling-how-page-numbering-is-handled
    ConvertNumPageFieldsToPageRef(finalDocument);

    foreach (Section section in finalDocument.Sections)
    {
        if (section.HeadersFooters.Count == 0)
        {
            var pageSetup = section.PageSetup;
            pageSetup.FooterDistance = 0;
            pageSetup.HeaderDistance = 0;
        }
        section.HeadersFooters.LinkToPrevious(false);
    }

    // This needs to be called in order to update the new fields with page numbers.
    finalDocument.UpdatePageLayout();

    finalDocument.Save(destination, null);
}

tmp_BASTEST_101.docx (12.8 KB)

@bsmithlti The problem occurs because protection is applied to whole document. For example if you append not protected document protected on, the final document will be protected:

Document protectedDoc = new Document(@"C:\Temp\protected.docx");
Document notProtectedDoc = new Document(@"C:\Temp\not_protcted.docx");
protectedDoc.AppendDocument(notProtectedDoc, ImportFormatMode.KeepSourceFormatting);
protectedDoc.Save(@"C:\Temp\out.docx");

But if you change the order, the final document will be not protected:

Document protectedDoc = new Document(@"C:\Temp\protected.docx");
Document notProtectedDoc = new Document(@"C:\Temp\not_protcted.docx");
notProtectedDoc.AppendDocument(protectedDoc, ImportFormatMode.KeepSourceFormatting);
notProtectedDoc.Save(@"C:\Temp\out.docx");

Please see our documentation to learn more about protection. If you need to restrict editing of the final document, you can protect the output document, like shown in the following code:

Document protectedDoc = new Document(@"C:\Temp\protected.docx");
Document notProtectedDoc = new Document(@"C:\Temp\not_protcted.docx");
notProtectedDoc.AppendDocument(protectedDoc, ImportFormatMode.KeepSourceFormatting);
// Protect the document (editable ranges will be editable in the output doc)
notProtectedDoc.Protect(ProtectionType.ReadOnly);
notProtectedDoc.Save(@"C:\Temp\out.docx");

Okay one more try here. I am working with two documents coming in as streams. The first document (tmp_BASTEST-000) is protected with “Filling in Forms” and password protected (admin1). The second document (tmp_BASTEST-001) is protected with “No Changes” and password protected (admin2) two areas are set here to allow everybody to edit.

My intention is to create my main document with tmp_BASTEST-000 so that hopefully the password admin1 would work for the entire document. When I append tmp_BASTEST-001 my intention is for this document to be protected as intended and only allow the two regions to be edited and password protected with the admin1 password from the primary document. Is what I’m trying to do something that should work?

Here is what I’m working with now. In my merged document the tmp_BASTEST-000 content is working as intened. The tmp_BASTEST-000 content is locked down and not allowing editing in the regions I specified.

public static MemoryStream GetMergedDocumentStream(IEnumerable<Stream> streams)
{
    var documentStreams = streams.ToList();

    if (documentStreams.Count == 0)
        return null;

    var finalDocStream = new MemoryStream();
    var firstStream = documentStreams.ElementAt(0);
    firstStream.Position = 0;

    var finalDoc = new Document(firstStream);
    documentStreams.RemoveAt(0);

    foreach (var stream in documentStreams)
    {
        stream.Position = 0;
        var nextDoc = new Document(stream);

        finalDoc.AppendDocument(nextDoc, ImportFormatMode.KeepSourceFormatting);
    }

    finalDoc.Save(finalDocStream, null);
    return finalDocStream;
}

MergedDocument.docx (11.6 KB)
tmp_BASTEST-000.docx (10.4 KB)
tmp_BASTEST-001.docx (10.3 KB)

@bsmithlti No, this will not work. As I already mentioned protection is applied to the whole document, so you cannot have different protection types applied to different section in the document.
The only exception is Section.ProtectedForForms which will work if the whole document is protected for forms. In this case you can have one section protected another not. But it is impossible to have different protection types applied in one document.

What about this? If I pass in only one of those documents the protection is gone when the document is created.

public static MemoryStream GetMergedDocumentStreamAspose(IEnumerable<Stream> streams)
{
    var documentStreams = streams.ToList();
    var finalDocStream = new MemoryStream();

    if (documentStreams.Count == 0)
        return finalDocStream;

    var firstStream = documentStreams.ElementAt(0);
    firstStream.Position = 0;
    var finalDocument = new Document(firstStream);
    documentStreams.RemoveAt(0);

    DocumentBuilder docBuilder = new DocumentBuilder(finalDocument);

    foreach (var stream in documentStreams)
    {
        docBuilder.InsertBreak(BreakType.PageBreak);

        stream.Position = 0;
        var nextDocument = new Document(stream);

        finalDocument.AppendDocument(nextDocument, ImportFormatMode.KeepSourceFormatting);
    }

    finalDocument.Save(finalDocStream, null);
    return finalDocStream;
}

@bsmithlti In your cade you pass null as SaveOptions parameter, it is incorrect. you have to pass either SaveOptions instance or SaveFormat:

finalDocument.Save(finalDocStream, SaveFormat.Docx);

I have modified your code as shown above and used the following code for testing:

using (FileStream fs = File.OpenRead(@"C:\Temp\tmp_BASTEST-000.docx"))
{
    List<Stream> docStreams = new List<Stream>();
    docStreams.Add(fs);

    MemoryStream ms = GetMergedDocumentStreamAspose(docStreams);

    File.WriteAllBytes(@"C:\Temp\out.docx", ms.ToArray());
}

Protection is preserved in the output document.

Alexey, thank you for your responses, but for whatever reason adding the document type still does not resolve this for me. I can tell you that loading these into an XDocument and XmlDocument is not causing any issues for me just trying to load into this Document object for some reason. I also tried using WordML with the same result. I don’t know if there is something else you would like me to try, let me know.

finalDocument.Save(finalDocStream, SaveFormat.Docx);

@bsmithlti Could you please attach your output documents produced on your side when you pass only one document in the method? I will check the output document and provide you more information.

Alexey, I’ve attatched that output for you here.tmp_BASTEST-000.docx (11.9 KB)

@bsmithlti As I can see the document you have attached was not produced by Aspose.Words or it has been postprocessed later. Could you please check whether the following code will also produce the document with wrong protection:

Document doc = new Document(@"C:\Temp\in.docx");
doc.Save(@"C:\Temp\out.docx");

Alexey I think we’ve got a good idea what part of our issue is that we’re using an older version of Aspose (13.4) which doesn’t seem to support NodeType.EDITABLE_RANGE_START. Do you know which version may have the first stable implementation of that?

@bsmithlti This feature has been introduced in 13.8 version of Aspose.Words.