使用 OpenXML 时以正确的顺序插入子元素

Insert child element in the right order when working with OpenXML

我正在使用 DocumentFormat.OpenXml 库修改 .docx 文档。我知道 element ordering is important,否则文档将无法通过架构验证,并可能导致文档无法在 Word 中打开。

现在我需要向 DocumentSettingsPart 添加一个 DocumentProtection 元素。我需要将这个子元素插入父元素中的正确位置。

架构如下所示:

子元素有很多可能的排序方式。目前我正在像这样添加这个元素:

var documentProtection = new DocumentProtection()
{
    // do the configuration
};

DocumentSettingsPart settings = doc.MainDocumentPart.DocumentSettingsPart;
var rootElement = settings.RootElement;
var prevElement = 
                rootElement.GetFirstChild<DoNotTrackFormatting>() ??
                rootElement.GetFirstChild<DoNotTrackMoves>() ??
                rootElement.GetFirstChild<TrackRevisions>() ??
                rootElement.GetFirstChild<RevisionView>() ??
                rootElement.GetFirstChild<DocumentType>() ??
                rootElement.GetFirstChild<StylePaneSortMethods>() ??
                // SNIP
                rootElement.GetFirstChild<Zoom>() ??
                rootElement.GetFirstChild<View>() ??
                (OpenXmlLeafElement)rootElement.GetFirstChild<WriteProtection>();
rootElement.InsertAfter(documentProtection, prevElement);

即我正在尝试查找文档中是否已经存在应该在我的元素之前出现的任何可能元素。然后在该元素之后插入 DocumentProtection。考虑到元素的数量,这个列表变得非常无聊。

是否有更好的方法来添加 DocumentProtection 使其符合模式并且不涉及所有可能元素的枚举?

没有很好的方法来实现您想要的。您将不得不修改集合,并负责保持顺序正确。

Settings class 上使用 ILSpy 你会发现实现者在基础 class 上使用了一个辅助方法 SetElement<T> 来获取一个位置和一个实例插入。

不幸的是,辅助方法被标记为 internal,因此如果您尝试子 class Settings,我们将无法利用它。相反,我重新实现了所需的功能,因此您将拥有 Settings 的子 class,它确实为 DocumentProtection 提供了 属性,但使用重新实现的解决方案来查找插入节点的正确位置:

设置扩展

public class SettingsExt: Settings
{
    // contruct based on XML
    public SettingsExt(string outerXml)
        :base(outerXml)
    {
        // empty
    }

    public DocumentProtection DocumentProtection
    {
        // get is easy
        get
        {
            return this.GetFirstChild<DocumentProtection>();
        }
        // reimplemented SetElement based on 
        // reversed engineered Settings class
        set
        {

            // eleTagNames is a static string[] declared later
            // it holds all the names of the elements in the right order
            int sequenceNumber = eleTagNames
                .Select((s, i) => new { s= s, idx = i })
                    .Where(s => s.s == "documentProtection")
                    .Select((s) => s.idx)
                    .First(); 
            OpenXmlElement openXmlElement = this.FirstChild;

            OpenXmlElement refChild = null;
            while (openXmlElement != null)
            {
                // a bit naive
                int currentSequence = eleTagNames
                    .Select((s, i) => new { s = s, idx = i })
                    .Where(s => s.s == openXmlElement.LocalName)
                    .Select((s) => s.idx)
                    .First(); ; 
                if (currentSequence == sequenceNumber)
                {
                    if (openXmlElement is DocumentProtection)
                    {
                        refChild = openXmlElement.PreviousSibling();
                        this.RemoveChild<OpenXmlElement>(openXmlElement);
                        break;
                    }
                    refChild = openXmlElement;
                }
                else
                {
                    if (currentSequence > sequenceNumber)
                    {
                        break;
                    }
                    refChild = openXmlElement;
                }
                openXmlElement = openXmlElement.NextSibling();
            }
            if (value != null)
            {
                this.InsertAfter(value, refChild);
            }
        }
    }
    
