mcpField
D
docx-editor-cn

docx-editor-cn

github.com/gostyanドキュメント46253 回インストール15 週間前に更新
リポジトリ

docx-editor-cn について

DOCX の作成、編集、分析を行うためのスキルです。JavaScript による文書生成や XML を使った既存ファイルの編集に加え、中国語の学術文書向けにページ、見出し、表、キャプション、数式の書式を示します。

主な機能

  1. 01DOCX 内容の読み取りと分析
  2. 02新規文書の作成と検証
  3. 03XML の展開、編集、再パッケージ
  4. 04三線表と数式の挿入
  5. 05中国語学術文書の書式規則
  6. 06変更履歴と形式変換への対応

活用シーン

  1. 01書式を整えた中国語論文の生成
  2. 02既存文書への表や数式の追加
  3. 03変更履歴の抽出と文書の整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gostyan/docx-skill-4-cn-paper --skill docx-editor-cn

インストール後、Claude Code で/docx-editor-cnを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE.txt
package-lock.json
package.json
README.md
scripts/
__init__.py
accept_changes.py
comment.py
convert_paper.js
formula.py
mathml-to-docx.js
new_doc.js
scripts/office/
pack.py
scripts/office/helpers/
__init__.py
merge_runs.py
simplify_redlines.py
scripts/office/schemas/ecma/fouth-edition/
opc-contentTypes.xsd
opc-coreProperties.xsd
opc-digSig.xsd
opc-relationships.xsd
scripts/office/schemas/ISO-IEC29500-4_2016/
dml-chart.xsd
dml-chartDrawing.xsd
dml-diagram.xsd
dml-lockedCanvas.xsd
dml-main.xsd
dml-picture.xsd
dml-spreadsheetDrawing.xsd
dml-wordprocessingDrawing.xsd
pml.xsd
shared-additionalCharacteristics.xsd
shared-bibliography.xsd

詳細

DOCX の作成、編集、分析

概要

.docx ファイルは、XML ファイルを含む ZIP アーカイブです。

クイックリファレンス

タスク方法
コンテンツの読み取り・分析pandoc を使用するか、展開して生の XML を確認します
新しいドキュメントの作成node scripts/new_doc.js(先に CONTENT セクションを編集してください)
既存のドキュメントの編集展開 → XML を編集 → 再パッケージ化します。詳しくは「既存のドキュメントの編集」を参照してください
三线表の挿入(XML 編集)python scripts/table.py unpacked/ "1-1" "标题" --headers … --rows …
ブロック数式の挿入(XML 編集)python scripts/formula.py unpacked/ "LaTeX" 1 --anchor "锚文本"
.doc から .docx への変換

編集する前に、旧形式の .doc ファイルを変換する必要があります。

python scripts/office/soffice.py --headless --convert-to docx document.doc
コンテンツの読み取り
# Text extraction with tracked changes
pandoc --track-changes=all document.docx -o output.md

# Raw XML access
python scripts/office/unpack.py document.docx unpacked/
画像への変換
python scripts/office/soffice.py --headless --convert-to pdf document.docx
pdftoppm -jpeg -r 150 document.pdf page
変更履歴の承認

LibreOffice が必要です。すべての変更履歴を承認した、変更履歴のないドキュメントを作成するには、次のようにします。

python scripts/accept_changes.py input.docx output.docx

新しいドキュメントの作成

JavaScript で .docx ファイルを生成し、検証します。インストール:npm install -g docx

セットアップ
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
        Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
        TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
        VerticalAlign, PageNumber, PageBreak } = require('docx');

const doc = new Document({ sections: [{ children: [/* content */] }] });
Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));
検証

ファイルを作成したら、検証してください。検証に失敗した場合は、展開して XML を修正し、再パッケージ化してください。

python scripts/office/validate.py doc.docx
ページサイズ
// Chinese academic papers use A4 with 2.5cm margins on all sides
sections: [{
  properties: {
    page: {
      size: {
        width: 11906,   // A4 width in DXA
        height: 16838   // A4 height in DXA
      },
      margin: { top: 1418, right: 1418, bottom: 1418, left: 1418 } // 2.5cm = 1418 DXA
    }
  },
  children: [/* content */]
}]

一般的な用紙サイズ(DXA 単位、1440 DXA = 1 インチ、567 DXA = 1 cm):

用紙幅高さコンテンツ幅(余白 2.5cm)
A4(中国標準)11,90616,8389,070
US レター12,24015,8409,404

横向き: docx-js は内部で幅と高さを入れ替えるため、縦向きの寸法を指定し、入れ替えは docx-js に任せてください。

size: {
  width: 12240,   // Pass SHORT edge as width
  height: 15840,  // Pass LONG edge as height
  orientation: PageOrientation.LANDSCAPE  // docx-js swaps them in the XML
},
// Content width = 15840 - left margin - right margin (uses the long edge)
スタイル(学術論文の書式設定)

重要:学術論文、授業設計、または数学文書を作成する際は、以下の中国語の学術書式基準に厳密に従ってください。

1. ページ全体と本文(【ページ全体と本文の規範】)

  • ページ:A4(11906 × 16838 DXA)、すべての余白は 2.5cm(1418 DXA)。
  • フォント:中国語 = SimSun(宋体)、英語/数式/コード = Cambria Math(デフォルト)。英語のテキストには Times New Roman も使用できます。
  • サイズ:12pt(小四)→ docx-js では size: 24(ハーフポイント)。
  • 段落:1行目を2文字分字下げ(firstLine: 480 DXA)、行間はデフォルトで単一(line: 240, lineRule: LineRuleType.AUTO)。代替設定:固定20pt(line: 400, lineRule: LineRuleType.EXACT)、または1.5倍(line: 360, lineRule: LineRuleType.AUTO)。段落前後の間隔は0pt。

