附錄九 純文字檔排版程式


簡介 TXT2NOCR.ASM 程式

在這個附錄堙A小木偶想介紹一個小小工具程式,純文字排版程式 ( 這是小木偶取的名字,可能不太貼切,但我才疏學淺,以無法在想出更好的名稱了 ),看個標題,可能很不容易了解小木偶在這個附錄堥鴝雪Q寫什麼樣的程式。所以底下小木偶先大致說一說這個程式的來龍去脈。

在早先的 DOS 時代堙A大部分的文書處理程式處理純文字檔時,都是藉由使用者按下『Enter』鍵時表示換行,猶如打字機一般。每一行字的尾端都有換行符號表示換行,此換行符號是以兩個字元表示,一般而言,第一個字元是 ASCII 字元的 0DH,它表示歸位 ( carriage return ) 的意思,即游標回到此行的最左邊。第二個字元是 0AH,它表示 line feed 的意思,也就是到下一行 ( 假如有下一行的話 ) 或增加一行 ( 假如沒有下一行,也就是此行是最後一行的話 ) 的意思。以 Internet Explorer 6 Service Pack 1 的 readme.txt 檔 ( 在 "C:\Program Files\Internet Explorer\readme.txt" ) 為例,當你用記事本開啟時,無論如何調整,每一行的寬度都是固定的,所以右邊會有一部份空白。如下圖︰

如果你用 DEBUG 載入

C:\Program Files\Internet Explorer>debug readme.txt [Enter]
-d 100 [Enter]
1670:0100  20 20 20 20 20 20 20 20-20 20 20 20 20 2D 2D 2D                ---
1670:0110  2D 2D 2D 2D 2D 2D 2D 2D-2D 2D 2D 2D 2D 2D 2D 2D   ----------------
1670:0120  2D 2D 2D 2D 2D 2D 2D 2D-2D 2D 2D 2D 2D 2D 2D 2D   ----------------
1670:0130  2D 2D 2D 2D 2D 2D 2D 2D-2D 2D 2D 0D 0A 20 20 20   -----------..
1670:0140  20 20 20 20 20 20 20 20-20 20 4D 69 63 72 6F 73             Micros
1670:0150  6F 66 74 20 49 6E 74 65-72 6E 65 74 20 45 78 70   oft Internet Exp
1670:0160  6C 6F 72 65 72 20 36 20-53 65 72 76 69 63 65 20   lorer 6 Service
1670:0170  50 61 63 6B 20 31 20 A4-CE 0D 0A 20 20 20 20 20   Pack 1 ....
-d [Enter]
1670:0180  20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20
1670:0190  20 20 20 20 20 4F 75 74-6C 6F 6F 6B 28 54 4D 29        Outlook(TM)
1670:01A0  20 45 78 70 72 65 73 73-0D 0A 20 20 20 20 20 20    Express..
1670:01B0  20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20
1670:01C0  20 20 20 20 20 20 20 20-20 C5 AA A7 DA C0 C9 AE            .......
1670:01D0  D7 0D 0A 20 20 20 20 20-20 20 20 20 20 20 20 20   ...
1670:01E0  20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20
1670:01F0  32 30 30 32 20 A6 7E 20-38 20 A4 EB 0D 0A 20 20   2002 .~ 8 ....

就可以很明顯的看到每一行之後都有 0DH、0AH,因為有這兩個字元,強迫換行,所以在 Windows 系統底下的文書處理器,如 WORD、小作家、Word Pro 等等都無法順利排版,你可以看見把視窗範圍擴大後,右邊仍有空白。換句話說,這類文字檔不能藉由改變 WORD 等文書處理器的版面或紙張大小而自動調整每一行的文字數目。現在個人電腦上的作業系統主流是 Windows,其上的文書處理器以 Word 佔大部分,即使不用 Word,也無法順利做到自動排版,其原因就是每行後面的 0DH、0AH 作怪。

這個附錄堙A小木偶將實作一個工具小程式,TXT2NOCR.ASM,它可以把這種換行字元除去,這樣在 Windows 作業系統下的文書處理器能順利排版。但是並不是所有的 0DH、0AH 都要除去,在一般純文字檔堙A每兩個段落之間是以多對的 0DH、0AH 表示段落,像這種表示段落的換行符號就不可除去,而是以一對 0DH、0AH 換行符號代替。


TXT2NOCR.EXE 的功能及用法

TXT2NOCR 的主要功能是把純文字檔每一行後面的換行符號除去,與下一行連接以形成同一個段落。但是後來小木偶發現有些純文字檔並不是像上面 IE6 Service Pack 1 的 readme.txt 一樣,每一行後面緊接著一對換行符號而每一段落中間相隔一行,例如小木偶在 internet 上找到一篇『西遊記第 30 回』的 031.TXT 檔案是每一行之間都空一行,而每一段落的第一行是四個空白來分段,它的樣子像這樣︰

所以小木偶加入一個參數,『/N』,/N 後面接上一個 1∼9 的數字,此數字表示要連續多少對的換行符號以內,會被 TXT2NOCR 被認為是同一段落,以 031.TXT 而言,應該下『/N2』參數,TXT2NOCR 就會把連續兩個換行符號視為同一段落。變成

了。另,/N 的內定值是 1。

在 031.TXT 檔堙A每一段落是以 4 個空白開始,但並不是所有的檔案都如此,所以小木偶再加上一個參數,『/S』,此參數指定多少個空白以上,才被 TXT2NOCR 視為一個段落的開始。/S 的內定值是 2,當省略『/S』時, TXT2NOCR 自動以兩個空白當做一段落的開始。

但是,如果檔案是像這樣︰

的話,每一行前面的空白應該被忽略的,所以小木偶再增一個『/I』參數,/I 後面也是接上一個 1∼9 的數字,此數字表示會被 TXT2NOCR 所忽略每一行前面的字數。當『/S』所指定的數字是被『/I』所忽略之後的,以這個『death_of_ASM』檔案為例,原文中第一∼第三行前有 5 個空白,第 5 行前有 9 個空白,可以下『/I5 /S4』使 1∼3 行第一段,5∼9行為第二段 ( 事實上,你可以下『/I5』參數就可以了,因為原文中的段落已經相隔一行了。 )。/I 的內定值是 0,不過你無法輸入 /I0 參數,只可以輸入 /I1 到 /I9。

