如何用瀏覽器讀取本地檔案(相容IE8),new bing能幫我嗎?

2023-03-27 18:00:45

瀏覽器讀寫檔案?

有一份老舊而精巧的程式碼(2006或更早),帶js的html,可以只用瀏覽器來處理一些二進位制存檔資料。

檔案的讀寫怎麼辦?通過變動的方法來完成。

利用十六進位制編輯軟體如WinHEX,直接複製十六進位制數值為字串,貼到一個TextArea以輸入;

同樣處理過的資料也是生成十六進位制字串,用WinHEX以ASCII Hex的格式貼上到新檔案中。

很巧妙,也有點繁瑣。

FileReader:

最近找到了該程式的漢化版,也是好多年前的了,發現裡面設計了直接用<input>來載入檔案的功能,用的vbs呼叫Msxml2.XMLHTTP物件來處理。

可是現在的瀏覽器基本不支援vbs了,就想改一下,用js來完成。

上網搜尋了一堆,XMLHTTP/XMLHttpRequest的程式碼,有些需要伺服器端支援,結果都不理想,或瀏覽器顯示無法建立物件之類的。

剛好new bing的申請也通過了,一番對話式交流,新搜尋引擎初試身手,效果不錯,直接給出了範例程式碼。

只要是支援HTML5的現代瀏覽器,呼叫FileReader,那叫一個駕輕就熟!

依葫蘆畫瓢,花了一點功夫也就改造完成了。

這時又回想起一個問題,舊瀏覽器(IE10以下,原網銀的最愛)怎麼辦。

如何相容IE8?

有這個必要嗎?

都3202年了,不支援HTML5、不支援ES6的瀏覽器就該回垃圾堆或封存至博物館!

網銀都終於要求相容Edge啦!

但無聊也是無聊,找了臺老電腦遠端其桌面,繼續隨便搜搜「JS讀取本地文字檔案 IE8相容」,結果是有一大堆,可大多都是標題黨。

在有些參考資料中,提到使用「Microsoft.XMLDOM」物件,試了好久,更適用於文字資料;

FSO物件貌似也只能OpenTextFile或OpenAsTextStream。

以上方法,讀取到的是文字資料,都少了很多非可列印/顯示字元,當然不行。

還是要用ADODB.Stream,因為它有個方法Read(),可讀取二進位制資料流。

ADODB.Stream:

碰到兩個問題:

  1. 瀏覽器無法建立物件。安全問題,容易給惡意程式隨意讀寫客戶的資料,一般不開放這個功能,改改登入檔,強行開通吧。
  2. 讀不到二進位制資料。在IE8中偵錯程式,一到adodb.Read()後面,產生的東西卻不是個object,typeof測試一下,unknown...

正想放棄adodb.stream,又搜到一些資料,既然.Read()不行,嚴肅地建議我用.ReadText()。

讀取文字,用來處理二進位制資料,這也行?

adodb.stream支援對字元集的轉換處理,剛好有個神奇的字元集系列:

ISO-8859-1~15

ISO-8859-1(Latin1/Windows-1252),MySQL人士比較熟悉,軟體預設的拉丁西歐字元集,它的特點在於對單位元組完全編碼,1Byte=8bit, 它把所有256個碼位全部都排滿了。

所以,可用它儲存任意二進位制資料而不會丟失。

至於編碼解碼的處理是另外一回事,後續折騰也是由此……

程式好像改造成功,一個資料也沒少,不管是否為可列印/顯示字元,或者對拉丁來說全是合法字元!

然~~而,讀取的資料好像有幾個地方有差異!不多、但是不同!

換成變體標準ISO-8859-15差異少了許多,但還是有差異。

十萬個為什麼?

差異原因

為什麼網上的範例程式碼對影象、聲音檔案進行復制都不出錯,我載入資料到程式中就有差異?

繼續追蹤偵錯,找到了端倪,有幾個特定的資料,讀取出來就變了。

比如84就固定轉化為1E80AC……

只是檔案複製的話,持續沿用ISO-8859 Latin字元集,同一個管道,怎麼In/Out、Read/Write,資料都不會變。

但是一旦用js將資料讀取為數值,在轉化為16進位制字串時,問題出現了。

因JavaScript引擎內部,所有字元都用 Unicode 表示。而Latin1字元集中的某些符號,在Unicode中是多位元組編碼。

ISO-8859-15中,這樣的字元比較少而已,當然,所處碼位也不同。

比如拉丁字元中用1位元組0x80表示了「」,而在Unicode中編碼是0x20AC;而「」則0x84變成了0x201E,等等……

剛好對上!

原二進位制資料就這樣被轉換了,編碼衝突,怎麼還原?

再問new bing,多次給出的程式碼也存在這個問題,繼續追問Unicode的編碼問題,認錯態度非常好,但是沒有解決方案。

那隻能這樣了:

手工轉換

懶得寫程式碼,new bing也不理我。