2. 見出し(【見出しの規範】)

  • 見出し1(一级标题):SimHei(黑体)、16pt(三号、size: 32)、太字、中央揃え、行間1.2倍(line: 288)。自動番号付け:アラビア数字の 1、2、3(見出しテキストに表示するか、一、二、 のように手動で前置)。下記の「見出しと参考文献の自動番号付け」を参照してください。
  • 見出し2(二级标题):SimHei(黑体)、14pt(四号、size: 28)、太字、左揃え、行間1.5倍(line: 360)。自動番号付け:1.1、1.2、2.1(章に連動するアラビア数字の小数表記)。
  • 見出し3(三级标题):SimHei(黑体)、12pt(小四、size: 24)、太字、左揃え、行間1.1倍(line: 264)。自動番号付け:1.1.1、2.4.1。

⚠️ 見出し番号付けのルール:3つの見出しレベルすべてに、10進数のみを使った単一の階層番号設定を使用してください。H2/H3 の番号をすべてアラビア数字に保ちながら、章をまたいで正しく連動(1.1、2.1、2.4 など)させるには、これが唯一の方法です。H1 レベルに CHINESE_COUNTING を使用すると、H2 は 2.4 ではなく 二.4 と表示されます。これは既知の OOXML 描画上の問題です。

3. 図、表、数式(【図表と数式の規範】)

  • 図のキャプション(图标题):SimSun(宋体)、11pt(size: 22)、中央揃え、太字。間隔:単一行間、前に0.5行分(before: 120)、後に3pt(after: 60)。位置:図の下。形式:「図 章-図番号」、例:「図 1-1」。
  • 表のキャプション(表标题):SimSun(宋体)、11pt(size: 22)、中央揃え、字下げなし、太字。間隔:単一行間、前に0.5行分(before: 120)、後に3pt(after: 60)。位置:表の上。形式:「表 章-表番号」、例:「表 1-1」。表は必ず三線表スタイル(上下は太線、ヘッダーの下は細線、それ以外の罫線なし)にしてください。
  • ブロック数式:数式テーブルのレイアウトを使用します。罫線なしの3列テーブル:[1cm のスペーサー | 中央揃えの数式 | 右揃えの式番号 1cm]。XML の生成には scripts/formula.py を使用してください。インライン数式は、斜体書式を適用したプレーンテキストとして記述します。

新しい文書の作成 — テンプレートスクリプトを使用してください:

# Copy the template, edit the CONTENT section, then run:
node scripts/new_doc.js
# Outputs output.docx with all styles, numbering, and page setup pre-configured.
# Change OUTPUT_PATH inside the script if needed.

scripts/new_doc.js には、すぐに使用できるヘルパー関数 h1/h2/h3(text)、body(text)、tableCaption/figCaption(label)、threeLineTable(headers, rows, colWidths)、ref(text)、blank() が用意されています。ファイル先頭の STYLES/NUMBERING/ページ定数は、以下のすべての仕様を実装する際の正式な基準です。docx-js の正確な値については、このファイルを参照してください。

リスト(Unicode の箇条書きは使用禁止)
// ❌ WRONG - never manually insert bullet characters
new Paragraph({ children: [new TextRun("• Item")] })  // BAD
new Paragraph({ children: [new TextRun("\u2022 Item")] })  // BAD

// ✅ CORRECT - use numbering config with LevelFormat.BULLET
const doc = new Document({
  numbering: {
    config: [
      { reference: "bullets",
        levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
          style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
      { reference: "numbers",
        levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
          style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
    ]
  },
  sections: [{
    children: [
      new Paragraph({ numbering: { reference: "bullets", level: 0 },
        children: [new TextRun("Bullet item")] }),
      new Paragraph({ numbering: { reference: "numbers", level: 0 },
        children: [new TextRun("Numbered item")] }),
    ]
  }]
});

// ⚠️ Each reference creates INDEPENDENT numbering
// Same reference = continues (1,2,3 then 4,5,6)
// Different reference = restarts (1,2,3 then 1,2,3)
表(三線表/Three-Line Table)

中国語の学術論文では、必ず三線表を使用してください:上下は太線(1.5pt)、ヘッダーの下は細線(0.75pt)、それ以外の罫線はなし。キャプションは表の上に配置します。

新しい文書の場合 — scripts/new_doc.js の threeLineTable() ヘルパーを使用してください:

// Inside the CONTENT array:
tableCaption('符号说明'),
threeLineTable(
  ['符号', '说明'],                       // headers
  [['S', '状态空间'], ['A', '动作空间']], // data rows
  [1800, 7270]                            // DXA column widths; must sum to 9070
),

既存の文書を編集する場合(XML)— scripts/table.py を使用してください:

# Insert after paragraph containing anchor text:
python scripts/table.py unpacked/ "1-1" "符号说明" \\
    --headers "符号,说明" \\
    --rows '[["S","状态空间"],["A","动作空间"]]' \\
    --anchor "以下是符号说明"

# Custom column widths (must sum to 9070):
python scripts/table.py unpacked/ "2-1" "参数" \\
    --headers "参数,取值,说明" \\
    --rows '[["lr","0.001","学习率"]]' \\
    --widths "1800,1500,5770"

# Print XML snippet only (no file modification):
python scripts/table.py --caption "示例" \\
    --headers "列1,列2" --rows '[["a","b"]]'

主なルール:

  • コンテンツ幅 = 9070 DXA(A4、余白2.5cm)。columnWidths の合計は9070にしてください。
  • 必ず WidthType.DXA を使用してください。WidthType.PERCENTAGE は使用しないでください。
  • 表の幅 = columnWidths の合計です。各セルにも一致する width を設定してください。
画像
// CRITICAL: type parameter is REQUIRED
new Paragraph({
  children: [new ImageRun({
    type: "png", // Required: png, jpg, jpeg, gif, bmp, svg
    data: fs.readFileSync("image.png"),
    transformation: { width: 200, height: 150 },
    altText: { title: "Title", description: "Desc", name: "Name" } // All three required
  })]
})
改ページ
// CRITICAL: PageBreak must be inside a Paragraph
new Paragraph({ children: [new PageBreak()] })

// Or use pageBreakBefore
new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })
目次
// The overridden Heading1/2/3 styles (defined in paragraphStyles above) include outlineLevel,
// which TableOfContents uses to build the TOC. Using heading: HeadingLevel.HEADING_X on
// each paragraph applies that overridden style automatically — giving both TOC support AND
// the custom SimHei/size formatting. Do NOT skip paragraphStyles and rely on built-in styles.
new TableOfContents("目录", { hyperlink: true, headingStyleRange: "1-3" })