最後為了增加一些彈性,分隔段落的符號,也可以不用 carriage return 和 line feed 這兩個字元,而可以自行指定,如果想自行指定的話,用『/P』參數,此參數後所接的字串被視為分隔段落的字串,會加在每一個段落的最前面,當然 TXT2NOCR 也會在這個字串前,也就是上個段落的最尾端,自動加上一對 carriage return 和 line feed。小木偶加上『/P』參數的原因是,可以很方便的把原來的文字檔轉換成 HTML 檔,只要加上『/P&LTP&GT』即可,為何不用『/P<P>』就好了呢?原來『<』和『>』是 DOS 重導字元,如果出現在命令列有特殊用途,所以不直接使用。假如省略『/P』參數的話,內定的分隔段落字串就是 0dh、0ah。

要被 TXT2NOCR 處理的檔案,必須放在第一個參數,不須以『/』開始。這個要被處理的檔案,小木偶稱為來源檔,它可以包含磁碟機名稱、路徑名稱,甚至是萬用字元。因為可以包含萬用字元,所以能一次處理很多檔案。

來源檔之後可以接目的檔名,目的檔是指被處理過後的資料要存放的檔案,目的檔也可以省略,如果省略目的檔,TXT2NOCR 會自動地在來源檔名之後加上『.000』作為副檔名,以保留原始檔,此時目的檔會被放在目前的路徑堙C假如來源檔含有萬用字元時,不管有沒有指定目的檔,TXT2NOCR 會自動地在目前的路徑中,為每個符合來源檔名的檔案建立以來源檔同名,但副檔名為『.000』的目的檔。質言之,當來源檔名含有萬用字元時,指定目的檔是無效的。

最後整理一下,TXT2NOCR 的語法是︰

TXT2NOCR 來源檔 [目的檔] [/P字串] [/S數字] [/I數字] [/N數字]

TXT2NOCR.ASM 原始檔

        page    ,132
;版權所有,請任意散佈使用,但不可出售圖利
;TXT2NOCR - TeXT TO NO CaRrier character and line feed text
;此程式是將純文字檔中的歸位換行字元消去,方便可以在 Windows 作業系統中的文書
;處理器排版。
                                                                   
;用法︰txt2nocr 來源檔 [目的檔] [/p字串] [/n數字] [/i數字] [/s數字]

; 來源檔:即表示要處理的檔案,可用萬用字元。
; 目的檔:目的檔名。若省略,則放在現在目錄,但處理後的副檔名為 .000 保存原檔案。
;     若來源檔含萬用字元,則目的檔與來源檔同名,但副檔名為 .000。
; /p︰段落分隔字串。如果有『/p』參數,表示以其後所接的『字串』加在每個段落之
;   後。如果沒有『/p』參數,則每一行表示一個段落,即以一對 0dh、0ah 為分隔
;   符號。字串長度不可超過 32 個位元組。『<』『>』這兩個字元,和 DOS 重新
;   導向的特殊符號一樣,分別以『<』、『>』表示,若『&』之後所接的不是
;   『LT』或『GT』則該『&』即為真正之『&』,非特殊符號之前導。
;  /n︰0dh、0ah 的數目。在此數目及其以下均被認為是同一段落,若省略,內定值為 1
;  /i︰忽略每行最前面的字元數。內定值為 0,表示不忽略。
;  /s︰緊接在 0dh、0ah 之後的空白字元等於或超過此數,也被視為段落開始。例如『/S2
;      』表示如果一行的最前面有兩個或超過兩個空白字元,也算一個段落的開始。

tab     equ     9
cr      equ     0dh
lf      equ     0ah
max     equ     0f800h

filedat         struc
f_attri         dd      ?       ;檔案屬性
f_create_time   dq      ?       ;建立時間
lst_access_time dq      ?       ;最後存取時間
lst_mdfy_time   dq      ?       ;最後修改時間
f_size_h        dd      ?       ;檔案大小,高位元
f_size_l        dd      ?       ;檔案大小,低位元
reserved        dq      ?
full_name       db      260 dup (?)
short_name      db      14 dup (?)
filedat         ends

        .386
;***********************************************************
stack   segment stack   use16
        dw      40h dup (0)
