1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472 |
- // Copyright (c) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Xml.Linq;
- using DocumentFormat.OpenXml.Packaging;
- namespace OpenXmlPowerTools
- {
- internal class ReverseRevisionsInfo
- {
- public bool InInsert;
- }
- public static class RevisionProcessor
- {
- public static WmlDocument RejectRevisions(WmlDocument document)
- {
- using (var streamDoc = new OpenXmlMemoryStreamDocument(document))
- {
- using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
- {
- RejectRevisions(doc);
- }
- return streamDoc.GetModifiedWmlDocument();
- }
- }
- public static void RejectRevisions(WordprocessingDocument doc)
- {
- // Reject revisions for those revisions that can't be rejected by inverting the sense
- // of the revision, and then accepting.
- RejectRevisionsForPart(doc.MainDocumentPart);
- foreach (HeaderPart part in doc.MainDocumentPart.HeaderParts)
- {
- RejectRevisionsForPart(part);
- }
- foreach (FooterPart part in doc.MainDocumentPart.FooterParts)
- {
- RejectRevisionsForPart(part);
- }
- if (doc.MainDocumentPart.EndnotesPart is EndnotesPart endnotesPart1)
- {
- RejectRevisionsForPart(endnotesPart1);
- }
- if (doc.MainDocumentPart.FootnotesPart is FootnotesPart footnotesPart1)
- {
- RejectRevisionsForPart(footnotesPart1);
- }
- if (doc.MainDocumentPart.StyleDefinitionsPart is StyleDefinitionsPart styleDefinitionsPart1)
- {
- RejectRevisionsForStylesDefinitionPart(styleDefinitionsPart1);
- }
- // Invert the sense of the revisions and accept those reverse revisions.
- ReverseRevisions(doc);
- AcceptRevisionsForPart(doc.MainDocumentPart);
- foreach (HeaderPart part in doc.MainDocumentPart.HeaderParts)
- {
- AcceptRevisionsForPart(part);
- }
- foreach (FooterPart part in doc.MainDocumentPart.FooterParts)
- {
- AcceptRevisionsForPart(part);
- }
- if (doc.MainDocumentPart.EndnotesPart is EndnotesPart endnotesPart2)
- {
- AcceptRevisionsForPart(endnotesPart2);
- }
- if (doc.MainDocumentPart.FootnotesPart is FootnotesPart footnotesPart2)
- {
- AcceptRevisionsForPart(footnotesPart2);
- }
- if (doc.MainDocumentPart.StyleDefinitionsPart is StyleDefinitionsPart styleDefinitionsPart2)
- {
- AcceptRevisionsForStylesDefinitionPart(styleDefinitionsPart2);
- }
- }
- // Reject revisions for those revisions that can't be rejected by inverting the sense of the revision, and then accepting.
- private static void RejectRevisionsForPart(OpenXmlPart part)
- {
- XElement root = part.GetXElement();
- object newRoot = RejectRevisionsForPartTransform(root);
- root.ReplaceWith(newRoot);
- part.PutXElement();
- }
- private static object RejectRevisionsForPartTransform(XNode node)
- {
- if (node is XElement element)
- {
- ////////////////////////////////////////////////////////////////////////////////////////
- // Inserted Numbering Properties
- #if false
- <w:p>
- <w:pPr>
- <w:pStyle w:val="ListParagraph"/>
- <w:numPr>
- <w:ilvl w:val="0"/>
- <w:numId w:val="1"/>
- <w:ins w:id="0" w:author="Eric White" w:date="2017-03-26T03:50:00Z" />
- </w:numPr>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- </w:pPr>
- <w:r w:rsidRPr="009D59B3">
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t>This is a test.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.numPr && element.Element(W.ins) != null)
- {
- return null;
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Paragraph properties change
- #if false
- <w:p>
- <w:pPr>
- <w:pStyle w:val="ListParagraph"/>
- <w:numPr>
- <w:ilvl w:val="1"/>
- <w:numId w:val="2"/>
- </w:numPr>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:pPrChange w:id="0" w:author="Eric White" w:date="2017-03-26T04:55:00Z">
- <w:pPr>
- <w:pStyle w:val="ListParagraph"/>
- <w:numPr>
- <w:ilvl w:val="1"/>
- <w:numId w:val="1"/>
- </w:numPr>
- <w:ind w:left="1440" w:hanging="360"/>
- </w:pPr>
- </w:pPrChange>
- </w:pPr>
- <w:r>
- <w:t>When you click Online Video, you can paste in the embed code for the video you want to add.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.pPr && element.Element(W.pPrChange) is XElement pPrChange)
- {
- XElement newPPr = pPrChange.Element(W.pPr) is XElement pPr
- ? new XElement(pPr)
- : new XElement(W.pPr);
- newPPr.Add(RejectRevisionsForPartTransform(element.Element(W.rPr)));
- return RejectRevisionsForPartTransform(newPPr);
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Run properties change
- #if false
- <w:p w:rsidR="00615148" w:rsidRPr="00615148" w:rsidRDefault="00615148">
- <w:pPr>
- <w:rPr>
- <w:b/>
- <w:lang w:val="en-US"/>
- <w:rPrChange w:id="0" w:author="Eric White" w:date="2017-03-26T05:02:00Z">
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- </w:rPrChange>
- </w:rPr>
- </w:pPr>
- <w:r>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t>When you click Online Video, you can paste in the embed code for the video you want to add.</w:t>
- </w:r>
- <w:bookmarkStart w:id="1" w:name="_GoBack"/>
- </w:p>
- #endif
- if (element.Name == W.rPr && element.Element(W.rPrChange) is XElement rPrChange)
- {
- XElement newRPr = rPrChange.Element(W.rPr);
- return RejectRevisionsForPartTransform(newRPr);
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Field code numbering change
- #if false
- <w:p w:rsidR="00D46247" w:rsidRDefault="00D46247">
- <w:r>
- <w:fldChar w:fldCharType="begin"/>
- </w:r>
- <w:r>
- <w:instrText xml:space="preserve"> LISTNUM </w:instrText>
- </w:r>
- <w:r>
- <w:fldChar w:fldCharType="end">
- <w:numberingChange w:id="0" w:author="Eric White" w:date="2017-03-26T12:48:00Z" w:original="1)"/>
- </w:fldChar>
- </w:r>
- <w:r>
- <w:t xml:space="preserve"> Video provides a powerful way to help you prove your point.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.numberingChange)
- {
- return null;
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Change w:sectPr
- #if false
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:ins w:id="0" w:author="Eric White" w:date="2017-03-26T15:40:00Z"/>
- </w:rPr>
- <w:sectPr>
- <w:pgSz w:w="12240" w:h="15840"/>
- <w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720" w:header="720" w:footer="720" w:gutter="0"/>
- <w:cols w:space="720"/>
- <w:docGrid w:linePitch="360"/>
- <w:sectPrChange w:id="1" w:author="Eric White" w:date="2017-03-26T15:42:00Z">
- <w:sectPr w:rsidR="00620990" w:rsidSect="004E0757">
- <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/>
- </w:sectPr>
- </w:sectPrChange>
- </w:sectPr>
- </w:pPr>
- </w:p>
- #endif
- if (element.Name == W.sectPr && element.Element(W.sectPrChange) is XElement sectPrChange)
- {
- XElement newSectPr = sectPrChange.Element(W.sectPr);
- return RejectRevisionsForPartTransform(newSectPr);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // tblGridChange
- #if false
- <w:tblGrid>
- <w:gridCol w:w="1525"/>
- <w:gridCol w:w="3005"/>
- <w:gridCol w:w="3006"/>
- <w:tblGridChange w:id="1">
- <w:tblGrid>
- <w:gridCol w:w="3005"/>
- <w:gridCol w:w="3005"/>
- <w:gridCol w:w="3006"/>
- </w:tblGrid>
- </w:tblGridChange>
- </w:tblGrid>
- #endif
- if (element.Name == W.tblGrid && element.Element(W.tblGridChange) is XElement tblGridChange)
- {
- XElement newTblGrid = tblGridChange.Element(W.tblGrid);
- return RejectRevisionsForPartTransform(newTblGrid);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // tcPrChange
- #if false
- <w:tc>
- <w:tcPr>
- <w:tcW w:w="1525" w:type="dxa"/>
- <w:tcPrChange w:id="2" w:author="Eric White" w:date="2017-03-26T18:01:00Z">
- <w:tcPr>
- <w:tcW w:w="3005" w:type="dxa"/>
- </w:tcPr>
- </w:tcPrChange>
- </w:tcPr>
- <w:p>
- <w:r>
- <w:t>1</w:t>
- </w:r>
- </w:p>
- </w:tc>
- #endif
- if (element.Name == W.tcPr && element.Element(W.tcPrChange) is XElement tcPrChange)
- {
- XElement newTcPr = tcPrChange.Element(W.tcPr);
- return RejectRevisionsForPartTransform(newTcPr);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // trPrChange
- if (element.Name == W.trPr && element.Element(W.trPrChange) is XElement trPrChange)
- {
- XElement newTrPr = trPrChange.Element(W.trPr);
- return RejectRevisionsForPartTransform(newTrPr);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // tblPrExChange
- #if false
- <w:tblPrEx>
- <w:tblW w:w="0" w:type="auto"/>
- <w:tblPrExChange w:id="1" w:author="Eric White" w:date="2017-03-26T18:10:00Z">
- <w:tblPrEx>
- <w:tblW w:w="0" w:type="auto"/>
- </w:tblPrEx>
- </w:tblPrExChange>
- </w:tblPrEx>
- #endif
- #if false
- <w:tr w:rsidR="00097582" w:rsidTr="00F843C4">
- <w:tblPrEx>
- <w:tblW w:w="0" w:type="auto"/>
- <w:tblBorders>
- <w:top w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:left w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:bottom w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:right w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:insideH w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:insideV w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- </w:tblBorders>
- <w:tblPrExChange w:id="1" w:author="Eric White" w:date="2017-03-26T20:38:00Z">
- <w:tblPrEx>
- <w:tblW w:w="0" w:type="auto"/>
- <w:tblBorders>
- <w:top w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:left w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:bottom w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:right w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:insideH w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- <w:insideV w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
- </w:tblBorders>
- </w:tblPrEx>
- </w:tblPrExChange>
- </w:tblPrEx>
- #endif
- if (element.Name == W.tblPrEx && element.Element(W.tblPrExChange) is XElement tblPrExChange)
- {
- XElement newTblPrEx = tblPrExChange.Element(W.tblPrEx);
- return RejectRevisionsForPartTransform(newTblPrEx);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // tblPrChange
- #if false
- <w:tbl>
- <w:tblPr>
- <w:tblStyle w:val="GridTable4-Accent1"/>
- <w:tblW w:w="0" w:type="auto"/>
- <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>
- <w:tblPrChange w:id="0" w:author="Eric White" w:date="2017-03-26T20:05:00Z">
- <w:tblPr>
- <w:tblStyle w:val="TableGrid"/>
- <w:tblW w:w="0" w:type="auto"/>
- <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0" w:noHBand ="0" w:noVBand="1"/>
- </w:tblPr>
- </w:tblPrChange>
- </w:tblPr>
- #endif
- if (element.Name == W.tblPr && element.Element(W.tblPrChange) is XElement tblPrChange)
- {
- XElement newTrPr = tblPrChange.Element(W.tblPr);
- return RejectRevisionsForPartTransform(newTrPr);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // tblPrChange
- #if false
- <w:tc>
- <w:tcPr>
- <w:tcW w:w="3005" w:type="dxa"/>
- <w:cellDel w:id="8" w:author="Eric White" w:date="2017-03-26T21:12:00Z"/>
- <w:tcPrChange w:id="9" w:author="Eric White" w:date="2017-03-26T21:12:00Z">
- <w:tcPr>
- <w:tcW w:w="3005" w:type="dxa"/>
- <w:gridSpan w:val="2"/>
- <w:cellDel w:id="10" w:author="Eric White" w:date="2017-03-26T21:12:00Z"/>
- </w:tcPr>
- </w:tcPrChange>
- </w:tcPr>
- #endif
- if (element.Name == W.cellDel ||
- element.Name == W.cellMerge)
- {
- return null;
- }
- if (element.Name == W.tc &&
- element.Elements(W.tcPr).Elements(W.cellIns).Any())
- {
- return null;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(RejectRevisionsForPartTransform));
- }
- return node;
- }
- private static void RejectRevisionsForStylesDefinitionPart(StyleDefinitionsPart stylesDefinitionsPart)
- {
- XElement root = stylesDefinitionsPart.GetXElement();
- object newRoot = RejectRevisionsForStylesTransform(root);
- root.ReplaceWith(newRoot);
- stylesDefinitionsPart.PutXElement();
- }
- private static object RejectRevisionsForStylesTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Name == W.pPr && element.Element(W.pPrChange) is XElement pPrChange)
- {
- XElement newPPr = pPrChange.Element(W.pPr);
- return RejectRevisionsForStylesTransform(newPPr);
- }
- if (element.Name == W.rPr && element.Element(W.rPrChange) is XElement rPrChange)
- {
- XElement newRPr = rPrChange.Element(W.rPr);
- return RejectRevisionsForStylesTransform(newRPr);
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(RejectRevisionsForStylesTransform));
- }
- return node;
- }
- private static void ReverseRevisions(WordprocessingDocument doc)
- {
- ReverseRevisionsForPart(doc.MainDocumentPart);
- foreach (HeaderPart part in doc.MainDocumentPart.HeaderParts)
- {
- ReverseRevisionsForPart(part);
- }
- foreach (FooterPart part in doc.MainDocumentPart.FooterParts)
- {
- ReverseRevisionsForPart(part);
- }
- if (doc.MainDocumentPart.EndnotesPart != null)
- {
- ReverseRevisionsForPart(doc.MainDocumentPart.EndnotesPart);
- }
- if (doc.MainDocumentPart.FootnotesPart != null)
- {
- ReverseRevisionsForPart(doc.MainDocumentPart.FootnotesPart);
- }
- }
- private static void ReverseRevisionsForPart(OpenXmlPart part)
- {
- XElement root = part.GetXElement();
- var rri = new ReverseRevisionsInfo { InInsert = false };
- var newRoot = (XElement)ReverseRevisionsTransform(root, rri);
- newRoot = (XElement)RemoveRsidTransform(newRoot);
- root.ReplaceWith(newRoot);
- part.PutXElement();
- }
- private static object RemoveRsidTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Name == W.rsid)
- return null;
- return new XElement(element.Name,
- element.Attributes().Where(a => a.Name != W.rsid &&
- a.Name != W.rsidDel &&
- a.Name != W.rsidP &&
- a.Name != W.rsidR &&
- a.Name != W.rsidRDefault &&
- a.Name != W.rsidRPr &&
- a.Name != W.rsidSect &&
- a.Name != W.rsidTr),
- element.Nodes().Select(RemoveRsidTransform));
- }
- return node;
- }
- private static object MergeAdjacentTablesTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Element(W.tbl) != null)
- {
- IEnumerable<IGrouping<string, XElement>> grouped = element
- .Elements()
- .GroupAdjacent(e =>
- {
- if (e.Name != W.tbl)
- return "";
- XElement bidiVisual = e.Elements(W.tblPr).Elements(W.bidiVisual).FirstOrDefault();
- string bidiVisString = bidiVisual == null ? "" : "|bidiVisual";
- string key = "tbl" + bidiVisString;
- return key;
- });
- IEnumerable<object> newContent = grouped
- .Select(g =>
- {
- if (g.Key == "" || g.Count() == 1)
- return (object)g;
- int[] rolled = g
- .Select(tbl =>
- {
- IEnumerable<int> gridCols = tbl
- .Elements(W.tblGrid)
- .Elements(W.gridCol)
- .Attributes(W._w)
- .Select(a => (int)a)
- .Rollup(0, (s, i) => s + i);
- return gridCols;
- })
- .SelectMany(m => m)
- .Distinct()
- .OrderBy(w => w)
- .ToArray();
- var newTable = new XElement(W.tbl,
- g.First().Elements(W.tblPr),
- new XElement(W.tblGrid,
- rolled.Select((r, i) =>
- {
- int v;
- if (i == 0)
- v = r;
- else
- v = r - rolled[i - 1];
- return new XElement(W.gridCol,
- new XAttribute(W._w, v));
- })),
- g.Select(tbl =>
- {
- XElement fixedWidthsTbl = FixWidths(tbl);
- IEnumerable<XElement> newRows = fixedWidthsTbl.Elements(W.tr)
- .Select(tr =>
- {
- var newRow = new XElement(W.tr,
- tr.Attributes(),
- tr.Elements().Where(e => e.Name != W.tc),
- tr.Elements(W.tc).Select(tc =>
- {
- var w = (int?)tc
- .Elements(W.tcPr)
- .Elements(W.tcW)
- .Attributes(W._w)
- .FirstOrDefault();
- if (w == null)
- {
- return tc;
- }
- IEnumerable<XElement> cellsToLeft = tc
- .GetParent()
- .Elements(W.tc)
- .TakeWhile(btc => btc != tc)
- .ToList();
- var widthToLeft = 0;
- if (cellsToLeft.Any())
- {
- widthToLeft = cellsToLeft
- .Elements(W.tcPr)
- .Elements(W.tcW)
- .Attributes(W._w)
- .Select(wi => (int)wi)
- .Sum();
- }
- var rolledPairs = new[]
- {
- new
- {
- GridValue = 0,
- Index = 0
- }
- }
- .Concat(rolled
- .Select((r, i) => new
- {
- GridValue = r,
- Index = i + 1
- }))
- .ToList();
- var start = rolledPairs
- .FirstOrDefault(t => t.GridValue >= widthToLeft);
- if (start != null)
- {
- int gridsRequired = rolledPairs
- .Skip(start.Index)
- .TakeWhile(rp => rp.GridValue - start.GridValue < w)
- .Count();
- var tcPr = new XElement(W.tcPr,
- tc.Elements(W.tcPr).Elements().Where(e => e.Name != W.gridSpan),
- gridsRequired != 1
- ? new XElement(W.gridSpan,
- new XAttribute(W.val, gridsRequired))
- : null);
- var orderedTcPr = new XElement(W.tcPr,
- tcPr.Elements().OrderBy(e =>
- OrderTcPr.ContainsKey(e.Name) ? OrderTcPr[e.Name] : 999));
- var newCell = new XElement(W.tc,
- orderedTcPr,
- tc.Elements().Where(e => e.Name != W.tcPr));
- return newCell;
- }
- return tc;
- }));
- return newRow;
- });
- return newRows;
- }));
- return newTable;
- });
- return new XElement(element.Name,
- element.Attributes(),
- newContent);
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(MergeAdjacentTablesTransform));
- }
- return node;
- }
- private static object ReverseRevisionsTransform(XNode node, ReverseRevisionsInfo rri)
- {
- if (node is XElement element)
- {
- XElement parent = element
- .Ancestors()
- .FirstOrDefault(a => a.Name != W.sdtContent &&
- a.Name != W.sdt &&
- a.Name != W.hyperlink &&
- a.Name != W.smartTag);
- ////////////////////////////////////////////////////////////////////////////////////
- // Deleted run
- #if false
- <w:p>
- <w:r>
- <w:t xml:space="preserve">Video </w:t>
- </w:r>
- <w:del>
- <w:r>
- <w:delText xml:space="preserve">provides </w:delText>
- </w:r>
- </w:del>
- <w:r>
- <w:t>a powerful way to help you prove your point.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.del &&
- parent?.Name == W.p)
- {
- return new XElement(W.ins,
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Deleted paragraph mark
- #if false
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:del w:id="0" w:author="Eric White" w:date="2017-03-24T21:52:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:r>
- <w:t>Video provides a powerful way to help you prove your point.</w:t>
- </w:r>
- </w:p>
- <w:p>
- <w:r>
- <w:t>You can also type a keyword to search online for the video that best fits your document.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.del &&
- parent?.Name == W.rPr &&
- parent?.Parent?.Name == W.pPr)
- {
- return new XElement(W.ins);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Inserted paragraph mark
- #if false
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:ins w:id="0" w:author="Eric White" w:date="2017-03-24T21:58:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:r>
- <w:t xml:space="preserve">Video provides a powerful way to help you prove your point. </w:t>
- </w:r>
- </w:p>
- <w:p>
- <w:r>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t>When you click Online Video, you can paste in the embed code for the video you want to add.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.ins &&
- parent?.Name == W.rPr &&
- parent?.Parent?.Name == W.pPr)
- {
- return new XElement(W.del);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Inserted run
- #if false
- <w:p>
- <w:r>
- <w:t xml:space="preserve">Video </w:t>
- </w:r>
- <w:ins>
- <w:r>
- <w:t xml:space="preserve">provides </w:t>
- </w:r>
- </w:ins>
- <w:r>
- <w:t>a powerful way to help you prove your point.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.ins &&
- parent?.Name == W.p)
- {
- // TODO: Revisit. Why is newRri not used?
- var newRri = new ReverseRevisionsInfo { InInsert = true };
- return new XElement(W.del,
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Deleted table row
- #if false
- <w:tbl>
- <w:tr>
- <w:tc>
- <w:p>
- <w:r>
- <w:t>1</w:t>
- </w:r>
- </w:p>
- </w:tc>
- </w:tr>
- <w:tr>
- <w:trPr>
- <w:del w:id="0" w:author="Eric White" w:date="2017-03-24T22:15:00Z"/>
- </w:trPr>
- <w:tc>
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:del w:id="1" w:author="Eric White" w:date="2017-03-24T22:15:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:del w:id="2" w:author="Eric White" w:date="2017-03-24T22:15:00Z">
- <w:r>
- <w:delText>4</w:delText>
- </w:r>
- </w:del>
- </w:p>
- </w:tc>
- </w:tr>
- <w:tr>
- <w:tc>
- <w:p>
- <w:r>
- <w:t>7</w:t>
- </w:r>
- </w:p>
- </w:tc>
- </w:tr>
- </w:tbl>
- #endif
- if (element.Name == W.del &&
- parent?.Name == W.trPr)
- {
- return new XElement(W.ins);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Inserted table row
- #if false
- <w:tbl>
- <w:tr>
- <w:tc>
- <w:p>
- <w:r>
- <w:t>1</w:t>
- </w:r>
- </w:p>
- </w:tc>
- </w:tr>
- <w:tr>
- <w:trPr>
- <w:ins w:id="0" w:author="Eric White" w:date="2017-03-24T22:16:00Z"/>
- </w:trPr>
- <w:tc>
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:ins w:id="1" w:author="Eric White" w:date="2017-03-24T22:16:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:ins w:id="2" w:author="Eric White" w:date="2017-03-24T22:16:00Z">
- <w:r>
- <w:t>1a</w:t>
- </w:r>
- </w:ins>
- </w:p>
- </w:tc>
- </w:tr>
- <w:tr>
- <w:tc>
- <w:p>
- <w:r>
- <w:t>4</w:t>
- </w:r>
- </w:p>
- </w:tc>
- </w:tr>
- </w:tbl>
- #endif
- if (element.Name == W.ins &&
- parent?.Name == W.trPr)
- {
- return new XElement(W.del);
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Deleted math control character
- #if false
- <w:p w:rsidR="007F4E48" w:rsidRDefault="00C9403B">
- <m:oMathPara>
- <m:oMath>
- <m:r>
- <w:rPr>
- <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
- </w:rPr>
- <m:t>A=</m:t>
- </m:r>
- <m:r>
- <w:del w:id="0" w:author="Eric White" w:date="2017-03-24T22:53:00Z">
- <w:rPr>
- <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
- </w:rPr>
- <m:t>2</m:t>
- </w:del>
- </m:r>
- <m:r>
- <w:rPr>
- <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
- </w:rPr>
- <m:t>π</m:t>
- </m:r>
- #endif
- if (element.Name == W.del &&
- parent?.Name == M.r)
- {
- return new XElement(W.ins,
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Inserted math control character
- #if false
- <w:p w:rsidR="007F4E48" w:rsidRDefault="00C9403B">
- <m:oMathPara>
- <m:oMath>
- <m:r>
- <w:rPr>
- <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
- </w:rPr>
- <m:t>A=</m:t>
- </m:r>
- <m:r>
- <w:ins w:id="0" w:author="Eric White" w:date="2017-03-24T22:54:00Z">
- <w:rPr>
- <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
- </w:rPr>
- <m:t>2</m:t>
- </w:ins>
- </m:r>
- <m:r>
- <w:rPr>
- <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
- </w:rPr>
- <m:t>π</m:t>
- </m:r>
- #endif
- if (element.Name == W.ins &&
- parent?.Name == M.r)
- {
- return new XElement(W.del,
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // moveFrom / moveTo
- #if false
- <w:p>
- <w:r>
- <w:t>Video provides a powerful way.</w:t>
- </w:r>
- </w:p>
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:moveFrom w:id="0" w:author="Eric White" w:date="2017-03-24T23:18:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:moveFromRangeStart w:id="1" w:author="Eric White" w:date="2017-03-24T23:18:00Z" w:name="move478160808"/>
- <w:moveFrom w:id="2" w:author="Eric White" w:date="2017-03-24T23:18:00Z">
- <w:r>
- <w:t>When you click Online Video.</w:t>
- </w:r>
- </w:moveFrom>
- </w:p>
- <w:moveFromRangeEnd w:id="1"/>
- <w:p>
- <w:r>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t>You can also type a keyword.</w:t>
- </w:r>
- </w:p>
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:moveTo w:id="3" w:author="Eric White" w:date="2017-03-24T23:18:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:moveToRangeStart w:id="5" w:author="Eric White" w:date="2017-03-24T23:18:00Z" w:name="move478160808"/>
- <w:moveTo w:id="6" w:author="Eric White" w:date="2017-03-24T23:18:00Z">
- <w:r>
- <w:t>When you click Online Video.</w:t>
- </w:r>
- </w:moveTo>
- </w:p>
- <w:moveToRangeEnd w:id="5"/>
- <w:p>
- <w:r>
- <w:t>Make your document look professionally produced.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.moveFrom)
- {
- return new XElement(W.moveTo,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.moveFromRangeStart)
- {
- return new XElement(W.moveToRangeStart,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.moveFromRangeEnd)
- {
- return new XElement(W.moveToRangeEnd,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.moveTo)
- {
- return new XElement(W.moveFrom,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.moveToRangeStart)
- {
- return new XElement(W.moveFromRangeStart,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.moveToRangeEnd)
- {
- return new XElement(W.moveFromRangeEnd,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Deleted content control
- #if false
- <w:p>
- <w:customXmlDelRangeStart w:id="1" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
- <w:sdt>
- <w:sdtPr>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:id w:val="990292373"/>
- <w:placeholder>
- <w:docPart w:val="DefaultPlaceholder_-1854013440"/>
- </w:placeholder>
- <w:text/>
- </w:sdtPr>
- <w:sdtContent>
- <w:customXmlDelRangeEnd w:id="1"/>
- <w:r>
- <w:t>Video</w:t>
- </w:r>
- <w:customXmlDelRangeStart w:id="2" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
- </w:sdtContent>
- </w:sdt>
- <w:customXmlDelRangeEnd w:id="2"/>
- <w:r>
- <w:t xml:space="preserve"> provides a powerful way to help you prove your point.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.customXmlDelRangeStart)
- {
- return new XElement(W.customXmlInsRangeStart,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.customXmlDelRangeEnd)
- {
- return new XElement(W.customXmlInsRangeEnd,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Inserted content control
- #if false
- <w:p>
- <w:customXmlInsRangeStart w:id="0" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
- <w:sdt>
- <w:sdtPr>
- <w:id w:val="-473839966"/>
- <w:placeholder>
- <w:docPart w:val="DefaultPlaceholder_-1854013440"/>
- </w:placeholder>
- <w:text/>
- </w:sdtPr>
- <w:sdtContent>
- <w:customXmlInsRangeEnd w:id="0"/>
- <w:r>
- <w:t>Video</w:t>
- </w:r>
- <w:customXmlInsRangeStart w:id="1" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
- </w:sdtContent>
- </w:sdt>
- <w:customXmlInsRangeEnd w:id="1"/>
- <w:r>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t xml:space="preserve"> provides a powerful way to help you prove your point.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.customXmlInsRangeStart)
- {
- return new XElement(W.customXmlDelRangeStart,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.customXmlInsRangeEnd)
- {
- return new XElement(W.customXmlDelRangeEnd,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Moved content control
- #if false
- <w:p>
- <w:r>
- <w:t>Video provides a powerful way.</w:t>
- </w:r>
- </w:p>
- <w:customXmlMoveFromRangeStart w:id="0" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
- <w:moveFromRangeStart w:id="1" w:author="Eric White" w:date="2017-03-25T22:21:00Z" w:name="move478243824" w:displacedByCustomXml="next"/>
- <w:sdt>
- <w:sdtPr>
- <w:id w:val="-2060007328"/>
- <w:placeholder>
- <w:docPart w:val="DefaultPlaceholder_-1854013440"/>
- </w:placeholder>
- </w:sdtPr>
- <w:sdtContent>
- <w:customXmlMoveFromRangeEnd w:id="0"/>
- <w:p w:rsidR="00D306FD" w:rsidDel="001037E6" w:rsidRDefault="00D306FD">
- <w:pPr>
- <w:rPr>
- <w:moveFrom w:id="2" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
- <w:lang w:val="en-US"/>
- </w:rPr>
- </w:pPr>
- <w:moveFrom w:id="3" w:author="Eric White" w:date="2017-03-25T22:21:00Z">
- <w:r w:rsidDel="001037E6">
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t>When you click Online Video.</w:t>
- </w:r>
- </w:moveFrom>
- </w:p>
- <w:customXmlMoveFromRangeStart w:id="4" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
- </w:sdtContent>
- </w:sdt>
- <w:customXmlMoveFromRangeEnd w:id="4"/>
- <w:moveFromRangeEnd w:id="1"/>
- <w:p>
- <w:r>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t>You can also type a keyword.</w:t>
- </w:r>
- </w:p>
- <w:p>
- <w:r>
- <w:rPr>
- <w:lang w:val="en-US"/>
- </w:rPr>
- <w:t>To make your document look.</w:t>
- </w:r>
- </w:p>
- <w:customXmlMoveToRangeStart w:id="5" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
- <w:moveToRangeStart w:id="6" w:author="Eric White" w:date="2017-03-25T22:21:00Z" w:name="move478243824" w:displacedByCustomXml="next"/>
- <w:sdt>
- <w:sdtPr>
- <w:id w:val="-483622649"/>
- <w:placeholder>
- <w:docPart w:val="DC46F197491D4EC8B79DB4CE2D22E222"/>
- </w:placeholder>
- </w:sdtPr>
- <w:sdtContent>
- <w:customXmlMoveToRangeEnd w:id="5"/>
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:moveTo w:id="8" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:moveTo w:id="9" w:author="Eric White" w:date="2017-03-25T22:21:00Z">
- <w:r>
- <w:t>When you click Online Video.</w:t>
- </w:r>
- </w:moveTo>
- </w:p>
- <w:customXmlMoveToRangeStart w:id="10" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
- </w:sdtContent>
- </w:sdt>
- <w:customXmlMoveToRangeEnd w:id="10"/>
- <w:moveToRangeEnd w:id="6"/>
- <w:p>
- <w:ins w:id="11" w:author="Eric White" w:date="2017-03-25T22:21:00Z">
- <w:r>
- <w:t xml:space="preserve"> </w:t>
- </w:r>
- </w:ins>
- <w:r>
- <w:t>For example, you can add.</w:t>
- </w:r>
- </w:p>
- #endif
- if (element.Name == W.customXmlMoveFromRangeStart)
- {
- return new XElement(W.customXmlMoveToRangeStart,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.customXmlMoveFromRangeEnd)
- {
- return new XElement(W.customXmlMoveToRangeEnd,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.customXmlMoveToRangeStart)
- {
- return new XElement(W.customXmlMoveFromRangeStart,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- if (element.Name == W.customXmlMoveToRangeEnd)
- {
- return new XElement(W.customXmlMoveFromRangeEnd,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Deleted field code
- #if false
- <w:p>
- <w:pPr>
- <w:rPr>
- <w:del w:id="0" w:author="Eric White" w:date="2017-03-25T22:43:00Z"/>
- </w:rPr>
- </w:pPr>
- <w:del w:id="1" w:author="Eric White" w:date="2017-03-25T22:43:00Z">
- <w:r>
- <w:fldChar w:fldCharType="begin"/>
- </w:r>
- <w:r>
- <w:delInstrText xml:space="preserve"> D</w:delInstrText>
- </w:r>
- <w:r>
- <w:rPr>
- <w:color w:val="FF0000"/>
- </w:rPr>
- <w:delInstrText>A</w:delInstrText>
- </w:r>
- <w:r>
- <w:delInstrText xml:space="preserve">TE </w:delInstrText>
- </w:r>
- <w:r>
- <w:fldChar w:fldCharType="separate"/>
- </w:r>
- <w:r>
- <w:delText>25/03/2017</w:delText>
- </w:r>
- <w:r>
- <w:fldChar w:fldCharType="end"/>
- </w:r>
- </w:del>
- </w:p>
- #endif
- if (element.Name == W.delInstrText)
- {
- return new XElement(W.instrText,
- element.Attributes(), // pulls in xml:space attribute
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Change inserted instrText element to w:delInstrText
- if (element.Name == W.instrText && rri.InInsert)
- {
- return new XElement(W.delInstrText,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Change inserted text element to w:delText
- if (element.Name == W.t && rri.InInsert)
- {
- return new XElement(W.delText,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Change w:delText to w:t
- if (element.Name == W.delText)
- {
- return new XElement(W.t,
- element.Attributes(), // pulls in xml:space attribute
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- ////////////////////////////////////////////////////////////////////////////////////
- // Identity transform
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
- }
- return node;
- }
- public static WmlDocument AcceptRevisions(WmlDocument document)
- {
- using (var streamDoc = new OpenXmlMemoryStreamDocument(document))
- {
- using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
- {
- AcceptRevisions(doc);
- }
- return streamDoc.GetModifiedWmlDocument();
- }
- }
- public static void AcceptRevisions(WordprocessingDocument doc)
- {
- AcceptRevisionsForPart(doc.MainDocumentPart);
- foreach (HeaderPart part in doc.MainDocumentPart.HeaderParts)
- {
- AcceptRevisionsForPart(part);
- }
- foreach (FooterPart part in doc.MainDocumentPart.FooterParts)
- {
- AcceptRevisionsForPart(part);
- }
- if (doc.MainDocumentPart.EndnotesPart is EndnotesPart endnotesPart)
- {
- AcceptRevisionsForPart(endnotesPart);
- }
- if (doc.MainDocumentPart.FootnotesPart is FootnotesPart footnotesPart)
- {
- AcceptRevisionsForPart(footnotesPart);
- }
- if (doc.MainDocumentPart.StyleDefinitionsPart is StyleDefinitionsPart styleDefinitionsPart)
- {
- AcceptRevisionsForStylesDefinitionPart(styleDefinitionsPart);
- }
- }
- private static void AcceptRevisionsForStylesDefinitionPart(StyleDefinitionsPart stylesDefinitionsPart)
- {
- XElement root = stylesDefinitionsPart.GetXElement();
- object newRoot = AcceptRevisionsForStylesTransform(root);
- root.ReplaceWith(newRoot);
- stylesDefinitionsPart.PutXElement();
- }
- private static object AcceptRevisionsForStylesTransform(XNode node)
- {
- if (node is XElement element)
- {
- return element.Name == W.pPrChange || element.Name == W.rPrChange
- ? null
- : new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AcceptRevisionsForStylesTransform));
- }
- return node;
- }
- public static void AcceptRevisionsForPart(OpenXmlPart part)
- {
- XElement documentElement = part.GetXDocument().Root;
- documentElement = (XElement)RemoveRsidTransform(documentElement);
- documentElement = (XElement)FixUpDeletedOrInsertedFieldCodesTransform(documentElement);
- bool containsMoveFromMoveTo = documentElement.Descendants(W.moveFrom).Any();
- documentElement = (XElement)AcceptMoveFromMoveToTransform(documentElement);
- documentElement = AcceptMoveFromRanges(documentElement);
- // AcceptParagraphEndTagsInMoveFromTransform needs rewritten similar to AcceptDeletedAndMoveFromParagraphMarks
- documentElement = (XElement)AcceptParagraphEndTagsInMoveFromTransform(documentElement);
- documentElement = AcceptDeletedAndMovedFromContentControls(documentElement);
- documentElement = AcceptDeletedAndMoveFromParagraphMarks(documentElement);
- if (containsMoveFromMoveTo)
- {
- documentElement = (XElement)RemoveRowsLeftEmptyByMoveFrom(documentElement);
- }
- documentElement = (XElement)AcceptAllOtherRevisionsTransform(documentElement);
- documentElement = (XElement)AcceptDeletedCellsTransform(documentElement);
- documentElement = (XElement)MergeAdjacentTablesTransform(documentElement);
- documentElement = (XElement)AddEmptyParagraphToAnyEmptyCells(documentElement);
- documentElement.Descendants().Attributes().Where(a => a.Name == PT.UniqueId || a.Name == PT.RunIds).Remove();
- documentElement.Descendants(W.numPr).Where(np => !np.HasElements).Remove();
- var newXDoc = new XDocument(documentElement);
- part.PutXDocument(newXDoc);
- }
- // Note that AcceptRevisionsForElement is an incomplete implementation. It is not possible to accept all varieties of revisions
- // for a single paragraph. The paragraph may contain a marker for a deleted or inserted content control, as one example, of
- // which there are many. This method accepts simple revisions, such as deleted or inserted text, which is the most common use
- // case.
- public static XElement AcceptRevisionsForElement(XElement element)
- {
- XElement rElement = element;
- rElement = (XElement)RemoveRsidTransform(rElement);
- rElement = (XElement)AcceptMoveFromMoveToTransform(rElement);
- rElement = (XElement)AcceptAllOtherRevisionsTransform(rElement);
- rElement.Descendants().Attributes().Where(a => a.Name == PT.UniqueId || a.Name == PT.RunIds).Remove();
- rElement.Descendants(W.numPr).Where(np => !np.HasElements).Remove();
- return rElement;
- }
- private static object FixUpDeletedOrInsertedFieldCodesTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Name == W.p)
- {
- // 1 other
- // 2 w:del/w:r/w:fldChar
- // 3 w:ins/w:r/w:fldChar
- // 4 w:instrText
- // formulate new paragraph, looking for 4 that has 2 (or 3) before and after. Then put in a w:del (or w:ins), transforming w:instrText to w:delInstrText if w:del.
- // transform 1, 2, 3 as usual
- IEnumerable<int> groupedParaContentsKey = element.Elements().Select(e =>
- {
- if (e.Name == W.del && e.Elements(W.r).Elements(W.fldChar).Any())
- {
- return 2;
- }
- if (e.Name == W.ins && e.Elements(W.r).Elements(W.fldChar).Any())
- {
- return 3;
- }
- if (e.Name == W.r && e.Element(W.instrText) != null)
- {
- return 4;
- }
- return 1;
- });
- var zipped = element.Elements().Zip(groupedParaContentsKey, (e, k) => new { Ele = e, Key = k });
- var grouped = zipped.GroupAdjacent(z => z.Key).ToArray();
- int gLen = grouped.Length;
- //if (gLen != 1)
- // Console.WriteLine();
- IEnumerable<object> newParaContents = grouped
- .Select((g, i) =>
- {
- if (g.Key == 1 || g.Key == 2 || g.Key == 3)
- {
- return (object)g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele));
- }
- if (g.Key == 4)
- {
- if (i == 0 || i == gLen - 1)
- {
- return g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele));
- }
- if (grouped[i - 1].Key == 2 &&
- grouped[i + 1].Key == 2)
- {
- return new XElement(W.del,
- g.Select(gc => TransformInstrTextToDelInstrText(gc.Ele)));
- }
- if (grouped[i - 1].Key == 3 &&
- grouped[i + 1].Key == 3)
- {
- return new XElement(W.ins,
- g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele)));
- }
- return g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele));
- }
- throw new OpenXmlPowerToolsException("Internal error");
- });
- var newParagraph = new XElement(W.p,
- element.Attributes(),
- newParaContents);
- return newParagraph;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(FixUpDeletedOrInsertedFieldCodesTransform));
- }
- return node;
- }
- private static object TransformInstrTextToDelInstrText(XNode node)
- {
- if (node is XElement element)
- {
- return element.Name == W.instrText
- ? new XElement(W.delInstrText,
- element.Attributes(),
- element.Nodes())
- : new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(TransformInstrTextToDelInstrText));
- }
- return node;
- }
- private static object AddEmptyParagraphToAnyEmptyCells(XNode node)
- {
- if (node is XElement element)
- {
- return element.Name == W.tc && element.Elements().All(e => e.Name == W.tcPr)
- ? new XElement(W.tc,
- element.Attributes(),
- element.Elements(),
- new XElement(W.p))
- : new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AddEmptyParagraphToAnyEmptyCells));
- }
- return node;
- }
- private static readonly Dictionary<XName, int> OrderTcPr = new Dictionary<XName, int>
- {
- { W.cnfStyle, 10 },
- { W.tcW, 20 },
- { W.gridSpan, 30 },
- { W.hMerge, 40 },
- { W.vMerge, 50 },
- { W.tcBorders, 60 },
- { W.shd, 70 },
- { W.noWrap, 80 },
- { W.tcMar, 90 },
- { W.textDirection, 100 },
- { W.tcFitText, 110 },
- { W.vAlign, 120 },
- { W.hideMark, 130 },
- { W.headers, 140 }
- };
- private static XElement FixWidths(XElement tbl)
- {
- var newTbl = new XElement(tbl);
- int[] gridLines = tbl.Elements(W.tblGrid).Elements(W.gridCol).Attributes(W._w).Select(w => (int)w).ToArray();
- foreach (XElement tr in newTbl.Elements(W.tr))
- {
- var used = 0;
- int lastUsed = -1;
- foreach (XElement tc in tr.Elements(W.tc))
- {
- XAttribute tcW = tc.Elements(W.tcPr).Elements(W.tcW).Attributes(W._w).FirstOrDefault();
- if (tcW != null)
- {
- int gridSpan = (int?)tc.Elements(W.tcPr).Elements(W.gridSpan).Attributes(W.val).FirstOrDefault() ?? 1;
- int z = Math.Min(gridLines.Length - 1, lastUsed + gridSpan);
- int w = gridLines.Where((g, i) => i > lastUsed && i <= z).Sum();
- tcW.Value = w.ToString();
- lastUsed += gridSpan;
- used += gridSpan;
- }
- }
- }
- return newTbl;
- }
- private static object AcceptMoveFromMoveToTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Name == W.moveTo)
- {
- return element.Nodes().Select(AcceptMoveFromMoveToTransform);
- }
- if (element.Name == W.moveFrom)
- {
- return null;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AcceptMoveFromMoveToTransform));
- }
- return node;
- }
- private static XElement AcceptMoveFromRanges(XElement document)
- {
- // The following lists contain the elements that are between start/end elements.
- var startElementTagsInMoveFromRange = new List<XElement>();
- var endElementTagsInMoveFromRange = new List<XElement>();
- // Following are the elements that *may* be in a range that has both start and end elements.
- var potentialDeletedElements = new Dictionary<string, PotentialInRangeElements>();
- foreach (Tag tag in DescendantAndSelfTags(document))
- {
- if (tag.Element.Name == W.moveFromRangeStart)
- {
- string id = tag.Element.Attributes(W.id).First().Value;
- potentialDeletedElements.Add(id, new PotentialInRangeElements());
- continue;
- }
- if (tag.Element.Name == W.moveFromRangeEnd)
- {
- string id = tag.Element.Attributes(W.id).First().Value;
- if (potentialDeletedElements.ContainsKey(id))
- {
- startElementTagsInMoveFromRange.AddRange(potentialDeletedElements[id].PotentialStartElementTagsInRange);
- endElementTagsInMoveFromRange.AddRange(potentialDeletedElements[id].PotentialEndElementTagsInRange);
- potentialDeletedElements.Remove(id);
- }
- continue;
- }
- if (potentialDeletedElements.Count > 0)
- {
- if (tag.TagType == TagTypeEnum.Element &&
- tag.Element.Name != W.moveFromRangeStart &&
- tag.Element.Name != W.moveFromRangeEnd)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialDeletedElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- }
- continue;
- }
- if (tag.TagType == TagTypeEnum.EmptyElement &&
- tag.Element.Name != W.moveFromRangeStart &&
- tag.Element.Name != W.moveFromRangeEnd)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialDeletedElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- continue;
- }
- if (tag.TagType == TagTypeEnum.EndElement &&
- tag.Element.Name != W.moveFromRangeStart &&
- tag.Element.Name != W.moveFromRangeEnd)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialDeletedElements)
- {
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- }
- }
- }
- XElement[] moveFromElementsToDelete = startElementTagsInMoveFromRange
- .Intersect(endElementTagsInMoveFromRange)
- .ToArray();
- if (moveFromElementsToDelete.Any())
- {
- return (XElement)AcceptMoveFromRangesTransform(
- document, moveFromElementsToDelete);
- }
- return document;
- }
- private enum MoveFromCollectionType
- {
- ParagraphEndTagInMoveFromRange,
- Other
- }
- private static object AcceptParagraphEndTagsInMoveFromTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (W.BlockLevelContentContainers.Contains(element.Name))
- {
- List<IGrouping<MoveFromCollectionType, XElement>> groupedBodyChildren = element
- .Elements()
- .GroupAdjacent(c =>
- {
- BlockContentInfo pi = c.GetParagraphInfo();
- if (pi.ThisBlockContentElement != null)
- {
- bool paragraphMarkIsInMoveFromRange =
- pi.ThisBlockContentElement.Elements(W.moveFromRangeStart).Any() &&
- !pi.ThisBlockContentElement.Elements(W.moveFromRangeEnd).Any();
- if (paragraphMarkIsInMoveFromRange)
- {
- return MoveFromCollectionType.ParagraphEndTagInMoveFromRange;
- }
- }
- XElement previousContentElement = c
- .ContentElementsBeforeSelf()
- .FirstOrDefault(e => e.GetParagraphInfo().ThisBlockContentElement != null);
- if (previousContentElement != null)
- {
- BlockContentInfo pi2 = previousContentElement.GetParagraphInfo();
- if (c.Name == W.p &&
- pi2.ThisBlockContentElement.Elements(W.moveFromRangeStart).Any() &&
- !pi2.ThisBlockContentElement.Elements(W.moveFromRangeEnd).Any())
- {
- return MoveFromCollectionType.ParagraphEndTagInMoveFromRange;
- }
- }
- return MoveFromCollectionType.Other;
- })
- .ToList();
- // If there is only one group, and it's key is MoveFromCollectionType.Other
- // then there is nothing to do.
- if (groupedBodyChildren.Count() == 1 &&
- groupedBodyChildren.First().Key == MoveFromCollectionType.Other)
- {
- var newElement = new XElement(element.Name,
- element.Attributes(),
- groupedBodyChildren.Select(g =>
- {
- if (g.Key == MoveFromCollectionType.Other)
- return (object)g;
- // This is a transform that produces the first element in the
- // collection, except that the paragraph in the descendents is
- // replaced with a new paragraph that contains all contents of the
- // existing paragraph, plus subsequent elements in the group
- // collection, where the paragraph in each of those groups is
- // collapsed.
- return CoalesqueParagraphEndTagsInMoveFromTransform(g.First(), g);
- }));
- return newElement;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AcceptParagraphEndTagsInMoveFromTransform));
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AcceptParagraphEndTagsInMoveFromTransform));
- }
- return node;
- }
- private static object AcceptAllOtherRevisionsTransform(XNode node)
- {
- if (node is XElement element)
- {
- // Accept inserted text, inserted paragraph marks, etc.
- // Collapse all w:ins elements.
- if (element.Name == W.ins)
- {
- return element.Nodes().Select(AcceptAllOtherRevisionsTransform);
- }
- // Remove all of the following elements. These elements are processed in:
- // AcceptDeletedAndMovedFromContentControls
- // AcceptMoveFromMoveToTransform
- // AcceptDeletedAndMoveFromParagraphMarksTransform
- // AcceptParagraphEndTagsInMoveFromTransform
- // AcceptMoveFromRanges
- if (element.Name == W.customXmlDelRangeStart ||
- element.Name == W.customXmlDelRangeEnd ||
- element.Name == W.customXmlInsRangeStart ||
- element.Name == W.customXmlInsRangeEnd ||
- element.Name == W.customXmlMoveFromRangeStart ||
- element.Name == W.customXmlMoveFromRangeEnd ||
- element.Name == W.customXmlMoveToRangeStart ||
- element.Name == W.customXmlMoveToRangeEnd ||
- element.Name == W.moveFromRangeStart ||
- element.Name == W.moveFromRangeEnd ||
- element.Name == W.moveToRangeStart ||
- element.Name == W.moveToRangeEnd)
- {
- return null;
- }
- // Accept revisions in formatting on paragraphs.
- // Accept revisions in formatting on runs.
- // Accept revisions for applied styles to a table.
- // Accept revisions for grid revisions to a table.
- // Accept revisions for column properties.
- // Accept revisions for row properties.
- // Accept revisions for table level property exceptions.
- // Accept revisions for section properties.
- // Accept numbering revision in fields.
- // Accept deleted field code text.
- // Accept deleted literal text.
- // Accept inserted cell.
- if (element.Name == W.pPrChange ||
- element.Name == W.rPrChange ||
- element.Name == W.tblPrChange ||
- element.Name == W.tblGridChange ||
- element.Name == W.tcPrChange ||
- element.Name == W.trPrChange ||
- element.Name == W.tblPrExChange ||
- element.Name == W.sectPrChange ||
- element.Name == W.numberingChange ||
- element.Name == W.delInstrText ||
- element.Name == W.delText ||
- element.Name == W.cellIns)
- {
- return null;
- }
- // Accept revisions for deleted math control character.
- // Match m:f/m:fPr/m:ctrlPr/w:del, remove m:f.
- if (element.Name == M.f &&
- element.Elements(M.fPr).Elements(M.ctrlPr).Elements(W.del).Any())
- {
- return null;
- }
- // Accept revisions for deleted rows in tables.
- // Match w:tr/w:trPr/w:del, remove w:tr.
- if (element.Name == W.tr &&
- element.Elements(W.trPr).Elements(W.del).Any())
- {
- return null;
- }
- // Accept deleted text in paragraphs.
- if (element.Name == W.del)
- {
- return null;
- }
- // Accept revisions for vertically merged cells.
- // cellMerge with a parent of tcPr, with attribute w:vMerge="rest" transformed
- // to <w:vMerge w:val="restart"/>
- // cellMerge with a parent of tcPr, with attribute w:vMerge="cont" transformed
- // to <w:vMerge w:val="continue"/>
- if (element.Name == W.cellMerge &&
- element.Parent?.Name == W.tcPr &&
- (string)element.Attribute(W.vMerge) == "rest")
- {
- return new XElement(W.vMerge,
- new XAttribute(W.val, "restart"));
- }
- if (element.Name == W.cellMerge &&
- element.Parent?.Name == W.tcPr &&
- (string)element.Attribute(W.vMerge) == "cont")
- {
- return new XElement(W.vMerge,
- new XAttribute(W.val, "continue"));
- }
- // Otherwise do identity clone.
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AcceptAllOtherRevisionsTransform));
- }
- return node;
- }
- private static object CollapseParagraphTransform(XNode node)
- {
- if (node is XElement element)
- {
- return element.Name == W.p
- ? (object)element.Elements().Where(e => e.Name != W.pPr)
- : new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(CollapseParagraphTransform));
- }
- return node;
- }
- /// Accept deleted paragraphs.
- ///
- /// Group together all paragraphs that contain w:p/w:pPr/w:rPr/w:del elements. Make a
- /// second group for the content element immediately following a paragraph that contains
- /// a w:del element. The code uses the approach of dealing with paragraph content at
- /// 'levels', ignoring paragraph content at other levels. Form a new paragraph that
- /// contains the content of the grouped paragraphs with deleted paragraph marks, and the
- /// content of the paragraph immediately following a paragraph that contains a deleted
- /// paragraph mark. Include in the new paragraph the paragraph properties from the
- /// paragraph following. When assembling the new paragraph, use a transform that collapses
- /// the paragraph nodes when adding content, thereby preserving custom XML and content
- /// controls.
- private static void AnnotateBlockContentElements(XElement contentContainer)
- {
- // For convenience, there is a ParagraphInfo annotation on the contentContainer.
- // It contains the same information as the ParagraphInfo annotation on the first
- // paragraph.
- if (contentContainer.Annotation<BlockContentInfo>() != null)
- return;
- XElement firstContentElement = contentContainer
- .Elements()
- .DescendantsAndSelf()
- .FirstOrDefault(e => e.Name == W.p || e.Name == W.tbl);
- if (firstContentElement == null)
- return;
- // Add the annotation on the contentContainer.
- var currentContentInfo = new BlockContentInfo
- {
- PreviousBlockContentElement = null,
- ThisBlockContentElement = firstContentElement,
- NextBlockContentElement = null
- };
- // Add as annotation even though NextParagraph is not set yet.
- contentContainer.AddAnnotation(currentContentInfo);
- while (true)
- {
- currentContentInfo.ThisBlockContentElement.AddAnnotation(currentContentInfo);
- // Find next sibling content element.
- XElement nextContentElement;
- XElement current = currentContentInfo.ThisBlockContentElement;
- while (true)
- {
- nextContentElement = current
- .ElementsAfterSelf()
- .DescendantsAndSelf()
- .FirstOrDefault(e => e.Name == W.p || e.Name == W.tbl);
- if (nextContentElement != null)
- {
- currentContentInfo.NextBlockContentElement = nextContentElement;
- break;
- }
- current = current.GetParent();
- // When we've backed up the tree to the contentContainer, we're done.
- if (current == contentContainer) return;
- }
- currentContentInfo = new BlockContentInfo
- {
- PreviousBlockContentElement = currentContentInfo.ThisBlockContentElement,
- ThisBlockContentElement = nextContentElement,
- NextBlockContentElement = null
- };
- }
- }
- private static IEnumerable<BlockContentInfo> IterateBlockContentElements(XElement element)
- {
- XElement current = element.Elements().FirstOrDefault();
- if (current == null)
- yield break;
- AnnotateBlockContentElements(element);
- var currentBlockContentInfo = element.Annotation<BlockContentInfo>();
- if (currentBlockContentInfo != null)
- {
- while (true)
- {
- yield return currentBlockContentInfo;
- if (currentBlockContentInfo?.NextBlockContentElement == null)
- yield break;
- currentBlockContentInfo = currentBlockContentInfo.NextBlockContentElement.Annotation<BlockContentInfo>();
- }
- }
- }
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static class PT
- {
- public static readonly XNamespace pt = "http://www.codeplex.com/PowerTools/2009/RevisionAccepter";
- public static readonly XName UniqueId = pt + "UniqueId";
- public static readonly XName RunIds = pt + "RunIds";
- }
- private static void AnnotateRunElementsWithId(XElement element)
- {
- var runId = 0;
- foreach (XElement e in element.Descendants().Where(e => e.Name == W.r))
- {
- if (e.Name == W.r)
- {
- e.Add(new XAttribute(PT.UniqueId, runId++));
- }
- }
- }
- private static void AnnotateContentControlsWithRunIds(XElement element)
- {
- var sdtId = 0;
- foreach (XElement e in element.Descendants(W.sdt))
- {
- // old version
- //e.Add(new XAttribute(PT.RunIds,
- // e.Descendants(W.r).Select(r => r.Attribute(PT.UniqueId).Value).StringConcatenate(s => s + ",").Trim(',')),
- // new XAttribute(PT.UniqueId, sdtId++));
- e.Add(new XAttribute(PT.RunIds,
- e.DescendantsTrimmed(W.txbxContent)
- .Where(d => d.Name == W.r)
- .Select(r => r.Attribute(PT.UniqueId)?.Value)
- .StringConcatenate(s => s + ",")
- .Trim(',')),
- new XAttribute(PT.UniqueId, sdtId++));
- }
- }
- private static XElement AddBlockLevelContentControls(XElement newDocument, XElement original)
- {
- List<XElement> originalContentControls = original.Descendants(W.sdt).ToList();
- List<XElement> existingContentControls = newDocument.Descendants(W.sdt).ToList();
- IEnumerable<string> contentControlsToAdd = originalContentControls
- .Select(occ => occ.Attribute(PT.UniqueId)?.Value)
- .Except(existingContentControls.Select(ecc => ecc.Attribute(PT.UniqueId)?.Value));
- foreach (XElement contentControl in originalContentControls
- .Where(occ => contentControlsToAdd.Contains(occ.Attribute(PT.UniqueId)?.Value)))
- {
- // TODO - Need a slight modification here. If there is a paragraph
- // in the content control that contains no runs, then the paragraph isn't included in the
- // content control, because the following triggers off of runs.
- // To see an example of this, see example document "NumberingParagraphPropertiesChange.docxs"
- // find list of runs to surround
- string[] runIds = contentControl.Attribute(PT.RunIds)?.Value.Split(',');
- IEnumerable<XElement> runs = contentControl
- .Descendants(W.r)
- .Where(r => runIds != null && runIds.Contains(r.Attribute(PT.UniqueId)?.Value));
- // find the runs in the new document
- List<XElement> runsInNewDocument = runs
- .Select(r => newDocument
- .Descendants(W.r)
- .First(z => z.Attribute(PT.UniqueId)?.Value == r.Attribute(PT.UniqueId)?.Value))
- .ToList();
- // find common ancestor
- List<XElement> runAncestorIntersection = null;
- foreach (XElement run in runsInNewDocument)
- {
- runAncestorIntersection = runAncestorIntersection == null
- ? run.Ancestors().ToList()
- : run.Ancestors().Intersect(runAncestorIntersection).ToList();
- }
- if (runAncestorIntersection == null) continue;
- XElement commonAncestor = runAncestorIntersection.InDocumentOrder().Last();
- // find child of common ancestor that contains first run
- // find child of common ancestor that contains last run
- // create new common ancestor:
- // elements before first run child
- // add content control, and runs from first run child to last run child
- // elements after last run child
- XElement firstRunChild = commonAncestor
- .Elements()
- .First(c => c.DescendantsAndSelf()
- .Any(z => z.Name == W.r &&
- z.Attribute(PT.UniqueId)?.Value == runsInNewDocument.First().Attribute(PT.UniqueId)?.Value));
- XElement lastRunChild = commonAncestor
- .Elements()
- .First(c => c
- .DescendantsAndSelf()
- .Any(z => z.Name == W.r &&
- z.Attribute(PT.UniqueId)?.Value == runsInNewDocument.Last().Attribute(PT.UniqueId)?.Value));
- // If the list of runs for the content control is exactly the list of runs for the paragraph, then
- // create the content control surrounding the paragraph, not surrounding the runs.
- if (commonAncestor.Name == W.p &&
- commonAncestor
- .Elements()
- .FirstOrDefault(e => e.Name != W.pPr &&
- e.Name != W.commentRangeStart &&
- e.Name != W.commentRangeEnd) == firstRunChild &&
- commonAncestor
- .Elements()
- .LastOrDefault(e => e.Name != W.pPr &&
- e.Name != W.commentRangeStart &&
- e.Name != W.commentRangeEnd) == lastRunChild)
- {
- // TODO: Revisit. Why is newContentControl not used?
- // replace commonAncestor with content control containing commonAncestor
- var newContentControl = new XElement(contentControl.Name,
- contentControl.Attributes(),
- contentControl.Elements().Where(e => e.Name != W.sdtContent),
- new XElement(W.sdtContent, commonAncestor));
- var newContentControlOrdered = new XElement(contentControl.Name,
- contentControl.Attributes(),
- contentControl.Elements().OrderBy(e => OrderSdt.ContainsKey(e.Name) ? OrderSdt[e.Name] : 999));
- commonAncestor.ReplaceWith(newContentControlOrdered);
- continue;
- }
- List<XElement> elementsBeforeRange = commonAncestor
- .Elements()
- .TakeWhile(e => e != firstRunChild)
- .ToList();
- List<XElement> elementsInRange = commonAncestor
- .Elements()
- .SkipWhile(e => e != firstRunChild)
- .TakeWhile(e => e != lastRunChild.ElementsAfterSelf().FirstOrDefault())
- .ToList();
- List<XElement> elementsAfterRange = commonAncestor
- .Elements()
- .SkipWhile(e => e != lastRunChild.ElementsAfterSelf().FirstOrDefault())
- .ToList();
- // detatch from current parent
- commonAncestor.Elements().Remove();
- var newContentControl2 = new XElement(contentControl.Name,
- contentControl.Attributes(),
- contentControl.Elements().Where(e => e.Name != W.sdtContent),
- new XElement(W.sdtContent, elementsInRange));
- var newContentControlOrdered2 = new XElement(newContentControl2.Name,
- newContentControl2.Attributes(),
- newContentControl2.Elements().OrderBy(e => OrderSdt.ContainsKey(e.Name) ? OrderSdt[e.Name] : 999));
- commonAncestor.Add(
- elementsBeforeRange,
- newContentControlOrdered2,
- elementsAfterRange);
- }
- return newDocument;
- }
- private static readonly Dictionary<XName, int> OrderSdt = new Dictionary<XName, int>
- {
- { W.sdtPr, 10 },
- { W.sdtEndPr, 20 },
- { W.sdtContent, 30 },
- { W.bookmarkStart, 40 },
- { W.bookmarkEnd, 50 }
- };
- private static XElement AcceptDeletedAndMoveFromParagraphMarks(XElement element)
- {
- AnnotateRunElementsWithId(element);
- AnnotateContentControlsWithRunIds(element);
- var newElement = (XElement)AcceptDeletedAndMoveFromParagraphMarksTransform(element);
- XElement withBlockLevelContentControls = AddBlockLevelContentControls(newElement, element);
- return withBlockLevelContentControls;
- }
- private enum GroupingType
- {
- DeletedRange,
- Other
- }
- private class GroupingInfo
- {
- public GroupingType GroupingType;
- public int GroupingKey;
- }
- private static object AcceptDeletedAndMoveFromParagraphMarksTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (W.BlockLevelContentContainers.Contains(element.Name))
- {
- XElement bodySectPr = null;
- if (element.Name == W.body)
- bodySectPr = element.Element(W.sectPr);
- var currentKey = 0;
- var deletedParagraphGroupingInfo = new List<GroupingInfo>();
- var state = 0; // 0 = in non deleted paragraphs
- // 1 = in deleted paragraph
- // 2 - paragraph following deleted paragraphs
- foreach (BlockContentInfo c in IterateBlockContentElements(element))
- {
- if (c.ThisBlockContentElement.Name == W.p)
- {
- bool paragraphMarkIsDeletedOrMovedFrom = c
- .ThisBlockContentElement
- .Elements(W.pPr)
- .Elements(W.rPr)
- .Elements()
- .Any(e => e.Name == W.del || e.Name == W.moveFrom);
- if (paragraphMarkIsDeletedOrMovedFrom)
- {
- if (state == 0)
- {
- state = 1;
- currentKey += 1;
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.DeletedRange,
- GroupingKey = currentKey
- });
- continue;
- }
- if (state == 1)
- {
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.DeletedRange,
- GroupingKey = currentKey
- });
- continue;
- }
- if (state == 2)
- {
- state = 1;
- currentKey += 1;
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.DeletedRange,
- GroupingKey = currentKey
- });
- continue;
- }
- }
- if (state == 0)
- {
- currentKey += 1;
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.Other,
- GroupingKey = currentKey
- });
- }
- else if (state == 1)
- {
- state = 2;
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.DeletedRange,
- GroupingKey = currentKey
- });
- }
- else if (state == 2)
- {
- state = 0;
- currentKey += 1;
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.Other,
- GroupingKey = currentKey
- });
- }
- }
- else if (c.ThisBlockContentElement.Name == W.tbl || c.ThisBlockContentElement.Name.Namespace == M.m)
- {
- currentKey += 1;
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.Other,
- GroupingKey = currentKey
- });
- state = 0;
- }
- else
- {
- // otherwise keep the same state, put in the same group, and continue
- deletedParagraphGroupingInfo.Add(
- new GroupingInfo
- {
- GroupingType = GroupingType.Other,
- GroupingKey = currentKey
- });
- }
- }
- var zipped = IterateBlockContentElements(element).Zip(deletedParagraphGroupingInfo, (blc, gi) => new
- {
- BlockLevelContent = blc,
- GroupingInfo = gi
- });
- var groupedParagraphs = zipped
- .GroupAdjacent(z => z.GroupingInfo.GroupingKey);
- // Create a new block level content container.
- var newBlockLevelContentContainer = new XElement(element.Name,
- element.Attributes(),
- element.Elements().Where(e => e.Name == W.tcPr),
- groupedParagraphs.Select((g, i) =>
- {
- if (g.First().GroupingInfo.GroupingType == GroupingType.DeletedRange)
- {
- var newParagraph = new XElement(W.p,
- #if false
- // previously, this was set to g.First()
- // however, this caused test [InlineData("RP/RP052-Deleted-Para-Mark.docx")] to lose paragraph numbering for a paragraph
- // that we did not want to loose it for.
- // the question is - when coalescing multiple paragraphs due to deleted paragraph marks, should we be taking the paragraph
- // properties from the first or the last in the sequence of coalesced paragraph. It is possible that we should take Last
- // when accepting revisions, but First when rejecting revisions.
- g.First().BlockLevelContent.ThisBlockContentElement.Elements(W.pPr),
- #endif
- g.Last().BlockLevelContent.ThisBlockContentElement.Elements(W.pPr),
- g.Select(z => CollapseParagraphTransform(z.BlockLevelContent.ThisBlockContentElement)));
- // if this contains the last paragraph in the document, and if there is no content,
- // and if the paragraph mark is deleted, then nuke the paragraph.
- bool allIsDeleted = AllParaContentIsDeleted(newParagraph);
- if (allIsDeleted &&
- g.Last().BlockLevelContent.ThisBlockContentElement.Elements(W.pPr).Elements(W.rPr)
- .Elements(W.del).Any() &&
- (g.Last().BlockLevelContent.NextBlockContentElement == null ||
- g.Last().BlockLevelContent.NextBlockContentElement.Name == W.tbl))
- return null;
- return (object)newParagraph;
- }
- return g.Select(z =>
- {
- var newEle = new XElement(z.BlockLevelContent.ThisBlockContentElement.Name,
- z.BlockLevelContent.ThisBlockContentElement.Attributes(),
- z.BlockLevelContent.ThisBlockContentElement.Nodes()
- .Select(AcceptDeletedAndMoveFromParagraphMarksTransform));
- return newEle;
- });
- }),
- bodySectPr);
- return newBlockLevelContentContainer;
- }
- // Otherwise, identity clone.
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AcceptDeletedAndMoveFromParagraphMarksTransform));
- }
- return node;
- }
- // Determine if the paragraph contains any content that is not deleted.
- private static bool AllParaContentIsDeleted(XElement p)
- {
- // needs collapse
- // dir, bdo, sdt, ins, moveTo, smartTag
- var testP = (XElement)CollapseTransform(p);
- IEnumerable<XElement> childElements = testP.Elements();
- IEnumerable<XElement> contentElements = childElements
- .Where(ce =>
- {
- bool? b = IsRunContent(ce.Name);
- if (b != null)
- return (bool)b;
- throw new Exception("Internal error 20, found element " + ce.Name.ToString());
- });
- return !contentElements.Any();
- }
- // dir, bdo, sdt, ins, moveTo, smartTag
- private static object CollapseTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Name == W.dir ||
- element.Name == W.bdr ||
- element.Name == W.ins ||
- element.Name == W.moveTo ||
- element.Name == W.smartTag)
- {
- return element.Elements();
- }
- if (element.Name == W.sdt)
- {
- return element.Elements(W.sdtContent).Elements();
- }
- if (element.Name == W.pPr)
- {
- return null;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(CollapseTransform));
- }
- return node;
- }
- private static bool? IsRunContent(XName ceName)
- {
- // is content
- // r, fldSimple, hyperlink, oMath, oMathPara, subDoc
- if (ceName == W.r ||
- ceName == W.fldSimple ||
- ceName == W.hyperlink ||
- ceName == W.subDoc ||
- ceName == W.smartTag ||
- ceName == W.smartTagPr ||
- ceName.Namespace == M.m)
- {
- return true;
- }
- // not content
- // bookmarkStart, bookmarkEnd, commentRangeStart, commentRangeEnd, del, moveFrom, proofErr
- if (ceName == W.bookmarkStart ||
- ceName == W.bookmarkEnd ||
- ceName == W.commentRangeStart ||
- ceName == W.commentRangeEnd ||
- ceName == W.customXmlDelRangeStart ||
- ceName == W.customXmlDelRangeEnd ||
- ceName == W.customXmlInsRangeStart ||
- ceName == W.customXmlInsRangeEnd ||
- ceName == W.customXmlMoveFromRangeStart ||
- ceName == W.customXmlMoveFromRangeEnd ||
- ceName == W.customXmlMoveToRangeStart ||
- ceName == W.customXmlMoveToRangeEnd ||
- ceName == W.del ||
- ceName == W.moveFrom ||
- ceName == W.moveFromRangeStart ||
- ceName == W.moveFromRangeEnd ||
- ceName == W.moveToRangeStart ||
- ceName == W.moveToRangeEnd ||
- ceName == W.permStart ||
- ceName == W.permEnd ||
- ceName == W.proofErr)
- {
- return false;
- }
- return null;
- }
- private static IEnumerable<Tag> DescendantAndSelfTags(XElement element)
- {
- yield return new Tag
- {
- Element = element,
- TagType = TagTypeEnum.Element
- };
- var iteratorStack = new Stack<IEnumerator<XElement>>();
- iteratorStack.Push(element.Elements().GetEnumerator());
- while (iteratorStack.Count > 0)
- {
- if (iteratorStack.Peek().MoveNext())
- {
- XElement currentXElement = iteratorStack.Peek().Current ??
- throw new OpenXmlPowerToolsException("Internal error.");
- if (!currentXElement.Nodes().Any())
- {
- yield return new Tag
- {
- Element = currentXElement,
- TagType = TagTypeEnum.EmptyElement
- };
- continue;
- }
- yield return new Tag
- {
- Element = currentXElement,
- TagType = TagTypeEnum.Element
- };
- iteratorStack.Push(currentXElement.Elements().GetEnumerator());
- continue;
- }
- iteratorStack.Pop();
- if (iteratorStack.Count > 0)
- {
- yield return new Tag
- {
- Element = iteratorStack.Peek().Current,
- TagType = TagTypeEnum.EndElement
- };
- }
- }
- yield return new Tag
- {
- Element = element,
- TagType = TagTypeEnum.EndElement
- };
- }
- private class PotentialInRangeElements
- {
- public readonly List<XElement> PotentialStartElementTagsInRange;
- public readonly List<XElement> PotentialEndElementTagsInRange;
- public PotentialInRangeElements()
- {
- PotentialStartElementTagsInRange = new List<XElement>();
- PotentialEndElementTagsInRange = new List<XElement>();
- }
- }
- private enum TagTypeEnum
- {
- Element,
- EndElement,
- EmptyElement
- }
- private class Tag
- {
- public XElement Element;
- public TagTypeEnum TagType;
- }
- private static object AcceptDeletedAndMovedFromContentControlsTransform(
- XNode node,
- XElement[] contentControlElementsToCollapse,
- XElement[] moveFromElementsToDelete)
- {
- if (node is XElement element)
- {
- if (element.Name == W.sdt && contentControlElementsToCollapse.Contains(element))
- {
- return element
- .Elements(W.sdtContent)
- .Nodes()
- .Select(n => AcceptDeletedAndMovedFromContentControlsTransform(n, contentControlElementsToCollapse,
- moveFromElementsToDelete));
- }
- if (moveFromElementsToDelete.Contains(element))
- return null;
- return new XElement(element.Name,
- element.Attributes(),
- element
- .Nodes()
- .Select(n => AcceptDeletedAndMovedFromContentControlsTransform(n, contentControlElementsToCollapse,
- moveFromElementsToDelete)));
- }
- return node;
- }
- private static XElement AcceptDeletedAndMovedFromContentControls(XElement documentRootElement)
- {
- // The following lists contain the elements that are between start/end elements.
- var startElementTagsInDeleteRange = new List<XElement>();
- var endElementTagsInDeleteRange = new List<XElement>();
- var startElementTagsInMoveFromRange = new List<XElement>();
- var endElementTagsInMoveFromRange = new List<XElement>();
- // Following are the elements that *may* be in a range that has both start and end elements.
- var potentialDeletedElements = new Dictionary<string, PotentialInRangeElements>();
- var potentialMoveFromElements = new Dictionary<string, PotentialInRangeElements>();
- foreach (Tag tag in DescendantAndSelfTags(documentRootElement))
- {
- if (tag.Element.Name == W.customXmlDelRangeStart)
- {
- string id = tag.Element.Attributes(W.id).First().Value;
- potentialDeletedElements.Add(id, new PotentialInRangeElements());
- continue;
- }
- if (tag.Element.Name == W.customXmlDelRangeEnd)
- {
- string id = tag.Element.Attributes(W.id).First().Value;
- if (potentialDeletedElements.ContainsKey(id))
- {
- startElementTagsInDeleteRange.AddRange(potentialDeletedElements[id].PotentialStartElementTagsInRange);
- endElementTagsInDeleteRange.AddRange(potentialDeletedElements[id].PotentialEndElementTagsInRange);
- potentialDeletedElements.Remove(id);
- }
- continue;
- }
- if (tag.Element.Name == W.customXmlMoveFromRangeStart)
- {
- string id = tag.Element.Attributes(W.id).First().Value;
- potentialMoveFromElements.Add(id, new PotentialInRangeElements());
- continue;
- }
- if (tag.Element.Name == W.customXmlMoveFromRangeEnd)
- {
- string id = tag.Element.Attributes(W.id).First().Value;
- if (potentialMoveFromElements.ContainsKey(id))
- {
- startElementTagsInMoveFromRange.AddRange(potentialMoveFromElements[id].PotentialStartElementTagsInRange);
- endElementTagsInMoveFromRange.AddRange(potentialMoveFromElements[id].PotentialEndElementTagsInRange);
- potentialMoveFromElements.Remove(id);
- }
- continue;
- }
- if (tag.Element.Name == W.sdt)
- {
- if (tag.TagType == TagTypeEnum.Element)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialDeletedElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- }
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialMoveFromElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- }
- continue;
- }
- if (tag.TagType == TagTypeEnum.EmptyElement)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialDeletedElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialMoveFromElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- continue;
- }
- if (tag.TagType == TagTypeEnum.EndElement)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialDeletedElements)
- {
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialMoveFromElements)
- {
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- continue;
- }
- throw new PowerToolsInvalidDataException("Should not have reached this point.");
- }
- if (potentialMoveFromElements.Any() &&
- tag.Element.Name != W.moveFromRangeStart &&
- tag.Element.Name != W.moveFromRangeEnd &&
- tag.Element.Name != W.customXmlMoveFromRangeStart &&
- tag.Element.Name != W.customXmlMoveFromRangeEnd)
- {
- if (tag.TagType == TagTypeEnum.Element)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialMoveFromElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- }
- continue;
- }
- if (tag.TagType == TagTypeEnum.EmptyElement)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialMoveFromElements)
- {
- id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- continue;
- }
- if (tag.TagType == TagTypeEnum.EndElement)
- {
- foreach (KeyValuePair<string, PotentialInRangeElements> id in potentialMoveFromElements)
- {
- id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
- }
- }
- }
- }
- XElement[] contentControlElementsToCollapse = startElementTagsInDeleteRange
- .Intersect(endElementTagsInDeleteRange)
- .ToArray();
- XElement[] elementsToDeleteBecauseMovedFrom = startElementTagsInMoveFromRange
- .Intersect(endElementTagsInMoveFromRange)
- .ToArray();
- if (contentControlElementsToCollapse.Length > 0 ||
- elementsToDeleteBecauseMovedFrom.Length > 0)
- {
- object newDoc = AcceptDeletedAndMovedFromContentControlsTransform(documentRootElement,
- contentControlElementsToCollapse, elementsToDeleteBecauseMovedFrom);
- return newDoc as XElement;
- }
- return documentRootElement;
- }
- private static object AcceptMoveFromRangesTransform(
- XNode node,
- XElement[] elementsToDelete)
- {
- if (node is XElement element)
- {
- return elementsToDelete.Contains(element)
- ? null
- : new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => AcceptMoveFromRangesTransform(n, elementsToDelete)));
- }
- return node;
- }
- private static object CoalesqueParagraphEndTagsInMoveFromTransform(
- XNode node,
- IGrouping<MoveFromCollectionType, XElement> g)
- {
- if (node is XElement element)
- {
- if (element.Name == W.p)
- {
- return new XElement(W.p,
- element.Attributes(),
- element.Elements(),
- g.Skip(1).Select(CollapseParagraphTransform));
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n =>
- CoalesqueParagraphEndTagsInMoveFromTransform(n, g)));
- }
- return node;
- }
- private enum DeletedCellCollectionType
- {
- DeletedCell,
- Other
- }
- // For each table row, group deleted cells plus the cell before any deleted cell.
- // Produce a new cell that has gridSpan set appropriately for group, and clone everything
- // else.
- private static object AcceptDeletedCellsTransform(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Name == W.tr)
- {
- var groupedCells = element
- .Elements()
- .GroupAdjacent(e =>
- {
- XElement cellAfter = e.ElementsAfterSelf(W.tc).FirstOrDefault();
- bool cellAfterIsDeleted = cellAfter != null &&
- cellAfter.Descendants(W.cellDel).Any();
- if (e.Name == W.tc &&
- (cellAfterIsDeleted || e.Descendants(W.cellDel).Any()))
- {
- var a = new
- {
- CollectionType = DeletedCellCollectionType.DeletedCell,
- Disambiguator = new[] { e }
- .Concat(e.SiblingsBeforeSelfReverseDocumentOrder())
- .FirstOrDefault(z => z.Name == W.tc && !z.Descendants(W.cellDel).Any())
- };
- return a;
- }
- var a2 = new
- {
- CollectionType = DeletedCellCollectionType.Other,
- Disambiguator = e
- };
- return a2;
- });
- var tr = new XElement(W.tr,
- element.Attributes(),
- groupedCells.Select(g =>
- {
- if (g.Key.CollectionType == DeletedCellCollectionType.DeletedCell &&
- g.First().Descendants(W.cellDel).Any())
- {
- return null;
- }
- if (g.Key.CollectionType == DeletedCellCollectionType.Other)
- {
- return (object)g;
- }
- XElement gridSpanElement = g
- .First()
- .Elements(W.tcPr)
- .Elements(W.gridSpan)
- .FirstOrDefault();
- int gridSpan = gridSpanElement != null ? (int)gridSpanElement.Attribute(W.val) : 1;
- int newGridSpan = gridSpan + g.Count() - 1;
- XElement currentTcPr = g.First().Elements(W.tcPr).FirstOrDefault();
- var newTcPr = new XElement(W.tcPr,
- currentTcPr?.Attributes(),
- new XElement(W.gridSpan,
- new XAttribute(W.val, newGridSpan)),
- currentTcPr?.Elements().Where(e => e.Name != W.gridSpan));
- var orderedTcPr = new XElement(W.tcPr,
- newTcPr.Elements().OrderBy(e => OrderTcPr.ContainsKey(e.Name) ? OrderTcPr[e.Name] : 999));
- var newTc = new XElement(W.tc,
- orderedTcPr,
- g.First().Elements().Where(e => e.Name != W.tcPr));
- return (object)newTc;
- }));
- return tr;
- }
- // Identity clone
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(AcceptDeletedCellsTransform));
- }
- return node;
- }
- #if false
- <w:tr>
- <w:tc>
- <w:tcPr>
- <w:tcW w:w="5016"
- w:type="dxa" />
- </w:tcPr>
- </w:tc>
- </w:tr>
- #endif
- private static readonly XName[] BlockLevelElements =
- {
- W.p,
- W.tbl,
- W.sdt,
- W.del,
- W.ins,
- M.oMath,
- M.oMathPara,
- W.moveTo
- };
- private static object RemoveRowsLeftEmptyByMoveFrom(XNode node)
- {
- if (node is XElement element)
- {
- if (element.Name == W.tr)
- {
- bool nonEmptyCells = element
- .Elements(W.tc)
- .Any(tc => tc.Elements().Any(tcc => BlockLevelElements.Contains(tcc.Name)));
- if (nonEmptyCells)
- {
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(RemoveRowsLeftEmptyByMoveFrom));
- }
- return null;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(RemoveRowsLeftEmptyByMoveFrom));
- }
- return node;
- }
- public static readonly XName[] TrackedRevisionsElements =
- {
- W.cellDel,
- W.cellIns,
- W.cellMerge,
- W.customXmlDelRangeEnd,
- W.customXmlDelRangeStart,
- W.customXmlInsRangeEnd,
- W.customXmlInsRangeStart,
- W.del,
- W.delInstrText,
- W.delText,
- W.ins,
- W.moveFrom,
- W.moveFromRangeEnd,
- W.moveFromRangeStart,
- W.moveTo,
- W.moveToRangeEnd,
- W.moveToRangeStart,
- W.numberingChange,
- W.pPrChange,
- W.rPrChange,
- W.sectPrChange,
- W.tblGridChange,
- W.tblPrChange,
- W.tblPrExChange,
- W.tcPrChange,
- W.trPrChange
- };
- public static bool PartHasTrackedRevisions(OpenXmlPart part)
- {
- return part.GetXDocument()
- .Descendants()
- .Any(e => TrackedRevisionsElements.Contains(e.Name));
- }
- public static bool HasTrackedRevisions(WmlDocument document)
- {
- using (var streamDoc = new OpenXmlMemoryStreamDocument(document))
- {
- using (WordprocessingDocument wdoc = streamDoc.GetWordprocessingDocument())
- {
- return RevisionAccepter.HasTrackedRevisions(wdoc);
- }
- }
- }
- public static bool HasTrackedRevisions(WordprocessingDocument doc)
- {
- if (PartHasTrackedRevisions(doc.MainDocumentPart))
- {
- return true;
- }
- if (doc.MainDocumentPart.HeaderParts.Any(PartHasTrackedRevisions))
- {
- return true;
- }
- if (doc.MainDocumentPart.FooterParts.Any(PartHasTrackedRevisions))
- {
- return true;
- }
- if (doc.MainDocumentPart.EndnotesPart is EndnotesPart endnotesPart)
- {
- if (PartHasTrackedRevisions(endnotesPart)) return true;
- }
- if (doc.MainDocumentPart.FootnotesPart is FootnotesPart footnotesPart)
- {
- if (PartHasTrackedRevisions(footnotesPart)) return true;
- }
- return false;
- }
- }
- public partial class WmlDocument
- {
- public WmlDocument AcceptRevisions(WmlDocument document)
- {
- return RevisionAccepter.AcceptRevisions(document);
- }
- public bool HasTrackedRevisions(WmlDocument document)
- {
- return RevisionAccepter.HasTrackedRevisions(document);
- }
- }
- public class BlockContentInfo
- {
- public XElement PreviousBlockContentElement;
- public XElement ThisBlockContentElement;
- public XElement NextBlockContentElement;
- }
- public static class RevisionAccepterExtensions
- {
- private static void InitializeParagraphInfo(XElement contentContext)
- {
- if (!W.BlockLevelContentContainers.Contains(contentContext.Name))
- throw new ArgumentException(
- "GetParagraphInfo called for element that is not child of content container");
- XElement prev = null;
- foreach (XElement content in contentContext.Elements())
- {
- // This may return null, indicating that there is no descendant paragraph. For
- // example, comment elements have no descendant elements.
- XElement paragraph = content
- .DescendantsAndSelf()
- .FirstOrDefault(e => e.Name == W.p || e.Name == W.tc || e.Name == W.txbxContent);
- if (paragraph != null && (paragraph.Name == W.tc || paragraph.Name == W.txbxContent))
- {
- paragraph = null;
- }
- var pi = new BlockContentInfo
- {
- PreviousBlockContentElement = prev,
- ThisBlockContentElement = paragraph
- };
- content.AddAnnotation(pi);
- prev = content;
- }
- }
- public static BlockContentInfo GetParagraphInfo(this XElement contentElement)
- {
- var paragraphInfo = contentElement.Annotation<BlockContentInfo>();
- if (paragraphInfo != null)
- return paragraphInfo;
- InitializeParagraphInfo(contentElement.Parent);
- return contentElement.Annotation<BlockContentInfo>();
- }
- public static IEnumerable<XElement> ContentElementsBeforeSelf(this XElement element)
- {
- XElement current = element;
- while (true)
- {
- BlockContentInfo pi = current.GetParagraphInfo();
- if (pi.PreviousBlockContentElement == null)
- yield break;
- yield return pi.PreviousBlockContentElement;
- current = pi.PreviousBlockContentElement;
- }
- }
- }
- }
- // Markup that this code processes:
- //
- // delText
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: MovedText.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to w:t element
- //
- // del (deleted run content)
- // Method: AcceptAllOtherRevisionsTransform
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements and descendant elements.
- // Reject:
- // Transform to w:ins element
- // Then Accept
- //
- // ins (inserted run content)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: InsertedParagraphsAndRuns.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Collapse these elements.
- // Reject:
- // Transform to w:del element, and child w:t transform to w:delText element
- // Then Accept
- //
- // ins (inserted paragraph)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: InsertedParagraphsAndRuns.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to w:del element
- // Then Accept
- //
- // del (deleted paragraph mark)
- // Method: AcceptDeletedAndMoveFromParagraphMarksTransform
- // Sample document: VariousTableRevisions.docx (deleted paragraph mark in paragraph in
- // content control)
- // Reviewed: tristan and zeyad ****************************************
- // Semantics:
- // Find all adjacent paragraps that have this element.
- // Group adjacent paragraphs plus the paragraph following paragraph that has this element.
- // Replace grouped paragraphs with a new paragraph containing the content from all grouped
- // paragraphs. Use the paragraph properties from the first paragraph in the group.
- // Reject:
- // Transform to w:ins element
- // Then Accept
- //
- // del (deleted table row)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Match w:tr/w:trPr/w:del, remove w:tr.
- // Reject:
- // Transform to w:ins
- // Then Accept
- //
- // ins (inserted table row)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to w:del
- // Then Accept
- //
- // del (deleted math control character)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: DeletedMathControlCharacter.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Match m:f/m:fPr/m:ctrlPr/w:del, remove m:f.
- // Reject:
- // Transform to w:ins
- // Then Accept
- //
- // ins (inserted math control character)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: InsertedMathControlCharacter.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to w:del
- // Then Accept
- //
- // moveTo (move destination paragraph mark)
- // Method: AcceptMoveFromMoveToTransform
- // Sample document: MovedText.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to moveFrom
- // Then Accept
- //
- // moveTo (move destination run content)
- // Method: AcceptMoveFromMoveToTransform
- // Sample document: MovedText.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Collapse these elements.
- // Reject:
- // Transform to moveFrom
- // Then Accept
- //
- // moveFrom (move source paragraph mark)
- // Methods: AcceptDeletedAndMoveFromParagraphMarksTransform, AcceptParagraphEndTagsInMoveFromTransform
- // Sample document: MovedText.docx
- // Reviewed: tristan and zeyad ****************************************
- // Semantics:
- // Find all adjacent paragraps that have this element or deleted paragraph mark.
- // Group adjacent paragraphs plus the paragraph following paragraph that has this element.
- // Replace grouped paragraphs with a new paragraph containing the content from all grouped
- // paragraphs.
- // This is handled in the same code that handles del (deleted paragraph mark).
- // Reject:
- // Transform to moveTo
- // Then Accept
- //
- // moveFrom (move source run content)
- // Method: AcceptMoveFromMoveToTransform
- // Sample document: MovedText.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to moveTo
- // Then Accept
- //
- // moveFromRangeStart
- // moveFromRangeEnd
- // Method: AcceptMoveFromRanges
- // Sample document: MovedText.docx
- // Semantics:
- // Find pairs of elements. Remove all elements that have both start and end tags in a
- // range.
- // Reject:
- // Transform to moveToRangeStart, moveToRangeEnd
- // Then Accept
- //
- // moveToRangeStart
- // moveToRangeEnd
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: MovedText.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to moveFromRangeStart, moveFromRangeEnd
- // Then Accept
- //
- // customXmlDelRangeStart
- // customXmlDelRangeEnd
- // customXmlMoveFromRangeStart
- // customXmlMoveFromRangeEnd
- // Method: AcceptDeletedAndMovedFromContentControls
- // Reviewed: tristan and zeyad ****************************************
- // Semantics:
- // Find pairs of start/end elements, matching id attributes. Collapse sdt
- // elements that have both start and end tags in a range.
- // Reject:
- // Transform to customXmlInsRangeStart, customXmlInsRangeEnd, customXmlMoveToRangeStart, customXmlMoveToRangeEnd
- // Then Accept
- //
- // customXmlInsRangeStart
- // customXmlInsRangeEnd
- // customXmlMoveToRangeStart
- // customXmlMoveToRangeEnd
- // Method: AcceptAllOtherRevisionsTransform
- // Reviewed: tristan and zeyad ****************************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to customXmlDelRangeStart, customXmlDelRangeEnd, customXmlMoveFromRangeStart, customXmlMoveFromRangeEnd
- // Then Accept
- //
- // delInstrText (deleted field code)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: NumberingParagraphPropertiesChange.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Transform to instrText
- // Then Accept
- // Note that instrText must be transformed to delInstrText when in a w:ins, in the same fashion that w:t must be transformed to w:delText when in w:ins
- //
- // ins (inserted numbering properties)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: InsertedNumberingProperties.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject
- // Remove the containing w:numPr
- //
- // pPrChange (revision information for paragraph properties)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: ParagraphAndRunPropertyRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace pPr with the pPr in pPrChange
- //
- // rPrChange (revision information for run properties)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: ParagraphAndRunPropertyRevisions.docx
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace rPr with the rPr in rPrChange
- //
- // rPrChange (revision information for run properties on the paragraph mark)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: ParagraphAndRunPropertyRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace rPr with the rPr in rPrChange.
- //
- // numberingChange (previous numbering field properties)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: NumberingFieldPropertiesChange.docx
- // Semantics:
- // Remove these elements.
- // Reject:
- // Remove these elements.
- // These are there for numbering created via fields, and are not important.
- //
- // numberingChange (previous paragraph numbering properties)
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: NumberingFieldPropertiesChange.docx
- // Semantics:
- // Remove these elements.
- // Reject:
- // Remove these elements.
- //
- // sectPrChange
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: SectionPropertiesChange.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace sectPr with the sectPr in sectPrChange
- //
- // tblGridChange
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: TableGridChange.docx
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace tblGrid with the tblGrid in tblGridChange
- //
- // tblPrChange
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: TableGridChange.docx
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace tblPr with the tblPr in tblPrChange
- //
- // tblPrExChange
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace tblPrEx with the tblPrEx in tblPrExChange
- //
- // tcPrChange
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: TableGridChange.docx
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace tcPr with the tcPr in tcPrChange
- //
- // trPrChange
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: VariousTableRevisions.docx
- // Reviewed: zeyad ***************************
- // Semantics:
- // Remove these elements.
- // Reject:
- // Replace trPr with the trPr in trPrChange
- //
- // celDel
- // Method: AcceptDeletedCellsTransform
- // Sample document: HorizontallyMergedCells.docx
- // Semantics:
- // Group consecutive deleted cells, and remove them.
- // Adjust the cell before deleted cells:
- // Increase gridSpan by the number of deleted cells that are removed.
- // Reject:
- // Remove this element
- //
- // celIns
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: HorizontallyMergedCells11.docx
- // Semantics:
- // Remove these elements.
- // Reject:
- // If a w:tc contains w:tcPr/w:cellIns, then remove the cell
- //
- // cellMerge
- // Method: AcceptAllOtherRevisionsTransform
- // Sample document: MergedCell.docx
- // Semantics:
- // Transform cellMerge with a parent of tcPr, with attribute w:vMerge="rest"
- // to <w:vMerge w:val="restart"/>.
- // Transform cellMerge with a parent of tcPr, with attribute w:vMerge="cont"
- // to <w:vMerge w:val="continue"/>
- //
- // The following items need to be addressed in a future release:
- // - inserted run inside deleted paragraph - moveTo is same as insert
- // - must increase w:val attribute of the w:gridSpan element of the
- // cell immediately preceding the group of deleted cells by the
- // ***sum*** of the values of the w:val attributes of w:gridSpan
- // elements of each of the deleted cells.
|