string sFile ="c:\\temp\\export.rtf" //MODIFIED_HERE add filename // Export RTF // @(#)DOORS %full_filespec: rtfexp.dxl~16.1.3:dxl:ERS#1 % %date_created: Mon Feb 27 12:09:29 2006 % // Copyright © 2003 Telelogic AB. All rights reserved. // See DOORS manuals for copying conditions. // Copy this file to a different location before modifying it. /* Rich Text Format output - exports "book" format (object heading and text) or "table" format (all current columns). */ pragma runLim,0 #include #include const string level_s[] = { "None","1","2","3","4","5","6","7","8","9","10" } const string buttons[] = { "None", "Every page" , "First page only" } const string rtfTitle = "Export RTF" const string progressTitle = "RTF Export Progress" DBE advancedBreakBefore DBE advancedColumnTitles DBE advancedIncludeBorders Module m = null int iNumObjects = 0 const string thinBorder = "\\brdrs\\brdrw10" const int columnTitleNone = 0 const int columnTitleEvery = 1 const int columnTitleFirst = 2 int currentLayout = layoutTypeBook int columnTitle = columnTitleEvery int rtfPageBreakLevel = 0 bool includeBorders = true const int defaultPageWidth = 8748 const string rowDefine = "\\trowd \\trgaph108\\trleft-108\\trbrdrt\\brdrs\\brdrw10 \\trbrdrl\\brdrs\\brdrw10 \\trbrdrb\\brdrs\\brdrw10 \\trbrdrr\\brdrs\\brdrw10 \\trbrdrh\\brdrs\\brdrw10 \\trbrdrv\\brdrs\\brdrw10 " const string cellDefine = "\\clvertalt\\clbrdrt\\brdrs\\brdrw10 \\clbrdrl\\brdrs\\brdrw10 \\clbrdrb\\brdrs\\brdrw10 \\clbrdrr \\brdrs\\brdrw10 \\cltxlrtb \\cellx" const string rowBegin = "\\pard \\nowidctlpar\\widctlpar\\intbl\\adjustright {" const string rowEnd = "}\\pard \\nowidctlpar\\widctlpar\\intbl\\adjustright {\\row }" const string tableEnd = "\\pard \\nowidctlpar\\widctlpar\\adjustright " const string paraBreak = "\\par\\pard\\plain " const string defaultFontTag const string rtfHeader = "{\\rtf1\\ansi\\deflang1024" const string cellBreak = "\\cell\\plain" const string pageBreak = "\\page" bool issueWarnings = true const int maxHeadingStyles = 9 const string styleSelect[maxHeadingStyles + 1] string defaultStyles[21] // Guide to font tables: the goal is to cover as many character // sets as possible. We only worry about the distinction between // Arial (used for headings) and Times New Roman (used for text) // for the ANSI character set -- all others use Arial. // getRichTextFragment will map any font changes in the // text to the appropriate font number in the given table // character sets covered are: // ANSI 0 // SYMBOL 2 // SHIFTJIS 128 // HANGUL 129 // JOHAB 130 // GB2312 134 // CHINESE 136 // GREEK 161 // TURKISH 162 // VIETNAMESE 163 // HEBREW 177 // ARABIC 178 // BALTIC 186 // RUSSIAN 204 // THAI 222 // EASTEUROPE 238 const string fontTable = "\\deff1{\\fonttbl {\\f0\\fswiss\\fcharset0 Arial;} {\\f1\\froman\\fprq2\\fcharset0 Tahoma;} {\\f2\\froman\\fprq2\\fcharset2 Symbol;} {\\f3\\fswiss\\fprq2\\fcharset128 Arial Unicode MS;} {\\f4\\fswiss\\fprq2\\fcharset129 Arial Unicode MS;} {\\f5\\fswiss\\fprq2\\fcharset130 Arial Unicode MS;} {\\f6\\fswiss\\fprq2\\fcharset134 Arial Unicode MS;} {\\f7\\fswiss\\fprq2\\fcharset136 Arial Unicode MS;} {\\f8\\fswiss\\fcharset161 Arial;} {\\f9\\fswiss\\fcharset162 Arial;} {\\f10\\fswiss\\fcharset163 Arial;} {\\f11\\fswiss\\fcharset177 Arial;} {\\f12\\fswiss\\fcharset178 Arial;} {\\f13\\fswiss\\fcharset186 Arial;} {\\f14\\fswiss\\fcharset204 Arial;} {\\f15\\fswiss\\fprq2\\fcharset222 Arial Unicode MS;} {\\f16\\fswiss\\fcharset238 Arial;} }" void dumpFontTable(Stream st) { st << fontTable } void constructDefaultStyleSheet() { // Create a default style sheet, using font 0 (Arial) for headings // and font 1 (Times New Roman) for text defaultStyles[0] = "{\\widctlpar \\f1\\fs20 \\snext0 Normal;}\n" styleSelect[0] = "\\plain \\widctlpar \\f1\\fs20 " defaultStyles[1] = "{\\s1\\sb240\\sa60\\keepn\\widctlpar \\b\\f0\\fs28\\kerning28 \\sbasedon0\\snext0 heading 1;}\n" styleSelect[1] = "\\s1\\sb240\\sa60\\keepn\\widctlpar \\b\\f0\\fs28\\kerning28 " defaultStyles[2] = "{\\s2\\sb240\\sa60\\keepn\\widctlpar \\b\\i\\f0 \\sbasedon0\\snext0 heading 2;}\n" styleSelect[2] = "\\s2\\sb240\\sa60\\keepn\\widctlpar \\b\\i\\f0 " defaultStyles[3] = "{\\s3\\sb240\\sa60\\keepn\\widctlpar \\f0 \\sbasedon0\\snext0 heading 3;}\n" styleSelect[3] = "\\s3\\sb240\\sa60\\keepn\\widctlpar \\f0 " defaultStyles[4] = "{\\s4\\sb240\\sa60\\keepn\\widctlpar \\b\\f0 \\sbasedon0\\snext0 heading 4;}\n" styleSelect[4] = "\\s4\\sb240\\sa60\\keepn\\widctlpar \\b\\f0 " defaultStyles[5] = "{\\s5\\sb240\\sa60\\widctlpar \\f0\\fs22 \\sbasedon0\\snext0 heading 5;}\n" styleSelect[5] = "\\s5\\sb240\\sa60\\widctlpar \\f0\\fs22 " defaultStyles[6] = "{\\s6\\sb240\\sa60\\widctlpar \\i\\f1\\fs22 \\sbasedon0\\snext0 heading 6;}\n" styleSelect[6] = "\\s6\\sb240\\sa60\\widctlpar \\i\\f1\\fs22 " defaultStyles[7] = "{\\s7\\sb240\\sa60\\widctlpar \\f0\\fs20 \\sbasedon0\\snext0 heading 7;}\n" styleSelect[7] = "\\s7\\sb240\\sa60\\widctlpar \\f0\\fs20 " defaultStyles[8] = "{\\s8\\sb240\\sa60\\widctlpar \\i\\f0\\fs20 \\sbasedon0\\snext0 heading 8;}\n" styleSelect[8] = "\\s8\\sb240\\sa60\\widctlpar \\i\\f0\\fs20 " defaultStyles[9] = "{\\s9\\sb240\\sa60\\widctlpar \\b\\i\\f0\\fs18 \\sbasedon0\\snext0 heading 9;}\n" styleSelect[9] = "\\s9\\sb240\\sa60\\widctlpar \\b\\i\\f0\\fs18 " defaultStyles[10] = "{\\*\\cs10 \\additive Default Paragraph Font;}\n" defaultStyles[17] = "{\\s17\\widctlpar \\f1\\fs20 \\sbasedon0\\snext17 footnote text;}\n" defaultStyles[18] = "{\\*\\cs18 \\additive\\super \\sbasedon10 footnote reference;}\n" defaultStyles[19] = "{\\s19\\widctlpar\\tqc\\tx4153\\tqr\\tx8306 \\f1\\fs20 \\sbasedon0\\snext19 header;}\n" defaultStyles[20] = "{\\s20\\widctlpar\\tqc\\tx4153\\tqr\\tx8306 \\f1\\fs20 \\sbasedon0\\snext20 footer;}\n" } void dumpStyleSheet(Stream st) { int count constructDefaultStyleSheet st << "{\\stylesheet\n" for (count = 0; count <= maxHeadingStyles; count++) { st << defaultStyles[count] } st << defaultStyles[10] st << defaultStyles[17] st << defaultStyles[18] st << defaultStyles[19] st << defaultStyles[20] st << "}\n" } void dumpRTFAttr(Module m, string prefix, attr, bool newStyle) { current = m if (!exists attribute attr) return if (newStyle) { cout << prefix cout << probeAttr_(m, attr) cout << "}\n" } else { cout << probeAttr_(m, attr) "\n" } } string getHeadingText(Object o, bool &isFrontMatter, bool isInputStringPartOfTable) { if (!hasCopyableHeading o) { isFrontMatter = false return "" } string s = richTextFragment( richTextWithOle o.AHeading, fontTable, isInputStringPartOfTable) isFrontMatter = (o.AHeading "" == cFrontMatter) return s } string getBodyText(Object o, bool isInputStringPartOfTable) { if (!hasCopyableText o) { return "" } if (issueWarnings && containsUnregisteredOle(o.ALongText)) { warningBox "Object " (identifier o) " contains one or more unregistered OLE objects in '" ALongText "'.\n\nThese OLE objects will be exported as pictures." } string s = richTextFragment( richTextWithOle o.ALongText, fontTable, isInputStringPartOfTable) if (s == cEmptyCell) s = "" return s } //Function to get the main column attribute in a table string getTableAttributes(Object o) { if(!hasCopyableTableAttributes(o)) { return "" } string attributeName = o.(reserved AMainColumnAttribute) if (issueWarnings && containsUnregisteredOle(o.attributeName)) { warningBox "Object " (identifier o) " contains one or more unregistered OLE objects in '" attributeName "'.\n\nThese OLE objects will be exported as pictures." } string s = richTextFragment( richTextWithOle o.attributeName, fontTable ) return s } void dumpSummaryInfo(Stream st, Module m) { // get summary info current = m if (exists attribute ARTFInfo) { dumpRTFAttr(m, "{\\info ", ARTFInfo, true) return } string title_string = m."Name" string subject = null if (exists attribute ASubject) subject = m.ASubject "" if (null subject) subject = m.ADescription "" string author = m."Created By" string operator = m."Last Modified By" Date creatim = m."Created On" Date revtim = m."Last Modified On" // translate date to string to get year,month,day string s_creatim = stringOf(dateOf(intOf(creatim)), "MM/dd/yyyy") "" string s_revtim = stringOf(dateOf(intOf(revtim)), "MM/dd/yyyy") "" st << "{\\info" st << "{\\title " title_string "}" st << "{\\subject " subject "}" st << "{\\author " author "}" st << "{\\operator " operator "}" st << "{\\creatim\\yr" s_creatim[6:9] "\\mo" s_creatim[0:1] "\\dy" s_creatim[3:4] "\\hr" s_creatim[11:12] "\\min" s_creatim[14:15] "}" st << "{\\revtim\\yr" s_revtim[6:9] "\\mo" s_revtim[0:1] "\\dy" s_revtim[3:4] "\\hr" s_revtim[11:12] "\\min" s_revtim[14:15] "}" st << "}\n" } bool goodToRead(Object o, string attribName) { return ((exists attribute attribName) && (canRead o.attribName)) } bool dumpPicturesBody(Object o, bool forTable, bool noBreaks) { Buffer pict = null Buffer shppict = null Buffer nonshppict = null bool retval = false if (goodToRead(o,APict)) { pict = create setempty pict pict += o.APict "" if (length pict == 0) { delete pict shppict = null } } if (goodToRead(o,AShpPict)) { shppict = create setempty shppict shppict += o.AShpPict "" if (length shppict == 0) { delete shppict shppict = null } } if (goodToRead(o,ANonShpPict)) { nonshppict = create setempty nonshppict nonshppict += o.ANonShpPict "" if (length nonshppict == 0) { delete nonshppict nonshppict = null } } if (((!null pict) || (!null shppict) || (!null nonshppict)) && (!noBreaks)) { if (!forTable) { cout << paraBreak cout << styleSelect[0] } else { cout << "\\pard\\plain\\intbl" } } if (!null pict) { cout << "{\\pict" cout << pict cout << "}\n" retval = true } if (!null shppict) { cout << "{\\*\\shppict{\\pict" cout << shppict cout << "}}\n" retval = true } if (!null nonshppict) { cout << "{\\nonshppict\\pict" cout << nonshppict cout << "}\n" retval = true } if (!null pict) delete pict if (!null shppict) delete shppict if (!null nonshppict) delete nonshppict return retval } bool dumpPictures(Object o, bool hasRecordedStyle, bool forTable) { // V4 parser /* TODO: build up picture or OLE object from embedded WMF or OLE object. Currently only dump backing markup which may not reflect changes (like editing OLE object, or reimporting picture, or pictures/ole objects inserted outwith RTF parsing process */ // OLE objects if (goodToRead(o,ARTFObj) && goodToRead(o,ARTFObjdata) && goodToRead(o,ARTFObjclass) && goodToRead(o,APict)) { Buffer oledata = create setempty oledata oledata += o.ARTFObjdata "" if ((length oledata == 0) || (null (o.ARTFObj "")) || (null (o.ARTFObjclass ""))) { delete oledata oledata = null } else { // OLE object wingding if (!forTable) { cout << paraBreak cout << styleSelect[0] } else { cout << "\\pard\\plain\\intbl" } cout << "{\\object " cout << o.ARTFObj "" cout << "{\\*\\objclass " cout << o.ARTFObjclass "}" cout << "{\\*\\objdata " cout << oledata cout << "}\n" cout << "{\\result {" dumpPicturesBody(o,forTable,true) cout << "}}}\n" delete oledata return true } } return dumpPicturesBody(o,forTable,false) } //------------------------- Export table code --------------------------- string justString(string colString) { if (colString == "Left") return "\\ql " if (colString == "Right") return "\\qr " if (colString == "Left and Right") return "\\qj " if (colString == "Center") return "\\qc " return "" } void exportTable(DB exportBox, bool includeNumbers, string filename) { int noCols = 0 int i bool bDoneFirstObj = false bool hasPictures = false Object o Column c for c in m do noCols++ int column_width[noCols] string column_justify[noCols] i = 0 for c in m do { column_width[i] = width c column_justify[i] = justify c i++ } cout = write(filename) current = m if (exists attribute AHeader) { cout << "{" cout << probeAttr_(m, AHeader) } else { cout << rtfHeader } dumpFontTable(cout) dumpStyleSheet(cout) // All these attribute only exist with v4 parser dumpRTFAttr(m, "{\\filetbl ", AFileTable, true) dumpRTFAttr(m, "{\\colortbl ", AColorTable, true) dumpRTFAttr(m, "{\\revtbl ", ARevTable, true) dumpSummaryInfo(cout, m) if (exists attribute ARTFDocFormat) { cout << "\n" Buffer attrRTFDocFormatValue = create attrRTFDocFormatValue = probeAttr_(m, ARTFDocFormat) Regexp aftnsepTag = regexp "\\\\aftnsep " Regexp aftnsepcTag = regexp "\\\\aftnsepc " int len = length attrRTFDocFormatValue int idxAfterTag = 0 int idxBeforeTag = 0 string afterTag = "" //remove aftnsep tag Buffer withoutAftnsepTag = create if(aftnsepTag attrRTFDocFormatValue) { idxAfterTag = ((end 0) +1) idxBeforeTag = ((start 0) -1) withoutAftnsepTag = attrRTFDocFormatValue[0:idxBeforeTag] afterTag = attrRTFDocFormatValue[idxAfterTag:(len-1)] withoutAftnsepTag += afterTag } else { withoutAftnsepTag += attrRTFDocFormatValue } //remove aftnsepc tag Buffer withoutAftnsepcTag = create len = length( withoutAftnsepTag) if(aftnsepcTag withoutAftnsepTag) { idxAfterTag = ((end 0) +1) idxBeforeTag = ((start 0) -1) withoutAftnsepcTag = withoutAftnsepTag[0:idxBeforeTag] afterTag = withoutAftnsepTag[idxAfterTag:(len-1)] withoutAftnsepcTag += afterTag } else { withoutAftnsepcTag += withoutAftnsepTag } cout << stringOf(withoutAftnsepcTag) cout << "\n" delete attrRTFDocFormatValue delete withoutAftnsepTag delete withoutAftnsepcTag } string titleFormat if (columnTitle == columnTitleEvery) { titleFormat = "\\trowd \\trgaph108\\trleft-108\\trhdr " "\\plain" styleSelect[4] } else { titleFormat = "\\trowd \\trgaph108\\trleft-108 " "\\plain" styleSelect[4] } string tableFormat = "\\trowd \\trgaph108\\trleft-108 " int total_width = 0 for(i=0;i 9) levelOut = 9 if (!null heading) { cout << styleSelect[levelOut] cout << justString column_justify[i] if (includeNumbers) { cout << (number o " ") } cout << heading } hasPictures = false if (showPictures m) { hasPictures = dumpPictures(o,false,true) } if ((!hasPictures) && (!hasPicture o)) { /* dump object text */ if (!null text_string) { if (!null heading) { /* insert paragraph break after heading */ cout << "\\par\\pard\\plain\\intbl" } cout << styleSelect[0] cout << justString column_justify[i] cout << text_string } else { /* not a picture, no text */ if ((null heading) && (!oleIsObject o) && includeNumbers) { /* completely empty, dump number as per DOORS display */ cout << styleSelect[levelOut] cout << justString column_justify[i] cout << (number o " ") } } } }//end of if(null attributeName) else { string tableAttributeValues = getTableAttributes(o) if(!null tableAttributeValues) { cout << styleSelect[levelOut] cout << justString column_justify[i] cout << tableAttributeValues } } } cout << "\\cell \\pard \\intbl " i++ } cout << "\\pard\\plain\\intbl\\row\n" bDoneFirstObj = true } exportEndProgress cout << "\\pard" cout << "\\par" cout << "}" close cout } //------------------------- Export book code --------------------------- void rtfDumpTable(Object t) { string heading,text_string bool isFrontMatter = false Object r, c int cellwidth = 0 int cells = 0 int noRows = 0 for r in t do { noRows++ cells = 0 for c in r do { if (!cell c) continue if (!isVisible c) continue cells++ } if (cells == 0) noRows-- } if (noRows == 0) return cout << paraBreak cout << styleSelect[0] for r in t do { cells = 0 for c in r do { if (!isVisible c) continue cells++ } if (cells == 0) continue // begin row int count cellwidth = defaultPageWidth / cells cout << "\n" rowDefine "\n" for (count = 1; count <= cells; count++) { cout << cellDefine cout << (count * cellwidth) "\n" } cout << rowBegin for c in r do { if (!isVisible c) continue // begin cell //If the object cannot be read.. if(!canRead c) { cout << cellBreak " " continue } string attributeName = c.(reserved AMainColumnAttribute) if(null attributeName) { heading = getHeadingText(c,isFrontMatter, true) if (!null heading) { cout << heading " " cout << "\n" } text_string = getBodyText( c, true) if ((!null heading) && (!null text_string)) { //The object text in a table was not getting exported. //There was a missing rowBegin which inhibited the export. //Hence the previous row begin is ended and there is a new //row begin for the object text cout << "}" cout << "\\par" cout << rowBegin } if (!null text_string) { cout << text_string cout << "\n" } } else { string tableAttributesValue = getTableAttributes(c) if(!null tableAttributesValue) { cout << tableAttributesValue cout << "\n" } } cout << cellBreak " " } // end row cout << rowEnd "\n" } cout << tableEnd "\n" } int rtfDumpTableMarker(Object o, int previouslev) { int actual = level o if (previouslev > actual and rtfPageBreakLevel >= actual) { cout << pageBreak } cout << paraBreak cout << styleSelect[actual] cout << cTableMarkerString "\n" return actual } int rtfDumpBookObject(Object o, int previouslev, bool includeNumbers) { int actual string heading, text_string bool isFrontMatter = false heading = getHeadingText(o, isFrontMatter, false) if (isFrontMatter) return 0 text_string = getBodyText( o, false) // page breaks occur when object level decreases and passes page break level actual = level o if (previouslev > actual and rtfPageBreakLevel >= actual) { cout << pageBreak } if (actual > 9) actual = 9 if (!null heading) { cout << paraBreak cout << styleSelect[actual] if (includeNumbers) { cout << (number o " ") } cout << heading cout << "\n" } if (showPictures (module o)) { if (dumpPictures(o,false,false)) return actual } if ((!null text_string) && (!hasPicture o)) { cout << paraBreak cout << styleSelect[0] cout << text_string cout << "\n" } if ((null heading) && (null text_string)) { if ((!hasPicture o) && (!oleIsObject o) && includeNumbers) { /* totally empty object, dump heading number as per display */ cout << paraBreak cout << styleSelect[actual] cout << (number o " ") } else { cout << paraBreak cout << styleSelect[0] cout << "\n" } } return actual } void exportBook(DB exportBox, bool includeNumbers, string file) { Object o int previouslev = 0 cout = write file current = m if (exists attribute AHeader) { cout << "{" cout << probeAttr_(m, AHeader) } else { cout << rtfHeader } dumpFontTable(cout) dumpStyleSheet(cout) // All these attribute only exist with v4 parser dumpRTFAttr(m, "{\\filetbl ", AFileTable, true) dumpRTFAttr(m, "{\\colortbl ", AColorTable, true) dumpRTFAttr(m, "{\\revtbl ", ARevTable, true) dumpSummaryInfo(cout, m) exportInitProgress(exportBox, progressTitle, "Exporting Object ", iNumObjects) for o in document m do { if (exportCheckProgress) break if (table o) { if (tableContents m) { rtfDumpTable o } else { previouslev = rtfDumpTableMarker(o,previouslev) exportStepProgress } } else { if (!isDocumentIgnore o) { previouslev = rtfDumpBookObject(o,previouslev,includeNumbers) exportStepProgress } } } exportEndProgress cout << "}" close cout } void initRTFData() { Object o m = null iNumObjects = 0 m = current if (null m) { ack "No current module" halt } for o in m do iNumObjects++ } //------------------------- Export UI builder code --------------------------- /* //MODIFIED_HERE started block comment void exportLayoutChanged(DBE xx) { currentLayout = get xx if (currentLayout == layoutTypeBook) { inactive(advancedColumnTitles) inactive(advancedIncludeBorders) } else { active(advancedColumnTitles) active(advancedIncludeBorders) } } void exportHeadingsChanged(DBE xx) { } void userAdvancedUIRefresh(DB theBox) { } //------------------------------------------------------------------------ void userAdvancedUISetup(DB exportBox, DBE theTab, Skip s) { advancedBreakBefore = radioBox(exportBox,"Break before:",level_s,rtfPageBreakLevel) advancedBreakBefore->"left"->"inside"->theTab advancedBreakBefore->"right"->"unattached" advancedBreakBefore->"top"->"inside"->theTab advancedBreakBefore->"bottom"->"unattached" put(s,0,advancedBreakBefore) advancedColumnTitles = radioBox(exportBox,"Column titles:",buttons,columnTitle) advancedColumnTitles->"left"->"inside"->theTab advancedColumnTitles->"right"->"unattached" advancedColumnTitles->"top"->"spaced"->advancedBreakBefore advancedColumnTitles->"bottom"->"unattached" put(s,1,advancedColumnTitles) advancedIncludeBorders = toggle(exportBox,"Include borders",includeBorders) advancedIncludeBorders->"left"->"inside"->theTab advancedIncludeBorders->"right"->"unattached" advancedIncludeBorders->"top"->"spaced"->advancedColumnTitles advancedIncludeBorders->"bottom"->"unattached" put(s,2,advancedIncludeBorders) if (currentLayout == layoutTypeBook) { inactive(advancedColumnTitles) inactive(advancedIncludeBorders) } } bool userExportOnClose(DB exportBox) { return true } bool userExportCode(string fname, int layout, bool includeHeadings, bool warnUnregOLE, DB exportBox) { issueWarnings = warnUnregOLE rtfPageBreakLevel = get advancedBreakBefore columnTitle = get advancedColumnTitles includeBorders = get advancedIncludeBorders initRTFData if (layout == layoutTypeBook) { exportBook(exportBox, includeHeadings, fname) } else { exportTable(exportBox, includeHeadings, fname) } setenv("EXPORTDIRECTORY", getDirOfNoSlash(fname)) return true } //-------------------------------------------------------------------- // check status if ((current Module) != null) { // check status if ((current Object) != null) { #include makeExporterUI(rtfTitle, helpExportRichText, true, false, "rtf", "Rich Text Format") block exportBox } else { ack "There are no objects in the current view." } } else { ack "There is no current module to export." } */ //MODIFIED_HERE ended block comment //MODIFIED_HERE added remaining 3 lines of code m=current exportTable(null, true, sFile) ack "Done output is in " sFile le) ack "Done output is in "