stack   ends
;***********************************************************
data    segment use16
psp_param       db      80h dup (0)
cur_file        filedat <0>
para_str_len    dw      0               ;段落分隔字串長度
para_str        db      32 dup (?)      ;段落分隔字串
sr_fn           db      260 dup (0)     ;來源檔名,含路徑名稱
tr_fn           db      260 dup (0),0   ;目的檔名
ext_of_tr_fn    db      '.000',0        ;目的檔名之副檔名
param_len       dw      ?       ;命令列輸入之參數長度
sr_fn_addr      dw      ?       ;來源檔名位址
tr_fn_addr      dw      ?       ;目的檔名位址
tr_path_addr    dw      ?       ;目的檔路徑名開始位址,僅在 status 之 bit 7=1 時有用
tr_path_end     dw      ?       ;目的檔路徑名結束位址,僅在 status 之 bit 7=1 時有用
sr_handle       dw      ?       ;來源檔之檔案代碼
tr_handle       dw      ?       ;目的檔之檔案代碼
seg_sr          dw      ?       ;存放來源檔內容的區段
seg_tr          dw      ?       ;存放目的檔內容的區段
bytes_read      dw      ?       ;每次讀取之位元組數
bytes_store     dw      ?       ;在目的檔資料區的位元組數
source_pnter    dw      ?       ;來源檔內容之指標
findfile        dw      ?
num_cr_lf       dw      1
num_ignore      dw      0
num_space       dw      2
sp_begin_line   dw      0
file_size       dd      ?
file_size_pbcd  dt      ?
tr_f_size       dd      ?       ;目的檔長度
second_begin    db      ?       ;程式開始時的時間︰秒
minute_begin    db      ?       ;程式開始時的時間︰分
hour_begin      db      ?       ;程式開始時的時間︰時
day_begin       db      ?       ;程式開始時的時間︰日
month_begin     db      ?       ;程式開始時的時間︰月
year_begin      db      ?       ;程式開始時的時間︰年,由 1980 年起到現在的年數
status          db      0
;bit0︰若為 0,表示每個段落之間沒有分隔字串,亦即每一行就是一個段落
;      若為 1,表示每個段落間,有分隔字串。例如在 HTML 檔以 <p> 作為段落開始
;bit1︰若為 0,表示原始檔案尚未讀取至檔案尾
;      若為 1,表示原始檔案已經讀取至檔案尾
;bit2︰在印出檔案大小時,此位元為 0,表示前面的 0 不印出,等到第一個非零數字時,
;      此位元為 1,後面的數字不管是不是零,都要印出來
;bit3︰若為 0,表示來源檔不含萬用字元;若為 1,表示來源檔含有萬用字元
;bit4︰若為 0,使用者沒有輸入『/I』參數。若為 1,使用者指定『/I』參數
;bit5︰若為 0,使用者沒有輸入『/N』參數。若為 1,使用者指定『/N』參數
;bit6︰若為 0,使用者沒有輸入『/S』參數。若為 1,使用者指定『/S』參數
;bit7︰若為 0,表示用者完全沒有指定目的檔或有指定包含路徑的目的檔名
;      若為 1,表示使用者僅輸入目的檔之路徑或磁碟機名,並沒有輸入目的檔名
msg0    db      '版權所有,請任意散佈使用,但不可出售圖利',cr,lf
        db      'TXT2NOCR - TeXT TO NO CaRrier character and line feed text',cr,lf
        db      '此程式是將純文字檔中的歸位換行字元消去,方便可以在 Windows 作業系統'
        db      '中的文書',cr,lf,'處理器排版。',cr,lf,cr,lf
        db      '用法︰txt2nocr 來源檔 [目的檔] [/P字串] [/N數字] [/I數字] [/S數字]'
        db      cr,lf,cr,lf,' 來源檔:即表示要處理的檔案,可用萬用字元。',cr,lf
        db      ' 目的檔:目的檔名。若省略,則放在現在目錄,但處理後的副檔名為 .000'
        db      ' 保存原檔案。',cr,lf,'     若來源檔含萬用字元,則目的檔與來源檔'
        db      '同名,但副檔名為 .000。',cr,lf
        db      ' /P︰段落分隔字串。如果有『/P』參數,表示以其後所接的『字串』加在'
        db      '每個段落之前',cr,lf,'   。如果沒有『/P』參數,則每一行表示一個段'
        db      '落,內定一對 0dh、0ah 為分隔符號',cr,lf
        db      '   。字串長度不可超過 32 個位元組。特殊符號以『&』為前導,可用'
        db      '的特殊符號有',cr,lf,'   『<』『>』『&CR』『&LF』分別表示『<'
        db      '』『>』、0dh、0ah。若『&』之後所接',cr,lf,'   的字不是上述四種,'
        db      '則該『&』即為真正的『&』,非特殊符號之前導。',cr,lf
        db      '  /N︰連續出現 0dh、0ah 之對數,在此數及其以下均被認為是同一段落,'
        db      '此數範圍為 1',cr,lf,'      到 9,若省略,內定值為 1,也就是說以 1'
        db      ' 對 0dh、0ah 連接的兩行會刪去 0dh、',cr,lf,'      0ah 而連接成同一'
        db      '段落。',cr,lf,'  /I︰忽略每行最前面的字元數。內定值為 0,表示不忽'
        db      '略。',cr,lf,'  /S︰緊接在 0dh、0ah 之後的空白除去 /I 指定之字數後'
        db      '等於或超過此數,也被視為段',cr,lf,'      落開始。例如『/S2』表示如'
        db      '果一行的最前面有兩個或超過兩個空白字元,也算一個',cr,lf
        db      '      段落的開始。',cr,lf,'按任意鍵繼續顯示……$'
msg1    db      cr,lf,'開啟檔案錯誤。$'
msg2    db      cr,lf,'目的檔已存在。$'
msg3    db      cr,lf,'記憶體不足。$'
msg4    db      cr,lf,'建立目的檔錯誤。$'
msg5    db      cr,lf,'完成。$'
msg6    db      cr,lf,'若檔案 LEE.TXT 內容為︰( 須注意每行後面均有一對看不見的'
        db      ' 0dh、0ah )',cr,lf,'喜見外弟又言別 李益'
        db      cr,lf,cr,lf,'十年離亂後,長大一相逢;',cr,lf
        db      '問姓驚初見,稱名憶舊容。',cr,lf,cr,lf,'別來滄海事,語罷暮天鐘;'
        db      cr,lf,'明日巴陵道,秋山又幾重。'
        db      cr,lf,cr,lf,'執行 TXT2NOCR LEE.TXT /P<P>'
        db      ' 後變成︰',cr,lf,'喜見外弟又言別 李益',cr,lf
        db      '<P>十年離亂後,長大一相逢;問姓驚初見,稱名憶舊容。',cr,lf
        db      '<P>別來滄海事,語罷暮天鐘;明日巴陵道,秋山又幾重。',cr,lf,cr,lf
        db      '內定值為 /N1,表示連續出現 1 對或 1 對以下的 0dh、0ah 均為同一段落'
        db      '。換句話說,',cr,lf,'要連續 2 對 0dh、0ah 連在一起,才會分段。'
        db      '故第一行單獨一段落,3、4 行為同段落,',cr,lf,'5、6 行為另一段落。'
        db      cr,lf,cr,lf,'執行 TXT2NOCR LEE.TXT /P<P> /N2 後變成︰',cr,lf
        db      '喜見外弟又言別 李益十年離亂後,長大一相逢;問姓驚初見,稱名憶舊'
        db      '容。別來滄海事,語罷暮天鐘;明日巴陵道,秋山又幾重。',cr,lf,cr,lf
        db      '因指定 /N2,所以全部視為一段落。',cr,lf,'按任意鍵繼續顯示……$'