剛好又搜尋到一位國外友人的程式碼,問題解決,但是TA用的是437字元集,直接把256個字元的編碼做了個對照查詢表,轉換函數非常之長,我也不想改用437了。

我直接輸出了ISO-8859-15字元集所有00~FF用js轉換為數值的結果,和真實二進位制資料相比,也就存在8處不同的Unicode字元嘛,處理過程中查詢修正一下就行了。

搞定!

還是要自己寫……其實也不費力。

範例,讀取並顯示為HEX

<input type="file" id="fileInput" title="Choose a file" onchange="dispHex(this);"/>
<br />
<h1 id='hexh1'>16進位制</h1>
<textarea id='display' title='16進位制內容顯示' style='width:600px;height:400px;'></textarea>
<script language='javascript' type='text/javascript'>
	function dispHex(f){
		var hexString = "";
		//獲取顯示框
		var disp = document.getElementById("display");
		if (window.FileReader){
			alert("H5");
			var file = f.files[0];
			var reader = new FileReader();
			//新增讀取完成事件
			reader.onload = function(e) {
				var buffer = e.target.result;
				var array = new Uint8Array(buffer);
				for (var i = 0; i < array.length; i++) {
					//將每個元素轉化為16進位制字串,並補齊兩位,用空格分隔
					var s= '0' + array[i].toString(16);
					s = s.substr(s.length - 2, 2);
					hexString += s;
				}
				//將字串顯示在頁面上
				disp.textContent = hexString;
			};
			reader.readAsArrayBuffer(file);
		}
		else if (typeof window.ActiveXObject != 'undefined') {
			alert("IE8及以下,請啟用ActiveX控制元件互動!\n如不能開啟檔案,請將網頁下載至本地執行。\n如何開啟Adodb.Stream請自行搜尋……");
			var file = f.value;	//網頁不在本地,則為fakepath
			//f.select();
			//file = document.selection.createRange().text
			hexString = AdodbReadHexFromFile(file);
			disp.value = hexString;
		}
		else alert("Other!");
	}

	//使用ActiveX,要求瀏覽器開啟相應功能和安全設定
	function AdodbReadHexFromFile(fileURL){
		var binStr, hexStr="";
		//ADODB方式,需啟用:https://www.cnblogs.com/weiweictgu/archive/2007/03/02/661940.html
		var inStream = new ActiveXObject("ADODB.Stream");
			inStream.Type = 2;	//adTypeBinary = 1,2為Text
			inStream.Open();
			//Latin1(Windows-1252):ISO-8859-1)單位元組完全編碼
			inStream.CharSet = "iso-8859-15";
			//轉換結果iso-8859-15比iso-8859-1更接近原資料?更少收錄Unicode中的多位元組符號。
			inStream.LoadFromFile(fileURL);
			//Read()為二進位制陣列,可結果typeof=unknown.WHY.?.所以用ReadText變通...
			binStr = inStream.ReadText();
			inStream.Close();
			inStream = null;

		//binStr2HEX
		//https://www.codeproject.com/articles/17825/reading-and-writing-binary-files-using-jscript
		var ISO885915=[8364,352,353,381,382,338,339,376];	//單位元組集中包含的Unicode字元編碼
		var HexOrg=['A4','A6','A8','B4','B8','BC','BD','BE'];	//上述字元Latin真實16進位制值,由比較而來,437/Latin1同理可得
		var isoCheckNum=ISO885915.length;
		for (var i=0 ; i<binStr.length ; i++) {
			var curCode=binStr.charCodeAt(i);
			//charCodeAt可能會解釋成多位元組,有字元會被錯誤轉換,如'€',無視CharSet LatinX
			//因JavaScript引擎內部,所有字元都用 Unicode 表示
			var s,isUnicode=false;
			if(curCode>=256){
				var j;		//IE8不支援indexOf()
				for(j=0;j<isoCheckNum;j++) if(curCode==ISO885915[j]) break;
				if(j<isoCheckNum) isUnicode=true;
			}
			if(isUnicode) s=HexOrg[j];	//查表將Unicode字元轉換回正確的單位元組16進位制
			else{
				s= '0' + curCode.toString(16).toUpperCase();
				//確保是兩位數的HEX
				var bytes = 2;
				//var bytes=Math.floor(s.length/2)*2;			//多位元組內容處理
				s = s.substr(s.length - bytes, bytes);
			}
			hexStr += s;
		}
		return hexStr;
	}
</script>

後記

ISO-8859之外,其它字元集也一般可使用,只要找到衝突編碼並轉換處理即可;

js生成檔案供瀏覽器下載,現代瀏覽器用blob加createObjectURL輕鬆搞定;

在分離FileReader一段程式碼至子函數時,對非同步操作的處理還鬧過小問題,Promise的邏輯還真是有些不適合本中老年;

IE8?不想玩了。

奇怪地是,再過了幾天,問new bing本文類似的問題,它拒絕給出範例程式碼,說它不會,說它沒有記憶,說IE8沒法處理這個讀取本地二進位制檔案的問題……