// Apply to heading paragraphs — heading property applies the overridden style:
new Paragraph({
  heading: HeadingLevel.HEADING_1, // → w:pStyle "Heading1" (SimHei 16pt, outlineLevel:0)
  children: [new TextRun("一、引言")]
})
new Paragraph({
  heading: HeadingLevel.HEADING_2, // → w:pStyle "Heading2" (SimHei 14pt, outlineLevel:1)
  children: [new TextRun("1.1 研究背景")]
})
ヘッダー/フッター
sections: [{
  properties: {
    page: { margin: { top: 1418, right: 1418, bottom: 1418, left: 1418 } } // 2.5cm = 1418 DXA
  },
  headers: {
    default: new Header({ children: [new Paragraph({ children: [new TextRun("Header")] })] })
  },
  footers: {
    default: new Footer({ children: [new Paragraph({
      children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })]
    })] })
  },
  children: [/* content */]
}]
見出しの自動番号付け(章ごとにリセット)

H1 では、漢数字をテキストとして直接記述します(一、二、三)。H2/H3 では、章ごとの番号参照(sections_c1、sections_c2、...)を使ったWord の自動番号付けを使用します。章の境界ごとに H2/H3 のカウンターが自動的にリセットされるため、章内のセクションを追加・削除すると、番号がすべて自動更新されます。

重要 — 番号書式のルール:

複数レベルの設定で H1 に LevelFormat.CHINESE_COUNTING を使用してはいけません。H2 のテキスト内の %1 が「二」に展開され、「二.4」のようになるためです。すべてのレベルで必ず LevelFormat.DECIMAL を使用してください。

番号参照は buildNumberingConfig(chapterCount) によって動的に生成されます。各章には、2 レベル(H2、H3)の独自の sections_c{N} 参照が割り当てられます。これにより、H2/H3 は章ごとにリセットされ、番号付けはすべて Word が処理します。

// H1 — Chinese numerals in text (no Word numbering)
h1Chinese('一、引言')   // → "一、引言"  (Heading1 style)

// H2 — Word auto-numbered, chapter.section format (1.1, 2.4)
h2('研究背景')          // → "1.1  研究背景"  (Word: sections_c1, level 0)
h2('文献综述')          // → "1.2  文献综述"

// H3 — Word auto-numbered, chapter.section.subsection format (1.1.1)
h3('研究现状')          // → "1.1.1  研究现状"  (Word: sections_c1, level 1)
h3('存在不足')          // → "1.1.2  存在不足"

// Next chapter — H2/H3 auto-reset with new chapter prefix
h1Chinese('二、方法')   // _chapter increments, sections_c2 used
h2('算法设计')          // → "2.1  算法设计"  (reset + "2." prefix)
h3('数据结构')          // → "2.1.1  数据结构"

_chapter カウンターは h1Chinese() によってインクリメントされ、h2()、h3()、figCaption()、tableCaption() で使用されて、適切な番号参照/SEQ 識別子が選択されます。

キャプションの自動番号付け(SEQ フィールド)

キャプションでは、章ごとの自動番号付けに Word のSEQ フィールドを使用します。章番号はテキストで記述し、シーケンスカウンターは章ごとに設定します(例:figure_c3、table_c1)。

// Figure captions — auto: "图 章-序  描述"
figCaption('系统架构图')   // → "图 1-1  系统架构图"  (SEQ figure_c1)
figCaption('数据流图')     // → "图 1-2  数据流图"

// Table captions — auto: "表 章-序  描述"
tableCaption('符号说明')   // → "表 1-1  符号说明"  (SEQ table_c1)

説明文のみを渡してください。「图 3-1」や「表 1-1」のような番号を手動で含めないでください。番号付けは SEQ フィールドが自動で処理します。

Markdown の番号の削除

Markdown を変換する際は、これらのヘルパーを使って入力テキストから手動で付けられた番号を削除してください。

stripH1Number('一、引言')          // → "引言"
stripH2Number('1.1 研究背景')     // → "研究背景"
stripCaptionNumber('图 3-1 系统') // → "系统"
ブロック数式のレイアウト

ブロック数式では、数式番号を右揃えにしながら数式を中央揃えにするため、枠線なしの 3 列テーブルを使用します。scripts/formula.py を使って数式ブロックを自動生成し、挿入してください。

# Generate a formula block and insert after paragraph containing anchor text:
python scripts/formula.py unpacked/ "Q_n(x,a) = r + \\gamma \\max_{a'} Q_{n-1}" 1 --anchor "由此可得"

# Output XML snippet only (no insertion):
python scripts/formula.py --latex "E=mc^2" --number 2

レイアウト(A4、コンテンツ幅 9070 DXA):

┌──────────┬──────────────────────────┬──────────┐
│  1cm gap │   formula   (centered)   │   (n)    │
│ 567 DXA  │        7936 DXA          │ 567 DXA  │
└──────────┴──────────────────────────┴──────────┘
  no border throughout; single line spacing

直接 XML を編集する場合は、次のひな形を使用します。