msg7    db      cr,lf,'若檔案 LEE.TXT 內容為︰( 須注意每行後面均有一對看不見的'
        db      ' 0dh、0ah )',cr,lf,'喜見外弟又言別 李益'
        db      cr,lf,cr,lf,'十年離亂後,長大一相逢;',cr,lf
        db      '問姓驚初見,稱名憶舊容。',cr,lf,cr,lf,'別來滄海事,語罷暮天鐘;'
        db      cr,lf,'明日巴陵道,秋山又幾重。',cr,lf,cr,lf,'執行 TXT2NOCR LEE.'
        db      'TXT /P<P> /I4 後變成︰',cr,lf,'外弟又言別 李益',cr,lf
        db      '<P>離亂後,長大一相逢;驚初見,稱名憶舊容。',cr,lf
        db      '<P>滄海事,語罷暮天鐘;巴陵道,秋山又幾重。',cr,lf,cr,lf
        db      '因為 /I4 忽略前 4 個字,所以『喜見』、『十年』、『問姓』等被截除。'
        db      cr,lf,'( 一個中文字用兩個位元組表示 )。',cr,lf,cr,lf
        db      '執行 TXT2NOCR LEE.TXT 後變成︰',cr,lf
        db      '喜見外弟又言別 李益',cr,lf,'十年離亂後,長大一相逢;問姓驚初見,'
        db      '稱名憶舊容。',cr,lf,'別來滄海事,語罷暮天鐘;明日巴陵道,秋山又幾'
        db      '重。',cr,lf,cr,lf,'因為沒有 /P 參數,用內定的 0dh、0ah 作為分段落'
        db      '的符號。',cr,lf,'按任意鍵繼續顯示……$'
msg8    db      cr,lf,cr,lf,cr,lf,'若檔案 LEE.TXT 內容為︰',cr,lf,cr,lf
        db      '    喜見外弟又言別 李益',cr,lf,'    十年離亂後,長大一相逢;'
        db      cr,lf,cr,lf,'問姓驚初見,稱名憶舊容。',cr,lf,'    別來滄海事,'
        db      '語罷暮天鐘;',cr,lf,'明日巴陵道,秋山又幾重。',cr,lf
        db      cr,lf,'執行 TXT2NOCR LEE.TXT /S4 /Pxyz後變成︰',cr,lf,cr,lf
        db      'xyz喜見外弟又言別 李益',cr,lf,'xyz十年離亂後,長大一相逢;',cr,lf
        db      'xyz問姓驚初見,稱名憶舊容。',cr,lf,'xyz別來滄海事,語罷暮天鐘;明'
        db      '日巴陵道,秋山又幾重。',cr,lf,cr,lf,'因 /S4 表示每行前的空白超'
        db      '過或等於 4 個就視為一個段落,所以第 1、3、6 行各行形成',cr,lf
        db      '一段落,又同時內定 /N1,所以第 5 行也形成一段落。',cr,lf,cr,lf
msg9    db      cr,lf,'$'
msga    db      ' ==> $'
msgb    db      ' bytes)$'
data    ends
;***********************************************************
txt2ncr segment use16
        assume  cs:txt2ncr,ds:data
;-----------------------------------------------------------
del_cr_lf       proc    near
                sub     edx,edx
                push    es
                mov     tr_f_size,edx   ;使目的檔長度變 0
read_sr_0:      mov     cx,max          ;讀取來源檔內容,存放於 GS:0000
                sub     dx,dx
read_sr_1:      mov     bx,sr_handle
                push    ds
                mov     ds,seg_sr
                mov     ah,3fh
                int     21h
                pop     ds
                cmp     ax,cx           ;如果 AX<CX 表示已經讀至檔案尾
                jb      last_rd
                and     status,0fdh     ;尚未讀至檔案尾,清除 STATUS 的 BIT1 為 0
                jmp     short save_bytes_rd
last_rd:        or      status,2        ;讀至檔案尾,設定 STATUS 的 BIT1 為 1
save_bytes_rd:  add     ax,dx           ;加上先前未處理的
                mov     bytes_read,ax   ;實際讀取的位元組數存於 bytes_read

                sub     ax,ax
                mov     gs,seg_sr
                mov     bytes_store,ax
                mov     source_pnter,ax
                mov     di,ax
                mov     es,seg_tr       ;ES:DI=目的檔指標

line_deal:      mov     bx,source_pnter ;開始處理這一行了,先檢查這一行的字數,這一行
                sub     cx,cx           ;從 source_pnter 所指之處開始
                mov     si,bx           ;CX=這一行的字數,但不含 0dh、0ah
                mov     dx,bytes_read
calc_n_char0:   cmp     byte ptr gs:[bx],cr
                je      fnd_cr
calc_n_char1:   inc     cx
                inc     bx
                dec     dx
                jz      end_read_0      ;若 DX=0,表示已經處理完所讀入的資料,但檔案
                jmp     calc_n_char0    ;並非以 0dh、0ah 結尾,跳到end_read_0 處

end_read_0:     test    status,2        ;先檢查是否已讀取到檔案尾
                jnz     end_read_1
                mov     bx,source_pnter ;若尚未讀取至檔案尾端,則把剩下未處理的資料移到
                sub     di,di           ;GS:0000 處,再寫入目的檔與讀取來源檔
move_remain:    mov     al,gs:[bx]      ;把剩下的資料 ( 在 GS:BX 處,長度 CX )移到 GS:00
                mov     gs:[di],al
                inc     bx
                inc     di
                loop    move_remain
                push    di
                call    write
                pop     dx
                mov     cx,max
                sub     cx,dx
                jmp     read_sr_1
end_read_1:     mov     ax,dx           ;來源檔已全部讀取完,且來源檔不以 0dh、0ah 結尾
                jmp     end_read_2

