Категория

Блокировка редактируемых областей в документах Word с помощью JavaScript

2026-09-30 09:24:31 Allen Yang
AI Summarize:
ChatGPT
ChatGPT ✓
Claude ✓
Grok ✓
Perplexity ✓
Quick
Quick
Concise overview
Highlights
Key takeaways
Detailed
Structured explanation
Brief
One sentence summary
Summarize |

The document after an editable range is set; the lightly shaded paragraph is the editable range

Представьте шаблон договора, который отправляется десяткам клиентов. Юридический отдел тщательно проработал каждую статью, и единственное, что должен изменить каждый получатель, — это блок подписи, название проекта и дата принятия. Дайте им полностью редактируемый файл Word, и кто-нибудь неизбежно переформулирует статью о штрафах или удалит раздел об ответственности. Заблокируйте весь документ, и никто не сможет заполнить поля. На самом деле вам нужно выборочное редактирование — способ сказать: «эти конкретные абзацы можно редактировать, всё остальное заморожено».

Именно это и дают вам редактируемые диапазоны. Вы защищаете весь документ как доступный только для чтения, а затем размещаете пару маркеров разрешений вокруг абзацев, которые хотите оставить открытыми. Любой, кто откроет файл в Word, сможет печатать внутри отмеченной области, но не сможет изменить ни одного символа за её пределами. Spire.Doc for JavaScript предоставляет эту возможность в браузере через WebAssembly, так что вы можете создавать защищённые документы из приложения React без обращения к серверу — шрифты и входные файлы управляются через виртуальную файловую систему в памяти (VFS).

В этом руководстве рассматриваются обе части рабочего процесса:

Если вы ещё не подключили Spire.Doc к своему проекту, начните с Интеграция Spire.Doc for JavaScript в проект React. Приведённые ниже фрагменты предполагают, что модуль WebAssembly загружен и готов.


Задать редактируемый диапазон

Процесс состоит из трёх этапов. Сначала загрузите файлы шрифтов и целевой документ Word в виртуальную файловую систему WASM с помощью FetchFileToVFS. Затем создайте экземпляр Document, загрузите файл, вызовите Protect, чтобы заблокировать весь документ как доступный только для чтения, и создайте пару PermissionStart / PermissionEnd с одинаковым идентификатором — эти два маркера обрамляют абзац, который вы хотите оставить редактируемым. Наконец, сохраните файл, прочитайте его из VFS, оберните в Blob и инициируйте скачивание.

function App() {
  const SetEditableRange = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the input document into VFS
    const inputFileName = "SetEditableRange.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);

    // Create a document object and load the document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Protect the whole document: everything outside the editable range is read-only
    doc.Protect({ type: docModule.ProtectionType.AllowOnlyReading, password: "password" });

    // Create the permission markers: a start and an end with the same id form one editable range
    const start = new docModule.PermissionStart(doc, "testID");
    const end = new docModule.PermissionEnd(doc, "testID");

    // Insert the markers into the first paragraph: the start at the beginning, the end appended at the end
    doc.Sections.get_Item(0).Paragraphs.get_Item(0).ChildObjects.Insert(0, start);
    doc.Sections.get_Item(0).Paragraphs.get_Item(0).ChildObjects.Add(end);

    // Save the document
    const outputFileName = "Set Editable Range.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
    doc.Dispose();

    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

 return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Set Editable Range in a Word Document</h1>
      <button onClick={SetEditableRange}>
        Generate
      </button>
    </div>
  );
}
export default App;

В образце документа поля, которые разрешено заполнять рецензенту, имеют лёгкую заливку — это чисто визуальная подсказка для читателя, и она не влияет на то, как редактируемый диапазон определяется в коде. После установки маркеров Word рассматривает абзац с заливкой как редактируемый, а все остальные абзацы как заблокированные.

The document after an editable range is set; the lightly shaded paragraph is the editable range


Удалить редактируемый диапазон

Удаление редактируемого диапазона — это один обход: пройдитесь по каждому разделу и каждому абзацу, проверьте каждый объект в коллекции ChildObjects абзаца и извлеките все, что является PermissionStart или PermissionEnd.

Одна тонкость застаёт людей врасплох: ChildObjects.Remove уменьшает коллекцию на месте, поэтому каждый элемент после удалённого сдвигается на один индекс вперёд. Если вы увеличиваете счётчик цикла во время удаления, каждое удаление приводит к пропуску следующего маркера — и чем больше маркеров, тем больше их останется.