<w:tbl>
  <w:tblPr>
    <w:tblW w:w="9070" w:type="dxa"/>
    <w:tblBorders>
      <w:top w:val="none"/><w:left w:val="none"/>
      <w:bottom w:val="none"/><w:right w:val="none"/>
      <w:insideH w:val="none"/><w:insideV w:val="none"/>
    </w:tblBorders>
  </w:tblPr>
  <w:tblGrid>
    <w:gridCol w:w="567"/><w:gridCol w:w="7936"/><w:gridCol w:w="567"/>
  </w:tblGrid>
  <w:tr>
    <w:tc><w:tcPr><w:tcW w:w="567" w:type="dxa"/></w:tcPr>
      <w:p/></w:tc>
    <w:tc><w:tcPr><w:tcW w:w="7936" w:type="dxa"/></w:tcPr>
      <w:p><w:pPr><w:jc w:val="center"/>
        <w:spacing w:line="240" w:lineRule="auto"/></w:pPr>
        <m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
          
        </m:oMath>
      </w:p></w:tc>
    <w:tc><w:tcPr><w:tcW w:w="567" w:type="dxa"/></w:tcPr>
      <w:p><w:pPr><w:jc w:val="right"/>
        <w:spacing w:line="240" w:lineRule="auto"/></w:pPr>
        <w:r><w:t>(%NUMBER%)</w:t></w:r>
      </w:p></w:tc>
  </w:tr>
</w:tbl>
参考文献(GB/T 7714-2015)

上記で定義した "references" 番号参照を使用します。GB/T 7714-2015 の文献種別に従って、テキストを手動で書式設定してください。

// Reference list style (add to paragraphStyles)
{ id: "Reference", name: "Reference", basedOn: "Normal",
  run: { font: { ascii: "Cambria Math", eastAsia: "SimSun" }, size: 24 },
  paragraph: { spacing: { line: 240, lineRule: LineRuleType.AUTO },
               indent: { left: 480, hanging: 480 } } }

// Usage — one Paragraph per entry:
new Paragraph({
  style: "Reference",
  numbering: { reference: "references", level: 0 },
  children: [new TextRun("Author. "),
             new TextRun({ text: "Title", italics: true }),
             new TextRun("[J]. Journal, Year, Vol(Issue): Pages.")]
})

GB/T 7714-2015 の一般的な書式テンプレート(各項目を入力):

種別タグ書式
期刊论文[J]作者. 题名[J]. 刊名, 年, 卷(期): 起止页.
专著[M]作者. 书名[M]. 出版地: 出版者, 出版年: 起止页.
学位论文[D]作者. 题名[D]. 保存地: 保存单位, 年份.
会议论文[C]作者. 题名[C]//会议名. 出版地: 出版者, 年: 起止页.
网络资源[EB/OL]作者. 题名[EB/OL]. (发布日期)[引用日期]. URL.
フッター(ページ番号)

フッターのページ番号は中央揃えです。A4、余白 2.5 cm を使用します。

const { Footer, Paragraph, TextRun, PageNumber, AlignmentType } = require('docx');

sections: [{
  properties: {
    page: {
      size: { width: 11906, height: 16838 },           // A4
      margin: { top: 1418, right: 1418, bottom: 1418, left: 1418 } // 2.5cm
    }
  },
  footers: {
    default: new Footer({
      children: [
        new Paragraph({
          alignment: AlignmentType.CENTER,
          children: [new TextRun({ children: [PageNumber.CURRENT] })]
        })
      ]
    })
  },
  children: [/* content */]
}]

// For "第 X 页 共 Y 页" style:
new Paragraph({
  alignment: AlignmentType.CENTER,
  children: [
    new TextRun("第 "),
    new TextRun({ children: [PageNumber.CURRENT] }),
    new TextRun(" 页  共 "),
    new TextRun({ children: [PageNumber.TOTAL_PAGES] }),
    new TextRun(" 页")
  ]
})

docx-js の重要なルール
  • ページサイズを明示的に設定する - 中国語の学術文書では、必ず A4(11906 × 16838 DXA)、余白 2.5 cm(1418 DXA)を使用します
  • 横向きの場合は縦向きの寸法を渡す - docx-js は内部で幅と高さを入れ替えます。短辺を width、長辺を height に指定し、orientation: PageOrientation.LANDSCAPE を設定します
  • \n は使用しない - 独立した Paragraph 要素を使用します
  • Unicode の箇条書き記号は使用しない - 番号付け設定とともに LevelFormat.BULLET を使用します
  • PageBreak は Paragraph 内に置く - 単独で置くと無効な XML が生成されます
  • ImageRun には type が必要 - png、jpg などを必ず指定します
  • 見出しの番号付け:章ごとの Word 自動番号付け — H1 は h1Chinese() を使って、テキスト内に漢数字(一、二、三)を指定します。H2/H3 は、buildNumberingConfig(chapterCount) で生成される章ごとの sections_c{N} 参照を使って、Word の自動番号付けを行います。複数レベルの設定では、LevelFormat.CHINESE_COUNTING を絶対に使用しないでください。見出しのテキストには手動の番号を付けずに渡します — 番号付けはすべて Word が処理します。
  • 図表のキャプション:番号を手動で付けない — 「图 3-1 」などの接頭辞を付けずに figCaption('説明') を使用します。SEQ フィールドが「図 章-連番 説明」を自動生成します。tableCaption() も同様です。Markdown の入力を整えるには stripCaptionNumber() を使用します。
  • 表は必ず三線表にする - 上下の罫線は太線(size:12、1.5pt)、ヘッダー下の罫線は細線(size:6、0.75pt)を使用します。それ以外の罫線はすべて BorderStyle.NONE に設定し、ヘッダーセルには塗りつぶし色を設定しません
  • 表のキャプションには先頭行インデントを付けない - キャプションの段落で indent: { firstLine: 0 } を指定して、Normal スタイルの firstLine を上書きします
  • ブロック数式には 3 列の表を使用する - [567 DXA のスペーサー | 7936 DXA の中央揃え数式 | 567 DXA の右揃え番号]。生成には scripts/formula.py を使用します。最終的な docx では、プレーンな $$ LaTeX を絶対に使用しないでください
  • 表の width は必ず DXA で設定する - WidthType.PERCENTAGE は使用しないでください(Google Docs で表示が崩れます)
  • 表には幅を 2 か所設定する - columnWidths 配列とセルの width の両方を設定し、値を一致させます
  • 表の幅は columnWidths の合計にする - DXA の場合、合計が正確に一致するようにします
  • セルのマージンは必ず設定する - 読みやすい余白を確保するため、margins: { top: 80, bottom: 80, left: 120, right: 120 } を使用します
  • ShadingType.CLEAR を使用する - 表の網かけに SOLID を使用しないでください
  • 目次:heading: HeadingLevel.HEADING_X を使用する - 上書きされた Heading スタイル(outlineLevel を含む)が適用され、目次での認識とカスタム書式が同時に有効になります
  • 組み込みスタイルを上書きする - "Heading1"、"Heading2" など、正確な ID を使用します。font: { ascii, eastAsia, hAnsi } のオブジェクト形式を必ず使用します — font: "SimHei" の文字列形式の省略記法では ASCII スロットのみが設定され、中国語の文字は Normal フォントにフォールバックします
  • outlineLevel を含める - 目次には必須です(H1 は 0、H2 は 1 など)