fnd_cr:         cmp     word ptr gs:[bx],0a0dh
                jne     calc_n_char1    ;檢查是否只是 0dh 而已,真正換行應該是 0dh、0ah
                mov     ax,2            ;0dh、0ah 共有兩個位元組長
end_read_2:     jcxz    many_cr_lf      ;CX=此行字數。若 CX=0 表示此行只有 0dh、0ah
                add     source_pnter,cx ;找到該行的結尾了
                sub     bytes_read,cx
                add     source_pnter,ax ;使 source_pnter 指向下一行開始
                sub     bytes_read,ax   ;使 bytes_read=剩下未處理的位元組數
                cmp     cx,num_ignore   ;檢查此行字數是否比 num_ignore 大
                jbe     line_deal       ;若 CX<=num_ignore,此行刪除
                add     si,num_ignore   ;若 CX>num_ignore,此行不刪除
                sub     cx,num_ignore   ;,但忽略前面 num_ignore 個字
                mov     bx,si           ;BX 指向該行去除 num_ignore 後的第一個字
                sub     dx,dx
nxt_space_char: cmp     byte ptr gs:[bx],' '    ;檢查空白字元數,存於 DX
                jne     not_space
                inc     bx
                inc     dx              ;DX=每行在去除 num_ignore 之後的空白字數
                jmp     nxt_space_char

not_space:      cmp     dx,num_space    ;比較該行最前面的空白字元數,是否大於 num_space
                jb      no_para_str_0   ;若空白字數比 num_space 小,則不是段落開始
                call    insert_para_str ;若空白字數比 num_space 大,則插入段落分隔字串
no_para_str_0:  add     si,dx           ;去除該行前的空白
                sub     cx,dx
nxt_char_save:  mov     al,gs:[si]      ;把去除空白及 num_ignore 個字後的字,存於 ES:DI
                stosb
                inc     si
                inc     bytes_store
                loop    nxt_char_save
                cmp     bytes_store,max
                jb      if_end_read
                call    write
if_end_read:    cmp     bytes_read,0    ;若檔案不以 0dh、0ah 結尾,須檢查 bytes_read 是
                jz      eof             ;否為 0,若為 0,表示已讀取之資料已處理完
                jmp     line_deal

many_cr_lf:     inc     cx              ;計算有幾對 0dh、0ah,記錄於 CX
calc_cr_n:      cmp     word ptr gs:[bx],0a0dh
                jne     end_calc_cr_n
                inc     cx
                add     bx,ax
                sub     bytes_read,ax
                jz      eof
                jmp     calc_cr_n
end_calc_cr_n:  cmp     cx,num_cr_lf    ;若 CX>num_cr_lf 時,才插入段落分隔符號
                jbe     no_para_str_1
                call    insert_para_str
no_para_str_1:  mov     source_pnter,bx
                jmp     line_deal

eof:            call    write
                jc      exit_del_cr
                test    status,2
                jnz     finish_deal
                jmp     read_sr_0
finish_deal:    pop     es
                clc
                ret

exit_del_cr:    pop     es
                stc
                ret
del_cr_lf       endp
;---------------------------------------
insert_para_str proc    near
                push    cx
                push    si
                mov     cx,para_str_len
                mov     si,offset para_str
                add     bytes_store,cx
                rep     movsb
                pop     si
                pop     cx
                ret
insert_para_str endp
;-----------------------------------------------------------
write   proc    near
                sub     di,di
                mov     ah,40h
                sub     edx,edx         ;指向要寫入資料的位址
                mov     bx,tr_handle
                push    ds
                mov     cx,bytes_store
                mov     ds,seg_tr
                int     21h
                mov     dx,ax
                pop     ds
                add     tr_f_size,edx
                mov     bytes_store,di
                ret
write   endp
;-----------------------------------------------------------
;檔案名稱有時包含路徑名或磁碟機名,此副程式乃是尋找主檔名所在位址
;輸入︰DS:SI-檔案名稱位址,檔名符合 ASCIZ
;輸出︰DS:SI-主檔名位址
fnd_main_addr   proc    near
                push    dx
                mov     dx,si
fnd_main_0:     cmp     byte ptr [si],0         ;由來源檔檔名尾端向前尋找來源檔之主
                je      fnd_main_1              ;檔名,此主檔名不含路徑與磁碟機名
                inc     si
                jmp     fnd_main_0
fnd_main_1:     dec     si
                cmp     byte ptr [si],'\'       ;找到『\』或『:』或者到達 sr_fn_addr
                je      fnd_main_2              ;所指的位址處,表示找到主檔名
                cmp     byte ptr [si],':'
                je      fnd_main_2
                cmp     si,dx
                je      fnd_main_3
                jmp     fnd_main_1
fnd_main_2:     inc     si
fnd_main_3:     pop     dx
                ret
fnd_main_addr   endp
;-----------------------------------------------------------
;當使用者沒有指定目的檔或來源檔含萬用字元時,目的檔均和來源檔同
;名,且目的檔副檔名為『.000』,這樣使得目的檔存放於目前所在目錄
;輸入-SI︰來源檔名(可含路徑名)位址
fill_tr_fn1     proc    near
                mov     di,offset tr_fn
fill_tr_fn1     endp
fill_tr_fn0     proc    near
                mov     si,sr_fn_addr
                call    fnd_main_addr   ;找到主檔名,位於 SI 所指位址
fill_tf_4:      lodsb                   ;把來源檔檔名存入 DI 所指位址
                or      al,al
                jz      fill_tf_5
                stosb
                jmp     fill_tf_4
fill_tf_5:      mov     si,offset ext_of_tr_fn  ;再存入 『.000』當作目的檔副檔名
                mov     cx,5
                rep     movsb
                mov     tr_fn_addr,offset tr_fn 
                ret
fill_tr_fn0     endp
;---------------------------------------
get_number      proc    near
                lodsb
                dec     cx
                sub     al,'0'
                jbe     num_err
                cmp     al,9
                ja      num_err
                cbw
                clc
                ret
num_err:        stc
                ret