function App() {
  const RemoveEditableRange = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

      // Load the input document into VFS
      const inputFileName = "RemoveEditableRange.docx";
      await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);

      // Create a document object and load the document
      const doc = new docModule.Document();
      doc.LoadFromFile(inputFileName);

      // Iterate over every section and paragraph and delete the permission markers
      for (let i = 0; i < doc.Sections.Count; i++) {
        const section = doc.Sections.get_Item(i);
        for (let j = 0; j < section.Body.Paragraphs.Count; j++) {
          const paragraph = section.Body.Paragraphs.get_Item(j);

          // Remove on a match; the collection shrinks, so the index is not incremented
          for (let k = 0; k < paragraph.ChildObjects.Count;) {
            const obj = paragraph.ChildObjects.get_Item(k);
            if (obj instanceof docModule.PermissionStart || obj instanceof docModule.PermissionEnd) {
              paragraph.ChildObjects.Remove(obj);
            } else {
              k++;
            }
          }
        }
      }

      // Save the document
      const outputFileName = "Remove Editable Range.docx";
      doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Release resources
    doc.Dispose();

    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

 return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Remove Editable Ranges from a Word Document</h1>
      <button onClick={RemoveEditableRange}>
        Generate
      </button>
    </div>
  );
}
export default App;

Удаление маркеров лишь перерисовывает границу того, что можно редактировать — сам текст и всё форматирование остаются нетронутыми.

The document after the editable range markers are removed; the content and formatting stay unchanged


Полный жизненный цикл защиты

В реальном процессе утверждения вы редко делаете что-то одно. Типичный цикл выглядит так:

  1. Защитить — Вызовите doc.Protect с AllowOnlyReading (или AllowOnlyFormFields) и паролем. Теперь весь документ заблокирован.
  2. Отметить — Оберните каждый редактируемый рецензентом абзац в пару PermissionStart / PermissionEnd с одним идентификатором. Эти области становятся единственными местами, где рецензент может печатать.
  3. Снять отметку — Когда раунд проверки завершён, пройдите по документу и удалите все маркеры разрешений. Области снова присоединяются к телу, доступному только для чтения.
  4. Снять защиту — Вызовите doc.Unprotect("password"), чтобы полностью снять защиту с документа и вернуть его в полностью редактируемое состояние для следующего этапа обработки.

Ключевая идея в том, что защита и редактируемые диапазоны — это два независимых слоя. Защита решает, заблокирован ли документ вообще; пара маркеров решает, какие фрагменты освобождены от этой блокировки. Вы можете добавлять и удалять маркеры сколько угодно раз, не затрагивая состояние защиты, и включать или выключать защиту, не нарушая маркеры — но маркеры имеют силу только пока защита активна.


Часто задаваемые вопросы

Редактируемый диапазон задан, но содержимое внутри него всё ещё нельзя редактировать

Почему это происходит: Маркеры разрешений сами по себе инертны. Они лишь создают исключения из ограничения на весь документ, поэтому если Protect никогда не вызывался, то нет ограничения, из которого можно сделать исключение, и маркеры ничего не делают. Второе требование — PermissionStart и PermissionEnd должны иметь одинаковую строку идентификатора — Word рассматривает их как пару только при совпадении идентификаторов.

Решение: Сначала включите ограничение редактирования, затем создайте оба маркера с одинаковым идентификатором:

// Enable protection first so that the markers mean something
document.Protect({ type: wasmModule.ProtectionType.AllowOnlyReading, password: "password" });

// The start and the end must use the same id
const start = new wasmModule.PermissionStart(document, "testID");
const end = new wasmModule.PermissionEnd(document, "testID");

Некоторые маркеры пропускаются при удалении редактируемых диапазонов

Почему это происходит: Каждый вызов ChildObjects.Remove уменьшает коллекцию на один элемент, сдвигая индекс каждого последующего элемента вниз. Если счётчик цикла увеличивается в той же итерации, что и удаление, элемент, сдвинувшийся на текущую позицию, не проверяется — он пропускается, и проблема усугубляется с каждым дополнительным маркером.

Решение: Либо удерживайте индекс на месте во время удаления (увеличивайте его только когда удаления не было), либо сначала соберите целевые объекты, а затем удаляйте их в обратном порядке:

for (let k = 0; k < paragraph.ChildObjects.Count;) {
  const obj = paragraph.ChildObjects.get_Item(k);
  if (obj instanceof wasmModule.PermissionStart || obj instanceof wasmModule.PermissionEnd) {
    paragraph.ChildObjects.Remove(obj);
    // Do not increment k here: check the new object at the current index
  } else {
    k++;
  }
}

Документ всё ещё доступен только для чтения после удаления маркеров

Почему это происходит: Маркеры лишь определяют, какие области освобождены от блокировки — они не являются самой блокировкой. Их удаление просто убирает исключения; основная защита, установленная Protect, всё ещё действует, поэтому весь документ остаётся доступным только для чтения.

Решение: После удаления маркеров и когда ограничение больше не нужно, вызовите Unprotect с исходным паролем:

document.Unprotect("password");

См. также