既存の文書の編集

以下の 3 つの手順を順番どおりに実行してください。

手順 1:展開
python scripts/office/unpack.py document.docx unpacked/

XML を抽出して整形し、隣接する run を結合し、スマートクォートを XML エンティティ(&#x201C; など)に変換して、編集後も維持されるようにします。run の結合をスキップするには --merge-runs false を使用します。

手順 2:XML を編集

unpacked/word/ 内のファイルを編集します。パターンについては、以下の XML リファレンスを参照してください。

変更履歴とコメントの作成者には、ユーザーが別の名前を明示的に指定しない限り、「Claude」を使用します。

文字列の置換には Edit ツールを直接使用してください。Python スクリプトは作成しないでください。 スクリプトを使うと不要に複雑になります。Edit ツールを使えば、置換対象を正確に確認できます。

重要:新しい内容にはスマートクォートを使用してください。 アポストロフィや引用符を含むテキストを追加する場合は、スマートクォートが生成されるように XML エンティティを使用します。


<w:t>Here&#x2019;s a quote: &#x201C;Hello&#x201D;</w:t>
エンティティ文字
&#x2018;‘(左シングル)
&#x2019;’(右シングル/アポストロフィ)
&#x201C;“(左ダブル)
&#x201D;”(右ダブル)

コメントの追加: comment.py を使用して、複数の XML ファイルにまたがる定型処理を行います(テキストは XML エスケープ済みである必要があります)。

python scripts/comment.py unpacked/ 0 "Comment text with &amp; and &#x2019;"
python scripts/comment.py unpacked/ 1 "Reply text" --parent 0  # reply to comment 0
python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author"  # custom author name

次に、document.xml にマーカーを追加します(XML リファレンスの「コメント」を参照してください)。

ステップ 3:パック
python scripts/office/pack.py unpacked/ output.docx --original document.docx

自動修復で検証し、XMLを圧縮してDOCXを作成します。スキップするには --validate false を使用します。

自動修復で修正される項目:

  • durableId >= 0x7FFFFFFF(有効なIDを再生成します)
  • 空白を含む <w:t> に xml:space="preserve" がない場合

自動修復で修正されない項目:

  • 不正なXML、無効な要素の入れ子、リレーションシップの欠落、スキーマ違反
よくある落とし穴
  • <w:r> 要素全体を置き換える:変更履歴を追加するときは、<w:r>...</w:r> ブロック全体を、兄弟要素となる <w:del>...<w:ins>... に置き換えます。変更履歴タグを run の内部に挿入しないでください。
  • <w:rPr> の書式を保持する:太字やフォントサイズなどを維持するため、元の run の <w:rPr> ブロックを変更履歴の run にコピーします。
  • styles.xml を再生成しない:既存のドキュメントを編集する場合、テンプレートのスタイルはすべて word/styles.xml にあります。document.xml の内容のみを編集し、明示的に依頼された場合を除き、styles.xml を上書きしたり再作成したりしないでください。そうするとユーザーのテンプレートが消去されます。
  • <w:pStyle> 参照を保持する:新しい段落を挿入する際は、同じ種類の隣接する段落から <w:pStyle w:val="..."/> をコピーします。<w:pStyle> を省略すると、文書の既定スタイルに暗黙的にフォールバックし、テンプレートの見出しや本文の書式が失われます。
  • スマート引用符は展開後に XML エンコードされる:展開ステップでは " / " が &#x201C; / &#x201D; エンティティに変換されます。中国語の引用符を含む見出しテキストを document.xml で検索する場合は、生の Unicode 文字ではなく、エンティティ形式(&#x201C;)で検索します。

XML リファレンス

スキーマ準拠
  • <w:pPr> 内の要素の順序:<w:pStyle>、<w:numPr>、<w:spacing>、<w:ind>、<w:jc>、最後に <w:rPr>
  • 空白:先頭または末尾に空白を含む <w:t> に xml:space="preserve" を追加します
  • RSID:8 桁の 16 進数である必要があります(例:00AB1234)
変更履歴

挿入:

<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
  <w:r><w:t>inserted text</w:t></w:r>
</w:ins>

削除:

<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
  <w:r><w:delText>deleted text</w:delText></w:r>
</w:del>

<w:del> 内では:<w:t> の代わりに <w:delText> を、<w:instrText> の代わりに <w:delInstrText> を使用します。

最小限の編集 - 変更箇所のみをマークします:


<w:r><w:t>The term is </w:t></w:r>
<w:del w:id="1" w:author="Claude" w:date="...">
  <w:r><w:delText>30</w:delText></w:r>
</w:del>
<w:ins w:id="2" w:author="Claude" w:date="...">
  <w:r><w:t>60</w:t></w:r>
</w:ins>
<w:r><w:t> days.</w:t></w:r>

段落/リスト項目全体の削除 - 段落の内容をすべて削除する場合は、段落記号も削除としてマークし、次の段落と結合するようにします。<w:pPr><w:rPr> 内に <w:del/> を追加します:

<w:p>
  <w:pPr>
    <w:numPr>...</w:numPr>  
    <w:rPr>
      <w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"/>
    </w:rPr>
  </w:pPr>
  <w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
    <w:r><w:delText>Entire paragraph content being deleted...</w:delText></w:r>
  </w:del>
</w:p>

<w:pPr><w:rPr> 内に <w:del/> がないと、変更を承認した際に空の段落/リスト項目が残ります。

別の作成者による挿入を取り消す - その作成者の挿入内に削除を入れ子にします:

<w:ins w:author="Jane" w:id="5">
  <w:del w:author="Claude" w:id="10">
    <w:r><w:delText>their inserted text</w:delText></w:r>
  </w:del>
</w:ins>

別の作成者による削除を復元する - その作成者の削除を変更せず、後に挿入を追加します:

<w:del w:author="Jane" w:id="5">
  <w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
<w:ins w:author="Claude" w:id="10">
  <w:r><w:t>deleted text</w:t></w:r>
</w:ins>
コメント

comment.py の実行後(ステップ 2 を参照)、document.xml にマーカーを追加します。返信の場合は --parent フラグを使用し、親のマーカー内に入れ子にします。

重要:<w:commentRangeStart> と <w:commentRangeEnd> は <w:r> の兄弟要素であり、決して <w:r> の内部には配置しません。


<w:commentRangeStart w:id="0"/>
<w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
  <w:r><w:delText>deleted</w:delText></w:r>
</w:del>
<w:r><w:t> more text</w:t></w:r>
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>


<w:commentRangeStart w:id="0"/>
  <w:commentRangeStart w:id="1"/>
  <w:r><w:t>text</w:t></w:r>
  <w:commentRangeEnd w:id="1"/>
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="1"/></w:r>
画像
  1. word/media/ に画像ファイルを追加します
  2. word/_rels/document.xml.rels にリレーションシップを追加します:
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
  1. [Content_Types].xml にコンテンツタイプを追加します:
<Default Extension="png" ContentType="image/png"/>
  1. document.xml で参照します:
<w:drawing>
  <wp:inline>
    <wp:extent cx="914400" cy="914400"/>  
    <a:graphic>
      <a:graphicData uri=".../picture">
        <pic:pic>
          <pic:blipFill><a:blip r:embed="rId5"/></pic:blipFill>
        </pic:pic>
      </a:graphicData>
    </a:graphic>
  </wp:inline>
</w:drawing>

依存関係

  • pandoc:テキスト抽出および LaTeX→OMML 数式変換(scripts/formula.py には pandoc ≥ 2.0 が必要です)
  • docx:npm install docx(新規ドキュメント)
  • temml:npm install temml(Word ネイティブ数式用の LaTeX → MathML 変換)
  • fast-xml-parser:npm install fast-xml-parser(docx 変換用の MathML 解析)
  • LibreOffice:PDF 変換(scripts/office/soffice.py により、サンドボックス環境向けに自動設定されます)
  • Poppler:画像用の pdftoppm

Markdown から Word への変換(中国語の学術論文)

このセクションでは、LaTeX 数式、表、引用を含む Markdown 論文を、中国の学術標準に準拠した適切な書式の Word 文書に変換するための包括的な解決策を説明します。

クイックスタート
# Install dependencies
npm install docx temml fast-xml-parser

# Run conversion
node scripts/new_doc.js
重大な問題と解決策

本番環境での使用中に、以下の8つの問題が特定され、解決されました。すべての解決策は scripts/new_doc.js と scripts/mathml-to-docx.js に実装されています。

問題 1:三線表の中間罫線が表示される

問題:三线表の本文行の罫線が非表示にならず、表示されていました。

解決策:本文行のすべての罫線を NONE に設定し、以下の罫線のみを残します。

  • ヘッダー上部:THICK(1.5pt)
  • ヘッダー下部:THIN(0.75pt)
  • 最終行下部:THICK(1.5pt)
const THICK = { style: BorderStyle.SINGLE, size: 12, color: '000000' };
const THIN  = { style: BorderStyle.SINGLE, size: 6,  color: '000000' };
const NONE  = { style: BorderStyle.NONE,   size: 0,  color: 'FFFFFF' };

// Header row
cellOf(h, colWidths[i], { top: THICK, bottom: THIN, left: NONE, right: NONE }, true)

// Body rows - ALL borders NONE except last row bottom
cellOf(cell, colWidths[i], {
  top: NONE,
  bottom: isLastRow ? THICK : NONE,
  left: NONE,
  right: NONE,
})
問題 2:数式テーブルの罫線が表示される

問題:ブロック数式テーブル(3列レイアウト)の罫線が表示されていました。

解決策:insideHorizontal と insideVertical を含むすべての罫線を NONE に設定します。

return new Table({
  width: { size: CONTENT_W, type: WidthType.DXA },
  columnWidths: [567, 7936, 567],
  borders: {
    top: NONE,
    bottom: NONE,
    left: NONE,
    right: NONE,
    insideHorizontal: NONE,  // CRITICAL: Must include these
    insideVertical: NONE,    // CRITICAL: Must include these
  },
  rows: [new TableRow({ children: [leftCell, formulaCell, numberCell] })],
});
問題 3:見出し番号—章ごとにリセットする Word の自動番号付け

問題:当初、見出し番号は手動の JS カウンターを使って段落テキストに埋め込まれていました。そのため、Word でコンテンツを追加・削除しても、番号が更新されませんでした。

解決策:H2/H3 では、章ごとの番号付け参照を使用した Word 標準の自動番号付けを使います。H1 では漢数字をテキスト内に保持します(一、二、三)。buildNumberingConfig(chapterCount) 関数は、2つのレベルを持つ sections_c{N} 番号付け参照を各章に対して生成します。章の境界ごとに _chapter をインクリメントし、H2/H3 を新しい番号付け参照に切り替えることで、自動的に番号をリセットします。

let _chapter = 0;

function h1Chinese(text) {  // Chinese numeral in text
  _chapter++;
  return new Paragraph({
    heading: HeadingLevel.HEADING_1,
    indent: { firstLine: 0 },
    children: [new TextRun(text)],
  });
}

function h2(text) {  // Word auto-numbered (level 0 in per-chapter ref)
  return new Paragraph({
    heading: HeadingLevel.HEADING_2,
    numbering: { reference: `sections_c${_chapter}`, level: 0 },
    indent: { firstLine: 0 },
    children: [new TextRun(text)],
  });
}

function h3(text) {  // Word auto-numbered (level 1 in per-chapter ref)
  return new Paragraph({
    heading: HeadingLevel.HEADING_3,
    numbering: { reference: `sections_c${_chapter}`, level: 1 },
    indent: { firstLine: 0 },
    children: [new TextRun(text)],
  });
}
問題 4:見出しの英字・数字に誤ったフォントが使われる(Cambria Math ではなく SimSun)

問題:見出し、図のキャプション、表のキャプションの英字テキストと数字が、Cambria Math ではなく SimSun(宋体)で表示されていました。

解決策:ascii、eastAsia、hAnsi プロパティを使用した混合フォント設定を使います。

// Style definition for headings
{
  id: 'Heading1', name: 'Heading 1', basedOn: 'Normal',
  run: {
    font: {
      ascii: 'Cambria Math',    // English letters, numbers
      eastAsia: 'SimHei',        // Chinese characters (黑体)
      hAnsi: 'Cambria Math',     // Western European characters
    },
    size: 32, bold: true,
  },
  // ... paragraph settings
}

// For captions (SimSun body font for Chinese)
font: { ascii: 'Cambria Math', eastAsia: 'SimSun', hAnsi: 'Cambria Math' }
問題 5:ブロック数式に Word の数式エディターが使われない

問題:LaTeX 数式が、Word 標準の数式ではなくプレーンテキストまたは画像としてレンダリングされていました。

解決策:temml(LaTeX→MathML)+ mathml-to-docx.js(MathML→OMML)のパイプラインを使います。

const temml = require('temml');
const { mathmlToDocxChildren } = require('./mathml-to-docx');

function latexToMath(latex) {
  const mathml = temml.renderToString(latex, { displayMode: true, throwOnError: false });
  const children = mathmlToDocxChildren(mathml);
  if (children && children.length) {
    return new Math({ children });
  }
  // Fallback
  return new Math({ children: [new MathRun(latex)] });
}

// Usage in formula table
const mathObj = latexToMath('Q_n(x, a) = r + \\gamma V_{n-1}(y)');
問題 6:タイトルの英字に誤ったフォントが使われる

問題:ドキュメントタイトルの英字テキストが、Cambria Math ではなく SimSun で表示されていました。

解決策:タイトル段落にも同じ混合フォントを適用します。

new Paragraph({
  alignment: AlignmentType.CENTER,
  children: [new TextRun({
    text: '论文标题 Paper Title',
    bold: true,
    size: 36,
    font: { ascii: 'Cambria Math', eastAsia: 'SimHei', hAnsi: 'Cambria Math' },
  })],
})
問題 7:インライン数式の検出条件が厳密でない

問題:インライン数式の正規表現が通常の数字(「1992」など)や英単語(「Agent」、「Watkins」など)にも一致し、誤って数式オブジェクトに変換していました。

解決策:実際の数式内容にのみ一致する厳密な正規表現を使います。

// Detection function - returns true only for real math content
function containsMath(text) {
  // Greek letters
  if (/[αβγδεζηθικλμνξπρστυφχψωΓΔΘΛΞΠΣΦΨΩ]/.test(text)) return true;
  // Unicode subscript/superscript characters
  if (/[₀₁₂₃₄₅₆₇₈₉ₙₓᵢₜₛ⁰¹²³⁴⁵⁶⁷⁸⁹ⁿⁱ]/.test(text)) return true;
  // Math operators and special symbols
  if (/[∞∑∏∫≤≥≠≈→←↔∈∉⊂⊃∀∃∧∨×÷±∓·…⋯′″⟨⟩]/.test(text)) return true;
  // Starred symbols like π*, Q*
  if (/[A-Z]\*/.test(text)) return true;
  // Explicit $...$ LaTeX
  if (/\$[^$]+\$/.test(text)) return true;
  return false;
}

// Parsing regex - strict matching for inline formulas
const mathPattern = /\$([^$]+)\$|([A-Z][₀₁₂₃₄₅₆₇₈₉ₙₓᵢₜₛ⁰¹²³⁴⁵⁶⁷⁸⁹ⁿⁱ]+\*?\s*\([^)]+\))|([A-Z]\s*\([^)]*[αβγδεζηθικλμνξπρστυφχψωΓΔΘΛΞΠΣΦΨΩ₀₁₂₃₄₅₆₇₈₉ₙₓᵢₜₛ][^)]*\))|([αβγδεζηθικλμνξπρστυφχψωΓΔΘΛΞΠΣΦΨΩ][₀₁₂₃₄₅₆₇₈₉ₙₓᵢₜₛ⁰¹²³⁴⁵⁶⁷⁸⁹ⁿⁱ]*\*?)|([A-Za-z][₀₁₂₃₄₅₆₇₈₉ₙₓᵢₜₛ⁰¹²³⁴⁵⁶⁷⁸⁹ⁿⁱ]+\*?)|([A-Z]\*)/g;

