using System; using System.Collections.Generic; using System.Text; using System.IO; using Csla; using Csla.Data; using System.Data; using System.Data.SqlClient; //----------------- using System.Drawing; using System.Text.RegularExpressions; using LBWordLibrary; using System.Drawing.Imaging; using Volian.Base.Library; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime; using System.Linq; namespace VEPROMS.CSLA.Library { public enum VolianStatusType { Initialize, Update, Complete } public delegate void VolianStatusChange(VolianStatusType type, int count, string text); public partial class Document { public string DocumentTitle { get { if (_LibTitle == string.Empty) return string.Format("Document {0}", _DocID); return _LibTitle; } } private static LBApplicationClass _MyApp = null; public static LBApplicationClass MyApp { get { if (_MyApp == null) _MyApp = new LBApplicationClass(); return _MyApp; } } public void UpdateDRoUsages(List roids) { _DocumentDROUsageCount = -1; if (DocumentDROUsages.Count > 0) { List delList = new List(); foreach (DocumentDROUsage myUsage in DocumentDROUsages) { string roidkey = string.Format("{0}:{1}", myUsage.RODbID, myUsage.ROID); if (roids.Contains(roidkey)) roids.Remove(roidkey);// If in both, nothing to do else delList.Add(myUsage); } foreach (DocumentDROUsage myUsage in delList) DocumentDROUsages.Remove(myUsage); // If only in old, remove it } foreach (string roidkey in roids) { string[] parts = roidkey.Split(":".ToCharArray()); DocumentDROUsages.Add(parts[1], RODb.GetJustRoDb(int.Parse(parts[0]))); } } private int _Unique = 0; private string Unique { get { string retval = string.Empty; if (_Unique != 0) retval = "_" + _Unique.ToString(); _Unique++; return retval; } } public void StatusChanged(VolianStatusType type, int count, string text) { //if (Parent != null && Parent.Parent != null && Parent.Parent.Parent is DisplayTabControl) //{ // DisplayTabControl tc = Parent.Parent.Parent as DisplayTabControl; // tc.ONStatusChanged(this, new DisplayTabControlStatusEventArgs(type, count, text)); //} } public void RestoreWordDoc(ItemInfo myItemInfo) { DocumentAuditInfo savDocAuditInfo = null; using (DocumentAuditInfoList dail = DocumentAuditInfoList.Get(DocID)) { if (dail.Count > 0) { DocumentAuditInfo dai = dail[0]; foreach (DocumentAuditInfo tmpa in dail) { FileInfo tmpFile = new FileInfo(string.Format(@"{0}\tmp_{1}{2}{3}", VlnSettings.TemporaryFolder, DocID, Unique, tmpa.FileExtension)); while (tmpFile.Exists) { tmpFile = new FileInfo(string.Format(@"{0}\tmp_{1}{2}{3}", VlnSettings.TemporaryFolder, DocID, Unique, tmpa.FileExtension)); } FileStream fs = tmpFile.Create(); if (tmpa.DocContent != null) fs.Write(tmpa.DocContent, 0, tmpa.DocContent.Length); fs.Close(); LBDocumentClass myDoc = null; try { myDoc = MyApp.Documents.Open(tmpFile.FullName, false); savDocAuditInfo = tmpa; myDoc.Close(); break; } catch (Exception ex) { if (myDoc != null) myDoc.Close(); continue; // get next document audit item. } } } if (savDocAuditInfo != null) { FileExtension = savDocAuditInfo.FileExtension; DocContent = savDocAuditInfo.DocContent; DocAscii = savDocAuditInfo.DocAscii; Config = savDocAuditInfo.Config; UserID = savDocAuditInfo.UserID; DTS = savDocAuditInfo.DTS; Save(); List roids = new List(); DocumentInfo docinfo = DocumentInfo.Get(DocID); string pdfTmp = MSWordToPDF.ToPDFReplaceROs(docinfo, roids, myItemInfo, StatusChanged); FileInfo pdfFile = new FileInfo(pdfTmp); FileStream fs = pdfFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); Byte[] buf = new byte[pdfFile.Length]; fs.Read(buf, 0, buf.Length); fs.Close(); pdfFile.Delete(); Pdf.DeleteAll(DocID); // clear the PDF table for DocID first DocStyle myDocStyle = myItemInfo.ActiveSection.MyDocStyle; SectionConfig sc = myItemInfo.ActiveSection.MyConfig as SectionConfig; if (sc != null && sc.Section_WordMargin == "Y") { using (Pdf myPdf = Pdf.MakePdf(this, MSWordToPDF.DebugStatus, 0, 0, 0, 0, (double)myItemInfo.MSWordPageCount, buf)) {; } } else { using (Pdf myPdf = Pdf.MakePdf(this, MSWordToPDF.DebugStatus, (int)myDocStyle.Layout.TopMargin, (int)myDocStyle.Layout.PageLength, (int)myDocStyle.Layout.LeftMargin, (int)myDocStyle.Layout.PageWidth, (double)myItemInfo.MSWordPageCount, buf)) {; } } UpdateDRoUsages(roids); Save(); } } } // B2023-093 This method is called before editing or printing a Word section and will convert it the Word .DOCX format if needed. // Note that the core logic was taken from frmSectionProperties.cs and modified to convert both .RTF and .DOC files // The conversion to DOCX is needs to be done only one time per Word section // B2023-109 Moved the setting of docInfo in the try block to handle if itmInfo, MyContent, MyEntry, MyDocument is null // Also modified the error log statements in the Catch to put the ItemID and the section number and title out to the error log public static void ConvertWordSectionToDOCX(ItemInfo itmInfo) { FrmPopupStatusMessage pmsg = null; DSOFile myfile = null; LBWordLibrary.LBApplicationClass ap = null; LBWordLibrary.LBDocumentClass doc = null; string orgFilename = null; string filename = null; FileInfo fi = null; FileStream fs = null; SectionInfo msi = null; Section sec = null; SectionConfig cfg = null; DocumentInfo docInfo = null; try { // check the Word file extension that is saved in the tblDocuments SQL database table docInfo = itmInfo.MyContent.MyEntry.MyDocument; if (docInfo.FileExtension.ToUpper() == ".DOCX") return; // already a DOCX - no need to convert // show user a status window of the Word section being converted to DOCX // use the section number (DisplayNumber) unless the length is zero, then use the section title (DisplayText) string statMsg = itmInfo.DisplayNumber; if (statMsg.Length == 0) statMsg = itmInfo.DisplayText; pmsg = new FrmPopupStatusMessage("Converting This Section to Word DOCX", statMsg); pmsg.Show(); // Get Document as file - it's placed in the user's temporary folder (like we do when we edit a Word section) myfile = new DSOFile(docInfo); // Open MSWord App ap = new LBWordLibrary.LBApplicationClass(); doc = ap.Documents.Open(myfile.FullName); // Older versions of PROMS saved the Word section as either a RTF or the old-style Word DOC file // In either case, we want to convert it to a Word DOCX file // So now create a file name with the .DOCX extension orgFilename = myfile.FullName.ToUpper(); filename = orgFilename.Replace(".RTF", ".DOCX").Replace(".DOC", ".DOCX"); // we want to convert either .RTF or .DOC Word sections // This calls Word's convert function to convert the opened .DOC or .RTF to DOCX and save it to our new DOCX file name doc.SaveAs(filename, LBWordLibrary.LBWdSaveFormat.wdFormatXMLDocument); // Convert to Word DOCX doc.Close(); doc = null; // Now read in the new .DOCX file and save the contents to the SQL database fi = new FileInfo(filename); fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);// B2016-053 long len = fs.Length; byte[] ByteArray = new byte[len]; int nBytesRead = fs.Read(ByteArray, 0, (int)len); bool isLibraryDocument = (docInfo.LibTitle != null && docInfo.LibTitle != ""); Document myDoc = null; if (isLibraryDocument) myDoc = Document.Get(docInfo.DocID); else myDoc = Document.MakeDocument(null, ByteArray, docInfo.DocAscii, docInfo.Config, ".DOCX"); // update the document information in the database msi = itmInfo as SectionInfo; sec = msi.Get(); cfg = sec.SectionConfig; if (!isLibraryDocument) cfg.MySection.MyContent.MyEntry.MyDocument = myDoc; // resetting MyDocument will clear the library doc link - so don't do if a library document else cfg.MySection.MyContent.MyEntry.MyDocument.DocContent = ByteArray; // only update .DocContent for library documents cfg.MySection.MyContent.MyEntry.MyDocument.FileExtension = ".DOCX"; // make sure the Word file extension is .DOCX cfg.MySection.MyContent.MyEntry.MyDocument.DTS = fi.LastWriteTimeUtc; cfg.MySection.MyContent.MyEntry.MyDocument.MarkDirty(); cfg.MySection.MyContent.MyEntry.Save(); // record in log file (aka error log) that conversion was done _MyLog.InfoFormat("Converted Word Section to DOCX - Old ID {0} - New ID {1} - {2}", docInfo.DocID, myDoc.DocID, statMsg); FileInfo orgFile = new FileInfo(orgFilename); orgFile.Delete();// delete the old temporary Word file (.DOC or .RTF) } catch (Exception ex) { _MyLog.ErrorFormat("Error converting Word section to DOCX - {0}", ex.Message); if (docInfo == null) { _MyLog.ErrorFormat("Error converting Word section to DOCX - ConvertWordSetionToDOXX: ItemID ={0} {1} {2}", itmInfo.ItemID, itmInfo.MyContent.Number, itmInfo.MyContent.Text); } else _MyLog.ErrorFormat("Error converting Word section to DOCX - ConvertWordSetionToDOXX: ItemID ={0} {1} {2} DOCID={3} LibTitle = {4}", itmInfo.ItemID, itmInfo.MyContent.Number, itmInfo.MyContent.Text, docInfo.DocID, docInfo.LibTitle); } finally { if (pmsg != null) pmsg.Close();// close the statue message if (ap != null) ap.Quit(); // close the Word app if (doc != null) doc.Close(); if (fs != null) fs.Close(); if (fi != null && fi.Exists) fi.Delete();// delete the temporary .DOCX file } } /// /// FixString processes the string returned and changes any symbols (0xF0??) to normal characters /// /// /// //private static string FixString(string str) //{ // StringBuilder results = new StringBuilder(); // foreach (char c in str) // { // if ((c & 0xFF00) == 0xF000) // results.Append((char)(c & 0xFF)); // else // results.Append((char)(c)); // } // return results.ToString(); //} } public partial class DocumentInfo { // B2017-249 Recover Temporary File And AutoSave support for MSWord private bool _ContentIsDirty = false; public bool ContentIsDirty { get { return _ContentIsDirty; } set { _ContentIsDirty = value; } } public string DocumentTitle { get { if (_LibTitle == string.Empty) return string.Format("Document {0}", _DocID); return _LibTitle; } } public string LibraryDocumentUsage { get { StringBuilder sb = new StringBuilder(); string sep = "\r\nUsed In:\r\n "; if (DocumentEntries == null) sb.Append("None"); else { int limit = 20; foreach (EntryInfo myEntry in DocumentEntries) { foreach (ItemInfo myItem in myEntry.MyContent.ContentItems) { --limit; if (limit < 0) { sb.Append(sep + "..."); return sb.ToString(); } ItemInfo proc = myItem.MyProcedure; sb.Append(sep + proc.DisplayNumber + " - " + proc.DisplayText); sep = "\r\n "; } } } return sb.ToString(); } } // C2019-033 - Used when saving a Word Attachment that is a Library Document (will get all usages) public string LibraryDocumentUsageAll { get { StringBuilder sb = new StringBuilder(); string sep = "\r\nUsed In:\r\n "; if (DocumentEntries == null) sb.Append("None"); else { foreach (EntryInfo myEntry in DocumentEntries) { foreach (ItemInfo myItem in myEntry.MyContent.ContentItems) { ItemInfo proc = myItem.MyProcedure; sb.Append(sep + proc.DisplayNumber + " - " + proc.DisplayText); sep = "\r\n "; } } } return sb.ToString(); } } public ItemInfoList LibraryDocumentUsageList { get { bool first = true; ItemInfoList iil = null; if (DocumentEntries == null) return null; foreach (EntryInfo myEntry in DocumentEntries) { foreach (ItemInfo myitem in myEntry.MyContent.ContentItems) { if (first) { iil = new ItemInfoList(myitem); first = false; } else iil.AddItem(myitem); } } return iil; } } #region DocumentConfig [NonSerialized] private DocumentConfig _DocumentConfig; public DocumentConfig DocumentConfig { get { return (_DocumentConfig != null ? _DocumentConfig : _DocumentConfig = new DocumentConfig(this)); } } public void RefreshConfig() { _DocumentConfig = null; } #endregion public static DocumentInfo GetByLibDocName(string libdoc, int versionID) { try { DocumentInfo tmp = DataPortal.Fetch(new LibDocCriteria(libdoc, versionID)); return tmp; } catch (Exception ex) { throw new DbCslaException("Error on DocumentInfo.GetByLibDocName", ex); } } [Serializable()] protected class LibDocCriteria { private string _LibDoc; public string LibDoc { get { return _LibDoc; } } private int _VersionID; public int VersionID { get { return _VersionID; } } public LibDocCriteria(string libdoc, int versionid) { _LibDoc = libdoc; _VersionID = versionid; } } private void DataPortal_Fetch(LibDocCriteria criteria) { if (_MyLog.IsDebugEnabled) _MyLog.DebugFormat("[{0}] DocumentInfo.DataPortal_Fetch", GetHashCode()); try { using (SqlConnection cn = Database.VEPROMS_SqlConnection) { using (SqlCommand cm = cn.CreateCommand()) { cm.CommandType = CommandType.StoredProcedure; cm.CommandText = "getDocumentByLibDoc"; cm.Parameters.AddWithValue("@LibDoc", criteria.LibDoc); cm.Parameters.AddWithValue("@VersionID", criteria.VersionID); cm.CommandTimeout = Database.DefaultTimeout; using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader())) { if (!dr.Read()) { _ErrorMessage = "No Record Found"; return; } ReadData(dr); } } } } catch (Exception ex) { if (_MyLog.IsErrorEnabled) _MyLog.Error("DocumentInfo.DataPortal_Fetch", ex); throw new DbCslaException("DocumentInfo.DataPortal_Fetch", ex); } } } public partial class DocumentInfoList { public static DocumentInfoList GetLibraries(bool forceload) { try { if (!forceload && _DocumentInfoList != null) return _DocumentInfoList; DocumentInfoList tmp = DataPortal.Fetch(new LibraryCriteria(true)); DocumentInfo.AddList(tmp); tmp.AddEvents(); _DocumentInfoList = tmp; return tmp; } catch (Exception ex) { throw new DbCslaException("Error on DocumentInfoList.Get", ex); } } [Serializable()] protected class LibraryCriteria { private bool _IsLibrary; public bool IsLibrary { get { return _IsLibrary; } set { _IsLibrary = value; } } public LibraryCriteria(bool islibrary) { _IsLibrary = islibrary; } } private void DataPortal_Fetch(LibraryCriteria criteria) { this.RaiseListChangedEvents = false; if (_MyLog.IsDebugEnabled) _MyLog.DebugFormat("[{0}] DocumentInfoList.DataPortal_Fetch", GetHashCode()); try { using (SqlConnection cn = Database.VEPROMS_SqlConnection) { using (SqlCommand cm = cn.CreateCommand()) { cm.CommandType = CommandType.StoredProcedure; cm.CommandText = "getLibraryDocuments"; cm.CommandTimeout = Database.DefaultTimeout; using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader())) { IsReadOnly = false; while (dr.Read()) this.Add(new DocumentInfo(dr)); IsReadOnly = true; } } } } catch (Exception ex) { if (_MyLog.IsErrorEnabled) _MyLog.Error("DocumentInfoList.DataPortal_Fetch", ex); throw new DbCslaException("DocumentInfoList.DataPortal_Fetch", ex); } this.RaiseListChangedEvents = true; } } public class DSOFile : IDisposable { private static readonly log4net.ILog _MyLog = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #region Fields private bool _IsDisposed; private DocumentInfo _MyDocument = null; private FileInfo _MyFile = null; private string _Extension = "DOC"; #endregion #region Properties public DocumentInfo MyDocument { get { return _MyDocument; } set { // B2017-249 Don't delete last DSOFile //TryDelete(); _MyDocument = value; CreateFile(); } } public FileInfo MyFile { get { return _MyFile; } } public string Extension { get { return _Extension; } set { _Extension = value; } } #endregion #region Private Methods private void TryDelete() { if (_MyDocument == null) return; if (_MyFile == null) return; // B2017-249 Recover Temporary File And AutoSave support for MSWord _MyFile.Refresh(); if (_MyFile.Exists && (TimeSpan.FromTicks(_MyDocument.DTS.Ticks - _MyFile.LastWriteTimeUtc.Ticks).TotalSeconds > 1.0)) { try { _MyFile.Delete(); } catch (IOException ex) { // _MyLog.Error("TryDelete", ex); } finally { _MyFile = null; _MyDocument = null; } } } private bool _Created = false; private int _Unique = 0; private string Unique { get { string retval = string.Empty; if (_Unique != 0) retval = "_" + _Unique.ToString(); _Unique++; return retval; } } private void CreateFile() { while (!_Created) CreateTemporaryFile(); } // B2017-249 Recover Temporary File And AutoSave support for MSWord public bool ContentIsDirty { get { _MyFile.Refresh(); return _MyFile.Exists && (_MyFile.LastWriteTimeUtc > StartDTS); } } private DateTime _StartDTS; public DateTime StartDTS { get { return _StartDTS; } set { _StartDTS = value; } } //B2017-260 Prefix the Temporary document file with server name and database name private static string _TempFilePrefix = string.Empty; public static string TempFilePrefix { get { return DSOFile._TempFilePrefix; } set { DSOFile._TempFilePrefix = value; } } private void CreateTemporaryFile() { _Unique = 0; try { if (_MyDocument != null) { //B2017-260 Prefix the Temporary document file with server name and database name _MyFile = new FileInfo(string.Format(@"{0}\{1}_tmp_{2}{3}{4}", VlnSettings.TemporaryFolder, TempFilePrefix, MyDocument.DocID, Unique, MyDocument.FileExtension)); FileInfo matchingFile = null; while (_MyFile.Exists) { // B2017-249 Recover Temporary File And AutoSave support for MSWord if (!Temporary) { double dtsDiff = TimeSpan.FromTicks(_MyFile.LastWriteTimeUtc.Ticks - _MyDocument.DTS.Ticks).TotalSeconds; if (dtsDiff > 1.0) { if (System.Windows.Forms.MessageBox.Show("Restore Unsaved version from " + String.Format("{0:MM/dd/yyyy HH:mm:ss}", _MyFile.LastWriteTime), "Restore Unsaved Version", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) { _MyDocument.ContentIsDirty = true; StartDTS = _MyFile.LastWriteTimeUtc; _Created = true; return; } else { _MyFile.Delete(); break; } } else if (dtsDiff > -.5 && dtsDiff < .5) { matchingFile = _MyFile; } } //B2017-260 Prefix the Temporary document file with server name and database name _MyFile = new FileInfo(string.Format(@"{0}\{1}_tmp_{2}{3}{4}", VlnSettings.TemporaryFolder, TempFilePrefix, MyDocument.DocID, Unique, MyDocument.FileExtension)); } // B2017-249 Recover Temporary File And AutoSave support for MSWord if (matchingFile != null) { _MyFile = matchingFile; _MyDocument.ContentIsDirty = false; StartDTS = _MyFile.LastWriteTimeUtc; _Created = true; return; } FileStream fs = _MyFile.Create(); if (MyDocument.DocContent != null) fs.Write(MyDocument.DocContent, 0, MyDocument.DocContent.Length); fs.Close(); _MyFile.CreationTimeUtc = _MyDocument.DTS; _MyFile.LastWriteTimeUtc = _MyDocument.DTS; // B2017-249 Recover Temporary File And AutoSave support for MSWord StartDTS = _MyFile.LastWriteTimeUtc; _Created = true; _MyDocument.ContentIsDirty = false; } } catch (Exception ex) { _MyLog.Error("***** Could not create " + _MyFile.FullName, ex); Console.WriteLine(ex.Message); } } public string FullName { get { return _MyFile.FullName; } set { if (FullName != value) _MyFile = new FileInfo(value); } } public void SaveFile(float length, string ascii, ItemInfo myItemInfo, bool cvtLibDoc, VolianStatusChange statusChange) { // TODO: Add Try & Catch logic if (_MyDocument == null) return; Document doc = _MyDocument.Get(); MyFile.Refresh(); // B2017-255 Get the latest file length before reading and saving FileStream fs = _MyFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // B2017-262 Verify that the file length is correct. long savLen2 = fs.Length; long savLen = _MyFile.Length; if (savLen2 != savLen) _MyLog.WarnFormat("FileInfoLength = {0}, FileStreamLength= {1}", savLen, savLen2); Byte[] buf = new byte[Math.Max(savLen, savLen2)]; fs.Read(buf, 0, buf.Length); fs.Close(); // Handle if this is a libdoc & the user wanted to convert to a non-library document. if (cvtLibDoc) { // if just one usage (this one), then just convert this to a non-library document. If there is more // than one usage, make a copy so that the rest of the usages still point to the library document. if (doc.DocumentEntryCount == 1) doc.LibTitle = null; else { // make new document with 'no' libtitle. Then link this to the item/content via the entry. // the data gets set below. Document tdoc = Document.MakeDocument(null, null, null, null, doc.FileExtension); // B2016-204 added doc.FileExtension _MyDocument = DocumentInfo.Get(doc.DocID); doc = tdoc; using (Entry ent = myItemInfo.MyContent.MyEntry.Get()) { ent.MyDocument = tdoc; ent.Save(); } } } doc.FileExtension = MyFile.Extension; doc.DocContent = buf; doc.DocAscii = ascii; //doc.UpdateDocAscii(_MyFile.FullName); DocumentConfig cfg = new DocumentConfig(doc); cfg.Edit_Initialized = true; doc.Config = cfg.ToString(); doc.UserID = Volian.Base.Library.VlnSettings.UserID; // B2017-262 use the date time stamp from the file rather than the clock doc.DTS = _MyFile.LastWriteTimeUtc; //doc.DTS = DateTime.Now.ToUniversalTime(); doc = doc.Save(); if (myItemInfo != null) // B2016-131 if myItemInfo is null, the lib doc is not referenced from any procedure. Just save changes, but don't generate a PDF { List roids = new List(); string pdfTmp = MSWordToPDF.ToPDFReplaceROs(_MyDocument, roids, myItemInfo, statusChange); FileInfo pdfFile = new FileInfo(pdfTmp); fs = pdfFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); buf = new byte[pdfFile.Length]; fs.Read(buf, 0, buf.Length); fs.Close(); pdfFile.Delete(); Pdf.DeleteAll(doc.DocID); // clear the PDF table for DocID first DocStyle myDocStyle = myItemInfo.ActiveSection.MyDocStyle; SectionConfig sc = myItemInfo.ActiveSection.MyConfig as SectionConfig; if (sc != null && sc.Section_WordMargin == "Y") { using (Pdf myPdf = Pdf.MakePdf(doc, MSWordToPDF.DebugStatus, 0, 0, 0, 0, (double)myItemInfo.MSWordPageCount, buf)) {; } } else { using (Pdf myPdf = Pdf.MakePdf(doc, MSWordToPDF.DebugStatus, (int)myDocStyle.Layout.TopMargin, (int)myDocStyle.Layout.PageLength, (int)myDocStyle.Layout.LeftMargin, (int)myDocStyle.Layout.PageWidth, (double)myItemInfo.MSWordPageCount, buf)) {; } } doc.UpdateDRoUsages(roids); doc.Save(); if (savLen != _MyFile.Length) _MyLog.ErrorFormat("DSO FRAMER: File size changed during Save for Word Document, beginSize = {0}, endSize = {1}", savLen, _MyFile.Length); } } #endregion #region Properties // B2017-249 Recover Temporary File And AutoSave support for MSWord private bool _Temporary = false; public bool Temporary { get { return _Temporary; } set { _Temporary = value; } } #endregion #region Constructors public DSOFile(DocumentInfo myDocument) { MyDocument = myDocument; } public DSOFile(DocumentInfo myDocument, bool temporary) { Temporary = temporary; MyDocument = myDocument; } #endregion #region Destructor ~DSOFile() { Dispose(false); } public void Dispose() { Dispose(false); GC.SuppressFinalize(this); } protected void Dispose(bool disposing) { if (!_IsDisposed) { _IsDisposed = true; // B2017-249 Don't Delete last DSOFile //TryDelete(); } } #endregion } public static class MSWordToPDF { #region Log4Net private static readonly log4net.ILog _MyLog = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #endregion #region Enums public enum DeviceCap { VERTRES = 10, DESKTOPVERTRES = 117 } #endregion #region Fields // B2019-123 Verify that the RO token is the correct format private static Regex _ROAccPageTokenPattern = new Regex("[<][^<>-]+-[^<>]+[>]"); // used to save Word sections with resolved ROs (for export generated from Approve) private static LBApplicationClass _MyApp = null; private static SectionInfo _MySection; private static Dictionary _DocReplace; private static bool _CloseWordWhenDone = true; private static System.Drawing.Color _OverrideColor = System.Drawing.Color.Transparent; private static System.Windows.Forms.Form _FormForPlotGraphics = null; private static int _DebugStatus = 0; // B2018-071 Save list of DocIDs for invalid document so that error log messages are not repeated private static List _MissingDocs = new List(); private static bool _Automatic = false; // B2022-083: Support Conditional RO Values --> Added an internal rochild dictionary cache specifically for printing // // As you loop through all of the specific RO AccPageKeys for the current MsWord Document Section, the code will first // check the internal rochild cache for the base (12) digit roid Key/Value, if it doesn't already exist, then load the // RoChild from the database, add it to the rochild print cache, and then based on the length of the roid, either (12) or (16), // return either the base RoChild Object with its collection of children return values or the specific return value RoChild object. // // Notes** // 1) Every base RoChild object must have one to many specific return values. // 2) The first RoChild return value in the "children" collection will always have a roid Ext of "0041" or and a accPage Ext of "A" // 3) The first RoChild return value in the "children" collection "0041/A" will also be used as the Default RoChild return value if a // specific RoChild return value (by (4) digit Extension) doesn't exist in the base RoChild Objects "children" collection of return values. // Ex: The specific RoChild return value for the roidExt "0044" doesn't exist now for some reason, then always return // the first RoChild return value "0041" as the Default RO return value for that RO Object // 4) Any (16) digit roids with an invalid/padded roid Ext (last (4) digits of roid = "0000") are automatically converted to use the "0041" // roid Ext so the RO's with the older roidExt formats will be backwards compatible with the new logic/code and they will now be more consistent like // the newer roidExt formats. The actual data in the database records with the roids that have the older "0000" extension (ROUsages & DROUsages Tables) // are not physically modified or updated in the source tables. The RofstLookup method "FormatRoidKey" is always called first in the code before attempting // to do any lookups or comparisons with any other roids. By standardizing the roid / accPageID formats and values first before running any other rules/logic, // dramatically simplifies the down stream code for how to process/convert the Ro values // 5) If a specific RoChild return value does not exist (maybe it was removed in the ROEditor / latest RO.FST file), then just return // the first RoChild return value in the "children" collection (0041) instead // B2022-088: Find Doc Ro button not working in Word Sections public static Dictionary RoPrintCache = new Dictionary(); #endregion #region Properties public static Dictionary DocReplace { get { return MSWordToPDF._DocReplace; } set { MSWordToPDF._DocReplace = value; } } public static bool CloseWordWhenDone { get { return MSWordToPDF._CloseWordWhenDone; } set { MSWordToPDF._CloseWordWhenDone = value; } } public static LBApplicationClass MyApp { get { if (_MyApp == null) _MyApp = new LBApplicationClass(); return _MyApp; } } public static int DebugStatus { get { return MSWordToPDF._DebugStatus; } set { MSWordToPDF._DebugStatus = value; } } public static System.Drawing.Color OverrideColor { get { return MSWordToPDF._OverrideColor; } set { MSWordToPDF._OverrideColor = value; } } public static System.Windows.Forms.Form FormForPlotGraphics { get { return MSWordToPDF._FormForPlotGraphics; } set { MSWordToPDF._FormForPlotGraphics = value; } } public static bool Automatic { // C2018-035 Don't use a MessageBox if in automatic (Baseline) testing mode. get { return MSWordToPDF._Automatic; } set { MSWordToPDF._Automatic = value; } } #endregion #region Public Methods public static string GetDocPdf(SectionInfo sect, Color overrideColor) { _MySection = sect; DocumentInfo mydoc = sect.MyContent.MyEntry.MyDocument; UpdateDocPdf(mydoc, overrideColor, sect); string fileName = GetFileName(sect); FileInfo fi = new FileInfo(fileName); FileStream fs = fi.Create(); PdfInfo myPdf = PdfInfo.Get(sect, false); if (myPdf != null && myPdf.DocPdf != null) fs.Write(myPdf.DocPdf, 0, myPdf.DocPdf.Length); fs.Close(); return fileName; } public static bool UpdateDocPdf(DocumentInfo mydoc, Color overrideColor, ItemInfo sect) { //B2019-144 Set the document text color to Red (overlay) or Black (normal) Color lastColor = MSWordToPDF.OverrideColor;// Remember last override color if (MSWordToPDF.OverrideColor != overrideColor) MSWordToPDF.OverrideColor = overrideColor; PdfInfo myPdf = PdfInfo.Get(sect, DocReplace != null); //B2019-144 Set the document text color to Red (overlay) or Black (normal) if (MSWordToPDF.OverrideColor != lastColor) MSWordToPDF.OverrideColor = lastColor; // Restore last override color //MSWordToPDF.OverrideColor = Color.Black; // B2019-090 reset to black text (when a Complete RO Report was created after printing procedure, X/Y Plot was in red text) return true; } public static bool SetDocPdf(DocumentInfo docInfo, ItemInfo sect) { string pdfTmp = null; List roids = new List(); try { pdfTmp = MSWordToPDF.ToPDFReplaceROs(docInfo, roids, sect, null); } catch (Exception ex) { if (!_MissingDocs.Contains(docInfo.DocID))// B2018-071 Only add the message once to the error log { _MyLog.WarnFormat("Error trying to create PDF DocID = {0}", docInfo.DocID);// 2018-071 MS Word section could not be converted to PDF _MissingDocs.Add(docInfo.DocID); } return false; } if (pdfTmp == null) return false; FileInfo pdfFile = new FileInfo(pdfTmp); FileStream fs = pdfFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); Byte[] buf = new byte[pdfFile.Length]; fs.Read(buf, 0, buf.Length); fs.Close(); // B2023-022 & B2023-023 commented out the deletion of the temporary Word section PDF file // These files are deleted when the procedure pdf file is closed after being generated. // PROMS was crashing because it could not find these temporary files to delete. //try //{ // pdfFile.Delete(); //} //catch { } using (Document doc = docInfo.Get()) { DocStyle myDocStyle = sect.ActiveSection.MyDocStyle; SectionConfig sc = sect.ActiveSection.MyConfig as SectionConfig; int ss = sect.MyDocVersion.DocVersionConfig.SelectedSlave; if (sc != null && sc.Section_WordMargin == "Y") { using (Pdf myPdf = Pdf.MakePdf(doc, ss * 10 + MSWordToPDF.DebugStatus, 0, 0, 0, 0, (double)sect.MSWordPageCount, buf)) {; } } else { using (Pdf myPdf1 = Pdf.MakePdf(doc, ss * 10 + MSWordToPDF.DebugStatus, (int)myDocStyle.Layout.TopMargin, (int)myDocStyle.Layout.PageLength, (int)myDocStyle.Layout.LeftMargin, (int)myDocStyle.Layout.PageWidth, (double)sect.MSWordPageCount, buf)) {; } } doc.UpdateDRoUsages(roids); doc.Save(); } docInfo.RefreshConfig(); return true; } public static string ToPDFReplaceROs(DocumentInfo doc, List roids, ItemInfo sect, VolianStatusChange statusChange) { if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) Volian.Base.Library.BaselineMetaFile.WriteLine("WrdSec SecNum=\"{0}\" SecTitle=\"{1}\" Itemid={2}", sect.ActiveSection.DisplayNumber, sect.ActiveSection.DisplayText, sect.ItemID); // C2018-003 fixed use of getting the active section return ToPDFReplaceROs(sect, false, roids, statusChange); } public static string ToPDFReplaceROs(ItemInfo sect, bool openPdf, List roids, VolianStatusChange statusChange) { string fileName = GetFileName(sect); DocStyle myDocStyle = sect.MyDocStyle; ProcedureInfo proc = sect.MyProcedure; DocVersionInfo dvi = proc.ActiveParent as DocVersionInfo; bool hasRos = false; ROFstInfo rofst = null; ROFSTLookup lookup = null; bool convertCaretToDeltaSymbol = (sect.ActiveSection != null) ? sect.ActiveSection.ActiveFormat.PlantFormat.FormatData.SectData.ConvertCaretToDelta : false; // C2018-003 fixed use of getting the active section if (dvi.DocVersionAssociationCount > 0) { hasRos = true; rofst = dvi.DocVersionAssociations[0].MyROFst; // The following code sets the DocVersionInfo and the OtherChild properties for the current RofstLookup instance (lookup), and also enables Caching // When printing or converting word sections to pdf, any RoChild "value" is the Unit Specific Value for the SelectedSlave lookup = rofst.GetROFSTLookup(dvi, Convert.ToString(sect.MyDocVersion.DocVersionConfig.SelectedSlave)); } // B2017-249 Recover Temporary File And AutoSave support for MSWord using (DSOFile myFile = new DSOFile(sect.MyContent.MyEntry.MyDocument, true)) { // Increase the priority of the Word Process so that the pdf creation happens quickly Process[] myProcessess = Process.GetProcessesByName("winword"); foreach (Process myProcess in myProcessess) { try { if (myProcess.PriorityClass != ProcessPriorityClass.High && myProcess.MainWindowTitle == string.Empty) myProcess.PriorityClass = ProcessPriorityClass.High; } catch (Exception ex) { while (ex != null) { Console.WriteLine("{0} - {1}", ex.GetType().Name, ex.Message); ex = ex.InnerException; } } } // Use positions relative to margins bool adjustMargins = true; // Set Application Level Properties //MyApp.Visible = false; // Open new Document LBDocumentClass myDoc = MyApp.Documents.Open(myFile.FullName, false); try { SectionConfig sc = sect.ActiveSection.MyConfig as SectionConfig; if (sc == null || sc.Section_WordMargin == "N") { AdjustMargins(myDocStyle, myDoc, true); // B2020-122 Reset RightIndent to zero if it is set AP Enhanced Background Documents/AP-RHR.2 // B2020-136 for Ginna (RGE) only. Causes problems for WCN Training/APs/AP30E-002 myDoc.Application.Selection.WholeStory(); if (myDoc.Application.Selection.ParagraphFormat.RightIndent != 0 && (sect.ActiveFormat.Name.StartsWith("RGE") || sect.ActiveFormat.Name.StartsWith("Ginna"))) { myDoc.Application.Selection.ParagraphFormat.RightIndent = 0; } } else { adjustMargins = false; // Use absolute positions } } catch (Exception ex) { // B2018-089 - Made error log output more useful AddErrorLogInfoMarginNotFixed(sect, "Word section could not adjust margins"); } string txtForBaseline = string.Empty; // C2018-018 save the contents of Word sections - after resolving RO values if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) Volian.Base.Library.BaselineMetaFile.WriteLine("++BgnTxt++"); LBSelection sel = MyApp.Selection; sel.WholeStory(); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) txtForBaseline = sel.Text; LBSelection selxy = hasRos ? FindXyPlot() : null; // look for XY Plot language typed into word section (not an RO) string pngFile = VlnSettings.TemporaryFolder + @"\XYPlot1.png"; //@"C:\Temp\XYPlot1.png"; try { File.Delete(pngFile); } catch { } int filecount = 1; while (selxy != null) { // B2017-007 Fixed logic to properly position an XY Plot string xyplot = selxy.Text; xyplot = xyplot.Replace("`", "\xB0"); xyplot = xyplot.Replace("\xF8", "\xB0"); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) { txtForBaseline = txtForBaseline.Replace("`", "\xB0"); txtForBaseline = txtForBaseline.Replace("\xF8", "\xB0"); } if (convertCaretToDeltaSymbol) { xyplot = xyplot.Replace("^", "\x394"); // delta xyplot = xyplot.Replace("\x7F", "\x394"); //delta if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) { txtForBaseline = txtForBaseline.Replace("^", "\x394"); txtForBaseline = txtForBaseline.Replace("\x7F", "\x394"); } } // The 16bit code must have kept the carriage returns in the word doc, if there // are carriage returns in the plot language. Count number of carriage return/newlines // so that they can be added back. int cnt = 0; int indxCr = xyplot.IndexOf("\r"); while (indxCr > 0) { cnt++; indxCr = xyplot.IndexOf("\r", indxCr + 1); } // Setting the selected text to "" actually sets it to "\r", thus cnt-- (subtract one from cnt since there's already a return in the string. cnt--; PointF pt = GetLocation(selxy, adjustMargins); float y = pt.Y; string resXyPlot = xyplot; string txt = FindEmbeddedText(selxy.Text, ref resXyPlot); if (txt != null) { selxy.Text = txt; xyplot = resXyPlot.Replace(">\r>", ">>\r"); } else { selxy.Text = string.Empty; if (cnt > 0) for (int icnt = 0; icnt < cnt; icnt++) selxy.Text = selxy.Text + "\r"; } pngFile = VlnSettings.TemporaryFolder + @"\XYPlot" + filecount.ToString() + @".png"; //@"C:\Temp\XYPlot1.png"; filecount++; try // C2018-035 if an error occurs make the error message more specific { RectangleF plotRect = CreatePlot(pngFile, xyplot, 600F, FormForPlotGraphics); float yAdjust = selxy.Font.Size; float xxx = pt.X + plotRect.X; if (xxx < 0 && xxx > -.5) xxx = 0; float yyy = yAdjust + y + plotRect.Y; LBShape shape = myDoc.Shapes.AddPicture(pngFile, xxx, yyy, selxy.Range); try { File.Delete(pngFile); } catch { } if (adjustMargins) { shape.RelativeVerticalPosition = LBWdRelativeVerticalPosition.wdRelativeVerticalPositionMargin; shape.RelativeHorizontalPosition = LBWdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin;// .wdRelativeHorizontalPositionMargin; } else { shape.RelativeVerticalPosition = LBWdRelativeVerticalPosition.wdRelativeVerticalPositionPage; shape.RelativeHorizontalPosition = LBWdRelativeHorizontalPosition.wdRelativeHorizontalPositionPage;// .wdRelativeHorizontalPositionMargin; } shape.LockAspectRatio = LBMsoTriState.msoTrue; shape.Width = plotRect.Width; shape.Left = xxx; shape.Top = pt.Y; } catch (Exception ex)// C2018-035 if an error occurs make the error message more specific { // C2018-035 Don't use a MessageBox if in automatic(Baseline) testing mode. if (!Automatic) System.Windows.Forms.MessageBox.Show(ex.Message, "X/Y Plot Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation); _MyLog.WarnFormat("Problem with XYPlot {0} - {1}", ex.GetType().Name, ex.Message); } selxy.WholeStory(); selxy = FindXyPlot(); } sel.WholeStory(); string roTokenForBaseline = string.Empty; if (statusChange != null) statusChange(VolianStatusType.Initialize, sel.End, "Refreshing ROs"); sel = hasRos ? FindRO() : null; int roCount = 0; // force Print of MS Word Attachment to Final without revisions and comments myDoc.ActiveWindow.View.ShowRevisionsAndComments = false; myDoc.ActiveWindow.View.RevisionsView = LBWdRevisionsView.wdRevisionsViewFinal; int lastStart = sel == null ? 0 : sel.Start; while (sel != null) { if (!string.IsNullOrEmpty(sel.Text)) { if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) roTokenForBaseline = sel.Text; if (statusChange != null) statusChange(VolianStatusType.Update, sel.Start, string.Format("{0} ROs Refreshed", roCount++)); // B2022-088: [JPR] Find Doc Ro button not working in Word Sections // B2022-098: [JPR] ROs not being resolved in Word Sections ROFSTLookup.rochild roc = GetCachedRoByAccPageID(lookup, sel.Text, convertCaretToDeltaSymbol); int roType = roc.type; string roValue = roc.value; // If roid is valid then add roidKey to the list of roids if (!string.IsNullOrEmpty(roc.roid)) { string roidkey = string.Format("{0}:{1}", rofst.RODbID, roc.roid); if (!roids.Contains(roidkey)) roids.Add(roidkey); } if (roType == 8) // Image { //Console.WriteLine("Image: {0} - {1}", sect.MyContent.Number, sect.MyContent.Text); bool imageROTokenReplaced = false; string[] vals = roValue.Split("\n".ToCharArray()); ROImageInfo roImage = ROImageInfo.GetByROFstID_FileName(rofst.ROFstID, vals[0]); if (roImage == null) { // need code to go and get an ROImaage if it exists roImage = rofst.GetROImageByFilename(vals[0], sect); } if (roImage != null) { ROImageFile roImageFile = new ROImageFile(roImage); float width = 72 * Int32.Parse(vals[3], System.Globalization.NumberStyles.AllowHexSpecifier) / 12.0F; int lines = Int32.Parse(vals[2], System.Globalization.NumberStyles.AllowHexSpecifier); float height = 72 * lines / 6.0F; PointF pt = GetLocation(sel, adjustMargins); //_MyLog.WarnFormat("pt.x={0},pt.y={1}", pt.X,pt.Y); float xxx = pt.X; if (xxx < 0 && xxx > -.5) xxx = 0; float yyy = pt.Y; //_MyLog.WarnFormat("xxx={0},yyy={1}", xxx, yyy); sel.Text = string.Empty; LBShape shape = myDoc.Shapes.AddPicture(roImageFile.MyFile.FullName, xxx, yyy, sel.Range); try { File.Delete(pngFile); } catch { } if (adjustMargins) { shape.RelativeVerticalPosition = LBWdRelativeVerticalPosition.wdRelativeVerticalPositionMargin; shape.RelativeHorizontalPosition = LBWdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin;// .wdRelativeHorizontalPositionMargin; } else { shape.RelativeVerticalPosition = LBWdRelativeVerticalPosition.wdRelativeVerticalPositionPage; shape.RelativeHorizontalPosition = LBWdRelativeHorizontalPosition.wdRelativeHorizontalPositionPage;// .wdRelativeHorizontalPositionMargin; } shape.LockAspectRatio = LBMsoTriState.msoTrue; shape.Width = width; shape.Height = height; shape.Left = xxx; shape.Top = pt.Y; //_MyLog.WarnFormat("Shape.Left={0}, Shape.Top={1}", shape.Left, shape.Top); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) txtForBaseline = txtForBaseline.Replace(roTokenForBaseline, string.Format("[{0}]", vals[0])); imageROTokenReplaced = true; } if (!imageROTokenReplaced) { sel.Text = string.Format("Bad Image Link (Missing Image File:{0})", vals[0]); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) txtForBaseline = txtForBaseline.Replace(roTokenForBaseline, sel.Text); } } else if (roType == 4) // X-Y Plot { try { // B2017-007 Fixed logic to properly position an XY Plot if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) txtForBaseline = txtForBaseline.Replace(sel.Text, roValue); // An X/Y Plot RO type might have text preceding the Plot Commands int pstart = roValue.IndexOf("< -.5) xxx = 0; float y = pt.Y; int idx = sel.Text.IndexOf("\r"); while ((idx > 0) && (idx < sel.Text.Length + 1)) { idx = sel.Text.IndexOf("\r", idx + 1); y += (float)sel.ParagraphFormat.LineSpacing; // get_Information(LBWdLineSpacing.wdLineSpaceSingle); } float yAdjust = sel.Font.Size; float yyy = yAdjust + y + plotRect.Y; LBShape shape = myDoc.Shapes.AddPicture(pngFile, xxx, yyy, sel.Range); try { File.Delete(pngFile); } catch { } if (adjustMargins) { shape.RelativeVerticalPosition = LBWdRelativeVerticalPosition.wdRelativeVerticalPositionMargin; shape.RelativeHorizontalPosition = LBWdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin;// .wdRelativeHorizontalPositionMargin; } else { shape.RelativeVerticalPosition = LBWdRelativeVerticalPosition.wdRelativeVerticalPositionPage; shape.RelativeHorizontalPosition = LBWdRelativeHorizontalPosition.wdRelativeHorizontalPositionPage;// .wdRelativeHorizontalPositionMargin; } shape.LockAspectRatio = LBMsoTriState.msoTrue; shape.Width = plotRect.Width; shape.Left = xxx; shape.Top = pt.Y; sel.WholeStory(); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) Volian.Base.Library.BaselineMetaFile.WriteLine("{0}", TextForBaseline.FixText(txtForBaseline, true)); } catch (Exception ex) { // something is wrong with the X/Y plot RO. Just print out the un-processed X/Y plot RO value if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) txtForBaseline = txtForBaseline.Replace(roTokenForBaseline, string.Format("BAD XYPLOT: {0}", roValue)); sel.Text = roValue; } } else { // if roValue is null, then InsertROValue will put in "RO Not Found" for the value float indent = (float)sel.get_Information(LBWdInformation.wdHorizontalPositionRelativeToTextBoundary); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) txtForBaseline = txtForBaseline.Replace(roTokenForBaseline, roValue); // B2017-217 Added logic so that underscores are not converted to underline // C2018-003 fixed use of getting the active section InsertROValue(sel, roValue, sect.ActiveFormat.PlantFormat.FormatData.ROData.UpRoIfPrevUpper, indent, (sect.ActiveSection != null) ? sect.ActiveSection.ActiveFormat.PlantFormat.FormatData.SectData.ConvertUnderscoreToUnderline : false); } } lastStart = sel.Start; sel = FindRO(); if (sel != null && !string.IsNullOrEmpty(sel.Text) && sel.Start == lastStart) { Console.WriteLine("Seems to be repeating find of ro that is not an ro: " + sel.Text); sel = null; } } if (statusChange != null) statusChange(VolianStatusType.Update, 0, "Creating PDF"); sel = MyApp.Selection; sel.WholeStory(); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) Volian.Base.Library.BaselineMetaFile.WriteLine("{0}", TextForBaseline.FixText(txtForBaseline, true)); if (sel.Range.Font.Color != (LBWdColor)9999999 || OverrideColor == System.Drawing.Color.Red)// B2017-144 If the font color is 9999999 then the color is mixed, and if the override color isn't red, don't change the color. sel.Range.Font.Color = (LBWdColor)WordColor(OverrideColor == System.Drawing.Color.Transparent ? System.Drawing.Color.Black : OverrideColor); sect.MSWordPageCount = myDoc.Length; fileName = CreatePDF(fileName, openPdf, MSWordToPDF.DebugStatus); if (DocReplace != null) { MyApp.ActiveDocument.SaveAs2000(); // save the word document containing resolved ROs DocReplace.Add(sect.MyContent.MyEntry.DocID, GetBytes(MyApp.ActiveDocument.FullName)); } CloseDocument(); // B2017-249 Try to Delete Temporary file try { myFile.MyFile.Delete(); } catch { } if (CloseWordWhenDone) { CloseAppAfterWait(); } if (statusChange != null) statusChange(VolianStatusType.Complete, 0, string.Empty); if (Volian.Base.Library.BaselineMetaFile.IsOpen && Volian.Base.Library.BaselineMetaFile.IncludeWordSecText) Volian.Base.Library.BaselineMetaFile.WriteLine("++EndTxt++"); // [jpr 2022.07.26] - For memory optimization //GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; //jsj 2-15-2016 - for memory garbage collection //GC.Collect(); return fileName; } } private static ROFSTLookup.rochild GetCachedRoByAccPageID(ROFSTLookup lookup, string selText, bool convertCaretToDeltaSymbol) { string accPageBase = string.Empty; string accPageExt = string.Empty; try { ROFSTLookup.rochild roc = ROFSTLookup.GetEmptyRoChild(); string accPageKey = lookup.FormatAccPageKey(selText, ref accPageBase, ref accPageExt); if (RoPrintCache == null) // B2022-141 if RoPrintCache is null then ititialize it MSWordToPDF.RoPrintCache = new Dictionary(); // Check if the Rochild is in the PrintCache (use base accPageID without specific extension) if (!RoPrintCache.ContainsKey(accPageKey)) //B2023-006 pass in the actual key instead of the base key.//old code=> accPageBase)) { // Lookup RoChild Info from database roc = lookup.GetROChildByAccPageID(accPageKey); //B2023-006 pass in the actual key instead of the base key.//old code=> accPageBase); // Check if RO is valid if (roc.ID < 0 || string.IsNullOrEmpty(roc.roid)) return ROFSTLookup.GetEmptyRoChild(); // If Valid Rochild, then add Rochild to Print Cache RoPrintCache.Add(accPageKey, roc); //B2023-006 pass in the actual key instead of the base key.//old code=> accPageBase, roc); } else // Get Base Rochild from Print Cache { roc = (ROFSTLookup.rochild)RoPrintCache[accPageKey]; //B2023-006 pass in the actual key instead of the base key.//old code=> [accPageBase]; } // Check if RO is a "Unit Information" value (Ex: , etc..) if (roc.roid.StartsWith("FFFF")) return roc; // All ROs at this point should have a specific accPageExt or the default (A/0041) if (roc.children != null && roc.children.Count() > 0) roc = (roc.children.Where(x => x.appid.EndsWith(accPageExt) && !string.IsNullOrEmpty(x.value)).Any()) ? roc.children.Where(x => x.appid.EndsWith(accPageExt)).Single() : roc.children.First(); // Check the RoType roc.type = ((roc.type & 4) == 4 && roc.value.StartsWith("< (8.5f * 72)) && newWidth > newLength) { if (myDoc.PageSetup.Orientation != LBWdOrientation.wdOrientLandscape) { myDoc.PageSetup.Orientation = LBWdOrientation.wdOrientLandscape; myDoc.PageSetup.RightMargin = 11 * 72 - newWidth; } else { myDoc.PageSetup.RightMargin = newRight; } } else { myDoc.PageSetup.RightMargin = newRight; // bug fix B2017-140, B2017-142 right margin was too short } try { // B2017-138 Add error handling for setting Bottom Margin myDoc.PageSetup.BottomMargin = newBottom; myDoc.PageSetup.LeftMargin = newLeft; myDoc.PageSetup.TopMargin = newTop; } catch (Exception ex) { if (_MySection != null) { // B2018-089 - Made error log output more useful AddErrorLogInfoMarginNotFixed(_MySection, "MSWord could not set Section margins"); } else { // B2018-089 - Made error log output more useful AddErrorLogInfoMarginNotFixed(myDoc, "MSWord could not set Document margins"); } } } else { try { // the + 1 in the next line allows for rounding. myDoc.PageSetup.BottomMargin = newBottom; myDoc.PageSetup.RightMargin = newRight; myDoc.PageSetup.LeftMargin = newLeft; myDoc.PageSetup.TopMargin = newTop; } catch (Exception ex) { if (_MySection != null) { // B2018-089 - Made error log output more useful AddErrorLogInfoMarginNotFixed(_MySection, "MSWord could not set Section margins"); } else { // B2018-089 - Made error log output more useful AddErrorLogInfoMarginNotFixed(myDoc, "MSWord could not set Document margins"); } } } } else { myDoc.PageSetup.TopMargin = 0; myDoc.PageSetup.LeftMargin = 0;// B2017-264 Limit the value for right margin so that it cannot be negative. float rm = (8.5F * 72) - newWidth + newLeft; myDoc.PageSetup.RightMargin = Math.Max(0, rm); // For OHLP, docstyle (ohlpdoc.in) in 16-bit for "Cover Page", pagelen seems to be incorrect. It would // put out text on a second page. The following code is to catch this condition and reset the bottom margin. // 11 * 72 -> 11 inches converted to points. newLength is printable part so BottomMargin is unprintable part float bm; if (newTop + 36 > ((11 * 72) - newLength)) // 36 is 1/2 inch bm = newTop + 36; // makes an 1/2 inch bottom margin else bm = (11 * 72) - newLength; // B2017-264 Limit the value for bottom margin so that it cannot be negative myDoc.PageSetup.BottomMargin = Math.Max(0, bm); } } /// /// This closes the MS Word Application, but, delays for about 1 second. /// It appears that closing MSWord to quickly causes a: /// "Microsoft Office Word has stopped working" error. /// public static void CloseAppAfterWait() { System.Windows.Forms.Application.DoEvents(); new CloseWordApp(_MyApp, 1000); _MyApp = null; } #endregion #region Private Methods private static void CloseDocument() { int attempts = 0; while (++attempts < 11) { System.Windows.Forms.Application.DoEvents(); if (TryToClose(attempts)) return; System.Threading.Thread.Sleep(500); //WaitMS(1000); } } private static bool TryToClose(int attempts) { try { MyApp.ActiveDocument.Close(false); return true; } catch (Exception ex) { _MyLog.WarnFormat("{0} - {1}, Attempt {2}", ex.GetType().Name, ex.Message, attempts); if (attempts >= 10) _MyLog.Error("Failed to close 10 times.", ex); return false; } } private static string CreatePDF(string fileName, bool openPdf, int DebugStatus) { // C2018-019 only allow one .pdf extension if (!fileName.ToUpper().EndsWith(".PDF")) fileName += ".pdf"; return MyApp.CreatePDF(fileName, openPdf, DebugStatus); } private static void InsertROValue(LBSelection sel, string roValue, bool upRoIfPrevUpper, float indent, bool convertUnderline) { if (string.IsNullOrEmpty(roValue)) { string orgtext = sel.Text; sel.Text = string.Format("RO ({0}) Not Found", orgtext.Replace("<", string.Empty).Replace(">", string.Empty)); sel.Font.Color = LBWdColor.wdColorRed; } else // RO has a value { if (upRoIfPrevUpper && sel.LastWasUpper) roValue = roValue.ToUpper(); // Convert Fortran formatted numbers to scientific notation. string tmp = ROFSTLookup.ConvertFortranFormatToScienctificNotation(roValue); // Only in Word sections, convert the # to superscripts and ~ to subscripts tmp = Regex.Replace(tmp, "[#](.*?)[#]", "\\up2 $1\\up0 ");// DOS Superscript tmp = Regex.Replace(tmp, "[~](.*?)[~]", "\\dn2 $1\\up0 ");// DOS Subscript tmp = tmp.Replace(@"\u160?", "\xA0"); tmp = tmp.Replace(@"\U160?", "\xA0"); tmp = tmp.Replace(@"[xB3]", "\xB3"); tmp = tmp.Replace(@"\U8209?", "-"); // fixes negative value in scientific notation tmp = tmp.Replace(@"\u8209?", "-"); // Look for superscript or subscript and insert the appropriate commands Match roMatch = Regex.Match(tmp, @"(.*?)\\(up2|dn2) (.*?)\\(up0|dn0) "); if (roMatch.Groups.Count == 5)// Superscript or subscript found { //sel.Font.Color = LBWdColor.wdColorRed; // B2017-177 Don't print text RO in red while (roMatch.Groups.Count == 5) { sel.TypeText(roMatch.Groups[1].Value); // output the text preceding the super or sub command sel.Font.Position = roMatch.Groups[2].Value == "up2" ? 2 : -2; // Shift the vertical position for super or sub sel.TypeText(roMatch.Groups[3].Value); // output the superscript or subscript sel.Font.Position = 0; // restore the vertical position tmp = tmp.Substring(roMatch.Length); // remove the processed text roMatch = Regex.Match(tmp, @"(.*?)\\(up2|dn2) (.*?)\\(up0|dn0) "); // check to see if the text contain another super or sub } // Add any remaining text if (!string.IsNullOrEmpty(tmp)) sel.TypeText(tmp); sel.Font.Color = LBWdColor.wdColorAutomatic; } else // if no superscripts or subscripts just output the text { roValue = roValue.Replace(@"\u160?", "\xA0"); roValue = roValue.Replace(@"\U160?", "\xA0"); roValue = roValue.Replace(@"[xB3]", "\xB3"); roValue = roValue.Replace(@"[XB3]", "\xB3"); sel.Text = roValue; // look for toggling of '_' to turn underlining on/off: AddPrecedingText(sel, roValue, indent, convertUnderline); // parse underlining } } } private static byte[] GetBytes(string filename) { // Reads the Word file containing the resolved ROs used when adding to DocReplace FileInfo fi = new FileInfo(filename); FileStream fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] buf = new byte[fi.Length]; fs.Read(buf, 0, buf.Length); fs.Close(); return buf; } private static string GetFileName(ItemInfo sect) { return VlnSettings.TemporaryFolder + @"\Doc_" + sect.MyContent.MyEntry.DocID.ToString() + ".Pdf"; } private static float ScalingFactor() { // B2019-069 needed to get the monitor's scaling factor - used in generating RO X/Y plot IntPtr desktop = GetDC(IntPtr.Zero); int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES); int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES); ReleaseDC(IntPtr.Zero, desktop); return (float)PhysicalScreenHeight / (float)LogicalScreenHeight; // a value of 1.50 represents monitor scaling set to 150% } private static PointF GetLocation(LBSelection sel, bool adjustMargins) { LBRange rng0 = MyApp.ActiveDocument.Range(0, 0); LBRange rng1 = MyApp.ActiveDocument.Range(sel.Start, sel.Start); float fx0 = adjustMargins ? (float)rng0.get_Information(LBWdInformation.wdHorizontalPositionRelativeToPage) : 0; for (int ix = sel.Start; ix > 0; ix--) // Find left most location - this was added since the first line may be centered or right justified { LBRange rngx = MyApp.ActiveDocument.Range(ix, ix); float fxx = adjustMargins ? (float)rngx.get_Information(LBWdInformation.wdHorizontalPositionRelativeToPage) : 0; if (fxx < fx0) fx0 = fxx; else break; } float fy0 = adjustMargins ? (float)rng0.get_Information(LBWdInformation.wdVerticalPositionRelativeToPage) : 0; float fx1 = (float)rng1.get_Information(LBWdInformation.wdHorizontalPositionRelativeToPage); float fy1 = (float)rng1.get_Information(LBWdInformation.wdVerticalPositionRelativeToPage); return new PointF(fx1 - fx0, fy1 - fy0); } private static string FindEmbeddedText(string p, ref string resXyPlot) { StringBuilder sb = new StringBuilder(); // contains embedded text StringBuilder xy = new StringBuilder(); // contains xyplot without embedded text. string origXy = p; bool findEmbeddedText = false; // get past opening command: int indxCmd = p.IndexOf("<"); indxCmd = p.IndexOf("<", indxCmd + 1); xy.Append(p.Substring(0, indxCmd)); // While there are xyplot commands, check for beginning/ending. Any text not in a command, save. while (indxCmd > 0) { // find end of command. Look for another beginning of command, newline, // regular text, or end of graph and handle each of these cases. int indxClose = GetNonStringCmdClose(p, indxCmd + 1); // p.IndexOf(">", indxCmd + 1); int nxtIndxCmd = p.IndexOf("<", indxCmd + 1); xy.Append(p.Substring(indxCmd, indxClose - indxCmd + 1)); xy.Append("\r"); // the xyplot code assumes a return exists. // get substrings between end index & start of next and if any // non white space text, add it to return string and flag that we have // embedded text. If there's a newline and just other white space put a // newline in return string. if (nxtIndxCmd > 0) { string mydebug = p.Substring(indxClose + 1); for (int i = indxClose + 1; i < nxtIndxCmd; i++) { if (!System.Char.IsWhiteSpace(p[i])) { sb.Append(p[i]); findEmbeddedText = true; } if (p[i] == '\r' || p[i] == ' ') sb.Append(p[i]); } } indxCmd = nxtIndxCmd; } xy.Append(">"); if (findEmbeddedText) { resXyPlot = xy.ToString(); return sb.ToString(); } resXyPlot = origXy; return null; } private static int GetNonStringCmdClose(string p, int indxCmd) { int indxClose = p.IndexOf(">", indxCmd); if (indxClose >= 0) { // if there are any quotes between the open of command and close, // need to see if the close command is quoted. if (p.IndexOf('"', indxCmd, indxClose - indxCmd) > -1) { // see how many quotes between open/close command, if // an odd number, the close command char '>' is in quotes. int countQuotes = 0; for (int i = indxCmd; i < indxClose; i++) if (p[i] == '"') countQuotes++; if ((countQuotes % 2) != 0) { // find quote after close, and then get next close. int closeQuote = p.IndexOf('"', indxClose); if (closeQuote < 0) return -1; // this is error, so return end not found. return p.IndexOf(">", closeQuote + 1); } } } return indxClose; } private static int WordColor(System.Drawing.Color color) { System.Drawing.Color c1 = System.Drawing.Color.FromArgb(0, color.B, color.G, color.R); return c1.ToArgb(); } private static LBSelection FindXyPlot() { LBSelection sel = MyApp.Selection; LBFind find = sel.Find; find.ClearFormatting(); // Search string format - this is MSWord wildcard format // If you do a search in MSWord, make sure wildcard box is checked and then press the // Special button to see the definitions of the various wildcards // [<] - Less-Than Character // [!<> ]@ - 1 or more characters not including Less-Than, Greater-Than or Space // - Dash // [!<> ]@ - 1 or more characters not including Less-Than, Greater-Than or Space // [>] - Greater-Than Character find.Text = "[<][<]G*[>][>]"; //find.Wrap = LBWdFindWrap.wdFindStop; find.Wrap = LBWdFindWrap.wdFindContinue; find.MatchCase = false; find.MatchWholeWord = false; find.MatchWildcards = true; find.MatchSoundsLike = false; find.MatchAllWordForms = false; if (find.Execute()) return sel; return null; } private static LBSelection FindRO() { int firstStart = 0; bool executeResult = false; LBSelection sel = null; while (!executeResult) { sel = MyApp.Selection; LBFind find = sel.Find; find.ClearFormatting(); // Search string format - this is MSWord wildcard format // If you do a search in MSWord, make sure wildcard box is checked and then press the // Special button to see the definitions of the various wildcards // [<] - Less-Than Character // [!<> -]@ - 1 or more characters not including Less-Than, Greater-Than, Dash or Space // - Dash // [!<>]@ - 1 or more characters not including Less-Than, Greater-Than. // a space be included as part of the accessory page id (Callaway, EOP Addendum 37) // [>] - Greater-Than Character find.Text = "[<][!<> -]@-[!<>]@[>]"; find.Wrap = LBWdFindWrap.wdFindContinue; find.MatchCase = false; find.MatchWholeWord = false; find.MatchWildcards = true; find.MatchSoundsLike = false; find.MatchAllWordForms = false; // B2022-053 in a Barakah word attachment (ST procedure set procedure DG-5204) the RO tokens are not resolved after a certain // point in a table. // Turns out this happens when a table cell had text that begins with a "<" and it does not end with a ">" and there is no // space or other word following that text in the table cell. For example, there was a table cell who's text // was "<90" cause the Word Find command to return an entire table row which cause PROMS to stop processing // the rest of the Word attachment section. bool tryagain = false; do { tryagain = false; executeResult = find.Execute(); // B2022-053 if the found text does not begin with a "<" and end with a ">", then // move past that text and try the Word Find function again. // B2022-088: [JPR] Find Doc Ro button not working in Word Sections // B2022-098: [JPR] ROs not being resolved in Word Sections if (executeResult && !string.IsNullOrEmpty(sel.Text) && !sel.Text.StartsWith("<") && !sel.Text.EndsWith(">")) { sel.MoveStart(LBWdUnits.wdCharacter, sel.Text.Length - 1); tryagain = true; } } while (tryagain); if (!executeResult) return null; // B2019-123 Word 2019 returns true when find string was not found // This line checks to make sure an RO was found. // B2019-138 Word section fails during RO Replacement // Calvert Cliffs/Working Draft Procedures/OI Procedures - Unit 1 - OI-43C.SATTACHMENT 1 if (sel.End != 0 && !_ROAccPageTokenPattern.IsMatch(sel.Text)) return null; // MS Word found 'invalid' ro when text had "[335.0 {0}\r\n" + " [{1}] {2}\r\n" + " in {3}\r\n" + " Document: {4}\r\n" + " ACTION REQUIRED: Should use Word Margins", msg, sect.ItemID, sect.ShortPath, sect.SearchDVPath.Replace("\a", "/"), sect.MyContent.MyEntry.MyDocument.LibTitle ?? sect.MyContent.MyEntry.DocID.ToString()); } private static void AddErrorLogInfoMarginNotFixed(LBDocumentClass myDoc, string msg) { // B2018-089 - Made error log output more useful _MyLog.WarnFormat("\r\n==> {0}\r\n" + " Document: {1}\r\n" + " ACTION REQUIRED: Should use Word Margins", msg, myDoc.FullName); } #endregion #region Kernel Level Methods [DllImport("User32.dll")] static extern IntPtr GetDC(IntPtr hwnd); [DllImport("User32.dll")] static extern int ReleaseDC(IntPtr hwnd, IntPtr dc); [DllImport("gdi32.dll")] static extern int GetDeviceCaps(IntPtr hdc, int nIndex); #endregion #region Private Class private class CloseWordApp : System.Windows.Forms.Timer { #region Fields // B2019-161 When tracking timing time this action private static VolianTimer _TimeActivity = new VolianTimer("DocumentExt.cs CloseWordApp_Tick", 1548); LBApplicationClass _MyApp; #endregion #region Constructor public LBApplicationClass MyApp { get { return _MyApp; } set { _MyApp = value; } } #endregion #region Public Methods public CloseWordApp(LBApplicationClass myApp, int interval) { MyApp = myApp; Interval = interval; Tick += new EventHandler(CloseWordApp_Tick); Enabled = true; } #endregion #region Private Methods private void CloseWordApp_Tick(object sender, EventArgs e) { _TimeActivity.Open(); Enabled = false; try // B2018-071 Keep code from crashing on close of MS Word { MyApp.Quit(false); } catch (Exception ex) { _MyLog.WarnFormat("MyApp Quit {0} {1}", ex.GetType().FullName, ex.Message); } Dispose(); _TimeActivity.Close(); } #endregion } #endregion } }