get_number      endp
;-----------------------------------------------------------
;檢查這個檔案是不是程式開始後才建立的?
check_just_now  proc    near
                mov     bx,offset cur_file.lst_mdfy_time
                mov     eax,[bx]
                rol     eax,7
                mov     dl,al
                and     dl,7fh
                cmp     dl,year_begin   ;比較符合檔案修改時間是否比程式執行時的時刻早
                jb      create_before   ;若比較早,則跳出副程式,旗標為 CY
                ja      create_after    ;若比較晚,也跳出副程式,旗標為 NC
                rol     eax,4
                mov     dl,al
                and     dl,0fh
                cmp     dl,month_begin
                jb      create_before
                ja      create_after
                rol     eax,5
                mov     dl,al
                and     dl,1fh
                cmp     dl,day_begin
                jb      create_before
                ja      create_after
                rol     eax,5
                mov     dl,al
                and     dl,1fh
                cmp     dl,hour_begin
                jb      create_before
                ja      create_after
                rol     eax,6
                mov     dl,al
                and     dl,3fh
                cmp     dl,minute_begin
                jb      create_before   ;假如某檔案修改時間與 TXT2NOCR 執行時間同
                ja      create_after    ;月同日同時同分,則被認為是 TXT2NOCR 因為
                sub     eax,eax         ;萬用字元而建立的,所以不處理該檔案
create_after:
create_before:  ret
check_just_now  endp
;---------------------------------------
;取得程式剛執行時的時間
get_begin_time  proc    near
                mov     ah,2ah
                int     21h
                sub     cx,1980
                mov     year_begin,cl
                mov     month_begin,dh
                mov     day_begin,dl
                mov     ah,2ch
                int     21h
                mov     hour_begin,ch
                mov     minute_begin,cl
                mov     second_begin,dh
                ret
get_begin_time  endp
;---------------------------------------
pause   proc    near
        mov     ah,0
        int     16h
        ret
pause   endp
;---------------------------------------
print_string    proc    near
        mov     ah,9
        int     21h
        ret
print_string    endp
;---------------------------------------
;以十進位,印出 EAX 堛獐あr
;status 的 bit2 表示前面的 0 要不要印出來
print_file_size proc    near
        and     status,0fbh     ;先假設高位元為 0
        mov     file_size,eax   ;--ST---;--ST1--;
        fild    file_size       ;  EAX  ;
        fbstp   file_size_pbcd
        mov     bx,offset file_size_pbcd+9
nxt_b:  mov     dl,[bx]
        mov     cl,1            ;CL=1 表示高位元,CL=0 表示低位元
        shr     dl,4
        jz      n_zero
        or      status,4
        add     dl,'0'
        call    print_char
high_b: mov     dl,[bx]
        mov     cl,0            ;CL=1 表示高位元,CL=0 表示低位元
        and     dl,0fh
        jz      n_zero
        or      status,4
        add     dl,'0'
        call    print_char
low_b:  dec     bx
        cmp     bx,offset file_size_pbcd
        jb      pnt_over
        jmp     nxt_b

n_zero: test    status,4
        jz      no_pnt
        add     dl,'0'
        call    print_char
no_pnt: test    cl,1
        jz      low_b
        jmp     high_b

pnt_over:       ret
print_file_size endp
;---------------------------------------
;印出『bytes)』字元來
print_r_bracket proc
        mov     dx,offset msgb
        call    print_string
        ret