// What it matches:
// - $...$ explicit LaTeX
// - Qₙ(x,a) - function with subscripts
// - V(αₙ) - function with Greek params
// - αₙ - Greek with subscript
// - xₙ - variable with subscript
// - Q* - starred variable

// What it does NOT match:
// - Plain numbers: 1992, 500
// - English words: Agent, Watkins, Dayan
// - Plain parentheses: (1992), (optional)
問題 8:引用が上付きにならない

問題:[1] や [2] などの参考文献引用が、上付きではなく通常のテキストとして表示されていました。

解決策:引用のパターンを検出し、superScript: true を適用します。

function containsCitation(text) {
  return /\[\d+\]/.test(text);
}

// In parseInlineContentWithCitations():
const combinedPattern = /(\[\d+\])|...; // Citation first in alternation

if (match[1]) {
  // Citation [n] - convert to superscript
  children.push(new TextRun({
    text: match[1],
    superScript: true,
  }));
}
問題 10:要旨の後と参考文献の前に改ページする

問題:要旨ページはキーワードの後で終わり、参考文献は新しいページから始まる必要があります。

解決策:pageBreak() ヘルパー関数を追加します。

// ISSUE 10 FIX: Page break helper
function pageBreak() {
  return new Paragraph({ children: [new PageBreak()] });
}

