Internet Explorer lets you write to the Windows file system by means of the harmless
function. It does not rely on potentially unsafe and often crippled ActiveX technology which is needed for reading files or anything locally. Nothing will be saved without express prior consent, a dialog allowing the selection of a folder requires the user to click or type ‘OK’ each time, and there is even an extra request for confirmation should the filename already exist. Because, if that is confirmed, the file will be overwritten.execCommand
()
Two types of text files are available for saving to: .txt
for plain text and .html
for rich text documents, ie. containing some markup. The "SaveAs" dialog window accessible with the
function comes from a standard system dll and is not the same dialog that the browser uses when Save As... is chosen from the File menu. Only the latter includes the famous execCommand
().mht
single-file option.
Files saved as .html
will be wrapped in <body>
tags. The head will look like this:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML><HEAD> <META http-equiv=Content-Type content="text/html;charset=iso-8859-1"></HEAD> <BODY>
The footer like this:
</BODY></HTML>
It is not possible to change the doctype or to save anything else in the document head section. Tags will be made uppercase and whitespace normalized. The filename may end in .htm
depending on the preference set in your Folder Options....
It will be clear that control is very limited and more advanced uses, with filenames ending in .css
for instance, are ruled out. Writing to the local filesystem with absolute power requires an ActiveXObject
and the 'Scripting.FileSystemObject'
.
The script is called upon submission of the form:
<form action="#" onsubmit="return savefile(this);">
Only whole documents can be saved with this method, so the text must be copied to a separate window or frame or some such object; this script uses an hidden <iframe>
element. The script creates the iframe if it doesn't already exist or opens a small popup window if it cannot be created, writes the text to it, and then calls the
function as a method of that newly created document.execCommand
()
The
method takes three arguments. The first is the command identifier, a string specifying the command to execute. The second parameter is a boolean indicating whether a dialog should be displayed. With execCommand
()SaveAs
as the comand, which always displays a dialog, it is optional
contrary to the documentation on the manufacturer's website. The required path and name of the file to create are passed in the third parameter, with the backslash character \
as separator.
If the user at some point canceled the Save dialog, the method returns false
. If the specified location is marked as read-only or for some technical reason the command did not execute, nothing is saved and the file has not been written, the same false
is returned. It is not possible to determine in the script which of the two is returned, except by asking. If, by the grace of God, the file is saved, the method returns a sure true
.
function savefile( f ) { f = f.elements; // reduce overhead var w = window.frames.w; if( !w ) { w = document.createElement( 'iframe' ); w.id = 'w'; w.style.display = 'none'; document.body.insertBefore( w, null ); w = window.frames.w; if( !w ) { w = window.open( '', '_temp', 'width=100,height=100' ); if( !w ) { window.alert( 'Sorry, the file could not be created.' ); return false; } } } var d = w.document, ext = f.ext.options[f.ext.selectedIndex], name = f.filename.value.replace( /\//g, '\\' ) + ext.text; d.open( 'text/plain', 'replace' ); d.charset = ext.value; if( ext.text==='.txt' ) { d.write( f.txt.value ); d.close(); } else { // '.html' d.close(); d.body.innerHTML = '\r\n' + f.txt.value + '\r\n'; } if( d.execCommand( 'SaveAs', null, name ) ){ window.alert( name + ' has been saved.' ); } else { window.alert( 'The file has not been saved.\nIs there a problem?' ); } w.close(); return false; // don't submit the form }
The last call to
affects a popup, but not an iframe.close
()
The ‘zero’ bytes that appear like extra spaces between the characters using the default UTF-16 Unicode encoding, may be surpressed by opening the file as text/plain
rather than text/html
, regardless of extension, and forcing the character set to UTF-8 as shown in the Javascript function above.
Should you happen to have some on your clipboard, a quick regular expression to remove this excess whitespace follows:
The reverse, adding a space before every character, is achieved with this regex:
The function behind the ‘Unnull’ button in the form on this page is built on this theme:
function unnull(s) { var r = new RegExp( '^\s*' + String.fromCharCode( 255, 254 ) ); return r.test( s ) || window.confirm( 'This does not appear to be a UTF-16 encoded text.\nProceed anyway?' ) ? s.replace( r, '' ).replace( /((\r?)\n )\1/g, '$2\n' ).replace( /(\s(.))/g, '$2' ) : s; }
The first few characters in a text that has undergone translation from UTF-16 to UTF-8, are always the same gibberish. The function takes advantage of this fact by checking for their presence with the regular expression r
before doing the conversion.