    // order of elements in the sequence!
    static readonly string[] eleTagNames = new string[]
    {
        "writeProtection",
        "view",
        "zoom",
        "removePersonalInformation",
        "removeDateAndTime",
        "doNotDisplayPageBoundaries",
        "displayBackgroundShape",
        "printPostScriptOverText",
        "printFractionalCharacterWidth",
        "printFormsData",
        "embedTrueTypeFonts",
        "embedSystemFonts",
        "saveSubsetFonts",
        "saveFormsData",
        "mirrorMargins",
        "alignBordersAndEdges",
        "bordersDoNotSurroundHeader",
        "bordersDoNotSurroundFooter",
        "gutterAtTop",
        "hideSpellingErrors",
        "hideGrammaticalErrors",
        "activeWritingStyle",
        "proofState",
        "formsDesign",
        "attachedTemplate",
        "linkStyles",
        "stylePaneFormatFilter",
        "stylePaneSortMethod",
        "documentType",
        "mailMerge",
        "revisionView",
        "trackRevisions",
        "doNotTrackMoves",
        "doNotTrackFormatting",
        "documentProtection",
        "autoFormatOverride",
        "styleLockTheme",
        "styleLockQFSet",
        "defaultTabStop",
        "autoHyphenation",
        "consecutiveHyphenLimit",
        "hyphenationZone",
        "doNotHyphenateCaps",
        "showEnvelope",
        "summaryLength",
        "clickAndTypeStyle",
        "defaultTableStyle",
        "evenAndOddHeaders",
        "bookFoldRevPrinting",
        "bookFoldPrinting",
        "bookFoldPrintingSheets",
        "drawingGridHorizontalSpacing",
        "drawingGridVerticalSpacing",
        "displayHorizontalDrawingGridEvery",
        "displayVerticalDrawingGridEvery",
        "doNotUseMarginsForDrawingGridOrigin",
        "drawingGridHorizontalOrigin",
        "drawingGridVerticalOrigin",
        "doNotShadeFormData",
        "noPunctuationKerning",
        "characterSpacingControl",
        "printTwoOnOne",
        "strictFirstAndLastChars",
        "noLineBreaksAfter",
        "noLineBreaksBefore",
        "savePreviewPicture",
        "doNotValidateAgainstSchema",
        "saveInvalidXml",
        "ignoreMixedContent",
        "alwaysShowPlaceholderText",
        "doNotDemarcateInvalidXml",
        "saveXmlDataOnly",
        "useXSLTWhenSaving",
        "saveThroughXslt",
        "showXMLTags",
        "alwaysMergeEmptyNamespace",
        "updateFields",
        "hdrShapeDefaults",
        "footnotePr",
        "endnotePr",
        "compat",
        "docVars",
        "rsids",
        "mathPr",
        "uiCompat97To2003",
        "attachedSchema",
        "themeFontLang",
        "clrSchemeMapping",
        "doNotIncludeSubdocsInStats",
        "doNotAutoCompressPictures",
        "forceUpgrade",
        "captions",
        "readModeInkLockDown",
        "smartTagType",
        "schemaLibrary",
        "shapeDefaults",
        "doNotEmbedSmartTags",
        "decimalSymbol",
        "listSeparator",
        "docId",
        "discardImageEditingData",
        "defaultImageDpi",
        "conflictMode"};

}

这个class的典型使用场景如下:

using (var doc = WordprocessingDocument.Open(@"c:\tmp\test.docx", true))
{

    var documentProtection = new DocumentProtection()
    {
        Formatting = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true)
    };

    DocumentSettingsPart settings = doc.MainDocumentPart.DocumentSettingsPart;

    // instantiate our ExtendedSettings class based on the
    // original Settings
    var extset = new SettingsExt(settings.Settings.OuterXml);

    // new or existing?
    if (extset.DocumentProtection == null)
    {
        extset.DocumentProtection = documentProtection;
    }
    else
    {
        // replace existing values
    }

    // this is key to make sure our own DOMTree is saved!
    // don't forget this
    settings.Settings = extset;
}