// Usage in CONTENT:
// After keywords (end of abstract)
body('关键词:强化学习;Q-learning'),
pageBreak(),  // <- Start new page after abstract

// Before references
pageBreak(),  // <- Start new page for references
new Paragraph({
  heading: HeadingLevel.HEADING_1,
  children: [new TextRun('参考文献')],
}),

重要:PageBreak は必ず Paragraph でラップする必要があります。単独では使用できません。

問題 11:数式テーブルのセルの垂直方向の配置

問題:3列の罫線なし数式テーブルで、数式と数式番号 (n) が垂直方向の中央に配置されず、位置がずれていました。

解決策:3つすべての TableCell 定義に verticalAlign: VerticalAlign.CENTER を追加します。

// First, add VerticalAlign to imports:
const {
  // ... other imports
  VerticalAlign,  // <-- Add this
} = require('docx');

// Then apply to each cell in the formula() function:
const leftCell = new TableCell({
  width: { size: 567, type: WidthType.DXA },
  borders: noBorders,
  shading: { fill: 'FFFFFF', type: ShadingType.CLEAR },
  verticalAlign: VerticalAlign.CENTER,  // <-- Add this
  children: [new Paragraph({ indent: { firstLine: 0 }, children: [] })],
});

const formulaCell = new TableCell({
  width: { size: 7936, type: WidthType.DXA },
  borders: noBorders,
  shading: { fill: 'FFFFFF', type: ShadingType.CLEAR },
  verticalAlign: VerticalAlign.CENTER,  // <-- Add this
  children: [new Paragraph({
    alignment: AlignmentType.CENTER,
    indent: { firstLine: 0 },
    children: [mathObj],
  })],
});

