WmlToHtmlConverter.cs 197 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430
  1. // Copyright (c) Microsoft. All rights reserved.
  2. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Drawing;
  7. using System.Drawing.Imaging;
  8. using System.Globalization;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Text;
  12. using System.Xml;
  13. using System.Xml.Linq;
  14. using System.Xml.Xsl;
  15. using DocumentFormat.OpenXml;
  16. using DocumentFormat.OpenXml.Drawing;
  17. using DocumentFormat.OpenXml.Drawing.Charts;
  18. using DocumentFormat.OpenXml.Office2010.Word.DrawingShape;
  19. using DocumentFormat.OpenXml.Packaging;
  20. using HTEXLib.COMM;
  21. using HTEXLib.COMM.Helpers;
  22. using HTEXLib.DOCX.OpenXmlTool;
  23. using HTEXLib.Helpers;
  24. using HTEXLib.Helpers.ShapeHelpers;
  25. using HTEXLib.Models.Inner;
  26. using HTEXLib.PPTX.Models;
  27. // 200e lrm - LTR
  28. // 200f rlm - RTL
  29. // todo need to set the HTTP "Content-Language" header, for instance:
  30. // Content-Language: en-US
  31. // Content-Language: fr-FR
  32. namespace OpenXmlPowerTools
  33. {
  34. public partial class WmlDocument
  35. {
  36. [SuppressMessage("ReSharper", "UnusedMember.Global")]
  37. public XElement ConvertToHtml(WmlToHtmlConverterSettings htmlConverterSettings)
  38. {
  39. return WmlToHtmlConverter.ConvertToHtml(this, htmlConverterSettings);
  40. }
  41. [SuppressMessage("ReSharper", "UnusedMember.Global")]
  42. public XElement ConvertToHtml(HtmlConverterSettings htmlConverterSettings)
  43. {
  44. WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings(htmlConverterSettings);
  45. return WmlToHtmlConverter.ConvertToHtml(this, settings);
  46. }
  47. }
  48. [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")]
  49. public class WmlToHtmlConverterSettings
  50. {
  51. public string PageTitle;
  52. public bool TableInnerCss;
  53. public string CssClassPrefix;
  54. public bool FabricateCssClasses;
  55. public string GeneralCss;
  56. public string AdditionalCss;
  57. public bool RestrictToSupportedLanguages;
  58. public bool RestrictToSupportedNumberingFormats;
  59. public Dictionary<string, Func<string, int, string, string>> ListItemImplementations;
  60. public Func<ImageInfo, XElement> ImageHandler;
  61. public WmlToHtmlConverterSettings()
  62. {
  63. PageTitle = "";
  64. CssClassPrefix = "pt-";
  65. TableInnerCss = true;
  66. FabricateCssClasses = true;
  67. GeneralCss = "span { white-space: pre-wrap; }";
  68. AdditionalCss = "";
  69. RestrictToSupportedLanguages = false;
  70. RestrictToSupportedNumberingFormats = false;
  71. ListItemImplementations = ListItemRetrieverSettings.DefaultListItemTextImplementations;
  72. }
  73. public WmlToHtmlConverterSettings(HtmlConverterSettings htmlConverterSettings)
  74. {
  75. TableInnerCss = htmlConverterSettings.TableInnerCss;
  76. PageTitle = htmlConverterSettings.PageTitle;
  77. CssClassPrefix = htmlConverterSettings.CssClassPrefix;
  78. FabricateCssClasses = htmlConverterSettings.FabricateCssClasses;
  79. GeneralCss = htmlConverterSettings.GeneralCss;
  80. AdditionalCss = htmlConverterSettings.AdditionalCss;
  81. RestrictToSupportedLanguages = htmlConverterSettings.RestrictToSupportedLanguages;
  82. RestrictToSupportedNumberingFormats = htmlConverterSettings.RestrictToSupportedNumberingFormats;
  83. ListItemImplementations = htmlConverterSettings.ListItemImplementations;
  84. ImageHandler = htmlConverterSettings.ImageHandler;
  85. }
  86. }
  87. [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")]
  88. public class HtmlConverterSettings
  89. {
  90. public bool TableInnerCss;
  91. public string PageTitle;
  92. public string CssClassPrefix;
  93. public bool FabricateCssClasses;
  94. public string GeneralCss;
  95. public string AdditionalCss;
  96. public bool RestrictToSupportedLanguages;
  97. public bool RestrictToSupportedNumberingFormats;
  98. public Dictionary<string, Func<string, int, string, string>> ListItemImplementations;
  99. public Func<ImageInfo, XElement> ImageHandler;
  100. public HtmlConverterSettings()
  101. {
  102. TableInnerCss = true;
  103. PageTitle = "";
  104. CssClassPrefix = "pt-";
  105. FabricateCssClasses = true;
  106. GeneralCss = "span { white-space: pre-wrap; }";
  107. AdditionalCss = "";
  108. RestrictToSupportedLanguages = false;
  109. RestrictToSupportedNumberingFormats = false;
  110. ListItemImplementations = ListItemRetrieverSettings.DefaultListItemTextImplementations;
  111. }
  112. }
  113. public static class HtmlConverter
  114. {
  115. public static XElement ConvertToHtml(WmlDocument wmlDoc, HtmlConverterSettings htmlConverterSettings)
  116. {
  117. WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings(htmlConverterSettings);
  118. return WmlToHtmlConverter.ConvertToHtml(wmlDoc, settings);
  119. }
  120. public static XElement ConvertToHtml(WordprocessingDocument wDoc, HtmlConverterSettings htmlConverterSettings)
  121. {
  122. WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings(htmlConverterSettings);
  123. return WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
  124. }
  125. }
  126. [SuppressMessage("ReSharper", "NotAccessedField.Global")]
  127. [SuppressMessage("ReSharper", "UnusedMember.Global")]
  128. public class ImageInfo
  129. {
  130. public string base64;
  131. public Bitmap Bitmap;
  132. public XAttribute ImgStyleAttribute;
  133. public string ContentType;
  134. public XElement DrawingElement;
  135. public string AltText;
  136. public string Mathxml;
  137. public const int EmusPerInch = 914400;
  138. public const int EmusPerCm = 360000;
  139. }
  140. public static class WmlToHtmlConverter
  141. {
  142. public static XElement ConvertToHtml(WmlDocument doc, WmlToHtmlConverterSettings htmlConverterSettings)
  143. {
  144. using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
  145. {
  146. using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
  147. {
  148. return ConvertToHtml(document, htmlConverterSettings);
  149. }
  150. }
  151. }
  152. public static XElement ConvertToHtml(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings htmlConverterSettings)
  153. {
  154. RevisionAccepter.AcceptRevisions(wordDoc);
  155. SimplifyMarkupSettings simplifyMarkupSettings = new SimplifyMarkupSettings
  156. {
  157. RemoveComments = true,
  158. RemoveContentControls = true,
  159. RemoveEndAndFootNotes = true,
  160. RemoveFieldCodes = false,
  161. RemoveLastRenderedPageBreak = true,
  162. RemovePermissions = true,
  163. RemoveProof = true,
  164. RemoveRsidInfo = true,
  165. RemoveSmartTags = true,
  166. RemoveSoftHyphens = true,
  167. RemoveGoBackBookmark = true,
  168. ReplaceTabsWithSpaces = false,
  169. };
  170. MarkupSimplifier.SimplifyMarkup(wordDoc, simplifyMarkupSettings);
  171. FormattingAssemblerSettings formattingAssemblerSettings = new FormattingAssemblerSettings
  172. {
  173. RemoveStyleNamesFromParagraphAndRunProperties = false,
  174. ClearStyles = false,
  175. RestrictToSupportedLanguages = htmlConverterSettings.RestrictToSupportedLanguages,
  176. RestrictToSupportedNumberingFormats = htmlConverterSettings.RestrictToSupportedNumberingFormats,
  177. CreateHtmlConverterAnnotationAttributes = true,
  178. OrderElementsPerStandard = false,
  179. ListItemRetrieverSettings =
  180. htmlConverterSettings.ListItemImplementations == null ?
  181. new ListItemRetrieverSettings()
  182. {
  183. ListItemTextImplementations = ListItemRetrieverSettings.DefaultListItemTextImplementations,
  184. } :
  185. new ListItemRetrieverSettings()
  186. {
  187. ListItemTextImplementations = htmlConverterSettings.ListItemImplementations,
  188. },
  189. };
  190. FormattingAssembler.AssembleFormatting(wordDoc, formattingAssemblerSettings);
  191. InsertAppropriateNonbreakingSpaces(wordDoc);
  192. CalculateSpanWidthForTabs(wordDoc);
  193. ReverseTableBordersForRtlTables(wordDoc);
  194. AdjustTableBorders(wordDoc);
  195. XElement rootElement = wordDoc.MainDocumentPart.GetXDocument().Root;
  196. FieldRetriever.AnnotateWithFieldInfo(wordDoc.MainDocumentPart);
  197. AnnotateForSections(wordDoc);
  198. XElement xhtml = (XElement)ConvertToHtmlTransform(wordDoc, htmlConverterSettings,
  199. rootElement, false, 0m);
  200. ReifyStylesAndClasses(htmlConverterSettings, xhtml);
  201. // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
  202. // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
  203. // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
  204. // for detailed explanation.
  205. //
  206. // If you further transform the XML tree returned by ConvertToHtmlTransform, you
  207. // must do it correctly, or entities will not be serialized properly.
  208. return xhtml;
  209. }
  210. private static void ReverseTableBordersForRtlTables(WordprocessingDocument wordDoc)
  211. {
  212. XDocument xd = wordDoc.MainDocumentPart.GetXDocument();
  213. foreach (var tbl in xd.Descendants(W.tbl))
  214. {
  215. var bidiVisual = tbl.Elements(W.tblPr).Elements(W.bidiVisual).FirstOrDefault();
  216. if (bidiVisual == null)
  217. continue;
  218. var tblBorders = tbl.Elements(W.tblPr).Elements(W.tblBorders).FirstOrDefault();
  219. if (tblBorders != null)
  220. {
  221. var left = tblBorders.Element(W.left);
  222. if (left != null)
  223. left = new XElement(W.right, left.Attributes());
  224. var right = tblBorders.Element(W.right);
  225. if (right != null)
  226. right = new XElement(W.left, right.Attributes());
  227. var newTblBorders = new XElement(W.tblBorders,
  228. tblBorders.Element(W.top),
  229. left,
  230. tblBorders.Element(W.bottom),
  231. right);
  232. tblBorders.ReplaceWith(newTblBorders);
  233. }
  234. foreach (var tc in tbl.Elements(W.tr).Elements(W.tc))
  235. {
  236. var tcBorders = tc.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault();
  237. if (tcBorders != null)
  238. {
  239. var left = tcBorders.Element(W.left);
  240. if (left != null)
  241. left = new XElement(W.right, left.Attributes());
  242. var right = tcBorders.Element(W.right);
  243. if (right != null)
  244. right = new XElement(W.left, right.Attributes());
  245. var newTcBorders = new XElement(W.tcBorders,
  246. tcBorders.Element(W.top),
  247. left,
  248. tcBorders.Element(W.bottom),
  249. right);
  250. tcBorders.ReplaceWith(newTcBorders);
  251. }
  252. }
  253. }
  254. }
  255. private static void ReifyStylesAndClasses(WmlToHtmlConverterSettings htmlConverterSettings, XElement xhtml)
  256. {
  257. if (htmlConverterSettings.FabricateCssClasses)
  258. {
  259. var usedCssClassNames = new HashSet<string>();
  260. var elementsThatNeedClasses = xhtml
  261. .DescendantsAndSelf()
  262. .Select(d => new
  263. {
  264. Element = d,
  265. Styles = d.Annotation<Dictionary<string, string>>(),
  266. })
  267. .Where(z => z.Styles != null);
  268. var augmented = elementsThatNeedClasses
  269. .Select(p => new
  270. {
  271. p.Element,
  272. p.Styles,
  273. StylesString = p.Element.Name.LocalName + "|" + p.Styles.OrderBy(k => k.Key).Select(s => string.Format("{0}: {1};", s.Key, s.Value)).StringConcatenate(),
  274. })
  275. .GroupBy(p => p.StylesString)
  276. .ToList();
  277. int classCounter = 1000000;
  278. var sb = new StringBuilder();
  279. sb.Append(Environment.NewLine);
  280. foreach (var grp in augmented)
  281. {
  282. string classNameToUse;
  283. var firstOne = grp.First();
  284. var styles = firstOne.Styles;
  285. if (styles.ContainsKey("PtStyleName"))
  286. {
  287. classNameToUse = htmlConverterSettings.CssClassPrefix + styles["PtStyleName"];
  288. if (usedCssClassNames.Contains(classNameToUse))
  289. {
  290. classNameToUse = htmlConverterSettings.CssClassPrefix +
  291. styles["PtStyleName"] + "-" +
  292. classCounter.ToString().Substring(1);
  293. classCounter++;
  294. }
  295. }
  296. else
  297. {
  298. classNameToUse = htmlConverterSettings.CssClassPrefix +
  299. classCounter.ToString().Substring(1);
  300. classCounter++;
  301. }
  302. usedCssClassNames.Add(classNameToUse);
  303. sb.Append(firstOne.Element.Name.LocalName + "." + classNameToUse + " {" + Environment.NewLine);
  304. foreach (var st in firstOne.Styles.Where(s => s.Key != "PtStyleName"))
  305. {
  306. var s = " " + st.Key + ": " + st.Value + ";" + Environment.NewLine;
  307. sb.Append(s);
  308. }
  309. sb.Append("}" + Environment.NewLine);
  310. var classAtt = new XAttribute("class", classNameToUse);
  311. foreach (var gc in grp)
  312. gc.Element.Add(classAtt);
  313. }
  314. var styleValue = htmlConverterSettings.GeneralCss + sb + htmlConverterSettings.AdditionalCss;
  315. SetStyleElementValue(xhtml, styleValue);
  316. }
  317. else
  318. {
  319. // Previously, the h:style element was not added at this point. However,
  320. // at least the General CSS will contain important settings.
  321. SetStyleElementValue(xhtml, htmlConverterSettings.GeneralCss + htmlConverterSettings.AdditionalCss);
  322. foreach (var d in xhtml.DescendantsAndSelf())
  323. {
  324. var style = d.Annotation<Dictionary<string, string>>();
  325. if (style == null)
  326. continue;
  327. var styleValue =
  328. style
  329. .Where(p => p.Key != "PtStyleName")
  330. .OrderBy(p => p.Key)
  331. .Select(e => string.Format("{0}: {1};", e.Key, e.Value))
  332. .StringConcatenate();
  333. XAttribute st = new XAttribute("style", styleValue);
  334. if (d.Attribute("style") != null)
  335. d.Attribute("style").Value += styleValue;
  336. else
  337. d.Add(st);
  338. }
  339. }
  340. if (htmlConverterSettings.TableInnerCss)
  341. {
  342. SetStyleElementValue(xhtml, htmlConverterSettings.GeneralCss + htmlConverterSettings.AdditionalCss);
  343. foreach (var d in xhtml.DescendantsAndSelf())
  344. {
  345. if (d.Name.LocalName.ToString().Equals("table") || d.Name.LocalName.ToString().Equals("tr") || d.Name.LocalName.ToString().Equals("td"))
  346. {
  347. var style = d.Annotation<Dictionary<string, string>>();
  348. if (style == null)
  349. continue;
  350. var styleValue =
  351. style
  352. .Where(p => p.Key != "PtStyleName")
  353. .OrderBy(p => p.Key)
  354. .Select(e => string.Format("{0}: {1};", e.Key, e.Value))
  355. .StringConcatenate();
  356. XAttribute st = new XAttribute("style", styleValue);
  357. if (d.Attribute("style") != null)
  358. d.Attribute("style").Value += styleValue;
  359. else
  360. d.Add(st);
  361. }
  362. }
  363. }
  364. }
  365. private static void SetStyleElementValue(XElement xhtml, string styleValue)
  366. {
  367. var styleElement = xhtml
  368. .Descendants(Xhtml.style)
  369. .FirstOrDefault();
  370. if (styleElement != null)
  371. styleElement.Value = styleValue;
  372. else
  373. {
  374. styleElement = new XElement(Xhtml.style, styleValue);
  375. var head = xhtml.Element(Xhtml.head);
  376. if (head != null)
  377. head.Add(styleElement);
  378. }
  379. }
  380. private static object ConvertToHtmlTransform(WordprocessingDocument wordDoc,
  381. WmlToHtmlConverterSettings settings, XNode node,
  382. bool suppressTrailingWhiteSpace,
  383. decimal currentMarginLeft)
  384. {
  385. var element = node as XElement;
  386. if (element == null) return null;
  387. // Transform the w:document element to the XHTML h:html element.
  388. // The h:head element is laid out based on the W3C's recommended layout, i.e.,
  389. // the charset (using the HTML5-compliant form), the title (which is always
  390. // there but possibly empty), and other meta tags.
  391. if (element.Name == W.document)
  392. {
  393. return new XElement(Xhtml.html,
  394. new XElement(Xhtml.head,
  395. new XElement(Xhtml.meta, new XAttribute("charset", "UTF-8")),
  396. settings.PageTitle != null
  397. ? new XElement(Xhtml.title, new XText(settings.PageTitle))
  398. : new XElement(Xhtml.title, new XText(string.Empty)),
  399. new XElement(Xhtml.meta,
  400. new XAttribute("name", "Generator"),
  401. new XAttribute("content", "PowerTools for Open XML"))),
  402. element.Elements()
  403. .Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft)));
  404. }
  405. // Transform the w:body element to the XHTML h:body element.
  406. if (element.Name == W.body)
  407. {
  408. return new XElement(Xhtml.body, CreateSectionDivs(wordDoc, settings, element));
  409. }
  410. // Transform the w:p element to the XHTML h:h1-h6 or h:p element (if the previous paragraph does not
  411. // have a style separator).
  412. if (element.Name == W.p)
  413. {
  414. return ProcessParagraph(wordDoc, settings, element, suppressTrailingWhiteSpace, currentMarginLeft);
  415. }
  416. // Transform hyperlinks to the XHTML h:a element.
  417. if (element.Name == W.hyperlink && element.Attribute(R.id) != null)
  418. {
  419. try
  420. {
  421. var a = new XElement(Xhtml.a,
  422. new XAttribute("href",
  423. wordDoc.MainDocumentPart
  424. .HyperlinkRelationships
  425. .First(x => x.Id == (string)element.Attribute(R.id))
  426. .Uri
  427. ),
  428. new XAttribute("target", "_blank"
  429. ),
  430. element.Elements(W.r).Select(run => ConvertRun(wordDoc, settings, run))
  431. );
  432. if (!a.Nodes().Any())
  433. a.Add(new XText(""));
  434. return a;
  435. }
  436. catch (UriFormatException)
  437. {
  438. return element.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft));
  439. }
  440. }
  441. // Transform hyperlinks to bookmarks to the XHTML h:a element.
  442. if (element.Name == W.hyperlink && element.Attribute(W.anchor) != null)
  443. {
  444. return ProcessHyperlinkToBookmark(wordDoc, settings, element);
  445. }
  446. // Transform contents of runs.
  447. if (element.Name == W.r)
  448. {
  449. return ConvertRun(wordDoc, settings, element);
  450. }
  451. // Transform w:bookmarkStart into anchor
  452. if (element.Name == W.bookmarkStart)
  453. {
  454. return ProcessBookmarkStart(element);
  455. }
  456. // Transform every w:t element to a text node.
  457. if (element.Name == W.t)
  458. {
  459. // We don't need to convert characters to entities in a UTF-8 document.
  460. // Further, we don't need &nbsp; entities for significant whitespace
  461. // because we are wrapping the text nodes in <span> elements within
  462. // which all whitespace is significant.
  463. return new XText(element.Value);
  464. }
  465. // Transform symbols to spans
  466. if (element.Name == W.sym)
  467. {
  468. var cs = (string)element.Attribute(W._char);
  469. var c = Convert.ToInt32(cs, 16);
  470. return new XElement(Xhtml.span, new XEntity(string.Format("#{0}", c)));
  471. }
  472. // Transform tabs that have the pt:TabWidth attribute set
  473. if (element.Name == W.tab)
  474. {
  475. return ProcessTab(element);
  476. }
  477. // Transform w:br to h:br.
  478. if (element.Name == W.br || element.Name == W.cr)
  479. {
  480. return ProcessBreak(element);
  481. }
  482. // Transform w:noBreakHyphen to '-'
  483. if (element.Name == W.noBreakHyphen)
  484. {
  485. return new XText("-");
  486. }
  487. // Transform w:tbl to h:tbl.
  488. if (element.Name == W.tbl)
  489. {
  490. return ProcessTable(wordDoc, settings, element, currentMarginLeft);
  491. }
  492. // Transform w:tr to h:tr.
  493. if (element.Name == W.tr)
  494. {
  495. try
  496. {
  497. return ProcessTableRow(wordDoc, settings, element, currentMarginLeft);
  498. }
  499. catch (Exception ex)
  500. {
  501. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  502. }
  503. }
  504. // Transform w:tc to h:td.
  505. if (element.Name == W.tc)
  506. {
  507. try
  508. {
  509. return ProcessTableCell(wordDoc, settings, element);
  510. }
  511. catch (Exception ex)
  512. {
  513. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  514. }
  515. }
  516. // Transform images
  517. if (element.Name == W.drawing || element.Name == W.pict || element.Name == W._object)
  518. {
  519. try
  520. {
  521. XElement xElement = ProcessImage(wordDoc, element, settings.ImageHandler);
  522. return xElement;
  523. }
  524. catch (Exception ex)
  525. {
  526. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  527. }
  528. }
  529. // Transform content controls.
  530. if (element.Name == W.sdt)
  531. {
  532. try
  533. {
  534. return ProcessContentControl(wordDoc, settings, element, currentMarginLeft);
  535. }
  536. catch (Exception ex)
  537. {
  538. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  539. }
  540. }
  541. // Transform smart tags and simple fields.
  542. if (element.Name == W.smartTag || element.Name == W.fldSimple)
  543. {
  544. try
  545. {
  546. return CreateBorderDivs(wordDoc, settings, element.Elements());
  547. }
  548. catch (Exception ex)
  549. {
  550. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  551. }
  552. }
  553. //处理 数学公式
  554. if (element.Name == M.oMath)
  555. {
  556. try
  557. {
  558. return ProcessOMath(element);
  559. }
  560. catch (Exception ex)
  561. {
  562. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  563. }
  564. }
  565. if (element.Name == M.oMathPara)
  566. {
  567. try
  568. {
  569. return element.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft));
  570. }
  571. catch (Exception ex)
  572. {
  573. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  574. }
  575. }
  576. //处理形状
  577. if (element.Name == MC.AlternateContent)
  578. {
  579. try
  580. {
  581. return ProcessAlternateContent(wordDoc, element, settings.ImageHandler);
  582. }
  583. catch (Exception ex)
  584. {
  585. throw new TransException(element, $"{element.Name}{ex.Message}", ex);
  586. }
  587. }
  588. //if (element.Name == MC.Choice)
  589. //{
  590. // return ProcessOMath(element);
  591. //}
  592. //if (element.Name == W.drawing &&element.Parent.Name==MC.Choice)
  593. //{
  594. // return ProcessOMath(element);
  595. //}
  596. // Ignore element.
  597. return null;
  598. }
  599. public static XElement ProcessAlternateContent(WordprocessingDocument wordDoc, XElement element, Func<ImageInfo, XElement> imageHandler)
  600. {
  601. /*
  602. <mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
  603. <mc:Choice Requires="w16se">
  604. <w16se:symEx w16se:font="Segoe UI Emoji" w16se:char="2192" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" />
  605. </mc:Choice>
  606. <mc:Fallback>
  607. <w:t xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">→</w:t>
  608. </mc:Fallback>
  609. </mc:AlternateContent>
  610. */
  611. var ch= element.Element(MC.Choice).Element(W.drawing);
  612. if (ch!=null)
  613. {
  614. var drawing = element.Element(MC.Choice).Element(W.drawing).Element(WP.inline);
  615. if (drawing == null)
  616. {
  617. drawing = element.Element(MC.Choice).Element(W.drawing).Element(WP.anchor);
  618. }
  619. var graphicData = drawing.Element(A.graphic).Element(A.graphicData).Elements();
  620. foreach (var elm in graphicData)
  621. {
  622. if (elm.Name == WPS.wsp)
  623. {
  624. (string html, HTEXLib.Shape shape) = ProcessWps(wordDoc, elm, imageHandler);
  625. XElement img;
  626. string mathxml = Globals.UnsupportedImage;
  627. var imageInfo = new ImageInfo()
  628. {
  629. ContentType = "image/x-emf",
  630. DrawingElement = element,
  631. AltText = "UnsupportedImageError",
  632. Mathxml = mathxml
  633. };
  634. double? widthInPoints = shape.style.position.cx;
  635. double? heightInPoints = shape.style.position.cy;
  636. img = imageHandler(imageInfo);
  637. if (widthInPoints != null && heightInPoints != null)
  638. {
  639. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  640. // string.Format(NumberFormatInfo.InvariantInfo,
  641. // "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  642. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  643. "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  644. }
  645. else
  646. {
  647. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  648. // string.Format(NumberFormatInfo.InvariantInfo,
  649. // "vertical-align: middle;"));
  650. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  651. "vertical-align: middle;"));
  652. }
  653. // XElement elmt = XElement.Parse(html);
  654. return img;
  655. }
  656. else if (elm.Name == WPG.wgp)
  657. {
  658. return ProcessWgp(wordDoc, elm, imageHandler);
  659. }
  660. }
  661. }
  662. else {
  663. var wt= element.Element(MC.Fallback).Element(W.t);
  664. if (wt!=null) {
  665. return XElement.Parse($"<span>{wt.Value}</span>");
  666. }
  667. }
  668. return null;
  669. }
  670. public static XElement ProcessWgp(WordprocessingDocument wordDoc, XElement element, Func<ImageInfo, XElement> imageHandler)
  671. {
  672. var grpSpPr = element.Element(WPG.grpSpPr);
  673. var ext = grpSpPr.GetWordNodeByPath("a:xfrm/a:ext");
  674. var cx = ext.Attribute("cx").Value;
  675. var cy = ext.Attribute("cy").Value;
  676. //var elms= element.Descendants(WPS.wsp);
  677. //StringBuilder stringBuilder = new StringBuilder();
  678. //foreach (var elm in elms) {
  679. // var emlt= ProcessWps(wordDoc, elm, imageHandler);
  680. // stringBuilder.Append(emlt);
  681. //}
  682. //string svg = stringBuilder.ToString();
  683. //svg = $"<div>{svg}</div>";
  684. //XElement elmt = XElement.Parse(svg);
  685. XElement img;
  686. string mathxml = Globals.UnsupportedImage;
  687. var imageInfo = new ImageInfo()
  688. {
  689. ContentType = "image/x-emf",
  690. DrawingElement = element,
  691. AltText = "UnsupportedImageError",
  692. Mathxml = mathxml
  693. };
  694. double? widthInPoints = int.Parse(cx) * 96.0 / 914400;
  695. double? heightInPoints = int.Parse(cy) * 96.0 / 914400;
  696. img = imageHandler(imageInfo);
  697. if (widthInPoints != null && heightInPoints != null)
  698. {
  699. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  700. // string.Format(NumberFormatInfo.InvariantInfo,
  701. // "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  702. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  703. "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  704. }
  705. else
  706. {
  707. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  708. // string.Format(NumberFormatInfo.InvariantInfo,
  709. // "vertical-align: middle;"));
  710. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  711. "vertical-align: middle;"));
  712. }
  713. return img;
  714. }
  715. public static XElement ProcessChart(WordprocessingDocument wordDoc, XElement containerElement, Func<ImageInfo, XElement> imageHandler,
  716. int? extentCx, int? extentCy, string altText, XElement element, string hyperlinkUri)
  717. {
  718. var ird = element.Attribute(R.id);
  719. var chartird = wordDoc.MainDocumentPart.Parts.FirstOrDefault(pp => pp.RelationshipId == ird.Value);
  720. ChartPart chartPart = wordDoc.MainDocumentPart.ChartParts.Where(x => x.Uri.ToString() == chartird.OpenXmlPart.Uri.ToString()).FirstOrDefault<ChartPart>();
  721. if (chartPart != null)
  722. {
  723. ChartColorStylePart ChartColorStylePart = null;
  724. ChartStylePart ChartStylePart = null;
  725. foreach (var idp in chartPart.Parts)
  726. {
  727. if (idp.OpenXmlPart is ChartColorStylePart ChartColorStyleParts)
  728. {
  729. ChartColorStylePart = ChartColorStyleParts;
  730. }
  731. if (idp.OpenXmlPart is ChartStylePart ChartStyleParts)
  732. {
  733. ChartStylePart = ChartStyleParts;
  734. }
  735. }
  736. if (ChartStylePart != null)
  737. {
  738. var ChartStyleChildren = ChartStylePart.ChartStyle.ChildElements;
  739. foreach (var child in ChartStyleChildren)
  740. {
  741. if (child is DocumentFormat.OpenXml.Office2013.Drawing.ChartStyle.StyleEntry StyleEntry)
  742. {
  743. // DoStyleEntry(StyleEntry);
  744. }
  745. ///OfficeArtExtensionList cs: extLst
  746. // MarkerLayoutProperties cs: dataPointMarkerLayout
  747. }
  748. }
  749. HTEXLib.Chart charts = DrawChart(wordDoc, chartPart.ChartSpace, element, imageHandler, null);
  750. // charts.links = slide.hyperlinks;
  751. // return new List<Item> { charts };
  752. XElement img = null;
  753. string mathxml = Globals.UnsupportedImage;
  754. var imageInfo = new ImageInfo()
  755. {
  756. ContentType = "image/x-emf",
  757. DrawingElement = element,
  758. AltText = "UnsupportedImageError",
  759. Mathxml = mathxml
  760. };
  761. img = imageHandler(imageInfo);
  762. double? widthInPoints = extentCx * 96.0 / 914400;
  763. double? heightInPoints = extentCy * 96.0 / 914400;
  764. if (widthInPoints != null && heightInPoints != null)
  765. {
  766. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  767. // string.Format(NumberFormatInfo.InvariantInfo,
  768. // "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  769. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  770. "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  771. }
  772. else
  773. {
  774. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  775. // string.Format(NumberFormatInfo.InvariantInfo,
  776. // "vertical-align: middle;"));
  777. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  778. "vertical-align: middle;"));
  779. }
  780. return img;
  781. }
  782. return null;
  783. }
  784. public static HTEXLib.Chart DrawChart(WordprocessingDocument wordDoc, DocumentFormat.OpenXml.Drawing.Charts.ChartSpace chartSpace, XElement element, Func<ImageInfo, XElement> ImageHandler, HTEXLib.Position positionc)
  785. {
  786. var Chart = chartSpace.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.Chart>();
  787. var ShapeProperties = chartSpace.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.ShapeProperties>();
  788. HTEXLib.Models.HTEX.ShapeStyle shapeStyleChart = WORDHelper.DoShapeProperties(wordDoc, ShapeProperties, element, ImageHandler, positionc);
  789. var TextProperties = chartSpace.GetFirstChild<TextProperties>();
  790. var style = chartSpace.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.Style>();
  791. var ColorMapOverride = chartSpace.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.ColorMapOverride>();
  792. var style2010 = chartSpace.GetFirstChild<DocumentFormat.OpenXml.Office2010.Drawing.Charts.Style>();
  793. var UserShapesReference = chartSpace.GetFirstChild<UserShapesReference>();
  794. var charts = DoPlotArea(Chart.PlotArea, wordDoc, element, ImageHandler, positionc);
  795. HTEXLib.Models.HTEX.ShapeStyle shapeStyle = null;
  796. if (Chart.Title != null)
  797. {
  798. var ChartShapeProperties = Chart.Title.ChartShapeProperties;
  799. shapeStyle = WORDHelper.DoShapeProperties(wordDoc, ChartShapeProperties, element, ImageHandler, positionc);
  800. }
  801. // var (PPTParagraphs, tBody) = DoChartTitle(Chart.Title);
  802. //TextBody textBody = DrawText(PPTParagraphs, tBody);
  803. //Shape shape= new Shape { type = "Sp",uid ="Sp-" +ShaHashHelper.GetSHA1(slide.slideIndex + "-" + (Chart.Title.GetType().Name + Chart.Title.OuterXml).Trim().ToLower()), shapeType = "rect",
  804. // textBody = textBody , sid = sid + "titel" };
  805. HTEXLib.Shape shape = new HTEXLib.Shape
  806. {
  807. type = "Sp",
  808. uid = Guid.NewGuid().ToString(),
  809. shapeType = "rect",
  810. // textBody = textBody,
  811. // sid = sid + "titel"
  812. };
  813. shape.style.fill = shapeStyle != null ? shapeStyle.fill : null;
  814. shape.style.border = shapeStyle != null ? shapeStyle.border : null;
  815. //var chart= new Chart {sid=sid,charts =charts,title=shape,type=type,uid = type+"-"+ ShaHashHelper.GetSHA1(slide.slideIndex + "-" + (Chart.GetType().Name + Chart.OuterXml).Trim().ToLower()) };
  816. var chart = new HTEXLib.Chart
  817. {
  818. // sid = sid,
  819. charts = charts,
  820. title = shape,
  821. // type = type,
  822. uid = Guid.NewGuid().ToString(),
  823. };
  824. HTEXLib.Position position = new HTEXLib.Position { /*cx = width, cy = height, x = left, y = top, rot = rot */};
  825. chart.style.position = position;
  826. chart.style.fill = shapeStyleChart != null ? shapeStyleChart.fill : null;
  827. chart.style.border = shapeStyleChart != null ? shapeStyleChart.border : null;
  828. chart.style.effect = shapeStyleChart != null ? shapeStyleChart.effect : null;
  829. chart.svg = new HTEXLib.Svg
  830. {
  831. type = "path",
  832. d = "M" + 0 + " " + 0 + ",L" + position.cx + " " + 0 + ",L" + position.cx + " " + position.cy + ",L" + 0 + " " + position.cy + " z",
  833. close = true
  834. //new Rect {
  835. // x=position.x,
  836. // y = position.y,
  837. // width=position.cx,
  838. // height=position.cy,
  839. // type="rect"
  840. // }
  841. };
  842. if (chart.style != null)
  843. {
  844. if (chart.style.fill.type == -1 || chart.style.fill.type == 0)
  845. {
  846. chart.style.fill = null;
  847. }
  848. if (chart.style.border != null && chart.style.border.type == "none")
  849. {
  850. chart.style.border = null;
  851. }
  852. }
  853. return chart;
  854. }
  855. public static List<HTEXLib.CommonChart> DoPlotArea(PlotArea plotArea, WordprocessingDocument wordDoc, XElement element, Func<ImageInfo, XElement> ImageHandler, HTEXLib.Position positionc)
  856. {
  857. List<HTEXLib.CommonChart> charts = new List<HTEXLib.CommonChart>();
  858. foreach (var child in plotArea.ChildElements)
  859. {
  860. string key = child.LocalName;
  861. IEnumerable<XElement> serNodes = null;
  862. switch (key)
  863. {
  864. //break块中不可以随意更换,此条件用于归类不同从Chart
  865. case "pieChart":
  866. case "ofPieChart":
  867. case "pie3DChart":
  868. case "doughnutChart":
  869. HTEXLib.PieChart pieChart = new HTEXLib.PieChart { chartType = "pie" };
  870. if (key.Equals("pie3DChart"))
  871. {
  872. pieChart.is3D = true;
  873. }
  874. pieChart.pieType = key;
  875. if (key.Equals("ofPieChart"))
  876. {
  877. var ofPieType = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:ofPieType");
  878. if (ofPieType != null)
  879. {
  880. //ofPieChart-pie ofPieChart-bar 子母饼图
  881. pieChart.pieType += "-" + ofPieType.Attribute("val").Value;
  882. }
  883. }
  884. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  885. pieChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  886. charts.Add(pieChart);
  887. break;
  888. case "lineChart":
  889. case "line3DChart":
  890. HTEXLib.LineChart lineChart = new HTEXLib.LineChart { chartType = "line" };
  891. if (key.Equals("line3DChart"))
  892. {
  893. lineChart.is3D = true;
  894. }
  895. lineChart.lineType = key;
  896. var LineGrouping = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:grouping");
  897. if (LineGrouping != null)
  898. {
  899. //standard stacked percentStacked
  900. lineChart.lineType += "-" + LineGrouping.Attribute("val").Value;
  901. }
  902. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  903. lineChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  904. charts.Add(lineChart);
  905. break;
  906. case "barChart":
  907. case "bar3DChart":
  908. var barDir = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:barDir");
  909. if (barDir != null)
  910. {
  911. if (barDir.Attribute("val").Value.Equals("bar"))
  912. {
  913. HTEXLib.BarChart barChart = new HTEXLib.BarChart { chartType = "bar" };
  914. charts.Add(barChart);
  915. if (key.Equals("bar3DChart"))
  916. {
  917. barChart.is3D = true;
  918. }
  919. barChart.barType = key;
  920. var BarGrouping = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:grouping");
  921. if (BarGrouping != null)
  922. {
  923. //standard stacked percentStacked
  924. barChart.barType += "-" + BarGrouping.Attribute("val").Value;
  925. }
  926. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  927. barChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  928. charts.Add(barChart);
  929. }
  930. else if (barDir.Attribute("val").Value.Equals("col"))
  931. {
  932. HTEXLib.ColChart colChart = new HTEXLib.ColChart { chartType = "col" };
  933. if (key.Equals("bar3DChart"))
  934. {
  935. colChart.is3D = true;
  936. }
  937. colChart.colType = key.Replace("bar", "col");
  938. var ColGrouping = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:grouping");
  939. if (ColGrouping != null)
  940. {
  941. //standard stacked percentStacked
  942. colChart.colType += "-" + ColGrouping.Attribute("val").Value;
  943. }
  944. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  945. colChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  946. charts.Add(colChart);
  947. }
  948. }
  949. break;
  950. case "areaChart":
  951. case "area3DChart":
  952. HTEXLib.AreaChart areaChart = new HTEXLib.AreaChart { chartType = "area" };
  953. if (key.Equals("area3DChart"))
  954. {
  955. areaChart.is3D = true;
  956. }
  957. areaChart.areaType = key;
  958. var AreaGrouping = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:grouping");
  959. if (AreaGrouping != null)
  960. {
  961. //standard stacked percentStacked
  962. areaChart.areaType += "-" + AreaGrouping.Attribute("val").Value;
  963. }
  964. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  965. areaChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  966. charts.Add(areaChart);
  967. break;
  968. case "scatterChart":
  969. case "bubbleChart":
  970. HTEXLib.ScatterChart scatterChart = new HTEXLib.ScatterChart { chartType = "scatter" };
  971. scatterChart.scatterType = key;
  972. if (key.Equals("scatterChart"))
  973. {
  974. var ScatterStyle = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:scatterStyle");
  975. if (ScatterStyle != null)
  976. {
  977. scatterChart.scatterType += "-" + ScatterStyle.Attribute("val").Value.Replace("Marker", "");
  978. }
  979. }
  980. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  981. scatterChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  982. charts.Add(scatterChart);
  983. break;
  984. case "radarChart":
  985. HTEXLib.RadarChart radarChart = new HTEXLib.RadarChart { chartType = "radar" };
  986. radarChart.radarType = key;
  987. var RadarStyle = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:radarStyle");
  988. if (RadarStyle != null)
  989. {
  990. radarChart.radarType += "-" + RadarStyle.Attribute("val").Value;
  991. }
  992. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  993. radarChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  994. charts.Add(radarChart);
  995. break;
  996. case "plotAreaRegion":
  997. HTEXLib.PlotAreaChart plotAreaChart = new HTEXLib.PlotAreaChart { chartType = "plotArea" };
  998. plotAreaChart.plotAreaType = key;
  999. var PlotSeries = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "cx:series");
  1000. if (PlotSeries != null)
  1001. {
  1002. plotAreaChart.plotAreaType += "-" + PlotSeries.Attribute("val").Value;
  1003. }
  1004. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  1005. plotAreaChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  1006. charts.Add(plotAreaChart);
  1007. break;
  1008. case "stockChart":
  1009. HTEXLib.StockChart stockChart = new HTEXLib.StockChart { chartType = "stock" };
  1010. stockChart.stockType = key;
  1011. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  1012. stockChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  1013. charts.Add(stockChart);
  1014. break;
  1015. case "surfaceChart":
  1016. case "surface3DChart":
  1017. HTEXLib.SurfaceChart surfaceChart = new HTEXLib.SurfaceChart { chartType = "surface" };
  1018. if (key.Equals("surface3DChart"))
  1019. {
  1020. surfaceChart.is3D = true;
  1021. }
  1022. surfaceChart.surfaceType = key;
  1023. var Wireframe = HTEXLib.ShapeHelper.GetPPTXNodeByPath(child, "c:wireframe");
  1024. if (Wireframe != null)
  1025. {
  1026. surfaceChart.surfaceType += "-" + Wireframe.Attribute("val").Value;
  1027. }
  1028. serNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(child, "c:ser");
  1029. surfaceChart.datas = ExtractChartData(serNodes, wordDoc, element, ImageHandler, positionc);
  1030. charts.Add(surfaceChart);
  1031. break;
  1032. }
  1033. }
  1034. return charts;
  1035. }
  1036. public static List<Dictionary<string, object>> ExtractChartData(IEnumerable<XElement> nodes, WordprocessingDocument wordDoc, XElement element, Func<ImageInfo, XElement> ImageHandler, HTEXLib.Position positionc)
  1037. {
  1038. if (nodes != null)
  1039. {
  1040. List<Dictionary<string, object>> listDict = new List<Dictionary<string, object>>();
  1041. foreach (XElement node in nodes)
  1042. {
  1043. if (HTEXLib.ShapeHelper.GetPPTXNodeByPath(node, "c:xVal") != null)
  1044. {
  1045. Dictionary<string, object> dict = new Dictionary<string, object>();
  1046. var xCvNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(node, "c:xVal/c:numRef/c:numCache/c:pt/c:v");
  1047. if (xCvNodes != null)
  1048. {
  1049. List<string> list = new List<string>();
  1050. foreach (XElement cvNode in xCvNodes)
  1051. {
  1052. list.Add(cvNode.Value);
  1053. }
  1054. dict.Add("xAxis", list);
  1055. }
  1056. var yCvNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(node, "c:yVal/c:numRef/c:numCache/c:pt/c:v");
  1057. if (yCvNodes != null)
  1058. {
  1059. List<string> list = new List<string>();
  1060. foreach (XElement cvNode in yCvNodes)
  1061. {
  1062. list.Add(cvNode.Value);
  1063. }
  1064. dict.Add("yAxis", list);
  1065. }
  1066. dict.Add("colName", "-");
  1067. listDict.Add(dict);
  1068. }
  1069. else
  1070. {
  1071. Dictionary<string, object> dict = new Dictionary<string, object>();
  1072. var spPr = HTEXLib.ShapeHelper.GetPPTXNodeByPath(node, "c:spPr");
  1073. if (spPr != null)
  1074. {
  1075. var ChartShapeProperties = new ChartShapeProperties(spPr.ToString());
  1076. HTEXLib.Models.HTEX.ShapeStyle shapeStyle = WORDHelper.DoShapeProperties(wordDoc, ChartShapeProperties, element, ImageHandler, positionc);
  1077. dict.Add("colStyle", shapeStyle);
  1078. }
  1079. else
  1080. {
  1081. dict.Add("colStyle", null);
  1082. }
  1083. var colNameNode = HTEXLib.ShapeHelper.GetPPTXNodeByPath(node, "c:tx/c:strRef/c:strCache/c:pt/c:v");
  1084. if (colNameNode != null)
  1085. {
  1086. dict.Add("colName", colNameNode.Value);
  1087. }
  1088. //name
  1089. var catNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(node, "c:cat/c:strRef/c:strCache/c:pt/c:v");
  1090. if (catNodes == null)
  1091. {
  1092. catNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(node, "c:cat/c:numRef/c:numCache/c:pt/c:v");
  1093. }
  1094. if (catNodes != null)
  1095. {
  1096. List<string> list = new List<string>();
  1097. foreach (XElement cvNode in catNodes)
  1098. {
  1099. list.Add(cvNode.Value);
  1100. }
  1101. dict.Add("xAxis", list);
  1102. }
  1103. //value
  1104. var valNodes = HTEXLib.ShapeHelper.GetPPTXNodeListByPath(node, "c:val/c:numRef/c:numCache/c:pt/c:v");
  1105. if (valNodes != null)
  1106. {
  1107. List<string> list = new List<string>();
  1108. foreach (XElement cvNode in valNodes)
  1109. {
  1110. list.Add(cvNode.Value);
  1111. }
  1112. dict.Add("yAxis", list);
  1113. }
  1114. listDict.Add(dict);
  1115. }
  1116. }
  1117. return listDict;
  1118. }
  1119. return null;
  1120. }
  1121. public static XElement ProcessDgm(WordprocessingDocument wordDoc, XElement containerElement, Func<ImageInfo, XElement> imageHandler,
  1122. int? extentCx, int? extentCy, string altText, XElement element, string hyperlinkUri)
  1123. {
  1124. var rdm = element.Attribute(R.dm);
  1125. var dgm = wordDoc.MainDocumentPart.Parts.FirstOrDefault(pp => pp.RelationshipId == rdm.Value);
  1126. XElement img = null;
  1127. string mathxml = Globals.UnsupportedImage;
  1128. var imageInfo = new ImageInfo()
  1129. {
  1130. ContentType = "image/x-emf",
  1131. DrawingElement = element,
  1132. AltText = "UnsupportedImageError",
  1133. Mathxml = mathxml
  1134. };
  1135. img = imageHandler(imageInfo);
  1136. double? widthInPoints = extentCx * 96.0 / 914400;
  1137. double? heightInPoints = extentCy * 96.0 / 914400;
  1138. if (widthInPoints != null && heightInPoints != null)
  1139. {
  1140. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  1141. // string.Format(NumberFormatInfo.InvariantInfo,
  1142. // "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  1143. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  1144. "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  1145. }
  1146. else
  1147. {
  1148. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  1149. // string.Format(NumberFormatInfo.InvariantInfo,
  1150. // "vertical-align: middle;"));
  1151. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  1152. "vertical-align: middle;"));
  1153. }
  1154. return img;
  1155. }
  1156. public static (string html, HTEXLib.Shape shape) ProcessWps(WordprocessingDocument wordDoc, XElement element, Func<ImageInfo, XElement> imageHandler)
  1157. {
  1158. // var wps = element.Element(MC.Choice).Element(W.drawing).Element(WP.anchor).Element(A.graphic).Element(A.graphicData).Element(WPS.wsp);
  1159. WordprocessingShape wsp = new WordprocessingShape(element.ToString());
  1160. var ext = element.GetWordNodeByPath("wps:spPr/a:xfrm/a:ext");
  1161. var cx = ext.Attribute("cx").Value;
  1162. var cy = ext.Attribute("cy").Value;
  1163. HTEXLib.Shape shape = new HTEXLib.Shape();
  1164. HTEXLib.Position position = new HTEXLib.Position { x = 0, y = 0, cx = double.Parse(cx) * 96.0 / 914400, cy = double.Parse(cy) * 96.0 / 914400 };
  1165. shape.style.position = position;
  1166. var ShapeStyle = WORDHelper.DoShapeProperties(wordDoc, wsp.GetFirstChild<DocumentFormat.OpenXml.Office2010.Word.DrawingShape.ShapeProperties>(), element, imageHandler, position);
  1167. SlideColor slideColor = WORDHelper.DoShapeStyle(wsp.GetFirstChild<DocumentFormat.OpenXml.Office2010.Word.DrawingShape.ShapeStyle>(), wordDoc);
  1168. if (ShapeStyle.border != null)
  1169. {
  1170. shape.style.border = ShapeStyle.border;
  1171. }
  1172. //从ShapeProperties 获取 p:spPr
  1173. //再从 ShapeStyle 获取 p:spPr
  1174. if (ShapeStyle.border == null || ShapeStyle.border.color == null || ShapeStyle.border.color.type == -1)
  1175. {
  1176. if (slideColor != null)
  1177. {
  1178. shape.style.border.color.solidFill = slideColor.LineColor;
  1179. shape.style.border.color.type = 2;
  1180. shape.style.border.outline = ShapeStyle.border != null && ShapeStyle.border.outline != null ? ShapeStyle.border.outline : null;
  1181. }
  1182. }
  1183. else
  1184. {
  1185. shape.style.border = ShapeStyle.border;
  1186. shape.style.border.outline = ShapeStyle.border != null && ShapeStyle.border.outline != null ? ShapeStyle.border.outline : ShapeStyle.border.outline != null ? ShapeStyle.border.outline : null;
  1187. }
  1188. if (ShapeStyle.fill == null || ShapeStyle.fill.type == -1)
  1189. {
  1190. if (slideColor != null)
  1191. {
  1192. shape.style.fill.solidFill = slideColor.FillColor;
  1193. shape.style.fill.type = 2;
  1194. }
  1195. }
  1196. else
  1197. {
  1198. shape.style.fill = ShapeStyle.fill;
  1199. }
  1200. var wpars = element.GetWordNodeListByPath("wps:txbx/w:txbxContent/w:p");
  1201. List<string> pars = new List<string>();
  1202. if (wpars != null)
  1203. {
  1204. foreach (var wpar in wpars)
  1205. {
  1206. if (wpar.Value != null)
  1207. {
  1208. pars.Add(wpar.Value);
  1209. }
  1210. }
  1211. }
  1212. var prstGeom = element.GetWordNodeByPath("wps:spPr/a:prstGeom");
  1213. if (prstGeom != null)
  1214. {
  1215. var shapeType = prstGeom.Attribute("prst").Value;
  1216. var svg = SvgHelper.GenShapeSvg(new PresetGeometry(prstGeom.ToString()), shapeType, position, null);
  1217. shape.svg = svg;
  1218. }
  1219. else
  1220. {
  1221. var custGeom = element.GetWordNodeByPath("wps:spPr/a:custGeom");
  1222. if (custGeom != null)
  1223. {
  1224. DocumentFormat.OpenXml.Drawing.CustomGeometry shapeTypeCustom = new CustomGeometry(custGeom.ToString());
  1225. shape = PPTXHelper.DoCustomGeometry(shapeTypeCustom, shape);
  1226. }
  1227. }
  1228. return (DrawingShape(shape, pars), shape);
  1229. }
  1230. public static string DrawingShape(HTEXLib.Shape shape, List<string> pars)
  1231. {
  1232. StringBuilder builder = new StringBuilder();
  1233. if (shape.svg != null)
  1234. {
  1235. builder.Append($"<svg xmlns='http://www.w3.org/2000/svg' style='width:{shape.style.position.cx}px;height:{shape.style.position.cy}px;rotate({shape.style.position.rot}deg); overflow: visible;'>");
  1236. string bdc = shape.style.border != null && shape.style.border.color != null && shape.style.border.color.solidFill != null ? shape.style.border.color.solidFill : "none";
  1237. string bds = shape.style.border != null && shape.style.border.stroke != null ? shape.style.border.stroke : "0";
  1238. double? bdw = shape.style.border != null && shape.style.border.outline != null && shape.style.border.outline.width != null ? shape.style.border.outline.width : 0;
  1239. string bgc = shape.style.fill != null && shape.style.fill != null && shape.style.fill.solidFill != null ? shape.style.fill.solidFill : "none";
  1240. if (shape.style.border != null && (shape.style.border.tailEnd != null || shape.style.border.headEnd != null))
  1241. {
  1242. builder.Append("<defs xmlns='http://www.w3.org/2000/svg'>");
  1243. builder.Append($"<marker id='marker{shape.uid}' viewBox='0 0 20 20' refX='1' refY='10' markerWidth='10' markerHeight='20' stroke='{bdc}' fill='{bdc}' orient='auto-start-reverse' markerUnits='strokeWidth'><path d='M 0 0 L 20 10 L 0 20 z' /></marker>");
  1244. builder.Append("</defs>");
  1245. }
  1246. string stm = "";
  1247. string edm = "";
  1248. if (shape.style.border != null && shape.style.border.headEnd != null)
  1249. {
  1250. stm = $" marker-start='url(#marker{ shape.uid})'";
  1251. }
  1252. if (shape.style.border != null && shape.style.border.tailEnd != null)
  1253. {
  1254. edm = $" marker-end='url(#marker{ shape.uid})'";
  1255. }
  1256. builder.Append($"<path xmlns='http://www.w3.org/2000/svg' d='{shape.svg.d}' fill='{bgc}' stroke='{bdc}' stroke-width='{bdw}' stroke-dasharray='{bds}' {stm} {edm} />");
  1257. if (pars.IsNotEmpty())
  1258. {
  1259. builder.Append($"<text x='{shape.style.position.cx / 2}' y='{shape.style.position.cy}' fill='#000000'>{string.Join("", pars)}</text>");
  1260. }
  1261. builder.Append("</svg>");
  1262. }
  1263. return builder.ToString();
  1264. }
  1265. public static XElement ProcessOMath(XElement element)
  1266. {
  1267. //XslCompiledTransform xslTransform = new XslCompiledTransform();
  1268. // xslTransform.Load(BaseConfigModel.ContentRootPath + "/Config/Core/OMML2MML.XSL");
  1269. XmlReader OMML2MML = XmlReader.Create(new StringReader(Globals.OMML2MML));
  1270. XslCompiledTransform xslTransform = new XslCompiledTransform();
  1271. xslTransform.Load(OMML2MML);
  1272. string mathXml = element.ToString();
  1273. string officeML = string.Empty;
  1274. using (TextReader tr = new StringReader(mathXml))
  1275. {
  1276. using (XmlReader reader = XmlReader.Create(tr))
  1277. {
  1278. using (MemoryStream ms = new MemoryStream())
  1279. {
  1280. XmlWriterSettings settings = xslTransform.OutputSettings.Clone();
  1281. settings.ConformanceLevel = ConformanceLevel.Fragment;
  1282. settings.OmitXmlDeclaration = true;
  1283. XmlWriter xw = XmlWriter.Create(ms, settings);
  1284. xslTransform.Transform(reader, xw);
  1285. ms.Seek(0, SeekOrigin.Begin);
  1286. using (StreamReader sr = new StreamReader(ms, Encoding.UTF8))
  1287. {
  1288. officeML = sr.ReadToEnd();
  1289. // Console.Out.WriteLine(officeML);
  1290. }
  1291. }
  1292. }
  1293. }
  1294. officeML = officeML.Replace("mml:", "");
  1295. XElement officeElement = XElement.Load(new StringReader(officeML));
  1296. return officeElement;
  1297. }
  1298. private static object ProcessHyperlinkToBookmark(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element)
  1299. {
  1300. var style = new Dictionary<string, string>();
  1301. var a = new XElement(Xhtml.a,
  1302. new XAttribute("href", "#" + (string)element.Attribute(W.anchor)),
  1303. element.Elements(W.r).Select(run => ConvertRun(wordDoc, settings, run)));
  1304. if (!a.Nodes().Any())
  1305. a.Add(new XText(""));
  1306. style.Add("text-decoration", "none");
  1307. a.AddAnnotation(style);
  1308. return a;
  1309. }
  1310. private static object ProcessBookmarkStart(XElement element)
  1311. {
  1312. var name = (string)element.Attribute(W.name);
  1313. if (name == null) return null;
  1314. var style = new Dictionary<string, string>();
  1315. var a = new XElement(Xhtml.a,
  1316. new XAttribute("id", name),
  1317. new XText(""));
  1318. if (!a.Nodes().Any())
  1319. a.Add(new XText(""));
  1320. style.Add("text-decoration", "none");
  1321. a.AddAnnotation(style);
  1322. return a;
  1323. }
  1324. private static object ProcessTab(XElement element)
  1325. {
  1326. var tabWidthAtt = element.Attribute(PtOpenXml.TabWidth);
  1327. if (tabWidthAtt == null) return null;
  1328. var leader = (string)element.Attribute(PtOpenXml.Leader);
  1329. var tabWidth = (decimal)tabWidthAtt;
  1330. var style = new Dictionary<string, string>();
  1331. XElement span;
  1332. if (leader != null)
  1333. {
  1334. var leaderChar = ".";
  1335. if (leader == "hyphen")
  1336. leaderChar = "-";
  1337. else if (leader == "dot")
  1338. leaderChar = ".";
  1339. else if (leader == "underscore")
  1340. leaderChar = "_";
  1341. var runContainingTabToReplace = element.Ancestors(W.r).First();
  1342. var fontNameAtt = runContainingTabToReplace.Attribute(PtOpenXml.pt + "FontName") ??
  1343. runContainingTabToReplace.Ancestors(W.p).First().Attribute(PtOpenXml.pt + "FontName");
  1344. var dummyRun = new XElement(W.r,
  1345. fontNameAtt,
  1346. runContainingTabToReplace.Elements(W.rPr),
  1347. new XElement(W.t, leaderChar));
  1348. var widthOfLeaderChar = CalcWidthOfRunInTwips(dummyRun);
  1349. bool forceArial = false;
  1350. if (widthOfLeaderChar == 0)
  1351. {
  1352. dummyRun = new XElement(W.r,
  1353. new XAttribute(PtOpenXml.FontName, "Arial"),
  1354. runContainingTabToReplace.Elements(W.rPr),
  1355. new XElement(W.t, leaderChar));
  1356. widthOfLeaderChar = CalcWidthOfRunInTwips(dummyRun);
  1357. forceArial = true;
  1358. }
  1359. if (widthOfLeaderChar != 0)
  1360. {
  1361. var numberOfLeaderChars = (int)(Math.Floor((tabWidth * 1440) / widthOfLeaderChar));
  1362. if (numberOfLeaderChars < 0)
  1363. numberOfLeaderChars = 0;
  1364. span = new XElement(Xhtml.span,
  1365. new XAttribute(XNamespace.Xml + "space", "preserve"),
  1366. " " + "".PadRight(numberOfLeaderChars, leaderChar[0]) + " ");
  1367. style.Add("margin", "0 0 0 0");
  1368. style.Add("padding", "0 0 0 0");
  1369. style.Add("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", tabWidth));
  1370. style.Add("text-align", "center");
  1371. if (forceArial)
  1372. style.Add("font-family", "Arial");
  1373. }
  1374. else
  1375. {
  1376. span = new XElement(Xhtml.span, new XAttribute(XNamespace.Xml + "space", "preserve"), " ");
  1377. style.Add("margin", "0 0 0 0");
  1378. style.Add("padding", "0 0 0 0");
  1379. style.Add("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", tabWidth));
  1380. style.Add("text-align", "center");
  1381. if (leader == "underscore")
  1382. {
  1383. style.Add("text-decoration", "underline");
  1384. }
  1385. }
  1386. }
  1387. else
  1388. {
  1389. #if false
  1390. var bidi = element
  1391. .Ancestors(W.p)
  1392. .Take(1)
  1393. .Elements(W.pPr)
  1394. .Elements(W.bidi)
  1395. .Where(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true)
  1396. .FirstOrDefault();
  1397. var isBidi = bidi != null;
  1398. if (isBidi)
  1399. span = new XElement(Xhtml.span, new XEntity("#x200f")); // RLM
  1400. else
  1401. span = new XElement(Xhtml.span, new XEntity("#x200e")); // LRM
  1402. #else
  1403. span = new XElement(Xhtml.span, new XEntity("#x00a0"));
  1404. #endif
  1405. style.Add("margin", string.Format(NumberFormatInfo.InvariantInfo, "0 0 0 {0:0.00}in", tabWidth));
  1406. style.Add("padding", "0 0 0 0");
  1407. }
  1408. span.AddAnnotation(style);
  1409. return span;
  1410. }
  1411. private static object ProcessBreak(XElement element)
  1412. {
  1413. XElement span = null;
  1414. var tabWidth = (decimal?)element.Attribute(PtOpenXml.TabWidth);
  1415. if (tabWidth != null)
  1416. {
  1417. span = new XElement(Xhtml.span);
  1418. span.AddAnnotation(new Dictionary<string, string>
  1419. {
  1420. { "margin", string.Format(NumberFormatInfo.InvariantInfo, "0 0 0 {0:0.00}in", tabWidth) },
  1421. { "padding", "0 0 0 0" }
  1422. });
  1423. }
  1424. var paragraph = element.Ancestors(W.p).FirstOrDefault();
  1425. var isBidi = paragraph != null &&
  1426. paragraph.Elements(W.pPr).Elements(W.bidi).Any(b => b.Attribute(W.val) == null ||
  1427. b.Attribute(W.val).ToBoolean() == true);
  1428. var zeroWidthChar = isBidi ? new XEntity("#x200f") : new XEntity("#x200e");
  1429. return new object[]
  1430. {
  1431. new XElement(Xhtml.br),
  1432. zeroWidthChar,
  1433. span,
  1434. };
  1435. }
  1436. private static object ProcessContentControl(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
  1437. XElement element, decimal currentMarginLeft)
  1438. {
  1439. var relevantAncestors = element.Ancestors().TakeWhile(a => a.Name != W.txbxContent);
  1440. var isRunLevelContentControl = relevantAncestors.Any(a => a.Name == W.p);
  1441. if (isRunLevelContentControl)
  1442. {
  1443. return element.Elements(W.sdtContent).Elements()
  1444. .Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft))
  1445. .ToList();
  1446. }
  1447. return CreateBorderDivs(wordDoc, settings, element.Elements(W.sdtContent).Elements());
  1448. }
  1449. // Transform the w:p element, including the following sibling w:p element(s)
  1450. // in case the w:p element has a style separator. The sibling(s) will be
  1451. // transformed to h:span elements rather than h:p elements and added to
  1452. // the element (e.g., h:h2) created from the w:p element having the (first)
  1453. // style separator (i.e., a w:specVanish element).
  1454. private static object ProcessParagraph(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
  1455. XElement element, bool suppressTrailingWhiteSpace, decimal currentMarginLeft)
  1456. {
  1457. // Ignore this paragraph if the previous paragraph has a style separator.
  1458. // We have already transformed this one together with the previous one.
  1459. var previousParagraph = element.ElementsBeforeSelf(W.p).LastOrDefault();
  1460. if (HasStyleSeparator(previousParagraph)) return null;
  1461. var elementName = GetParagraphElementName(element, wordDoc);
  1462. var isBidi = IsBidi(element);
  1463. var paragraph = (XElement)ConvertParagraph(wordDoc, settings, element, elementName,
  1464. suppressTrailingWhiteSpace, currentMarginLeft, isBidi);
  1465. // The paragraph conversion might have created empty spans.
  1466. // These can and should be removed because empty spans are
  1467. // invalid in HTML5.
  1468. paragraph.Elements(Xhtml.span).Where(e => e.IsEmpty).Remove();
  1469. foreach (var span in paragraph.Elements(Xhtml.span).ToList())
  1470. {
  1471. var v = span.Value;
  1472. if (v.Length > 0 && (char.IsWhiteSpace(v[0]) || char.IsWhiteSpace(v[v.Length - 1])) && span.Attribute(XNamespace.Xml + "space") == null)
  1473. span.Add(new XAttribute(XNamespace.Xml + "space", "preserve"));
  1474. }
  1475. while (HasStyleSeparator(element))
  1476. {
  1477. element = element.ElementsAfterSelf(W.p).FirstOrDefault();
  1478. if (element == null) break;
  1479. elementName = Xhtml.span;
  1480. isBidi = IsBidi(element);
  1481. var span = (XElement)ConvertParagraph(wordDoc, settings, element, elementName,
  1482. suppressTrailingWhiteSpace, currentMarginLeft, isBidi);
  1483. var v = span.Value;
  1484. if (v.Length > 0 && (char.IsWhiteSpace(v[0]) || char.IsWhiteSpace(v[v.Length - 1])) && span.Attribute(XNamespace.Xml + "space") == null)
  1485. span.Add(new XAttribute(XNamespace.Xml + "space", "preserve"));
  1486. paragraph.Add(span);
  1487. }
  1488. return paragraph;
  1489. }
  1490. private static object ProcessTable(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element, decimal currentMarginLeft)
  1491. {
  1492. var style = new Dictionary<string, string>();
  1493. style.AddIfMissing("border-collapse", "collapse");
  1494. style.AddIfMissing("border", "none");
  1495. var bidiVisual = element.Elements(W.tblPr).Elements(W.bidiVisual).FirstOrDefault();
  1496. var tblW = element.Elements(W.tblPr).Elements(W.tblW).FirstOrDefault();
  1497. if (tblW != null)
  1498. {
  1499. var type = (string)tblW.Attribute(W.type);
  1500. if (type != null && type == "pct")
  1501. {
  1502. var w = (int)tblW.Attribute(W._w);
  1503. style.AddIfMissing("width", (w / 50) + "%");
  1504. }
  1505. }
  1506. var tblInd = element.Elements(W.tblPr).Elements(W.tblInd).FirstOrDefault();
  1507. if (tblInd != null)
  1508. {
  1509. var tblIndType = (string)tblInd.Attribute(W.type);
  1510. if (tblIndType != null)
  1511. {
  1512. if (tblIndType == "dxa")
  1513. {
  1514. var width = (decimal?)tblInd.Attribute(W._w);
  1515. if (width != null)
  1516. {
  1517. style.AddIfMissing("margin-left",
  1518. width > 0m
  1519. ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", width / 20m)
  1520. : "0");
  1521. }
  1522. }
  1523. }
  1524. }
  1525. var tableDirection = bidiVisual != null ? new XAttribute("dir", "rtl") : new XAttribute("dir", "ltr");
  1526. style.AddIfMissing("margin-bottom", ".001pt");
  1527. var table = new XElement(Xhtml.table,
  1528. // TODO: Revisit and make sure the omission is covered by appropriate CSS.
  1529. // new XAttribute("border", "1"),
  1530. // new XAttribute("cellspacing", 0),
  1531. // new XAttribute("cellpadding", 0),
  1532. tableDirection,
  1533. element.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft)));
  1534. table.AddAnnotation(style);
  1535. var jc = (string)element.Elements(W.tblPr).Elements(W.jc).Attributes(W.val).FirstOrDefault() ?? "left";
  1536. XAttribute dir = null;
  1537. XAttribute jcToUse = null;
  1538. if (bidiVisual != null)
  1539. {
  1540. dir = new XAttribute("dir", "rtl");
  1541. if (jc == "left")
  1542. jcToUse = new XAttribute("align", "right");
  1543. else if (jc == "right")
  1544. jcToUse = new XAttribute("align", "left");
  1545. else if (jc == "center")
  1546. jcToUse = new XAttribute("align", "center");
  1547. }
  1548. else
  1549. {
  1550. jcToUse = new XAttribute("align", jc);
  1551. }
  1552. var tableDiv = new XElement(Xhtml.div,
  1553. dir,
  1554. jcToUse,
  1555. table);
  1556. return tableDiv;
  1557. }
  1558. [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
  1559. private static object ProcessTableCell(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element)
  1560. {
  1561. var style = new Dictionary<string, string>();
  1562. XAttribute colSpan = null;
  1563. XAttribute rowSpan = null;
  1564. var tcPr = element.Element(W.tcPr);
  1565. if (tcPr != null)
  1566. {
  1567. if ((string)tcPr.Elements(W.vMerge).Attributes(W.val).FirstOrDefault() == "restart")
  1568. {
  1569. var currentRow = element.Parent.ElementsBeforeSelf(W.tr).Count();
  1570. var currentCell = element.ElementsBeforeSelf(W.tc).Count();
  1571. var tbl = element.Parent.Parent;
  1572. int rowSpanCount = 1;
  1573. currentRow += 1;
  1574. while (true)
  1575. {
  1576. var row = tbl.Elements(W.tr).Skip(currentRow).FirstOrDefault();
  1577. if (row == null)
  1578. break;
  1579. var cell2 = row.Elements(W.tc).Skip(currentCell).FirstOrDefault();
  1580. if (cell2 == null)
  1581. break;
  1582. if (cell2.Elements(W.tcPr).Elements(W.vMerge).FirstOrDefault() == null)
  1583. break;
  1584. if ((string)cell2.Elements(W.tcPr).Elements(W.vMerge).Attributes(W.val).FirstOrDefault() == "restart")
  1585. break;
  1586. currentRow += 1;
  1587. rowSpanCount += 1;
  1588. }
  1589. rowSpan = new XAttribute("rowspan", rowSpanCount);
  1590. }
  1591. if (tcPr.Element(W.vMerge) != null &&
  1592. (string)tcPr.Elements(W.vMerge).Attributes(W.val).FirstOrDefault() != "restart")
  1593. return null;
  1594. if (tcPr.Element(W.vAlign) != null)
  1595. {
  1596. var vAlignVal = (string)tcPr.Elements(W.vAlign).Attributes(W.val).FirstOrDefault();
  1597. if (vAlignVal == "00")
  1598. style.AddIfMissing("vertical-align", "top");
  1599. else if (vAlignVal == "center")
  1600. style.AddIfMissing("vertical-align", "middle");
  1601. else if (vAlignVal == "bottom")
  1602. style.AddIfMissing("vertical-align", "bottom");
  1603. else
  1604. style.AddIfMissing("vertical-align", "middle");
  1605. }
  1606. style.AddIfMissing("vertical-align", "top");
  1607. if ((string)tcPr.Elements(W.tcW).Attributes(W.type).FirstOrDefault() == "dxa")
  1608. {
  1609. decimal width = (int)tcPr.Elements(W.tcW).Attributes(W._w).FirstOrDefault();
  1610. style.AddIfMissing("width", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", width / 20m));
  1611. }
  1612. if ((string)tcPr.Elements(W.tcW).Attributes(W.type).FirstOrDefault() == "pct")
  1613. {
  1614. decimal width = (int)tcPr.Elements(W.tcW).Attributes(W._w).FirstOrDefault();
  1615. style.AddIfMissing("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}%", width / 50m));
  1616. }
  1617. var tcBorders = tcPr.Element(W.tcBorders);
  1618. GenerateBorderStyle(tcBorders, W.top, style, BorderType.Cell);
  1619. GenerateBorderStyle(tcBorders, W.right, style, BorderType.Cell);
  1620. GenerateBorderStyle(tcBorders, W.bottom, style, BorderType.Cell);
  1621. GenerateBorderStyle(tcBorders, W.left, style, BorderType.Cell);
  1622. CreateStyleFromShd(style, tcPr.Element(W.shd));
  1623. var gridSpan = tcPr.Elements(W.gridSpan).Attributes(W.val).Select(a => (int?)a).FirstOrDefault();
  1624. if (gridSpan != null)
  1625. colSpan = new XAttribute("colspan", (int)gridSpan);
  1626. }
  1627. style.AddIfMissing("padding-top", "0");
  1628. style.AddIfMissing("padding-bottom", "0");
  1629. var cell = new XElement(Xhtml.td,
  1630. rowSpan,
  1631. colSpan,
  1632. CreateBorderDivs(wordDoc, settings, element.Elements()));
  1633. cell.AddAnnotation(style);
  1634. return cell;
  1635. }
  1636. private static object ProcessTableRow(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element,
  1637. decimal currentMarginLeft)
  1638. {
  1639. var style = new Dictionary<string, string>();
  1640. int? trHeight = (int?)element.Elements(W.trPr).Elements(W.trHeight).Attributes(W.val).FirstOrDefault();
  1641. if (trHeight != null)
  1642. style.AddIfMissing("height",
  1643. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", (decimal)trHeight / 1440m));
  1644. var htmlRow = new XElement(Xhtml.tr,
  1645. element.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft)));
  1646. if (style.Any())
  1647. htmlRow.AddAnnotation(style);
  1648. return htmlRow;
  1649. }
  1650. private static bool HasStyleSeparator(XElement element)
  1651. {
  1652. return element != null && element.Elements(W.pPr).Elements(W.rPr).Any(e => GetBoolProp(e, W.specVanish));
  1653. }
  1654. private static bool IsBidi(XElement element)
  1655. {
  1656. return element
  1657. .Elements(W.pPr)
  1658. .Elements(W.bidi)
  1659. .Any(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true);
  1660. }
  1661. private static XName GetParagraphElementName(XElement element, WordprocessingDocument wordDoc)
  1662. {
  1663. var elementName = Xhtml.p;
  1664. var styleId = (string)element.Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
  1665. if (styleId == null) return elementName;
  1666. var style = GetStyle(styleId, wordDoc);
  1667. if (style == null) return elementName;
  1668. var outlineLevel =
  1669. (int?)style.Elements(W.pPr).Elements(W.outlineLvl).Attributes(W.val).FirstOrDefault();
  1670. if (outlineLevel != null && outlineLevel <= 5)
  1671. {
  1672. elementName = Xhtml.xhtml + string.Format("h{0}", outlineLevel + 1);
  1673. }
  1674. return elementName;
  1675. }
  1676. private static XElement GetStyle(string styleId, WordprocessingDocument wordDoc)
  1677. {
  1678. var stylesPart = wordDoc.MainDocumentPart.StyleDefinitionsPart;
  1679. if (stylesPart == null) return null;
  1680. var styles = stylesPart.GetXDocument().Root;
  1681. return styles != null
  1682. ? styles.Elements(W.style).FirstOrDefault(s => (string)s.Attribute(W.styleId) == styleId)
  1683. : null;
  1684. }
  1685. private static object CreateSectionDivs(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element)
  1686. {
  1687. // note: when building a paging html converter, need to attend to new sections with page breaks here.
  1688. // This code conflates adjacent sections if they have identical formatting, which is not an issue
  1689. // for the non-paging transform.
  1690. var groupedIntoDivs = element
  1691. .Elements()
  1692. .GroupAdjacent(e =>
  1693. {
  1694. var sectAnnotation = e.Annotation<SectionAnnotation>();
  1695. return sectAnnotation != null ? sectAnnotation.SectionElement.ToString() : "";
  1696. });
  1697. // note: when creating a paging html converter, need to pay attention to w:rtlGutter element.
  1698. var divList = groupedIntoDivs
  1699. .Select(g =>
  1700. {
  1701. var sectPr = g.First().Annotation<SectionAnnotation>();
  1702. XElement bidi = null;
  1703. if (sectPr != null)
  1704. {
  1705. bidi = sectPr
  1706. .SectionElement
  1707. .Elements(W.bidi)
  1708. .FirstOrDefault(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true);
  1709. }
  1710. if (sectPr == null || bidi == null)
  1711. {
  1712. var div = new XElement(Xhtml.div, CreateBorderDivs(wordDoc, settings, g));
  1713. return div;
  1714. }
  1715. else
  1716. {
  1717. var div = new XElement(Xhtml.div,
  1718. new XAttribute("dir", "rtl"),
  1719. CreateBorderDivs(wordDoc, settings, g));
  1720. return div;
  1721. }
  1722. });
  1723. return divList;
  1724. }
  1725. private enum BorderType
  1726. {
  1727. Paragraph,
  1728. Cell,
  1729. };
  1730. /*
  1731. * Notes on line spacing
  1732. *
  1733. * the w:line and w:lineRule attributes control spacing between lines - including between lines within a paragraph
  1734. *
  1735. * If w:spacing w:lineRule="auto" then
  1736. * w:spacing w:line is a percentage where 240 == 100%
  1737. *
  1738. * (line value / 240) * 100 = percentage of line
  1739. *
  1740. * If w:spacing w:lineRule="exact" or w:lineRule="atLeast" then
  1741. * w:spacing w:line is in twips
  1742. * 1440 = exactly one inch from line to line
  1743. *
  1744. * Handle
  1745. * - ind
  1746. * - jc
  1747. * - numPr
  1748. * - pBdr
  1749. * - shd
  1750. * - spacing
  1751. * - textAlignment
  1752. *
  1753. * Don't Handle (yet)
  1754. * - adjustRightInd?
  1755. * - autoSpaceDE
  1756. * - autoSpaceDN
  1757. * - bidi
  1758. * - contextualSpacing
  1759. * - divId
  1760. * - framePr
  1761. * - keepLines
  1762. * - keepNext
  1763. * - kinsoku
  1764. * - mirrorIndents
  1765. * - overflowPunct
  1766. * - pageBreakBefore
  1767. * - snapToGrid
  1768. * - suppressAutoHyphens
  1769. * - suppressLineNumbers
  1770. * - suppressOverlap
  1771. * - tabs
  1772. * - textBoxTightWrap
  1773. * - textDirection
  1774. * - topLinePunct
  1775. * - widowControl
  1776. * - wordWrap
  1777. *
  1778. */
  1779. private static object ConvertParagraph(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
  1780. XElement paragraph, XName elementName, bool suppressTrailingWhiteSpace, decimal currentMarginLeft, bool isBidi)
  1781. {
  1782. var style = DefineParagraphStyle(paragraph, elementName, suppressTrailingWhiteSpace, currentMarginLeft, isBidi);
  1783. var rtl = isBidi ? new XAttribute("dir", "rtl") : new XAttribute("dir", "ltr");
  1784. var firstMark = isBidi ? new XEntity("#x200f") : null;
  1785. // Analyze initial runs to see whether we have a tab, in which case we will render
  1786. // a span with a defined width and ignore the tab rather than rendering the text
  1787. // preceding the tab and the tab as a span with a computed width.
  1788. var firstTabRun = paragraph
  1789. .Elements(W.r)
  1790. .FirstOrDefault(run => run.Elements(W.tab).Any());
  1791. var elementsPrecedingTab = firstTabRun != null
  1792. ? paragraph.Elements(W.r).TakeWhile(e => e != firstTabRun)
  1793. .Where(e => e.Elements().Any(c => c.Attributes(PtOpenXml.TabWidth).Any())).ToList()
  1794. : Enumerable.Empty<XElement>().ToList();
  1795. // TODO: Revisit
  1796. // For the time being, if a hyperlink field precedes the tab, we'll render it as before.
  1797. var hyperlinkPrecedesTab = elementsPrecedingTab
  1798. .Elements(W.r)
  1799. .Elements(W.instrText)
  1800. .Select(e => e.Value)
  1801. .Any(value => value != null && value.TrimStart().ToUpper().StartsWith("HYPERLINK"));
  1802. if (hyperlinkPrecedesTab)
  1803. {
  1804. var paraElement1 = new XElement(elementName,
  1805. rtl,
  1806. firstMark,
  1807. ConvertContentThatCanContainFields(wordDoc, settings, paragraph.Elements()));
  1808. paraElement1.AddAnnotation(style);
  1809. return paraElement1;
  1810. }
  1811. var txElementsPrecedingTab = TransformElementsPrecedingTab(wordDoc, settings, elementsPrecedingTab, firstTabRun);
  1812. var elementsSucceedingTab = firstTabRun != null
  1813. ? paragraph.Elements().SkipWhile(e => e != firstTabRun).Skip(1)
  1814. : paragraph.Elements();
  1815. var paraElement = new XElement(elementName,
  1816. rtl,
  1817. firstMark,
  1818. txElementsPrecedingTab,
  1819. ConvertContentThatCanContainFields(wordDoc, settings, elementsSucceedingTab));
  1820. paraElement.AddAnnotation(style);
  1821. return paraElement;
  1822. }
  1823. private static List<object> TransformElementsPrecedingTab(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
  1824. List<XElement> elementsPrecedingTab, XElement firstTabRun)
  1825. {
  1826. var tabWidth = firstTabRun != null
  1827. ? (decimal?)firstTabRun.Elements(W.tab).Attributes(PtOpenXml.TabWidth).FirstOrDefault() ?? 0m
  1828. : 0m;
  1829. var precedingElementsWidth = elementsPrecedingTab
  1830. .Elements()
  1831. .Where(c => c.Attributes(PtOpenXml.TabWidth).Any())
  1832. .Select(e => (decimal)e.Attribute(PtOpenXml.TabWidth))
  1833. .Sum();
  1834. var totalWidth = precedingElementsWidth + tabWidth;
  1835. var txElementsPrecedingTab = elementsPrecedingTab
  1836. .Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, 0m))
  1837. .ToList();
  1838. if (txElementsPrecedingTab.Count > 1)
  1839. {
  1840. var span = new XElement(Xhtml.span, txElementsPrecedingTab);
  1841. var spanStyle = new Dictionary<string, string>
  1842. {
  1843. { "display", "inline-block" },
  1844. { "text-indent", "0" },
  1845. { "width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}in", totalWidth) }
  1846. };
  1847. span.AddAnnotation(spanStyle);
  1848. }
  1849. else if (txElementsPrecedingTab.Count == 1)
  1850. {
  1851. var element = txElementsPrecedingTab.First() as XElement;
  1852. if (element != null)
  1853. {
  1854. var spanStyle = element.Annotation<Dictionary<string, string>>();
  1855. spanStyle.AddIfMissing("display", "inline-block");
  1856. spanStyle.AddIfMissing("text-indent", "0");
  1857. spanStyle.AddIfMissing("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}in", totalWidth));
  1858. }
  1859. }
  1860. return txElementsPrecedingTab;
  1861. }
  1862. private static Dictionary<string, string> DefineParagraphStyle(XElement paragraph, XName elementName,
  1863. bool suppressTrailingWhiteSpace, decimal currentMarginLeft, bool isBidi)
  1864. {
  1865. var style = new Dictionary<string, string>();
  1866. var styleName = (string)paragraph.Attribute(PtOpenXml.StyleName);
  1867. if (styleName != null)
  1868. style.Add("PtStyleName", styleName);
  1869. var pPr = paragraph.Element(W.pPr);
  1870. if (pPr == null) return style;
  1871. CreateStyleFromSpacing(style, pPr.Element(W.spacing), elementName, suppressTrailingWhiteSpace);
  1872. CreateStyleFromInd(style, pPr.Element(W.ind), elementName, currentMarginLeft, isBidi);
  1873. // todo need to handle
  1874. // - both
  1875. // - mediumKashida
  1876. // - distribute
  1877. // - numTab
  1878. // - highKashida
  1879. // - lowKashida
  1880. // - thaiDistribute
  1881. CreateStyleFromJc(style, pPr.Element(W.jc), isBidi);
  1882. CreateStyleFromShd(style, pPr.Element(W.shd));
  1883. // Pt.FontName
  1884. var font = (string)paragraph.Attributes(PtOpenXml.FontName).FirstOrDefault();
  1885. if (font != null)
  1886. CreateFontCssProperty(font, style);
  1887. DefineFontSize(style, paragraph);
  1888. DefineLineHeight(style, paragraph);
  1889. // vertical text alignment as of December 2013 does not work in any major browsers.
  1890. CreateStyleFromTextAlignment(style, pPr.Element(W.textAlignment));
  1891. style.AddIfMissing("margin-top", "0");
  1892. style.AddIfMissing("margin-left", "0");
  1893. style.AddIfMissing("margin-right", "0");
  1894. style.AddIfMissing("margin-bottom", ".001pt");
  1895. return style;
  1896. }
  1897. private static void CreateStyleFromInd(Dictionary<string, string> style, XElement ind, XName elementName,
  1898. decimal currentMarginLeft, bool isBidi)
  1899. {
  1900. if (ind == null) return;
  1901. var left = (decimal?)ind.Attribute(W.left);
  1902. if (left != null && elementName != Xhtml.span)
  1903. {
  1904. var leftInInches = (decimal)left / 1440 - currentMarginLeft;
  1905. style.AddIfMissing(isBidi ? "margin-right" : "margin-left",
  1906. leftInInches > 0m
  1907. ? string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", leftInInches)
  1908. : "0");
  1909. }
  1910. var right = (decimal?)ind.Attribute(W.right);
  1911. if (right != null)
  1912. {
  1913. var rightInInches = (decimal)right / 1440;
  1914. style.AddIfMissing(isBidi ? "margin-left" : "margin-right",
  1915. rightInInches > 0m
  1916. ? string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", rightInInches)
  1917. : "0");
  1918. }
  1919. var firstLine = (decimal?)ind.Attribute(W.firstLine);
  1920. if (firstLine != null && elementName != Xhtml.span)
  1921. {
  1922. var firstLineInInches = (decimal)firstLine / 1440m;
  1923. style.AddIfMissing("text-indent",
  1924. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", firstLineInInches));
  1925. }
  1926. var hanging = (decimal?)ind.Attribute(W.hanging);
  1927. if (hanging != null && elementName != Xhtml.span)
  1928. {
  1929. var hangingInInches = (decimal)-hanging / 1440m;
  1930. style.AddIfMissing("text-indent",
  1931. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", hangingInInches));
  1932. }
  1933. }
  1934. private static void CreateStyleFromJc(Dictionary<string, string> style, XElement jc, bool isBidi)
  1935. {
  1936. if (jc != null)
  1937. {
  1938. var jcVal = (string)jc.Attributes(W.val).FirstOrDefault() ?? "left";
  1939. if (jcVal == "left")
  1940. style.AddIfMissing("text-align", isBidi ? "right" : "left");
  1941. else if (jcVal == "right")
  1942. style.AddIfMissing("text-align", isBidi ? "left" : "right");
  1943. else if (jcVal == "center")
  1944. style.AddIfMissing("text-align", "center");
  1945. else if (jcVal == "both")
  1946. style.AddIfMissing("text-align", "justify");
  1947. }
  1948. }
  1949. private static void CreateStyleFromSpacing(Dictionary<string, string> style, XElement spacing, XName elementName,
  1950. bool suppressTrailingWhiteSpace)
  1951. {
  1952. if (spacing == null) return;
  1953. var spacingBefore = (decimal?)spacing.Attribute(W.before);
  1954. if (spacingBefore != null && elementName != Xhtml.span)
  1955. style.AddIfMissing("margin-top",
  1956. spacingBefore > 0m
  1957. ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", spacingBefore / 20.0m)
  1958. : "0");
  1959. var lineRule = (string)spacing.Attribute(W.lineRule);
  1960. if (lineRule == "auto")
  1961. {
  1962. var line = (decimal)spacing.Attribute(W.line);
  1963. if (line != 240m)
  1964. {
  1965. var pct = (line / 240m) * 100m;
  1966. style.Add("line-height", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}%", pct));
  1967. }
  1968. }
  1969. if (lineRule == "exact")
  1970. {
  1971. var line = (decimal)spacing.Attribute(W.line);
  1972. var points = line / 20m;
  1973. style.Add("line-height", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", points));
  1974. }
  1975. if (lineRule == "atLeast")
  1976. {
  1977. var line = (decimal)spacing.Attribute(W.line);
  1978. var points = line / 20m;
  1979. if (points >= 14m)
  1980. style.Add("line-height", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", points));
  1981. }
  1982. var spacingAfter = suppressTrailingWhiteSpace ? 0m : (decimal?)spacing.Attribute(W.after);
  1983. if (spacingAfter != null)
  1984. style.AddIfMissing("margin-bottom",
  1985. spacingAfter > 0m
  1986. ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", spacingAfter / 20.0m)
  1987. : "0");
  1988. }
  1989. private static void CreateStyleFromTextAlignment(Dictionary<string, string> style, XElement textAlignment)
  1990. {
  1991. if (textAlignment == null) return;
  1992. var verticalTextAlignment = (string)textAlignment.Attributes(W.val).FirstOrDefault();
  1993. if (verticalTextAlignment == null || verticalTextAlignment == "auto") return;
  1994. if (verticalTextAlignment == "top")
  1995. style.AddIfMissing("vertical-align", "top");
  1996. else if (verticalTextAlignment == "center")
  1997. style.AddIfMissing("vertical-align", "middle");
  1998. else if (verticalTextAlignment == "baseline")
  1999. style.AddIfMissing("vertical-align", "baseline");
  2000. else if (verticalTextAlignment == "bottom")
  2001. style.AddIfMissing("vertical-align", "bottom");
  2002. }
  2003. private static void DefineFontSize(Dictionary<string, string> style, XElement paragraph)
  2004. {
  2005. var sz = paragraph
  2006. .DescendantsTrimmed(W.txbxContent)
  2007. .Where(e => e.Name == W.r)
  2008. .Select(r => GetFontSize(r))
  2009. .Max();
  2010. if (sz != null)
  2011. style.AddIfMissing("font-size", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", sz / 2.0m));
  2012. }
  2013. private static void DefineLineHeight(Dictionary<string, string> style, XElement paragraph)
  2014. {
  2015. var allRunsAreUniDirectional = paragraph
  2016. .DescendantsTrimmed(W.txbxContent)
  2017. .Where(e => e.Name == W.r)
  2018. .Select(run => (string)run.Attribute(PtOpenXml.LanguageType))
  2019. .All(lt => lt != "bidi");
  2020. if (allRunsAreUniDirectional)
  2021. style.AddIfMissing("line-height", "108%");
  2022. }
  2023. /*
  2024. * Handle:
  2025. * - b
  2026. * - bdr
  2027. * - caps
  2028. * - color
  2029. * - dstrike
  2030. * - highlight
  2031. * - i
  2032. * - position
  2033. * - rFonts
  2034. * - shd
  2035. * - smallCaps
  2036. * - spacing
  2037. * - strike
  2038. * - sz
  2039. * - u
  2040. * - vanish
  2041. * - vertAlign
  2042. *
  2043. * Don't handle:
  2044. * - em
  2045. * - emboss
  2046. * - fitText
  2047. * - imprint
  2048. * - kern
  2049. * - outline
  2050. * - shadow
  2051. * - w
  2052. *
  2053. */
  2054. private static object ConvertRun(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement run)
  2055. {
  2056. var rPr = run.Element(W.rPr);
  2057. if (rPr == null)
  2058. return run.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, 0m));
  2059. // hide all content that contains the w:rPr/w:webHidden element
  2060. if (rPr.Element(W.webHidden) != null)
  2061. return null;
  2062. var style = DefineRunStyle(run);
  2063. object content = run.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, 0m));
  2064. if (rPr.Element(W.em) != null)
  2065. {
  2066. content = new XElement(Xhtml.dot, content);
  2067. }
  2068. // Wrap content in h:sup or h:sub elements as necessary.
  2069. if (rPr.Element(W.vertAlign) != null)
  2070. {
  2071. XElement newContent = null;
  2072. var vertAlignVal = (string)rPr.Elements(W.vertAlign).Attributes(W.val).FirstOrDefault();
  2073. switch (vertAlignVal)
  2074. {
  2075. case "superscript":
  2076. newContent = new XElement(Xhtml.sup, content);
  2077. break;
  2078. case "subscript":
  2079. newContent = new XElement(Xhtml.sub, content);
  2080. break;
  2081. }
  2082. if (newContent != null && newContent.Nodes().Any())
  2083. content = newContent;
  2084. }
  2085. var langAttribute = GetLangAttribute(run);
  2086. XEntity runStartMark;
  2087. XEntity runEndMark;
  2088. DetermineRunMarks(run, rPr, style, out runStartMark, out runEndMark);
  2089. if (style.Any() || langAttribute != null || runStartMark != null)
  2090. {
  2091. style.AddIfMissing("margin", "0");
  2092. style.AddIfMissing("padding", "0");
  2093. var xe = new XElement(Xhtml.span,
  2094. langAttribute,
  2095. runStartMark,
  2096. content,
  2097. runEndMark);
  2098. xe.AddAnnotation(style);
  2099. content = xe;
  2100. }
  2101. return content;
  2102. }
  2103. [SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
  2104. private static Dictionary<string, string> DefineRunStyle(XElement run)
  2105. {
  2106. var style = new Dictionary<string, string>();
  2107. var rPr = run.Elements(W.rPr).First();
  2108. var styleName = (string)run.Attribute(PtOpenXml.StyleName);
  2109. if (styleName != null)
  2110. style.Add("PtStyleName", styleName);
  2111. // W.bdr
  2112. if (rPr.Element(W.bdr) != null && (string)rPr.Elements(W.bdr).Attributes(W.val).FirstOrDefault() != "none")
  2113. {
  2114. style.AddIfMissing("border", "solid windowtext 1.0pt");
  2115. style.AddIfMissing("padding", "0");
  2116. }
  2117. // W.color
  2118. var color = (string)rPr.Elements(W.color).Attributes(W.val).FirstOrDefault();
  2119. if (color != null)
  2120. CreateColorProperty("color", color, style);
  2121. // W.highlight
  2122. var highlight = (string)rPr.Elements(W.highlight).Attributes(W.val).FirstOrDefault();
  2123. if (highlight != null)
  2124. CreateColorProperty("background", highlight, style);
  2125. // W.shd
  2126. var shade = (string)rPr.Elements(W.shd).Attributes(W.fill).FirstOrDefault();
  2127. if (shade != null)
  2128. CreateColorProperty("background", shade, style);
  2129. // Pt.FontName
  2130. var sym = run.Element(W.sym);
  2131. var font = sym != null
  2132. ? (string)sym.Attributes(W.font).FirstOrDefault()
  2133. : (string)run.Attributes(PtOpenXml.FontName).FirstOrDefault();
  2134. if (font != null)
  2135. CreateFontCssProperty(font, style);
  2136. // W.sz
  2137. var languageType = (string)run.Attribute(PtOpenXml.LanguageType);
  2138. var sz = GetFontSize(languageType, rPr);
  2139. if (sz != null)
  2140. style.AddIfMissing("font-size", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", sz / 2.0m));
  2141. // W.caps
  2142. if (GetBoolProp(rPr, W.caps))
  2143. style.AddIfMissing("text-transform", "uppercase");
  2144. // W.smallCaps
  2145. if (GetBoolProp(rPr, W.smallCaps))
  2146. style.AddIfMissing("font-variant", "small-caps");
  2147. // W.spacing
  2148. var spacingInTwips = (decimal?)rPr.Elements(W.spacing).Attributes(W.val).FirstOrDefault();
  2149. if (spacingInTwips != null)
  2150. style.AddIfMissing("letter-spacing",
  2151. spacingInTwips > 0m
  2152. ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", spacingInTwips / 20)
  2153. : "0");
  2154. // W.position
  2155. var position = (decimal?)rPr.Elements(W.position).Attributes(W.val).FirstOrDefault();
  2156. if (position != null)
  2157. {
  2158. style.AddIfMissing("position", "relative");
  2159. style.AddIfMissing("top", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", -(position / 2)));
  2160. }
  2161. // W.vanish
  2162. if (GetBoolProp(rPr, W.vanish) && !GetBoolProp(rPr, W.specVanish))
  2163. style.AddIfMissing("display", "none");
  2164. // W.u
  2165. if (rPr.Element(W.u) != null && (string)rPr.Elements(W.u).Attributes(W.val).FirstOrDefault() != "none")
  2166. style.AddIfMissing("text-decoration", "underline");
  2167. // W.i
  2168. style.AddIfMissing("font-style", GetBoolProp(rPr, W.i) ? "italic" : "normal");
  2169. // W.b
  2170. style.AddIfMissing("font-weight", GetBoolProp(rPr, W.b) ? "bold" : "normal");
  2171. // W.strike
  2172. if (GetBoolProp(rPr, W.strike) || GetBoolProp(rPr, W.dstrike))
  2173. style.AddIfMissing("text-decoration", "line-through");
  2174. return style;
  2175. }
  2176. private static decimal? GetFontSize(XElement e)
  2177. {
  2178. var languageType = (string)e.Attribute(PtOpenXml.LanguageType);
  2179. if (e.Name == W.p)
  2180. {
  2181. return GetFontSize(languageType, e.Elements(W.pPr).Elements(W.rPr).FirstOrDefault());
  2182. }
  2183. if (e.Name == W.r)
  2184. {
  2185. return GetFontSize(languageType, e.Element(W.rPr));
  2186. }
  2187. return null;
  2188. }
  2189. private static decimal? GetFontSize(string languageType, XElement rPr)
  2190. {
  2191. if (rPr == null) return null;
  2192. return languageType == "bidi"
  2193. ? (decimal?)rPr.Elements(W.szCs).Attributes(W.val).FirstOrDefault()
  2194. : (decimal?)rPr.Elements(W.sz).Attributes(W.val).FirstOrDefault();
  2195. }
  2196. private static void DetermineRunMarks(XElement run, XElement rPr, Dictionary<string, string> style, out XEntity runStartMark, out XEntity runEndMark)
  2197. {
  2198. runStartMark = null;
  2199. runEndMark = null;
  2200. // Only do the following for text runs.
  2201. if (run.Element(W.t) == null) return;
  2202. // Can't add directional marks if the font-family is symbol - they are visible, and display as a ?
  2203. var addDirectionalMarks = true;
  2204. if (style.ContainsKey("font-family"))
  2205. {
  2206. if (style["font-family"].ToLower() == "symbol")
  2207. addDirectionalMarks = false;
  2208. }
  2209. if (!addDirectionalMarks) return;
  2210. var isRtl = rPr.Element(W.rtl) != null;
  2211. if (isRtl)
  2212. {
  2213. runStartMark = new XEntity("#x200f"); // RLM
  2214. runEndMark = new XEntity("#x200f"); // RLM
  2215. }
  2216. else
  2217. {
  2218. var paragraph = run.Ancestors(W.p).First();
  2219. var paraIsBidi = paragraph
  2220. .Elements(W.pPr)
  2221. .Elements(W.bidi)
  2222. .Any(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true);
  2223. if (paraIsBidi)
  2224. {
  2225. runStartMark = new XEntity("#x200e"); // LRM
  2226. runEndMark = new XEntity("#x200e"); // LRM
  2227. }
  2228. }
  2229. }
  2230. private static XAttribute GetLangAttribute(XElement run)
  2231. {
  2232. const string defaultLanguage = "en-US"; // todo need to get defaultLanguage
  2233. var rPr = run.Elements(W.rPr).First();
  2234. var languageType = (string)run.Attribute(PtOpenXml.LanguageType);
  2235. string lang = null;
  2236. if (languageType == "western")
  2237. lang = (string)rPr.Elements(W.lang).Attributes(W.val).FirstOrDefault();
  2238. else if (languageType == "bidi")
  2239. lang = (string)rPr.Elements(W.lang).Attributes(W.bidi).FirstOrDefault();
  2240. else if (languageType == "eastAsia")
  2241. lang = (string)rPr.Elements(W.lang).Attributes(W.eastAsia).FirstOrDefault();
  2242. if (lang == null)
  2243. lang = defaultLanguage;
  2244. return lang != defaultLanguage ? new XAttribute("lang", lang) : null;
  2245. }
  2246. private static void AdjustTableBorders(WordprocessingDocument wordDoc)
  2247. {
  2248. // Note: when implementing a paging version of the HTML transform, this needs to be done
  2249. // for all content parts, not just the main document part.
  2250. var xd = wordDoc.MainDocumentPart.GetXDocument();
  2251. foreach (var tbl in xd.Descendants(W.tbl))
  2252. AdjustTableBorders(tbl);
  2253. wordDoc.MainDocumentPart.PutXDocument();
  2254. }
  2255. private static void AdjustTableBorders(XElement tbl)
  2256. {
  2257. var ta = tbl
  2258. .Elements(W.tr)
  2259. .Select(r => r
  2260. .Elements(W.tc)
  2261. .SelectMany(c =>
  2262. Enumerable.Repeat(c,
  2263. (int?)c.Elements(W.tcPr).Elements(W.gridSpan).Attributes(W.val).FirstOrDefault() ?? 1))
  2264. .ToArray())
  2265. .ToArray();
  2266. for (var y = 0; y < ta.Length; y++)
  2267. {
  2268. for (var x = 0; x < ta[y].Length; x++)
  2269. {
  2270. var thisCell = ta[y][x];
  2271. FixTopBorder(ta, thisCell, x, y);
  2272. FixLeftBorder(ta, thisCell, x, y);
  2273. FixBottomBorder(ta, thisCell, x, y);
  2274. FixRightBorder(ta, thisCell, x, y);
  2275. }
  2276. }
  2277. }
  2278. private static void FixTopBorder(XElement[][] ta, XElement thisCell, int x, int y)
  2279. {
  2280. if (y > 0)
  2281. {
  2282. var rowAbove = ta[y - 1];
  2283. if (x < rowAbove.Length - 1)
  2284. {
  2285. XElement cellAbove = ta[y - 1][x];
  2286. if (cellAbove != null &&
  2287. thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
  2288. cellAbove.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
  2289. {
  2290. ResolveCellBorder(
  2291. thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.top).FirstOrDefault(),
  2292. cellAbove.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.bottom).FirstOrDefault());
  2293. }
  2294. }
  2295. }
  2296. }
  2297. private static void FixLeftBorder(XElement[][] ta, XElement thisCell, int x, int y)
  2298. {
  2299. if (x > 0)
  2300. {
  2301. XElement cellLeft = ta[y][x - 1];
  2302. if (cellLeft != null &&
  2303. thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
  2304. cellLeft.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
  2305. {
  2306. ResolveCellBorder(
  2307. thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.left).FirstOrDefault(),
  2308. cellLeft.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.right).FirstOrDefault());
  2309. }
  2310. }
  2311. }
  2312. private static void FixBottomBorder(XElement[][] ta, XElement thisCell, int x, int y)
  2313. {
  2314. if (y < ta.Length - 1)
  2315. {
  2316. var rowBelow = ta[y + 1];
  2317. if (x < rowBelow.Length - 1)
  2318. {
  2319. XElement cellBelow = ta[y + 1][x];
  2320. if (cellBelow != null &&
  2321. thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
  2322. cellBelow.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
  2323. {
  2324. ResolveCellBorder(
  2325. thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.bottom).FirstOrDefault(),
  2326. cellBelow.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.top).FirstOrDefault());
  2327. }
  2328. }
  2329. }
  2330. }
  2331. private static void FixRightBorder(XElement[][] ta, XElement thisCell, int x, int y)
  2332. {
  2333. if (x < ta[y].Length - 1)
  2334. {
  2335. XElement cellRight = ta[y][x + 1];
  2336. if (cellRight != null &&
  2337. thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
  2338. cellRight.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
  2339. {
  2340. ResolveCellBorder(
  2341. thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.right).FirstOrDefault(),
  2342. cellRight.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.left).FirstOrDefault());
  2343. }
  2344. }
  2345. }
  2346. private static readonly Dictionary<string, int> BorderTypePriority = new Dictionary<string, int>()
  2347. {
  2348. { "single", 1 },
  2349. { "thick", 2 },
  2350. { "double", 3 },
  2351. { "dotted", 4 },
  2352. };
  2353. private static readonly Dictionary<string, int> BorderNumber = new Dictionary<string, int>()
  2354. {
  2355. {"single", 1 },
  2356. {"thick", 2 },
  2357. {"double", 3 },
  2358. {"dotted", 4 },
  2359. {"dashed", 5 },
  2360. {"dotDash", 5 },
  2361. {"dotDotDash", 5 },
  2362. {"triple", 6 },
  2363. {"thinThickSmallGap", 6 },
  2364. {"thickThinSmallGap", 6 },
  2365. {"thinThickThinSmallGap", 6 },
  2366. {"thinThickMediumGap", 6 },
  2367. {"thickThinMediumGap", 6 },
  2368. {"thinThickThinMediumGap", 6 },
  2369. {"thinThickLargeGap", 6 },
  2370. {"thickThinLargeGap", 6 },
  2371. {"thinThickThinLargeGap", 6 },
  2372. {"wave", 7 },
  2373. {"doubleWave", 7 },
  2374. {"dashSmallGap", 5 },
  2375. {"dashDotStroked", 5 },
  2376. {"threeDEmboss", 7 },
  2377. {"threeDEngrave", 7 },
  2378. {"outset", 7 },
  2379. {"inset", 7 },
  2380. };
  2381. private static void ResolveCellBorder(XElement border1, XElement border2)
  2382. {
  2383. if (border1 == null || border2 == null)
  2384. return;
  2385. if ((string)border1.Attribute(W.val) == "nil" || (string)border2.Attribute(W.val) == "nil")
  2386. return;
  2387. if ((string)border1.Attribute(W.sz) == "nil" || (string)border2.Attribute(W.sz) == "nil")
  2388. return;
  2389. var border1Val = (string)border1.Attribute(W.val);
  2390. var border1Weight = 1;
  2391. if (BorderNumber.ContainsKey(border1Val))
  2392. border1Weight = BorderNumber[border1Val];
  2393. var border2Val = (string)border2.Attribute(W.val);
  2394. var border2Weight = 1;
  2395. if (BorderNumber.ContainsKey(border2Val))
  2396. border2Weight = BorderNumber[border2Val];
  2397. if (border1Weight != border2Weight)
  2398. {
  2399. if (border1Weight < border2Weight)
  2400. BorderOverride(border2, border1);
  2401. else
  2402. BorderOverride(border1, border2);
  2403. }
  2404. if ((decimal)border1.Attribute(W.sz) > (decimal)border2.Attribute(W.sz))
  2405. {
  2406. BorderOverride(border1, border2);
  2407. return;
  2408. }
  2409. if ((decimal)border1.Attribute(W.sz) < (decimal)border2.Attribute(W.sz))
  2410. {
  2411. BorderOverride(border2, border1);
  2412. return;
  2413. }
  2414. var border1Type = (string)border1.Attribute(W.val);
  2415. var border2Type = (string)border2.Attribute(W.val);
  2416. if (BorderTypePriority.ContainsKey(border1Type) &&
  2417. BorderTypePriority.ContainsKey(border2Type))
  2418. {
  2419. var border1Pri = BorderTypePriority[border1Type];
  2420. var border2Pri = BorderTypePriority[border2Type];
  2421. if (border1Pri < border2Pri)
  2422. {
  2423. BorderOverride(border2, border1);
  2424. return;
  2425. }
  2426. if (border2Pri < border1Pri)
  2427. {
  2428. BorderOverride(border1, border2);
  2429. return;
  2430. }
  2431. }
  2432. var color1Str = (string)border1.Attribute(W.color);
  2433. if (color1Str == "auto")
  2434. color1Str = "000000";
  2435. var color2Str = (string)border2.Attribute(W.color);
  2436. if (color2Str == "auto")
  2437. color2Str = "000000";
  2438. if (color1Str != null && color2Str != null && color1Str != color2Str)
  2439. {
  2440. try
  2441. {
  2442. var color1 = Convert.ToInt32(color1Str, 16);
  2443. var color2 = Convert.ToInt32(color2Str, 16);
  2444. if (color1 < color2)
  2445. {
  2446. BorderOverride(border1, border2);
  2447. return;
  2448. }
  2449. if (color2 < color1)
  2450. {
  2451. BorderOverride(border2, border1);
  2452. }
  2453. }
  2454. // if the above throws ArgumentException, FormatException, or OverflowException, then abort
  2455. catch (Exception)
  2456. {
  2457. // Ignore
  2458. }
  2459. }
  2460. }
  2461. private static void BorderOverride(XElement fromBorder, XElement toBorder)
  2462. {
  2463. toBorder.Attribute(W.val).Value = fromBorder.Attribute(W.val).Value;
  2464. if (fromBorder.Attribute(W.color) != null)
  2465. toBorder.SetAttributeValue(W.color, fromBorder.Attribute(W.color).Value);
  2466. if (fromBorder.Attribute(W.sz) != null)
  2467. toBorder.SetAttributeValue(W.sz, fromBorder.Attribute(W.sz).Value);
  2468. if (fromBorder.Attribute(W.themeColor) != null)
  2469. toBorder.SetAttributeValue(W.themeColor, fromBorder.Attribute(W.themeColor).Value);
  2470. if (fromBorder.Attribute(W.themeTint) != null)
  2471. toBorder.SetAttributeValue(W.themeTint, fromBorder.Attribute(W.themeTint).Value);
  2472. }
  2473. private static void CalculateSpanWidthForTabs(WordprocessingDocument wordDoc)
  2474. {
  2475. // Note: when implementing a paging version of the HTML transform, this needs to be done
  2476. // for all content parts, not just the main document part.
  2477. // w:defaultTabStop in settings
  2478. var sxd = wordDoc.MainDocumentPart.DocumentSettingsPart.GetXDocument();
  2479. var defaultTabStopValue = (string)sxd.Descendants(W.defaultTabStop).Attributes(W.val).FirstOrDefault();
  2480. var defaultTabStop = defaultTabStopValue != null ? WordprocessingMLUtil.StringToTwips(defaultTabStopValue) : 720;
  2481. var pxd = wordDoc.MainDocumentPart.GetXDocument();
  2482. var root = pxd.Root;
  2483. if (root == null) return;
  2484. var newRoot = (XElement)CalculateSpanWidthTransform(root, defaultTabStop);
  2485. root.ReplaceWith(newRoot);
  2486. wordDoc.MainDocumentPart.PutXDocument();
  2487. }
  2488. // TODO: Refactor. This method is way too long.
  2489. [SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
  2490. private static object CalculateSpanWidthTransform(XNode node, int defaultTabStop)
  2491. {
  2492. var element = node as XElement;
  2493. if (element == null) return node;
  2494. // if it is not a paragraph or if there are no tabs in the paragraph,
  2495. // then no need to continue processing.
  2496. if (element.Name != W.p ||
  2497. !element.DescendantsTrimmed(W.txbxContent).Where(d => d.Name == W.r).Elements(W.tab).Any())
  2498. {
  2499. // TODO: Revisit. Can we just return the node if it is a paragraph that does not have any tab?
  2500. return new XElement(element.Name,
  2501. element.Attributes(),
  2502. element.Nodes().Select(n => CalculateSpanWidthTransform(n, defaultTabStop)));
  2503. }
  2504. var clonedPara = new XElement(element);
  2505. var leftInTwips = 0;
  2506. var firstInTwips = 0;
  2507. var ind = clonedPara.Elements(W.pPr).Elements(W.ind).FirstOrDefault();
  2508. if (ind != null)
  2509. {
  2510. // todo need to handle start and end attributes
  2511. var left = WordprocessingMLUtil.AttributeToTwips(ind.Attribute(W.left));
  2512. if (left != null)
  2513. leftInTwips = (int)left;
  2514. var firstLine = 0;
  2515. var firstLineAtt = WordprocessingMLUtil.AttributeToTwips(ind.Attribute(W.firstLine));
  2516. if (firstLineAtt != null)
  2517. firstLine = (int)firstLineAtt;
  2518. var hangingAtt = WordprocessingMLUtil.AttributeToTwips(ind.Attribute(W.hanging));
  2519. if (hangingAtt != null)
  2520. firstLine = -(int)hangingAtt;
  2521. firstInTwips = leftInTwips + firstLine;
  2522. }
  2523. // calculate the tab stops, in twips
  2524. var tabs = clonedPara
  2525. .Elements(W.pPr)
  2526. .Elements(W.tabs)
  2527. .FirstOrDefault();
  2528. if (tabs == null)
  2529. {
  2530. if (leftInTwips == 0)
  2531. {
  2532. tabs = new XElement(W.tabs,
  2533. Enumerable.Range(1, 100)
  2534. .Select(r => new XElement(W.tab,
  2535. new XAttribute(W.val, "left"),
  2536. new XAttribute(W.pos, r * defaultTabStop))));
  2537. }
  2538. else
  2539. {
  2540. tabs = new XElement(W.tabs,
  2541. new XElement(W.tab,
  2542. new XAttribute(W.val, "left"),
  2543. new XAttribute(W.pos, leftInTwips)));
  2544. tabs = AddDefaultTabsAfterLastTab(tabs, defaultTabStop);
  2545. }
  2546. }
  2547. else
  2548. {
  2549. if (leftInTwips != 0)
  2550. {
  2551. tabs.Add(
  2552. new XElement(W.tab,
  2553. new XAttribute(W.val, "left"),
  2554. new XAttribute(W.pos, leftInTwips)));
  2555. }
  2556. tabs = AddDefaultTabsAfterLastTab(tabs, defaultTabStop);
  2557. }
  2558. var twipCounter = firstInTwips;
  2559. var contentToMeasure = element.DescendantsTrimmed(z => z.Name == W.txbxContent || z.Name == W.pPr || z.Name == W.rPr).ToArray();
  2560. var currentElementIdx = 0;
  2561. while (true)
  2562. {
  2563. var currentElement = contentToMeasure[currentElementIdx];
  2564. if (currentElement.Name == W.br)
  2565. {
  2566. twipCounter = leftInTwips;
  2567. currentElement.Add(new XAttribute(PtOpenXml.TabWidth,
  2568. string.Format(NumberFormatInfo.InvariantInfo,
  2569. "{0:0.000}", (decimal)firstInTwips / 1440m)));
  2570. currentElementIdx++;
  2571. if (currentElementIdx >= contentToMeasure.Length)
  2572. break; // we're done
  2573. }
  2574. if (currentElement.Name == W.tab)
  2575. {
  2576. var runContainingTabToReplace = currentElement.Parent;
  2577. var fontNameAtt = runContainingTabToReplace.Attribute(PtOpenXml.pt + "FontName") ??
  2578. runContainingTabToReplace.Ancestors(W.p).First().Attribute(PtOpenXml.pt + "FontName");
  2579. var testAmount = twipCounter;
  2580. var tabAfterText = tabs
  2581. .Elements(W.tab)
  2582. .FirstOrDefault(t => WordprocessingMLUtil.StringToTwips((string)t.Attribute(W.pos)) > testAmount);
  2583. if (tabAfterText == null)
  2584. {
  2585. // something has gone wrong, so put 1/2 inch in
  2586. if (currentElement.Attribute(PtOpenXml.TabWidth) == null)
  2587. currentElement.Add(
  2588. new XAttribute(PtOpenXml.TabWidth, 720m));
  2589. break;
  2590. }
  2591. var tabVal = (string)tabAfterText.Attribute(W.val);
  2592. if (tabVal == "right" || tabVal == "end")
  2593. {
  2594. var textAfterElements = contentToMeasure
  2595. .Skip(currentElementIdx + 1);
  2596. // take all the content until another tab, br, or cr
  2597. var textElementsToMeasure = textAfterElements
  2598. .TakeWhile(z =>
  2599. z.Name != W.tab &&
  2600. z.Name != W.br &&
  2601. z.Name != W.cr)
  2602. .ToList();
  2603. var textAfterTab = textElementsToMeasure
  2604. .Where(z => z.Name == W.t)
  2605. .Select(t => (string)t)
  2606. .StringConcatenate();
  2607. var dummyRun2 = new XElement(W.r,
  2608. fontNameAtt,
  2609. runContainingTabToReplace.Elements(W.rPr),
  2610. new XElement(W.t, textAfterTab));
  2611. var widthOfTextAfterTab = CalcWidthOfRunInTwips(dummyRun2);
  2612. var delta2 = WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)) - widthOfTextAfterTab - twipCounter;
  2613. if (delta2 < 0)
  2614. delta2 = 0;
  2615. currentElement.Add(
  2616. new XAttribute(PtOpenXml.TabWidth,
  2617. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
  2618. GetLeader(tabAfterText));
  2619. twipCounter = Math.Max(WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)), twipCounter + widthOfTextAfterTab);
  2620. var lastElement = textElementsToMeasure.LastOrDefault();
  2621. if (lastElement == null)
  2622. break; // we're done
  2623. currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
  2624. if (currentElementIdx >= contentToMeasure.Length)
  2625. break; // we're done
  2626. continue;
  2627. }
  2628. if (tabVal == "decimal")
  2629. {
  2630. var textAfterElements = contentToMeasure
  2631. .Skip(currentElementIdx + 1);
  2632. // take all the content until another tab, br, or cr
  2633. var textElementsToMeasure = textAfterElements
  2634. .TakeWhile(z =>
  2635. z.Name != W.tab &&
  2636. z.Name != W.br &&
  2637. z.Name != W.cr)
  2638. .ToList();
  2639. var textAfterTab = textElementsToMeasure
  2640. .Where(z => z.Name == W.t)
  2641. .Select(t => (string)t)
  2642. .StringConcatenate();
  2643. if (textAfterTab.Contains("."))
  2644. {
  2645. var mantissa = textAfterTab.Split('.')[0];
  2646. var dummyRun4 = new XElement(W.r,
  2647. fontNameAtt,
  2648. runContainingTabToReplace.Elements(W.rPr),
  2649. new XElement(W.t, mantissa));
  2650. var widthOfMantissa = CalcWidthOfRunInTwips(dummyRun4);
  2651. var delta2 = WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)) - widthOfMantissa - twipCounter;
  2652. if (delta2 < 0)
  2653. delta2 = 0;
  2654. currentElement.Add(
  2655. new XAttribute(PtOpenXml.TabWidth,
  2656. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
  2657. GetLeader(tabAfterText));
  2658. var decims = textAfterTab.Substring(textAfterTab.IndexOf('.'));
  2659. dummyRun4 = new XElement(W.r,
  2660. fontNameAtt,
  2661. runContainingTabToReplace.Elements(W.rPr),
  2662. new XElement(W.t, decims));
  2663. var widthOfDecims = CalcWidthOfRunInTwips(dummyRun4);
  2664. twipCounter = Math.Max(WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)) + widthOfDecims, twipCounter + widthOfMantissa + widthOfDecims);
  2665. var lastElement = textElementsToMeasure.LastOrDefault();
  2666. if (lastElement == null)
  2667. break; // we're done
  2668. currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
  2669. if (currentElementIdx >= contentToMeasure.Length)
  2670. break; // we're done
  2671. continue;
  2672. }
  2673. else
  2674. {
  2675. var dummyRun2 = new XElement(W.r,
  2676. fontNameAtt,
  2677. runContainingTabToReplace.Elements(W.rPr),
  2678. new XElement(W.t, textAfterTab));
  2679. var widthOfTextAfterTab = CalcWidthOfRunInTwips(dummyRun2);
  2680. var delta2 = WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)) - widthOfTextAfterTab - twipCounter;
  2681. if (delta2 < 0)
  2682. delta2 = 0;
  2683. currentElement.Add(
  2684. new XAttribute(PtOpenXml.TabWidth,
  2685. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
  2686. GetLeader(tabAfterText));
  2687. twipCounter = Math.Max(WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)), twipCounter + widthOfTextAfterTab);
  2688. var lastElement = textElementsToMeasure.LastOrDefault();
  2689. if (lastElement == null)
  2690. break; // we're done
  2691. currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
  2692. if (currentElementIdx >= contentToMeasure.Length)
  2693. break; // we're done
  2694. continue;
  2695. }
  2696. }
  2697. if ((string)tabAfterText.Attribute(W.val) == "center")
  2698. {
  2699. var textAfterElements = contentToMeasure
  2700. .Skip(currentElementIdx + 1);
  2701. // take all the content until another tab, br, or cr
  2702. var textElementsToMeasure = textAfterElements
  2703. .TakeWhile(z =>
  2704. z.Name != W.tab &&
  2705. z.Name != W.br &&
  2706. z.Name != W.cr)
  2707. .ToList();
  2708. var textAfterTab = textElementsToMeasure
  2709. .Where(z => z.Name == W.t)
  2710. .Select(t => (string)t)
  2711. .StringConcatenate();
  2712. var dummyRun4 = new XElement(W.r,
  2713. fontNameAtt,
  2714. runContainingTabToReplace.Elements(W.rPr),
  2715. new XElement(W.t, textAfterTab));
  2716. var widthOfText = CalcWidthOfRunInTwips(dummyRun4);
  2717. var delta2 = WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)) - (widthOfText / 2) - twipCounter;
  2718. if (delta2 < 0)
  2719. delta2 = 0;
  2720. currentElement.Add(
  2721. new XAttribute(PtOpenXml.TabWidth,
  2722. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
  2723. GetLeader(tabAfterText));
  2724. twipCounter = Math.Max(WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)) + widthOfText / 2, twipCounter + widthOfText);
  2725. var lastElement = textElementsToMeasure.LastOrDefault();
  2726. if (lastElement == null)
  2727. break; // we're done
  2728. currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
  2729. if (currentElementIdx >= contentToMeasure.Length)
  2730. break; // we're done
  2731. continue;
  2732. }
  2733. if (tabVal == "left" || tabVal == "start" || tabVal == "num")
  2734. {
  2735. var delta = WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos)) - twipCounter;
  2736. currentElement.Add(
  2737. new XAttribute(PtOpenXml.TabWidth,
  2738. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta / 1440m)),
  2739. GetLeader(tabAfterText));
  2740. twipCounter = WordprocessingMLUtil.StringToTwips((string)tabAfterText.Attribute(W.pos));
  2741. currentElementIdx++;
  2742. if (currentElementIdx >= contentToMeasure.Length)
  2743. break; // we're done
  2744. continue;
  2745. }
  2746. }
  2747. if (currentElement.Name == W.t)
  2748. {
  2749. // TODO: Revisit. This is a quick fix because it doesn't work on Azure.
  2750. // Given the changes we've made elsewhere, though, this is not required
  2751. // for the first tab at least. We could also enhance that other change
  2752. // to deal with all tabs.
  2753. //var runContainingTabToReplace = currentElement.Parent;
  2754. //var paragraphForRun = runContainingTabToReplace.Ancestors(W.p).First();
  2755. //var fontNameAtt = runContainingTabToReplace.Attribute(PtOpenXml.FontName) ??
  2756. // paragraphForRun.Attribute(PtOpenXml.FontName);
  2757. //var languageTypeAtt = runContainingTabToReplace.Attribute(PtOpenXml.LanguageType) ??
  2758. // paragraphForRun.Attribute(PtOpenXml.LanguageType);
  2759. //var dummyRun3 = new XElement(W.r, fontNameAtt, languageTypeAtt,
  2760. // runContainingTabToReplace.Elements(W.rPr),
  2761. // currentElement);
  2762. //var widthOfText = CalcWidthOfRunInTwips(dummyRun3);
  2763. const int widthOfText = 0;
  2764. currentElement.Add(new XAttribute(PtOpenXml.TabWidth,
  2765. string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)widthOfText / 1440m)));
  2766. twipCounter += widthOfText;
  2767. currentElementIdx++;
  2768. if (currentElementIdx >= contentToMeasure.Length)
  2769. break; // we're done
  2770. continue;
  2771. }
  2772. currentElementIdx++;
  2773. if (currentElementIdx >= contentToMeasure.Length)
  2774. break; // we're done
  2775. }
  2776. return new XElement(element.Name,
  2777. element.Attributes(),
  2778. element.Nodes().Select(n => CalculateSpanWidthTransform(n, defaultTabStop)));
  2779. }
  2780. private static XAttribute GetLeader(XElement tabAfterText)
  2781. {
  2782. var leader = (string)tabAfterText.Attribute(W.leader);
  2783. if (leader == null)
  2784. return null;
  2785. return new XAttribute(PtOpenXml.Leader, leader);
  2786. }
  2787. private static XElement AddDefaultTabsAfterLastTab(XElement tabs, int defaultTabStop)
  2788. {
  2789. var lastTabElement = tabs
  2790. .Elements(W.tab)
  2791. .Where(t => (string)t.Attribute(W.val) != "clear" && (string)t.Attribute(W.val) != "bar")
  2792. .OrderBy(t => WordprocessingMLUtil.StringToTwips((string)t.Attribute(W.pos)))
  2793. .LastOrDefault();
  2794. if (lastTabElement != null)
  2795. {
  2796. if (defaultTabStop == 0)
  2797. defaultTabStop = 720;
  2798. var rangeStart = WordprocessingMLUtil.StringToTwips((string)lastTabElement.Attribute(W.pos)) / defaultTabStop + 1;
  2799. var tempTabs = new XElement(W.tabs,
  2800. tabs.Elements().Where(t => (string)t.Attribute(W.val) != "clear" && (string)t.Attribute(W.val) != "bar"),
  2801. Enumerable.Range(rangeStart, 100)
  2802. .Select(r => new XElement(W.tab,
  2803. new XAttribute(W.val, "left"),
  2804. new XAttribute(W.pos, r * defaultTabStop))));
  2805. tempTabs = new XElement(W.tabs,
  2806. tempTabs.Elements().OrderBy(t => WordprocessingMLUtil.StringToTwips((string)t.Attribute(W.pos))));
  2807. return tempTabs;
  2808. }
  2809. else
  2810. {
  2811. tabs = new XElement(W.tabs,
  2812. Enumerable.Range(1, 100)
  2813. .Select(r => new XElement(W.tab,
  2814. new XAttribute(W.val, "left"),
  2815. new XAttribute(W.pos, r * defaultTabStop))));
  2816. }
  2817. return tabs;
  2818. }
  2819. private static readonly HashSet<string> UnknownFonts = new HashSet<string>();
  2820. private static HashSet<string> _knownFamilies;
  2821. private static HashSet<string> KnownFamilies
  2822. {
  2823. get
  2824. {
  2825. if (_knownFamilies == null)
  2826. {
  2827. _knownFamilies = new HashSet<string>();
  2828. var families = FontFamily.Families;
  2829. foreach (var fam in families)
  2830. _knownFamilies.Add(fam.Name);
  2831. }
  2832. return _knownFamilies;
  2833. }
  2834. }
  2835. private static int CalcWidthOfRunInTwips(XElement r)
  2836. {
  2837. var fontName = (string)r.Attribute(PtOpenXml.pt + "FontName") ??
  2838. (string)r.Ancestors(W.p).First().Attribute(PtOpenXml.pt + "FontName");
  2839. if (fontName == null)
  2840. throw new OpenXmlPowerToolsException("Internal Error, should have FontName attribute");
  2841. if (UnknownFonts.Contains(fontName))
  2842. return 0;
  2843. var rPr = r.Element(W.rPr);
  2844. if (rPr == null)
  2845. throw new OpenXmlPowerToolsException("Internal Error, should have run properties");
  2846. var sz = GetFontSize(r) ?? 22m;
  2847. // unknown font families will throw ArgumentException, in which case just return 0
  2848. if (!KnownFamilies.Contains(fontName))
  2849. return 0;
  2850. // in theory, all unknown fonts are found by the above test, but if not...
  2851. FontFamily ff;
  2852. try
  2853. {
  2854. ff = new FontFamily(fontName);
  2855. }
  2856. catch (ArgumentException)
  2857. {
  2858. UnknownFonts.Add(fontName);
  2859. return 0;
  2860. }
  2861. var fs = FontStyle.Regular;
  2862. if (GetBoolProp(rPr, W.b) || GetBoolProp(rPr, W.bCs))
  2863. fs |= FontStyle.Bold;
  2864. if (GetBoolProp(rPr, W.i) || GetBoolProp(rPr, W.iCs))
  2865. fs |= FontStyle.Italic;
  2866. // Appended blank as a quick fix to accommodate &nbsp; that will get
  2867. // appended to some layout-critical runs such as list item numbers.
  2868. // In some cases, this might not be required or even wrong, so this
  2869. // must be revisited.
  2870. // TODO: Revisit.
  2871. var runText = r.DescendantsTrimmed(W.txbxContent)
  2872. .Where(e => e.Name == W.t)
  2873. .Select(t => (string)t)
  2874. .StringConcatenate() + " ";
  2875. var tabLength = r.DescendantsTrimmed(W.txbxContent)
  2876. .Where(e => e.Name == W.tab)
  2877. .Select(t => (decimal)t.Attribute(PtOpenXml.TabWidth))
  2878. .Sum();
  2879. if (runText.Length == 0 && tabLength == 0)
  2880. return 0;
  2881. int multiplier = 1;
  2882. if (runText.Length <= 2)
  2883. multiplier = 100;
  2884. else if (runText.Length <= 4)
  2885. multiplier = 50;
  2886. else if (runText.Length <= 8)
  2887. multiplier = 25;
  2888. else if (runText.Length <= 16)
  2889. multiplier = 12;
  2890. else if (runText.Length <= 32)
  2891. multiplier = 6;
  2892. if (multiplier != 1)
  2893. {
  2894. StringBuilder sb = new StringBuilder();
  2895. for (int i = 0; i < multiplier; i++)
  2896. sb.Append(runText);
  2897. runText = sb.ToString();
  2898. }
  2899. var w = MetricsGetter.GetTextWidth(ff, fs, sz, runText);
  2900. return (int)(w / 96m * 1440m / multiplier + tabLength * 1440m);
  2901. }
  2902. private static void InsertAppropriateNonbreakingSpaces(WordprocessingDocument wordDoc)
  2903. {
  2904. foreach (var part in wordDoc.ContentParts())
  2905. {
  2906. var pxd = part.GetXDocument();
  2907. var root = pxd.Root;
  2908. if (root == null) return;
  2909. var newRoot = (XElement)InsertAppropriateNonbreakingSpacesTransform(root);
  2910. root.ReplaceWith(newRoot);
  2911. part.PutXDocument();
  2912. }
  2913. }
  2914. // Non-breaking spaces are not required if we use appropriate CSS, i.e., "white-space: pre-wrap;".
  2915. // We only need to make sure that empty w:p elements are translated into non-empty h:p elements,
  2916. // because empty h:p elements would be ignored by browsers.
  2917. // Further, in addition to not being required, non-breaking spaces would change the layout behavior
  2918. // of spans having consecutive spaces. Therefore, avoiding non-breaking spaces has the additional
  2919. // benefit of leading to a more faithful representation of the Word document in HTML.
  2920. private static object InsertAppropriateNonbreakingSpacesTransform(XNode node)
  2921. {
  2922. XElement element = node as XElement;
  2923. if (element != null)
  2924. {
  2925. // child content of run to look for
  2926. // W.br
  2927. // W.cr
  2928. // W.dayLong
  2929. // W.dayShort
  2930. // W.drawing
  2931. // W.monthLong
  2932. // W.monthShort
  2933. // W.noBreakHyphen
  2934. // W.object
  2935. // W.pgNum
  2936. // W.pTab
  2937. // W.separator
  2938. // W.softHyphen
  2939. // W.sym
  2940. // W.t
  2941. // W.tab
  2942. // W.yearLong
  2943. // W.yearShort
  2944. if (element.Name == W.p)
  2945. {
  2946. // Translate empty paragraphs to paragraphs having one run with
  2947. // a normal space. A non-breaking space, i.e., \x00A0, is not
  2948. // required if we use appropriate CSS.
  2949. bool hasContent = element
  2950. .Elements()
  2951. .Where(e => e.Name != W.pPr)
  2952. .DescendantsAndSelf()
  2953. .Any(e =>
  2954. e.Name == W.dayLong ||
  2955. e.Name == W.dayShort ||
  2956. e.Name == W.drawing ||
  2957. e.Name == W.monthLong ||
  2958. e.Name == W.monthShort ||
  2959. e.Name == W.noBreakHyphen ||
  2960. e.Name == W._object ||
  2961. e.Name == W.pgNum ||
  2962. e.Name == W.ptab ||
  2963. e.Name == W.separator ||
  2964. e.Name == W.softHyphen ||
  2965. e.Name == W.sym ||
  2966. e.Name == W.t ||
  2967. e.Name == W.tab ||
  2968. e.Name == W.yearLong ||
  2969. e.Name == W.yearShort
  2970. );
  2971. if (hasContent == false)
  2972. return new XElement(element.Name,
  2973. element.Attributes(),
  2974. element.Nodes().Select(n => InsertAppropriateNonbreakingSpacesTransform(n)),
  2975. new XElement(W.r,
  2976. element.Elements(W.pPr).Elements(W.rPr),
  2977. new XElement(W.t, " ")));
  2978. }
  2979. return new XElement(element.Name,
  2980. element.Attributes(),
  2981. element.Nodes().Select(n => InsertAppropriateNonbreakingSpacesTransform(n)));
  2982. }
  2983. return node;
  2984. }
  2985. private class SectionAnnotation
  2986. {
  2987. public XElement SectionElement;
  2988. }
  2989. private static void AnnotateForSections(WordprocessingDocument wordDoc)
  2990. {
  2991. var xd = wordDoc.MainDocumentPart.GetXDocument();
  2992. var document = xd.Root;
  2993. if (document == null) return;
  2994. var body = document.Element(W.body);
  2995. if (body == null) return;
  2996. // move last sectPr into last paragraph
  2997. var lastSectPr = body.Elements(W.sectPr).LastOrDefault();
  2998. if (lastSectPr != null)
  2999. {
  3000. // if the last thing in the document is a table, Word will always insert a paragraph following that.
  3001. var lastPara = body
  3002. .DescendantsTrimmed(W.txbxContent)
  3003. .LastOrDefault(p => p.Name == W.p);
  3004. if (lastPara != null)
  3005. {
  3006. var lastParaProps = lastPara.Element(W.pPr);
  3007. if (lastParaProps != null)
  3008. lastParaProps.Add(lastSectPr);
  3009. else
  3010. lastPara.Add(new XElement(W.pPr, lastSectPr));
  3011. lastSectPr.Remove();
  3012. }
  3013. }
  3014. var reverseDescendants = xd.Descendants().Reverse().ToList();
  3015. var currentSection = InitializeSectionAnnotation(reverseDescendants);
  3016. foreach (var d in reverseDescendants)
  3017. {
  3018. if (d.Name == W.sectPr)
  3019. {
  3020. if (d.Attribute(XNamespace.Xmlns + "w") == null)
  3021. d.Add(new XAttribute(XNamespace.Xmlns + "w", W.w));
  3022. currentSection = new SectionAnnotation()
  3023. {
  3024. SectionElement = d
  3025. };
  3026. }
  3027. else
  3028. d.AddAnnotation(currentSection);
  3029. }
  3030. }
  3031. private static SectionAnnotation InitializeSectionAnnotation(IEnumerable<XElement> reverseDescendants)
  3032. {
  3033. var currentSection = new SectionAnnotation()
  3034. {
  3035. SectionElement = reverseDescendants.FirstOrDefault(e => e.Name == W.sectPr)
  3036. };
  3037. if (currentSection.SectionElement != null &&
  3038. currentSection.SectionElement.Attribute(XNamespace.Xmlns + "w") == null)
  3039. currentSection.SectionElement.Add(new XAttribute(XNamespace.Xmlns + "w", W.w));
  3040. // todo what should the default section props be?
  3041. if (currentSection.SectionElement == null)
  3042. currentSection = new SectionAnnotation()
  3043. {
  3044. SectionElement = new XElement(W.sectPr,
  3045. new XAttribute(XNamespace.Xmlns + "w", W.w),
  3046. new XElement(W.pgSz,
  3047. new XAttribute(W._w, 12240),
  3048. new XAttribute(W.h, 15840)),
  3049. new XElement(W.pgMar,
  3050. new XAttribute(W.top, 1440),
  3051. new XAttribute(W.right, 1440),
  3052. new XAttribute(W.bottom, 1440),
  3053. new XAttribute(W.left, 1440),
  3054. new XAttribute(W.header, 720),
  3055. new XAttribute(W.footer, 720),
  3056. new XAttribute(W.gutter, 0)),
  3057. new XElement(W.cols,
  3058. new XAttribute(W.space, 720)),
  3059. new XElement(W.docGrid,
  3060. new XAttribute(W.linePitch, 360)))
  3061. };
  3062. return currentSection;
  3063. }
  3064. private static object CreateBorderDivs(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, IEnumerable<XElement> elements)
  3065. {
  3066. return elements.GroupAdjacent(e =>
  3067. {
  3068. var pBdr = e.Elements(W.pPr).Elements(W.pBdr).FirstOrDefault();
  3069. if (pBdr != null)
  3070. {
  3071. var indStr = string.Empty;
  3072. var ind = e.Elements(W.pPr).Elements(W.ind).FirstOrDefault();
  3073. if (ind != null)
  3074. indStr = ind.ToString(SaveOptions.DisableFormatting);
  3075. return pBdr.ToString(SaveOptions.DisableFormatting) + indStr;
  3076. }
  3077. return e.Name == W.tbl ? "table" : string.Empty;
  3078. })
  3079. .Select(g =>
  3080. {
  3081. if (g.Key == string.Empty)
  3082. {
  3083. return (object)GroupAndVerticallySpaceNumberedParagraphs(wordDoc, settings, g, 0m);
  3084. }
  3085. if (g.Key == "table")
  3086. {
  3087. return g.Select(gc => ConvertToHtmlTransform(wordDoc, settings, gc, false, 0));
  3088. }
  3089. var pPr = g.First().Elements(W.pPr).First();
  3090. var pBdr = pPr.Element(W.pBdr);
  3091. var style = new Dictionary<string, string>();
  3092. GenerateBorderStyle(pBdr, W.top, style, BorderType.Paragraph);
  3093. GenerateBorderStyle(pBdr, W.right, style, BorderType.Paragraph);
  3094. GenerateBorderStyle(pBdr, W.bottom, style, BorderType.Paragraph);
  3095. GenerateBorderStyle(pBdr, W.left, style, BorderType.Paragraph);
  3096. var currentMarginLeft = 0m;
  3097. var ind = pPr.Element(W.ind);
  3098. if (ind != null)
  3099. {
  3100. var leftInInches = (decimal?)ind.Attribute(W.left) / 1440m ?? 0;
  3101. var hangingInInches = -(decimal?)ind.Attribute(W.hanging) / 1440m ?? 0;
  3102. currentMarginLeft = leftInInches + hangingInInches;
  3103. style.AddIfMissing("margin-left",
  3104. currentMarginLeft > 0m
  3105. ? string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", currentMarginLeft)
  3106. : "0");
  3107. }
  3108. var div = new XElement(Xhtml.div,
  3109. GroupAndVerticallySpaceNumberedParagraphs(wordDoc, settings, g, currentMarginLeft));
  3110. div.AddAnnotation(style);
  3111. return div;
  3112. })
  3113. .ToList();
  3114. }
  3115. private static IEnumerable<object> GroupAndVerticallySpaceNumberedParagraphs(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
  3116. IEnumerable<XElement> elements, decimal currentMarginLeft)
  3117. {
  3118. var grouped = elements
  3119. .GroupAdjacent(e =>
  3120. {
  3121. var abstractNumId = (string)e.Attribute(PtOpenXml.pt + "AbstractNumId");
  3122. if (abstractNumId != null)
  3123. return "num:" + abstractNumId;
  3124. var contextualSpacing = e.Elements(W.pPr).Elements(W.contextualSpacing).FirstOrDefault();
  3125. if (contextualSpacing != null)
  3126. {
  3127. var styleName = (string)e.Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
  3128. if (styleName == null)
  3129. return "";
  3130. return "sty:" + styleName;
  3131. }
  3132. return "";
  3133. })
  3134. .ToList();
  3135. var newContent = grouped
  3136. .Select(g =>
  3137. {
  3138. if (g.Key == "")
  3139. return g.Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft));
  3140. var last = g.Count() - 1;
  3141. return g.Select((e, i) => ConvertToHtmlTransform(wordDoc, settings, e, i != last, currentMarginLeft));
  3142. });
  3143. return (IEnumerable<object>)newContent;
  3144. }
  3145. private class BorderMappingInfo
  3146. {
  3147. public string CssName;
  3148. public decimal CssSize;
  3149. }
  3150. private static readonly Dictionary<string, BorderMappingInfo> BorderStyleMap = new Dictionary<string, BorderMappingInfo>()
  3151. {
  3152. { "single", new BorderMappingInfo() { CssName = "solid", CssSize = 1.0m }},
  3153. { "dotted", new BorderMappingInfo() { CssName = "dotted", CssSize = 1.0m }},
  3154. { "dashSmallGap", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
  3155. { "dashed", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
  3156. { "dotDash", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
  3157. { "dotDotDash", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
  3158. { "double", new BorderMappingInfo() { CssName = "double", CssSize = 2.5m }},
  3159. { "triple", new BorderMappingInfo() { CssName = "double", CssSize = 2.5m }},
  3160. { "thinThickSmallGap", new BorderMappingInfo() { CssName = "double", CssSize = 4.5m }},
  3161. { "thickThinSmallGap", new BorderMappingInfo() { CssName = "double", CssSize = 4.5m }},
  3162. { "thinThickThinSmallGap", new BorderMappingInfo() { CssName = "double", CssSize = 6.0m }},
  3163. { "thickThinMediumGap", new BorderMappingInfo() { CssName = "double", CssSize = 6.0m }},
  3164. { "thinThickMediumGap", new BorderMappingInfo() { CssName = "double", CssSize = 6.0m }},
  3165. { "thinThickThinMediumGap", new BorderMappingInfo() { CssName = "double", CssSize = 9.0m }},
  3166. { "thinThickLargeGap", new BorderMappingInfo() { CssName = "double", CssSize = 5.25m }},
  3167. { "thickThinLargeGap", new BorderMappingInfo() { CssName = "double", CssSize = 5.25m }},
  3168. { "thinThickThinLargeGap", new BorderMappingInfo() { CssName = "double", CssSize = 9.0m }},
  3169. { "wave", new BorderMappingInfo() { CssName = "solid", CssSize = 3.0m }},
  3170. { "doubleWave", new BorderMappingInfo() { CssName = "double", CssSize = 5.25m }},
  3171. { "dashDotStroked", new BorderMappingInfo() { CssName = "solid", CssSize = 3.0m }},
  3172. { "threeDEmboss", new BorderMappingInfo() { CssName = "ridge", CssSize = 6.0m }},
  3173. { "threeDEngrave", new BorderMappingInfo() { CssName = "groove", CssSize = 6.0m }},
  3174. { "outset", new BorderMappingInfo() { CssName = "outset", CssSize = 4.5m }},
  3175. { "inset", new BorderMappingInfo() { CssName = "inset", CssSize = 4.5m }},
  3176. };
  3177. private static void GenerateBorderStyle(XElement pBdr, XName sideXName, Dictionary<string, string> style, BorderType borderType)
  3178. {
  3179. string whichSide;
  3180. if (sideXName == W.top)
  3181. whichSide = "top";
  3182. else if (sideXName == W.right)
  3183. whichSide = "right";
  3184. else if (sideXName == W.bottom)
  3185. whichSide = "bottom";
  3186. else
  3187. whichSide = "left";
  3188. if (pBdr == null)
  3189. {
  3190. style.Add("border-" + whichSide, "none");
  3191. if (borderType == BorderType.Cell &&
  3192. (whichSide == "left" || whichSide == "right"))
  3193. style.Add("padding-" + whichSide, "5.4pt");
  3194. return;
  3195. }
  3196. var side = pBdr.Element(sideXName);
  3197. if (side == null)
  3198. {
  3199. style.Add("border-" + whichSide, "none");
  3200. if (borderType == BorderType.Cell &&
  3201. (whichSide == "left" || whichSide == "right"))
  3202. style.Add("padding-" + whichSide, "5.4pt");
  3203. return;
  3204. }
  3205. var type = (string)side.Attribute(W.val);
  3206. if (type == "nil" || type == "none")
  3207. {
  3208. style.Add("border-" + whichSide + "-style", "none");
  3209. var space = (decimal?)side.Attribute(W.space) ?? 0;
  3210. if (borderType == BorderType.Cell &&
  3211. (whichSide == "left" || whichSide == "right"))
  3212. if (space < 5.4m)
  3213. space = 5.4m;
  3214. style.Add("padding-" + whichSide,
  3215. space == 0 ? "0" : string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", space));
  3216. }
  3217. else
  3218. {
  3219. var sz = (int)side.Attribute(W.sz);
  3220. var space = (decimal?)side.Attribute(W.space) ?? 0;
  3221. var color = (string)side.Attribute(W.color);
  3222. if (color == null || color == "auto")
  3223. color = "windowtext";
  3224. else
  3225. color = ConvertColor(color);
  3226. decimal borderWidthInPoints = Math.Max(1m, Math.Min(96m, Math.Max(2m, sz)) / 8m);
  3227. var borderStyle = "solid";
  3228. if (BorderStyleMap.ContainsKey(type))
  3229. {
  3230. var borderInfo = BorderStyleMap[type];
  3231. borderStyle = borderInfo.CssName;
  3232. if (type == "double")
  3233. {
  3234. if (sz <= 8)
  3235. borderWidthInPoints = 2.5m;
  3236. else if (sz <= 18)
  3237. borderWidthInPoints = 6.75m;
  3238. else
  3239. borderWidthInPoints = sz / 3m;
  3240. }
  3241. else if (type == "triple")
  3242. {
  3243. if (sz <= 8)
  3244. borderWidthInPoints = 8m;
  3245. else if (sz <= 18)
  3246. borderWidthInPoints = 11.25m;
  3247. else
  3248. borderWidthInPoints = 11.25m;
  3249. }
  3250. else if (type.ToLower().Contains("dash"))
  3251. {
  3252. if (sz <= 4)
  3253. borderWidthInPoints = 1m;
  3254. else if (sz <= 12)
  3255. borderWidthInPoints = 1.5m;
  3256. else
  3257. borderWidthInPoints = 2m;
  3258. }
  3259. else if (type != "single")
  3260. borderWidthInPoints = borderInfo.CssSize;
  3261. }
  3262. if (type == "outset" || type == "inset")
  3263. color = "";
  3264. var borderWidth = string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", borderWidthInPoints);
  3265. style.Add("border-" + whichSide, borderStyle + " " + color + " " + borderWidth);
  3266. if (borderType == BorderType.Cell &&
  3267. (whichSide == "left" || whichSide == "right"))
  3268. if (space < 5.4m)
  3269. space = 5.4m;
  3270. style.Add("padding-" + whichSide,
  3271. space == 0 ? "0" : string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", space));
  3272. }
  3273. }
  3274. private static readonly Dictionary<string, Func<string, string, string>> ShadeMapper = new Dictionary<string, Func<string, string, string>>()
  3275. {
  3276. { "auto", (c, f) => c },
  3277. { "clear", (c, f) => f },
  3278. { "nil", (c, f) => f },
  3279. { "solid", (c, f) => c },
  3280. { "diagCross", (c, f) => ConvertColorFillPct(c, f, .75) },
  3281. { "diagStripe", (c, f) => ConvertColorFillPct(c, f, .75) },
  3282. { "horzCross", (c, f) => ConvertColorFillPct(c, f, .5) },
  3283. { "horzStripe", (c, f) => ConvertColorFillPct(c, f, .5) },
  3284. { "pct10", (c, f) => ConvertColorFillPct(c, f, .1) },
  3285. { "pct12", (c, f) => ConvertColorFillPct(c, f, .125) },
  3286. { "pct15", (c, f) => ConvertColorFillPct(c, f, .15) },
  3287. { "pct20", (c, f) => ConvertColorFillPct(c, f, .2) },
  3288. { "pct25", (c, f) => ConvertColorFillPct(c, f, .25) },
  3289. { "pct30", (c, f) => ConvertColorFillPct(c, f, .3) },
  3290. { "pct35", (c, f) => ConvertColorFillPct(c, f, .35) },
  3291. { "pct37", (c, f) => ConvertColorFillPct(c, f, .375) },
  3292. { "pct40", (c, f) => ConvertColorFillPct(c, f, .4) },
  3293. { "pct45", (c, f) => ConvertColorFillPct(c, f, .45) },
  3294. { "pct50", (c, f) => ConvertColorFillPct(c, f, .50) },
  3295. { "pct55", (c, f) => ConvertColorFillPct(c, f, .55) },
  3296. { "pct60", (c, f) => ConvertColorFillPct(c, f, .60) },
  3297. { "pct62", (c, f) => ConvertColorFillPct(c, f, .625) },
  3298. { "pct65", (c, f) => ConvertColorFillPct(c, f, .65) },
  3299. { "pct70", (c, f) => ConvertColorFillPct(c, f, .7) },
  3300. { "pct75", (c, f) => ConvertColorFillPct(c, f, .75) },
  3301. { "pct80", (c, f) => ConvertColorFillPct(c, f, .8) },
  3302. { "pct85", (c, f) => ConvertColorFillPct(c, f, .85) },
  3303. { "pct87", (c, f) => ConvertColorFillPct(c, f, .875) },
  3304. { "pct90", (c, f) => ConvertColorFillPct(c, f, .9) },
  3305. { "pct95", (c, f) => ConvertColorFillPct(c, f, .95) },
  3306. { "reverseDiagStripe", (c, f) => ConvertColorFillPct(c, f, .5) },
  3307. { "thinDiagCross", (c, f) => ConvertColorFillPct(c, f, .5) },
  3308. { "thinDiagStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
  3309. { "thinHorzCross", (c, f) => ConvertColorFillPct(c, f, .3) },
  3310. { "thinHorzStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
  3311. { "thinReverseDiagStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
  3312. { "thinVertStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
  3313. };
  3314. private static readonly Dictionary<string, string> ShadeCache = new Dictionary<string, string>();
  3315. // fill is the background, color is the foreground
  3316. private static string ConvertColorFillPct(string color, string fill, double pct)
  3317. {
  3318. if (color == "auto")
  3319. color = "000000";
  3320. if (fill == "auto")
  3321. fill = "ffffff";
  3322. var key = color + fill + pct.ToString(CultureInfo.InvariantCulture);
  3323. if (ShadeCache.ContainsKey(key))
  3324. return ShadeCache[key];
  3325. var fillRed = Convert.ToInt32(fill.Substring(0, 2), 16);
  3326. var fillGreen = Convert.ToInt32(fill.Substring(2, 2), 16);
  3327. var fillBlue = Convert.ToInt32(fill.Substring(4, 2), 16);
  3328. var colorRed = Convert.ToInt32(color.Substring(0, 2), 16);
  3329. var colorGreen = Convert.ToInt32(color.Substring(2, 2), 16);
  3330. var colorBlue = Convert.ToInt32(color.Substring(4, 2), 16);
  3331. var finalRed = (int)(fillRed - (fillRed - colorRed) * pct);
  3332. var finalGreen = (int)(fillGreen - (fillGreen - colorGreen) * pct);
  3333. var finalBlue = (int)(fillBlue - (fillBlue - colorBlue) * pct);
  3334. var returnValue = string.Format("{0:x2}{1:x2}{2:x2}", finalRed, finalGreen, finalBlue);
  3335. ShadeCache.Add(key, returnValue);
  3336. return returnValue;
  3337. }
  3338. private static void CreateStyleFromShd(Dictionary<string, string> style, XElement shd)
  3339. {
  3340. if (shd == null)
  3341. return;
  3342. var shadeType = (string)shd.Attribute(W.val);
  3343. var color = (string)shd.Attribute(W.color);
  3344. var fill = (string)shd.Attribute(W.fill);
  3345. if (ShadeMapper.ContainsKey(shadeType))
  3346. {
  3347. color = ShadeMapper[shadeType](color, fill);
  3348. }
  3349. if (color != null)
  3350. {
  3351. var cvtColor = ConvertColor(color);
  3352. if (!string.IsNullOrEmpty(cvtColor))
  3353. style.AddIfMissing("background", cvtColor);
  3354. }
  3355. }
  3356. private static readonly Dictionary<string, string> NamedColors = new Dictionary<string, string>()
  3357. {
  3358. {"black", "black"},
  3359. {"blue", "blue" },
  3360. {"cyan", "aqua" },
  3361. {"green", "green" },
  3362. {"magenta", "fuchsia" },
  3363. {"red", "red" },
  3364. {"yellow", "yellow" },
  3365. {"white", "white" },
  3366. {"darkBlue", "#00008B" },
  3367. {"darkCyan", "#008B8B" },
  3368. {"darkGreen", "#006400" },
  3369. {"darkMagenta", "#800080" },
  3370. {"darkRed", "#8B0000" },
  3371. {"darkYellow", "#808000" },
  3372. {"darkGray", "#A9A9A9" },
  3373. {"lightGray", "#D3D3D3" },
  3374. {"none", "" },
  3375. };
  3376. private static void CreateColorProperty(string propertyName, string color, Dictionary<string, string> style)
  3377. {
  3378. if (color == null)
  3379. return;
  3380. // "auto" color is black for "color" and white for "background" property.
  3381. if (color == "auto")
  3382. color = propertyName == "color" ? "black" : "white";
  3383. if (NamedColors.ContainsKey(color))
  3384. {
  3385. var lc = NamedColors[color];
  3386. if (lc == "")
  3387. return;
  3388. style.AddIfMissing(propertyName, lc);
  3389. return;
  3390. }
  3391. style.AddIfMissing(propertyName, "#" + color);
  3392. }
  3393. private static string ConvertColor(string color)
  3394. {
  3395. // "auto" color is black for "color" and white for "background" property.
  3396. // As this method is only called for "background" colors, "auto" is translated
  3397. // to "white" and never "black".
  3398. if (color == "auto")
  3399. color = "white";
  3400. if (NamedColors.ContainsKey(color))
  3401. {
  3402. var lc = NamedColors[color];
  3403. if (lc == "")
  3404. return "black";
  3405. return lc;
  3406. }
  3407. return "#" + color;
  3408. }
  3409. private static readonly Dictionary<string, string> FontFallback = new Dictionary<string, string>()
  3410. {
  3411. { "Arial", @"'{0}', 'sans-serif'" },
  3412. { "Arial Narrow", @"'{0}', 'sans-serif'" },
  3413. { "Arial Rounded MT Bold", @"'{0}', 'sans-serif'" },
  3414. { "Arial Unicode MS", @"'{0}', 'sans-serif'" },
  3415. { "Baskerville Old Face", @"'{0}', 'serif'" },
  3416. { "Berlin Sans FB", @"'{0}', 'sans-serif'" },
  3417. { "Berlin Sans FB Demi", @"'{0}', 'sans-serif'" },
  3418. { "Calibri Light", @"'{0}', 'sans-serif'" },
  3419. { "Gill Sans MT", @"'{0}', 'sans-serif'" },
  3420. { "Gill Sans MT Condensed", @"'{0}', 'sans-serif'" },
  3421. { "Lucida Sans", @"'{0}', 'sans-serif'" },
  3422. { "Lucida Sans Unicode", @"'{0}', 'sans-serif'" },
  3423. { "Segoe UI", @"'{0}', 'sans-serif'" },
  3424. { "Segoe UI Light", @"'{0}', 'sans-serif'" },
  3425. { "Segoe UI Semibold", @"'{0}', 'sans-serif'" },
  3426. { "Tahoma", @"'{0}', 'sans-serif'" },
  3427. { "Trebuchet MS", @"'{0}', 'sans-serif'" },
  3428. { "Verdana", @"'{0}', 'sans-serif'" },
  3429. { "Book Antiqua", @"'{0}', 'serif'" },
  3430. { "Bookman Old Style", @"'{0}', 'serif'" },
  3431. { "Californian FB", @"'{0}', 'serif'" },
  3432. { "Cambria", @"'{0}', 'serif'" },
  3433. { "Constantia", @"'{0}', 'serif'" },
  3434. { "Garamond", @"'{0}', 'serif'" },
  3435. { "Lucida Bright", @"'{0}', 'serif'" },
  3436. { "Lucida Fax", @"'{0}', 'serif'" },
  3437. { "Palatino Linotype", @"'{0}', 'serif'" },
  3438. { "Times New Roman", @"'{0}', 'serif'" },
  3439. { "Wide Latin", @"'{0}', 'serif'" },
  3440. { "Courier New", @"'{0}'" },
  3441. { "Lucida Console", @"'{0}'" },
  3442. };
  3443. private static void CreateFontCssProperty(string font, Dictionary<string, string> style)
  3444. {
  3445. if (FontFallback.ContainsKey(font))
  3446. {
  3447. style.AddIfMissing("font-family", string.Format(FontFallback[font], font));
  3448. return;
  3449. }
  3450. style.AddIfMissing("font-family", font);
  3451. }
  3452. private static bool GetBoolProp(XElement runProps, XName xName)
  3453. {
  3454. var p = runProps.Element(xName);
  3455. if (p == null)
  3456. return false;
  3457. var v = p.Attribute(W.val);
  3458. if (v == null)
  3459. return true;
  3460. var s = v.Value.ToLower();
  3461. if (s == "0" || s == "false")
  3462. return false;
  3463. if (s == "1" || s == "true")
  3464. return true;
  3465. return false;
  3466. }
  3467. private static object ConvertContentThatCanContainFields(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
  3468. IEnumerable<XElement> elements)
  3469. {
  3470. var grouped = elements
  3471. .GroupAdjacent(e =>
  3472. {
  3473. var stack = e.Annotation<Stack<FieldRetriever.FieldElementTypeInfo>>();
  3474. return stack == null || !stack.Any() ? (int?)null : stack.Select(st => st.Id).Min();
  3475. })
  3476. .ToList();
  3477. var txformed = grouped
  3478. .Select(g =>
  3479. {
  3480. var key = g.Key;
  3481. if (key == null)
  3482. return (object)g.Select(n => ConvertToHtmlTransform(wordDoc, settings, n, false, 0m));
  3483. var instrText = FieldRetriever.InstrText(g.First().Ancestors().Last(), (int)key)
  3484. .TrimStart('{').TrimEnd('}');
  3485. var parsed = FieldRetriever.ParseField(instrText);
  3486. if (parsed.FieldType != "HYPERLINK")
  3487. return g.Select(n => ConvertToHtmlTransform(wordDoc, settings, n, false, 0m));
  3488. var content = g.DescendantsAndSelf(W.r).Select(run => ConvertRun(wordDoc, settings, run));
  3489. var a = parsed.Arguments.Length > 0
  3490. ? new XElement(Xhtml.a, new XAttribute("href", parsed.Arguments[0]), new XAttribute("target","_blank"), content)
  3491. : new XElement(Xhtml.a, content);
  3492. var a2 = a as XElement;
  3493. if (!a2.Nodes().Any())
  3494. {
  3495. a2.Add(new XText(""));
  3496. return a2;
  3497. }
  3498. return a;
  3499. })
  3500. .ToList();
  3501. return txformed;
  3502. }
  3503. #region Image Processing
  3504. // Don't process wmf files (with contentType == "image/x-wmf") because GDI consumes huge amounts
  3505. // of memory when dealing with wmf perhaps because it loads a DLL to do the rendering?
  3506. // It actually works, but is not recommended.
  3507. public static readonly List<string> ImageContentTypes = new List<string>
  3508. {
  3509. "image/png", "image/gif", "image/tiff", "image/jpeg","image/x-wmf","image/x-emf","application/vnd.openxmlformats-officedocument.oleObject"
  3510. };
  3511. public static XElement ProcessImage(WordprocessingDocument wordDoc,
  3512. XElement element, Func<ImageInfo, XElement> imageHandler)
  3513. {
  3514. if (imageHandler == null)
  3515. {
  3516. return null;
  3517. }
  3518. if (element.Name == W.drawing)
  3519. {
  3520. return ProcessDrawing(wordDoc, element, imageHandler);
  3521. }
  3522. if (element.Name == W.pict || element.Name == W._object)
  3523. {
  3524. return ProcessPictureOrObject(wordDoc, element, imageHandler);
  3525. }
  3526. return null;
  3527. }
  3528. private static XElement ProcessDrawing(WordprocessingDocument wordDoc,
  3529. XElement element, Func<ImageInfo, XElement> imageHandler)
  3530. {
  3531. var containerElement = element.Elements()
  3532. .FirstOrDefault(e => e.Name == WP.inline || e.Name == WP.anchor);
  3533. if (containerElement == null) return null;
  3534. string hyperlinkUri = null;
  3535. var hyperlinkElement = element
  3536. .Elements(WP.inline)
  3537. .Elements(WP.docPr)
  3538. .Elements(A.hlinkClick)
  3539. .FirstOrDefault();
  3540. if (hyperlinkElement != null)
  3541. {
  3542. var rId = (string)hyperlinkElement.Attribute(R.id);
  3543. if (rId != null)
  3544. {
  3545. var hyperlinkRel = wordDoc.MainDocumentPart.HyperlinkRelationships.FirstOrDefault(hlr => hlr.Id == rId);
  3546. if (hyperlinkRel != null)
  3547. {
  3548. hyperlinkUri = hyperlinkRel.Uri.ToString();
  3549. }
  3550. }
  3551. }
  3552. var extentCx = (int?)containerElement.Elements(WP.extent)
  3553. .Attributes(NoNamespace.cx).FirstOrDefault();
  3554. var extentCy = (int?)containerElement.Elements(WP.extent)
  3555. .Attributes(NoNamespace.cy).FirstOrDefault();
  3556. var altText = (string)containerElement.Elements(WP.docPr).Attributes(NoNamespace.descr).FirstOrDefault() ??
  3557. ((string)containerElement.Elements(WP.docPr).Attributes(NoNamespace.name).FirstOrDefault() ?? "");
  3558. if (containerElement.Elements(A.graphic)
  3559. .Elements(A.graphicData) != null)
  3560. {
  3561. XElement rest = null;
  3562. var elms = containerElement.Elements(A.graphic)
  3563. .Elements(A.graphicData).Elements();
  3564. foreach (var elm in elms)
  3565. {
  3566. if (elm.Name == Pic._pic)
  3567. {
  3568. rest = ProcessPic(wordDoc, containerElement, imageHandler, extentCx, extentCy, altText, elm, hyperlinkUri);
  3569. break;
  3570. }
  3571. if (elm.Name == C.chart)
  3572. {
  3573. rest = ProcessChart(wordDoc, containerElement, imageHandler, extentCx, extentCy, altText, elm, hyperlinkUri);
  3574. break;
  3575. }
  3576. if (elm.Name == DGM.relIds)
  3577. {
  3578. rest = ProcessDgm(wordDoc, containerElement, imageHandler, extentCx, extentCy, altText, elm, hyperlinkUri);
  3579. break;
  3580. }
  3581. }
  3582. return rest;
  3583. }
  3584. else
  3585. {
  3586. return null;
  3587. }
  3588. }
  3589. public static XElement ProcessPic(WordprocessingDocument wordDoc, XElement containerElement, Func<ImageInfo, XElement> imageHandler,
  3590. int? extentCx, int? extentCy, string altText, XElement element, string hyperlinkUri)
  3591. {
  3592. var blipFill = containerElement.Elements(A.graphic)
  3593. .Elements(A.graphicData)
  3594. .Elements(Pic._pic).Elements(Pic.blipFill).FirstOrDefault();
  3595. if (blipFill == null) return null;
  3596. var imageRid = (string)blipFill.Elements(A.blip).Attributes(R.embed).FirstOrDefault();
  3597. if (imageRid == null) return null;
  3598. var pp3 = wordDoc.MainDocumentPart.Parts.FirstOrDefault(pp => pp.RelationshipId == imageRid);
  3599. if (pp3 == null) return null;
  3600. var imagePart = (ImagePart)pp3.OpenXmlPart;
  3601. if (imagePart == null) return null;
  3602. // If the image markup points to a NULL image, then following will throw an ArgumentOutOfRangeException
  3603. try
  3604. {
  3605. imagePart = (ImagePart)wordDoc.MainDocumentPart.GetPartById(imageRid);
  3606. }
  3607. catch (ArgumentOutOfRangeException)
  3608. {
  3609. return null;
  3610. }
  3611. var contentType = imagePart.ContentType;
  3612. if (!ImageContentTypes.Contains(contentType))
  3613. return null;
  3614. var uil = imagePart.Uri;
  3615. //WORDHelper.GetWordNodeByPath(wordDoc,"");
  3616. var Stream = imagePart.GetStream();
  3617. try
  3618. {
  3619. if (contentType == "image/x-wmf" || contentType == "image/x-emf")
  3620. {
  3621. return ProcessWmf(Stream, contentType, element, hyperlinkUri, imageHandler);
  3622. }
  3623. else
  3624. {
  3625. using (var bitmap = new Bitmap(Stream))
  3626. {
  3627. if (extentCx != null && extentCy != null)
  3628. {
  3629. var imageInfo = new ImageInfo()
  3630. {
  3631. Bitmap = bitmap,
  3632. ImgStyleAttribute = new XAttribute("style",
  3633. string.Format(NumberFormatInfo.InvariantInfo,
  3634. "width: {0}px; ",
  3635. // "height: {1}in",
  3636. (float)extentCx * 96.0 / (float)ImageInfo.EmusPerInch
  3637. // ,
  3638. //(float)extentCy*96.0 / (float)ImageInfo.EmusPerInch
  3639. )),
  3640. ContentType = contentType,
  3641. DrawingElement = element,
  3642. AltText = altText,
  3643. };
  3644. var imgElement2 = imageHandler(imageInfo);
  3645. if (hyperlinkUri != null)
  3646. {
  3647. return new XElement(XhtmlNoNamespace.a,
  3648. new XAttribute(XhtmlNoNamespace.href, hyperlinkUri),
  3649. imgElement2);
  3650. }
  3651. return imgElement2;
  3652. }
  3653. var imageInfo2 = new ImageInfo()
  3654. {
  3655. Bitmap = bitmap,
  3656. ContentType = contentType,
  3657. DrawingElement = element,
  3658. AltText = altText,
  3659. };
  3660. var imgElement = imageHandler(imageInfo2);
  3661. if (hyperlinkUri != null)
  3662. {
  3663. return new XElement(XhtmlNoNamespace.a,
  3664. new XAttribute(XhtmlNoNamespace.href, hyperlinkUri),
  3665. imgElement);
  3666. }
  3667. return imgElement;
  3668. }
  3669. }
  3670. }
  3671. catch
  3672. {
  3673. XElement img = null;
  3674. byte[] bytes = new byte[Stream.Length];
  3675. Stream.Read(bytes, 0, bytes.Length);
  3676. // 设置当前流的位置为流的开始
  3677. Stream.Seek(0, SeekOrigin.Begin);
  3678. string url = System.Convert.ToBase64String(bytes);
  3679. img = new XElement(Xhtml.img,
  3680. new XAttribute(NoNamespace.src, "data:" + contentType + ";base64," + url),
  3681. new XAttribute(NoNamespace.alt, "wrong image"));
  3682. return img;
  3683. }
  3684. finally
  3685. {
  3686. Stream.Close();
  3687. }
  3688. }
  3689. public static XElement ProcessWmf(Stream stream, string contentType, XElement element, string hyperlinkUri, Func<ImageInfo, XElement> imageHandler)
  3690. {
  3691. XElement img = null;
  3692. string base64 = "";
  3693. string altText = "";
  3694. if (contentType.Equals("image/x-wmf"))
  3695. {
  3696. bool compatible = false;
  3697. bool replaceSymbolFont = false;
  3698. WMFConverter.Wmf.WmfParser parser = new WMFConverter.Wmf.WmfParser();
  3699. WMFConverter.Svg.SvgGdi gdi = new WMFConverter.Svg.SvgGdi(compatible);
  3700. gdi.ReplaceSymbolFont = replaceSymbolFont;
  3701. parser.Parse(stream, gdi);
  3702. base64 = gdi.Document.InnerXml;
  3703. }
  3704. else if (contentType.Equals("image/x-emf"))
  3705. {
  3706. MemoryStream memoryStream = new MemoryStream();
  3707. //Metafile inFile = new Metafile(stream);
  3708. ////Metafile outFile = new Metafile(memoryStream, Graphics.FromHwnd(IntPtr.Zero).GetHdc(), EmfType.EmfOnly);
  3709. //inFile.Save(memoryStream, ImageFormat.Png);
  3710. //var ba = memoryStream.ToArray();
  3711. //mathxml = System.Convert.ToBase64String(ba);
  3712. //System.Drawing.Bitmap inFile = new System.Drawing.Bitmap(stream);
  3713. //Graphics graphics = Graphics.FromImage(inFile);
  3714. //graphics.Save();
  3715. //graphics.Dispose();
  3716. //inFile.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
  3717. //byte[] arr = new byte[memoryStream.Length];
  3718. //memoryStream.Position = 0;
  3719. //memoryStream.Read(arr, 0, (int)memoryStream.Length);
  3720. //memoryStream.Close();
  3721. //memoryStream.Dispose();
  3722. //mathxml = Convert.ToBase64String(arr);
  3723. //mathxml = "EmfBase64ConvertError";
  3724. base64 = Globals.UnsupportedImage;
  3725. contentType = "image/x-emf";
  3726. altText = "UnsupportedImageError";
  3727. }
  3728. try
  3729. {
  3730. var imageInfo = new ImageInfo()
  3731. {
  3732. ContentType = contentType,
  3733. DrawingElement = element,
  3734. AltText = altText,
  3735. Mathxml = base64
  3736. };
  3737. var style = (string)element.Elements(VML.shape).Attributes("style").FirstOrDefault();
  3738. float? widthInPoints = null;
  3739. float? heightInPoints = null;
  3740. if (style != null)
  3741. {
  3742. var tokens = style.Split(';');
  3743. widthInPoints = WidthInPoints(tokens);
  3744. heightInPoints = HeightInPoints(tokens);
  3745. }
  3746. img = imageHandler(imageInfo);
  3747. //var imgElement2 = imageHandler(imageInfo);
  3748. if (widthInPoints != null && heightInPoints != null)
  3749. {
  3750. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  3751. // string.Format(NumberFormatInfo.InvariantInfo,
  3752. // "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  3753. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  3754. "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  3755. }
  3756. else
  3757. {
  3758. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  3759. // string.Format(NumberFormatInfo.InvariantInfo,
  3760. // "vertical-align: middle;"));
  3761. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  3762. "vertical-align: middle;"));
  3763. }
  3764. if (hyperlinkUri != null)
  3765. {
  3766. return new XElement(XhtmlNoNamespace.a,
  3767. new XAttribute(XhtmlNoNamespace.href, hyperlinkUri),
  3768. img);
  3769. }
  3770. return img;
  3771. }
  3772. catch (OutOfMemoryException)
  3773. {
  3774. // the Bitmap class can throw OutOfMemoryException, which means the bitmap is messed up, so punt.
  3775. return null;
  3776. }
  3777. catch (ArgumentException e)
  3778. {
  3779. throw new Exception(e.StackTrace);
  3780. }
  3781. }
  3782. private static XElement ProcessPictureOrObject(WordprocessingDocument wordDoc,
  3783. XElement element, Func<ImageInfo, XElement> imageHandler)
  3784. {
  3785. var imageRid = (string)element.Elements(VML.shape).Elements(VML.imagedata).Attributes(R.id).FirstOrDefault();
  3786. if (imageRid == null) return null;
  3787. var pp = wordDoc.MainDocumentPart.Parts.FirstOrDefault(pp2 => pp2.RelationshipId == imageRid);
  3788. Stream stream = null;
  3789. try
  3790. {
  3791. if (pp == null) return null;
  3792. if (!pp.OpenXmlPart.ContentType.Equals("image/x-wmf") && !pp.OpenXmlPart.ContentType.Equals("image/x-emf"))
  3793. {
  3794. return ProcessObject(wordDoc, element, imageHandler);
  3795. }
  3796. string mathxml = "";
  3797. if (pp.OpenXmlPart.ContentType.Equals("image/x-wmf"))
  3798. {
  3799. stream = pp.OpenXmlPart.GetStream();
  3800. bool compatible = false;
  3801. bool replaceSymbolFont = false;
  3802. WMFConverter.Wmf.WmfParser parser = new WMFConverter.Wmf.WmfParser();
  3803. WMFConverter.Svg.SvgGdi gdi = new WMFConverter.Svg.SvgGdi(compatible);
  3804. gdi.ReplaceSymbolFont = replaceSymbolFont;
  3805. parser.Parse(stream, gdi);
  3806. mathxml = gdi.Document.InnerXml;
  3807. }
  3808. else if (pp.OpenXmlPart.ContentType.Equals("image/x-emf"))
  3809. {
  3810. ////var image= SixLabors.ImageSharp.Image.Load(pp.OpenXmlPart.GetStream());
  3811. ////image.Save(memoryStream, new SixLabors.ImageSharp.Formats.Png.PngEncoder());
  3812. //var stm= pp.OpenXmlPart.GetStream();
  3813. //var parser = new EMF2StringParser();
  3814. //parser.LineBreakCandidates = new[] { EmfPlusRecordType.EmfSelectObject, EmfPlusRecordType.EmfDeleteObject };
  3815. //parser.SpaceCandidates = new[] { EmfPlusRecordType.EmfIntersectClipRect };
  3816. //Metafile inFile = new Metafile(stm);
  3817. //parser.LoadMetaFile(inFile);
  3818. //parser.IsParseFailedLoggingEnabled = true;
  3819. //var expected = parser.GetCombinedStringFromLoadedMetaFile();
  3820. ////MemoryStream memoryStream = new MemoryStream();
  3821. ////byte[] bytes = new byte[stm.Length];
  3822. ////stm.Read(bytes, 0, bytes.Length);
  3823. ////stm.Seek(0, SeekOrigin.Begin);
  3824. ////
  3825. ////Metafile outFile = new Metafile(memoryStream, Graphics.FromHwnd(IntPtr.Zero).GetHdc(), EmfType.EmfOnly);
  3826. //// inFile.Save(memoryStream, ImageFormat.Png);
  3827. ////var ba = memoryStream.ToArray();
  3828. //application.EnablePartialTrustCode = true;
  3829. //mathxml = expected;
  3830. // MemoryStream memoryStream = new MemoryStream();
  3831. //Metafile inFile = new Metafile(pp.OpenXmlPart.GetStream());
  3832. ////Metafile outFile = new Metafile(memoryStream, Graphics.FromHwnd(IntPtr.Zero).GetHdc(), EmfType.EmfOnly);
  3833. //inFile.Save(memoryStream, ImageFormat.Png);
  3834. //var ba = memoryStream.ToArray();
  3835. //mathxml = System.Convert.ToBase64String(ba);
  3836. //System.Drawing.Bitmap inFile = new System.Drawing.Bitmap(pp.OpenXmlPart.GetStream());
  3837. //Graphics graphics = Graphics.FromImage(inFile);
  3838. //graphics.Save();
  3839. //graphics.Dispose();
  3840. //inFile.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
  3841. //byte[] arr = new byte[memoryStream.Length];
  3842. //memoryStream.Position = 0;
  3843. //memoryStream.Read(arr, 0, (int)memoryStream.Length);
  3844. //memoryStream.Close();
  3845. //memoryStream.Dispose();
  3846. // mathxml = Convert.ToBase64String(arr);
  3847. mathxml = "EmfBase64ConvertError";
  3848. }
  3849. string hyperlinkUri = null;
  3850. var contentType = pp.OpenXmlPart.ContentType;
  3851. if (!ImageContentTypes.Contains(contentType))
  3852. return null;
  3853. try
  3854. {
  3855. XElement img = null;
  3856. var imageInfo = new ImageInfo()
  3857. {
  3858. ContentType = contentType,
  3859. DrawingElement = element,
  3860. AltText = "",
  3861. Mathxml = mathxml,
  3862. };
  3863. var style = (string)element.Elements(VML.shape).Attributes("style").FirstOrDefault();
  3864. // if (style == null) {
  3865. // return imageHandler(imageInfo);
  3866. img = imageHandler(imageInfo);
  3867. // }
  3868. var tokens = style.Split(';');
  3869. var widthInPoints = WidthInPoints(tokens);
  3870. var heightInPoints = HeightInPoints(tokens);
  3871. // var imgElement2 = imageHandler(imageInfo);
  3872. if (widthInPoints != null && heightInPoints != null)
  3873. {
  3874. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  3875. // string.Format(NumberFormatInfo.InvariantInfo,
  3876. // "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  3877. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  3878. "width: {0}pt; height: {1}pt ; vertical-align: bottom;", widthInPoints, heightInPoints));
  3879. }
  3880. else
  3881. {
  3882. //imageInfo.ImgStyleAttribute = new XAttribute("style",
  3883. // string.Format(NumberFormatInfo.InvariantInfo,
  3884. // "vertical-align: middle;"));
  3885. img.SetAttributeValue("style", string.Format(NumberFormatInfo.InvariantInfo,
  3886. "vertical-align: middle;"));
  3887. }
  3888. if (hyperlinkUri != null)
  3889. {
  3890. return new XElement(XhtmlNoNamespace.a,
  3891. new XAttribute(XhtmlNoNamespace.href, hyperlinkUri),
  3892. img);
  3893. }
  3894. return img;
  3895. }
  3896. catch (OutOfMemoryException)
  3897. {
  3898. // the Bitmap class can throw OutOfMemoryException, which means the bitmap is messed up, so punt.
  3899. return null;
  3900. }
  3901. catch (ArgumentException e)
  3902. {
  3903. throw new Exception(e.StackTrace);
  3904. }
  3905. }
  3906. catch (ArgumentOutOfRangeException)
  3907. {
  3908. return null;
  3909. }
  3910. finally
  3911. {
  3912. if (stream != null)
  3913. stream.Close();
  3914. }
  3915. }
  3916. private static XElement ProcessObject(WordprocessingDocument wordDoc,
  3917. XElement element, Func<ImageInfo, XElement> imageHandler)
  3918. {
  3919. var imageRid = (string)element.Elements(VML.shape).Elements(VML.imagedata).Attributes(R.id).FirstOrDefault();
  3920. if (imageRid == null) return null;
  3921. try
  3922. {
  3923. var pp = wordDoc.MainDocumentPart.Parts.FirstOrDefault(pp2 => pp2.RelationshipId == imageRid);
  3924. if (pp == null) return null;
  3925. var imagePart = (ImagePart)pp.OpenXmlPart;
  3926. if (imagePart == null) return null;
  3927. string hyperlinkUri = null;
  3928. // WORDHelper.GetWordNodeByPath(wordDoc.MainDocumentPart.GetXDocument(), "//[@name='"+imagePart.Uri+"']");
  3929. using (var partStream = imagePart.GetStream())
  3930. {
  3931. var contentType = imagePart.ContentType;
  3932. if (!ImageContentTypes.Contains(contentType))
  3933. return null;
  3934. try
  3935. {
  3936. using (var bitmap = new Bitmap(partStream))
  3937. {
  3938. var imageInfo = new ImageInfo()
  3939. {
  3940. Bitmap = bitmap,
  3941. ContentType = contentType,
  3942. DrawingElement = element,
  3943. AltText = ""
  3944. };
  3945. var style = (string)element.Elements(VML.shape).Attributes("style").FirstOrDefault();
  3946. if (style == null) return imageHandler(imageInfo);
  3947. var tokens = style.Split(';');
  3948. var widthInPoints = WidthInPoints(tokens);
  3949. var heightInPoints = HeightInPoints(tokens);
  3950. if (widthInPoints != null && heightInPoints != null)
  3951. {
  3952. imageInfo.ImgStyleAttribute = new XAttribute("style",
  3953. string.Format(NumberFormatInfo.InvariantInfo,
  3954. "width: {0}pt; height: {1}pt", widthInPoints, heightInPoints));
  3955. }
  3956. var imgElement2 = imageHandler(imageInfo);
  3957. if (hyperlinkUri != null)
  3958. {
  3959. return new XElement(XhtmlNoNamespace.a,
  3960. new XAttribute(XhtmlNoNamespace.href, hyperlinkUri),
  3961. imgElement2);
  3962. }
  3963. return imgElement2;
  3964. }
  3965. }
  3966. catch (OutOfMemoryException)
  3967. {
  3968. return null;
  3969. }
  3970. catch (ArgumentException e)
  3971. {
  3972. return null; //throw new Exception(e.StackTrace);
  3973. }
  3974. finally { partStream.Close(); }
  3975. }
  3976. }
  3977. catch (ArgumentOutOfRangeException)
  3978. {
  3979. return null;
  3980. }
  3981. }
  3982. private static float? HeightInPoints(IEnumerable<string> tokens)
  3983. {
  3984. return SizeInPoints(tokens, "height");
  3985. }
  3986. private static float? WidthInPoints(IEnumerable<string> tokens)
  3987. {
  3988. return SizeInPoints(tokens, "width");
  3989. }
  3990. private static float? SizeInPoints(IEnumerable<string> tokens, string name)
  3991. {
  3992. var sizeString = tokens
  3993. .Select(t => new
  3994. {
  3995. Name = t.Split(':').First(),
  3996. Value = t.Split(':').Skip(1).Take(1).FirstOrDefault()
  3997. })
  3998. .Where(p => p.Name == name)
  3999. .Select(p => p.Value)
  4000. .FirstOrDefault();
  4001. if (sizeString != null &&
  4002. sizeString.Length > 2 &&
  4003. sizeString.Substring(sizeString.Length - 2) == "pt")
  4004. {
  4005. float size;
  4006. if (float.TryParse(sizeString.Substring(0, sizeString.Length - 2), out size))
  4007. return size;
  4008. }
  4009. return null;
  4010. }
  4011. #endregion
  4012. }
  4013. public static class HtmlConverterExtensions
  4014. {
  4015. public static void AddIfMissing(this Dictionary<string, string> style, string propName, string value)
  4016. {
  4017. if (style.ContainsKey(propName))
  4018. return;
  4019. style.Add(propName, value);
  4020. }
  4021. }
  4022. }