print_r_bracket endp
;---------------------------------------
;印出『(』字元來
print_l_bracket proc
        mov     dl,'('
print_l_bracket endp
;---------------------------------------
print_char      proc    near
        mov     ah,2
        int     21h
        ret
print_char      endp
;---------------------------------------
print_filename  proc    near
                mov     dl,[si]
                cmp     dl,0
                jz      print_over
                call    print_char
                inc     si
                jmp     print_filename
print_over:     ret
print_filename  endp
;---------------------------------------
;印出正在處理的檔名及大小
print_info      proc    near
                mov     dx,offset msg9
                call    print_string
                mov     si,sr_fn_addr
                call    print_filename
                call    print_l_bracket
                mov     eax,cur_file.f_size_l
                call    print_file_size
                call    print_r_bracket
                mov     dx,offset msga
                call    print_string
                mov     si,tr_fn_addr
                call    print_filename
                call    print_l_bracket
                ret
print_info      endp
;-----------------------------------------------------------
;因 AX=714E/714F 似乎無法真正取得來源檔全名 ( 含路徑名 ),故
;由 fill_sr_fn 填上來源檔全名
fill_sr_fn      proc    near
                mov     si,sr_fn_addr
                call    fnd_main_addr
                mov     dx,si
                mov     di,offset sr_fn
                mov     si,sr_fn_addr
nxt_path_char:  lodsb
                stosb
                cmp     si,dx
                jnz     nxt_path_char
                mov     si,offset cur_file.full_name
nxt_sr_name:    lodsb
                stosb
                or      al,al
                jnz     nxt_sr_name
                ret
fill_sr_fn      endp
;-----------------------------------------------------------
main    proc    far
mem_not_enogh:  mov     dx,offset msg3
                jmp     some_error

start:          push    ds
                sub     ax,ax
                push    ax
                mov     dx,data
                mov     es,dx           ;使 PSP 參數區的 80 個位元組移到
                mov     si,80h          ;data 區段中 psp_param
                cld
                mov     cx,40h
                mov     di,offset psp_param
                rep     movsw
                mov     ds,dx           ;使 DS=ES=data

                mov     ax,cs
                add     ax,1000h
                mov     seg_sr,ax       ;設定存放來源檔內容的段位址
                add     ax,1000h
                cmp     ax,9000h
                ja      mem_not_enogh
                mov     seg_tr,ax       ;設定存放目的檔內容的段位址

                and     status,0eh      ;先設定內定值︰無『/P』『/N』『/I』三個參數
                mov     ax,0a0dh        ;清除 status 的 BIT0,並使用內定段落分隔字
                mov     di,offset para_str      ;串,0dh、0ah,para_str_len=2
                mov     cx,2
                stosw
                mov     para_str_len,cx
                mov     num_cr_lf,1     ;除此之外,設定 num_cr_lf=1,num_ignore=0
                mov     num_ignore,0    ;num_space=2 (即如果一行的最前面有兩個空白字
                mov     num_space,cx    ;元或超過也算一個段落的開始)
                finit

                mov     si,offset psp_param
                lodsb
                cbw
                mov     cx,ax
                mov     param_len,ax
                jcxz    no_param
scan_slash:     mov     al,'/'          ;尋找『/』
                mov     di,si
                repne   scasb
                je      fnd_slash
                jmp     get_time

fnd_slash:      mov     si,di
                lodsb
                dec     cx
                and     al,0dfh
                cmp     al,'N'
                je      slash_n
                cmp     al,'S'
                je      slash_s
                cmp     al,'P'
                je      slash_p
                cmp     al,'I'
                jne     no_param

slash_i:        test    status,10h      ;找到『/I』參數
                jnz     no_param
                call    get_number
                jc      no_param
                mov     num_ignore,ax
                or      status,10h
                jmp     short chck_slash_end

slash_n:        test    status,20h      ;找到『/N』參數
                jnz     no_param
                call    get_number
                jc      no_param
                mov     num_cr_lf,ax
                or      status,20h
                jmp     short chck_slash_end

no_param:       mov     dx,offset msg0
                call    print_string
                call    pause
                mov     dx,offset msg6
                call    print_string
                call    pause
                mov     dx,offset msg7
                call    print_string
                call    pause
                mov     dx,offset msg8
some_error:     call    print_string
                mov     al,1
exit:           mov     ah,4ch
                int     21h

slash_s:        test    status,40h      ;找到『/S』參數
                jnz     no_param
                call    get_number
                or      status,40h
                mov     num_space,ax
chck_slash_end: cmp     byte ptr [si],' '
                je      scan_slash
                cmp     byte ptr [si],tab
                je      scan_slash
                cmp     byte ptr [si],'/'
                je      scan_slash
                cmp     byte ptr [si],cr
                jne     no_param
                jmp     short get_time

slash_p:        test    status,1        ;找到『P』參數了
                jnz     no_param
                or      status,1
                mov     dx,2
                mov     di,offset para_str+2    ;把『/P』後面接的字串存於 para_str 
get_para_str:   lodsb
                dec     cx
                cmp     al,cr
                je      get_time
                cmp     al,' '
                je      finsh_para_str
                cmp     al,'/'
                je      finsh_para_str
                cmp     al,'&'
                je      special_char
save_special:   stosb
                inc     para_str_len    ;增加段落分隔字串長度
                jmp     get_para_str

special_char:   lodsw
                sub     cx,dx
                and     ax,0dfdfh       ;『/P』之後所接的段落分隔符號含有『&』字元
                cmp     ax,544ch        ;檢查是否『>』或『<』
                je      special_lt
                cmp     ax,5447h
                je      special_gt
                cmp     ax,5243h
                je      special_cr
                cmp     ax,464ch
                je      special_lf
                add     cx,dx
                sub     si,dx           ;如果只是『&』,而其後並非『LT』或『GT』,
                mov     al,'&'          ;表示這是真正的『&』並非『<』『>』之前導
                jmp     save_special
special_lt:     mov     al,'<'          ;如果是『<』,僅存入『<』
                jmp     save_special
special_gt:     mov     al,'>'          ;如果是『>』,僅存入『>』
                jmp     save_special
special_cr:     mov     al,cr           ;如果是『&CR』,僅存入 0dh
                jmp     save_special
special_lf:     mov     al,lf           ;如果是『&LF』,僅存入 0ah
                jmp     save_special

finsh_para_str: jmp     scan_slash

get_time:       call    get_begin_time  ;取得 TXT2NOCR 開始執行時間
                mov     si,offset psp_param+1
nxt_param1:     lodsb                   ;尋找第一個檔案參數
                cmp     al,' '
                je      nxt_param1
                cmp     al,tab
                je      nxt_param1
                cmp     al,cr           ;如果第一個參數是 CR、『/』
                je      no_param        ;,表示使用者輸入錯誤
                cmp     al,'/'
                je      no_param
                mov     sr_fn_addr,si   ;找著了,將其位址記錄於 sr_fn_addr 變數
                dec     sr_fn_addr
nxt_param2:     lodsb                   ;尋找第一個參數尾端
                cmp     al,' '
                je      end_sr_fn
                cmp     al,tab
                je      end_sr_fn
                cmp     al,cr           ;如果第一個參數之後為 CR
                jne     nxt_param2
                mov     byte ptr [si-1],0       ;則將 CR 改成 0,並跳至 no_tr_fn1 處
                jmp     short no_tr_fn1

end_sr_fn:      mov     byte ptr [si-1],0       ;如果第一個參數之後為空白,表示還有其
nxt_param3:     lodsb                   ;他參數找著了,將其尾端改成 0,並檢查
                cmp     al,' '          ;尋找第二個參數
                je      nxt_param3
                cmp     al,tab
                je      nxt_param3
                cmp     al,'/'          ;如果第二個參數以『/』、
                je      no_tr_fn1       ;0dh 字元,表示沒有目的檔名
                cmp     al,cr
                je      no_tr_fn1
                mov     tr_fn_addr,si   ;找著了,將第二個參數之
                dec     tr_fn_addr      ;位址記錄於 tr_fn_addr
nxt_param4:     lodsb                   ;尋找第二個參數尾端
                cmp     al,' '
                je      end_tr_fn0
                cmp     al,tab
                je      end_tr_fn0
                cmp     al,cr
                je      end_tr_fn0
                cmp     al,'/'
                jne     nxt_param4
end_tr_fn0:     dec     si
                mov     byte ptr [si],0 ;找著了,將第二個參數尾端改成 0
                dec     si              ;SI 指向第二個參數的倒數第二個字
                cmp     byte ptr [si],'\'
                je      no_tr_fn0       ;檢查目的檔是否只有磁碟機名或路徑名
                cmp     byte ptr [si],':'
                jne     if_wild1
no_tr_fn0:      mov     dx,si           ;使用者沒輸入目的檔名,僅輸
                or      status,80h      ;入目的檔之路徑或磁碟機名
                inc     dx
                mov     si,tr_fn_addr
                mov     di,offset tr_fn
                mov     tr_path_addr,si
                mov     tr_path_end,dx
nxt_tr_path0:   lodsb
                stosb
                cmp     si,dx
                jne     nxt_tr_path0
                call    fill_tr_fn0
                jmp     short if_wild0

no_tr_fn1:      call    fill_tr_fn1
if_wild0:       mov     bx,sr_fn_addr   ;檢查來源檔是否含萬用字元
                and     status,0f7h     ;假定來源檔不含萬用字元
if_wild1:       cmp     byte ptr [bx],'*'
                je      fnd_wild_char
                cmp     byte ptr [bx],'?'
                je      fnd_wild_char
                cmp     byte ptr [bx],0
                je      no_wild_0
                inc     bx
                jmp     if_wild1

fnd_wild_char:  or      status,8        ;含萬用字元
no_wild_0:      mov     si,1            ;找第一個符合的檔名
                mov     di,offset cur_file
                mov     dx,sr_fn_addr
                mov     ax,714eh
                mov     cx,202fh
                int     21h
                jc      open_err
                test    status,8        ;不含萬用字元時,跳到 no_wild_1
                jz      no_wild_1
                mov     findfile,ax
                jmp     short get_sr_fn

nxt_file:       mov     si,1            ;找下一個符合的來源檔名
                mov     di,offset cur_file
                mov     ax,714fh
                mov     bx,findfile
                int     21h
                cmp     ax,12h
                je      finish
get_sr_fn:      call    fill_sr_fn      ;當輸入萬用字元時,填入來源檔名
                mov     sr_fn_addr,offset sr_fn
                call    check_just_now
                jae     nxt_file
                test    status,80h
                jz      no_tr_fn2
                mov     si,tr_path_addr ;當輸入萬用字元且僅輸入目的檔路徑時
                mov     di,offset tr_fn ;填入目的檔全名
nxt_tr_path1:   lodsb
                stosb
                cmp     si,tr_path_end
                jne     nxt_tr_path1
                call    fill_tr_fn0
                jmp     short no_wild_1
no_tr_fn2:      call    fill_tr_fn1     ;使用者沒有輸入目的路徑及檔名

no_wild_1:      mov     si,sr_fn_addr   ;開啟來源檔
                mov     ax,716ch
                mov     cx,20h
                sub     bx,bx
                mov     dx,1
                int     21h
                jc      open_err
                mov     sr_handle,ax
                mov     si,tr_fn_addr   ;建立目的檔
                mov     ax,716ch
                mov     cx,20h
                mov     bx,2292h
                mov     dx,10h
                int     21h
                mov     tr_handle,ax
                jnc     begin_del_cr
                mov     ah,3eh          ;錯誤產生了,關閉來源檔
                mov     bx,sr_handle
                int     21h
                mov     dx,offset msg4
                jmp     some_error
                mov     dx,offset msg2
                jmp     some_error
open_err:       mov     dx,offset msg1
                jmp     some_error

begin_del_cr:   call    print_info      ;印出正在處理的檔案及檔案大小
                call    del_cr_lf       ;處理中

                mov     eax,tr_f_size   ;印出目的檔大小
                call    print_file_size
                call    print_r_bracket
                mov     bx,tr_handle    ;關閉目的檔
                mov     ah,3eh
                int     21h
                mov     bx,sr_handle    ;關閉來源檔
                mov     ah,3eh
                int     21h
                test    status,8        ;檢查當含萬用字元時,是否
                jnz     nxt_file        ;所有檔案都處理完畢

finish:         mov     dx,offset msg5  ;完成
                mov     ah,9
                int     21h
                mov     al,0
                jmp     exit
                mov     al,0
                jmp     exit
main    endp
;-----------------------------------------------------------
txt2ncr ends
;***********************************************************
        end     start

組譯 TXT2NOCR.ASM

用 MASM 5.x 組譯時

D:\HomePage\SOURCE>set path=G:\ASM;%path% [Enter]

D:\HomePage\SOURCE>masm txt2nocr; [Enter]
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.


  50642 + 354014 Bytes symbol space free

      0 Warning Errors
      0 Severe  Errors

D:\HomePage\SOURCE>link txt2nocr; [Enter]

Microsoft (R) Personal Computer Linker  Version 2.40
Copyright (C) Microsoft Corp 1983, 1984, 1985.  All rights reserved.

D:\HomePage\SOURCE>dir txt2nocr.exe [Enter]

 Volume in drive D is FAT32_DATA1
 Volume Serial Number is 0CE7-3A6B
 Directory of D:\HomePage\SOURCE

TXT2NOCR EXE         6,733  08-30-04  15:19 TXT2NOCR.EXE
         1 file(s)          6,733 bytes
         0 dir(s)        9,018.15 MB free

D:\HomePage\SOURCE>

用 MASM 6.x 組譯時

D:\HomePage\SOURCE>path g:\masm611\bin;%path% [Enter]

D:\HomePage\SOURCE>ml /Zm txt2nocr.asm [Enter]
Microsoft (R) Macro Assembler Version 6.11
Copyright (C) Microsoft Corp 1981-1993.  All rights reserved.

 Assembling: txt2nocr.asm

Microsoft (R) Segmented Executable Linker  Version 5.31.009 Jul 13 1992
Copyright (C) Microsoft Corp 1984-1992.  All rights reserved.

Object Modules [.obj]: txt2nocr.obj
Run File [txt2nocr.exe]: "txt2nocr.exe"
List File [nul.map]: NUL
Libraries [.lib]:
Definitions File [nul.def]:

D:\HomePage\SOURCE>

這個程式太大,小木偶不打算解說。假如他有錯誤,請和我連絡,謝謝。