const numberCell = new TableCell({
  width: { size: 567, type: WidthType.DXA },
  borders: noBorders,
  shading: { fill: 'FFFFFF', type: ShadingType.CLEAR },
  verticalAlign: VerticalAlign.CENTER,  // <-- Add this
  children: [new Paragraph({
    alignment: AlignmentType.RIGHT,
    indent: { firstLine: 0 },
    children: [new TextRun(`(${number})`)],
  })],
});

結果:数式と数式番号が同じベースライン上で水平に揃うようになりました。

ファイル構造
scripts/
├── new_doc.js          # Main template with Issues 1-8, 10-11 fixes (~820 lines)
├── mathml-to-docx.js   # MathML→docx Math converter (~250 lines)
├── formula.py          # Block formula XML insertion (legacy)
├── table.py            # Table XML insertion (legacy)
└── office/
    ├── unpack.py       # DOCX→XML extraction
    ├── pack.py         # XML→DOCX assembly
    └── validate.py     # Document validation
Unicode から LaTeX へのマッピング

このスクリプトには、Unicode 数学記号を変換する包括的な機能が含まれています。

const UNICODE_TO_LATEX = {
  // Greek letters
  'α': '\\alpha', 'β': '\\beta', 'γ': '\\gamma', // ... etc
  // Subscripts
  '₀': '_0', '₁': '_1', '₂': '_2', // ... etc
  // Superscripts
  '²': '^2', '³': '^3', // ... etc
  // Special symbols
  '∞': '\\infty', '∑': '\\sum', '∫': '\\int', // ... etc
};
MathML から DOCX への変換

scripts/mathml-to-docx.js は、MathML(temml から取得)を docx の数式コンポーネントに変換します。

  • 分数:<mfrac> → MathFraction
  • 下付き文字:<msub> → MathSubScript
  • 上付き文字:<msup> → MathSuperScript
  • 組み合わせ:<msubsup> → MathSubSuperScript
  • 根号:<msqrt>、<mroot> → MathRadical
  • 総和:∑ を含む <munderover> → MathSum
  • 積分:∫ を含む <munderover> → MathIntegral
  • 行列:<mtable> → MathMatrix
新しい変換のワークフロー
  1. プロジェクトに new_doc.js のテンプレートをコピーします
  2. 依存関係をインストールします:npm install docx temml fast-xml-parser
  3. ドキュメントの構成をCONTENT セクションで編集します
  4. ヘルパー関数を使用します:
    • h1/h2/h3(text) - 自動採番される見出し
    • body(text) - 本文の段落(数式と引用を自動検出します)
    • formula(latex, number) - ブロック数式
    • threeLineTable(headers, rows, colWidths) - 三線表
    • tableCaption/figCaption(label) - キャプション
    • pageBreak() - 改ページ(Issue 10)
    • ref(text) - 参考文献の項目
  5. 実行します:node new_doc.js

コメント