TweetFollow Us on Twitter

XCMD Import Text
Volume Number:4
Issue Number:10
Column Tag:HyperChat®

XCMD Corner

By Donald Koscheka, Apple Computers, Inc.

Importing Text into Hypercard

A new controversy seems to be emerging in the Hypercard community. Some Hypercard pundits are discouraging the use of XCMDs and XFCNs in stack design.. Their most convincing argument is that those of us who jump into writing XCMDs aren’t giving ourselves an opportunity to see if HyperTalk can perform the task, perhaps equally as well as an XCMD.

I frequently consider writing an XCMD solution to a programming problem without first considering whether Hypertalk can do the same job for me. Recently, I needed to import Microsoft WORD files into Hypercard. What a wonderful opportunity to write an XCMD!

When I sat down to write the script to invoke the XCMD, I realized that I could write the entire WORD import routine in HyperTalk. Ed Wischmeyer of Apple Computer Inc. pointed out that although fields in HyperTalk prefer to see straight ASCII text, there is no such restriction on the contents of containers. Hypercard also allows you to open and read any file type you want; you aren’t restricted to reading text files. Of course, you need to figure out how to translate what’s in that container into a format that can be presented in a field.

The hard part of importing text from a Word file is not reading the data into hypercard but rather figuring how Word stores its text. By committing the import code to a simple Hypertalk script, I could concentrate my efforts at decoding Word’s file format.

To simplify my search through the file format, I made the assumption that I could ignore any formatting information such as rulers, font and style changes. I was after was the text portion of the file only. This turns out to be a valid assumption since I wanted to import the file into a Hypercard field as text.

Finding the text was a snap with John Mitchell’s “FEDIT+”. I created a Word file using WORD and then examined it in FEDIT+. I noticed that the text always started at location 256 in the file. Since the size of the file was larger than the size of the text plus this 256 byte header, I needed to determine where the end of text occurred (assuming that the formatting and ruler information follows the text in the file). Since I knew how long the text was, I again used FEDIT+ to search the 256 header portion of the file. This time I was looking for any portion of the header that contained a count of the number of bytes in the text. Since I knew that my file contained exactly 100 characters (bytes), all I had to do was find this number somewhere in the header portion of the file. I found something close to what I was looking for at offset 16 in the file. This location corresponded to the number of characters in the text portion of the file plus 256 which was the length of the header.

The creators of Microsoft Word may be reading this and wondering why I’m assuming that the text size is a 16-bit entity rather than a 32 bit number. I’m not. Since Hypercard text fields are currently limited to 32K bytes, and since I knew none of my word files were longer than this, I’m only interested in the low-order word of the text length.

Reading the text portion of a Microsoft Word file into a hypercard container requires the following steps: (1) Position the mark at byte 16 of the file. Read the byte at this position and multiply it by 256 making it the high-order half of the file length. Read the next byte and add it to the hight-order half of the length. Move 238 more bytes into the file (16+2+238 = 256). This is the start of the text portion of the file. Read the number of bytes calculated minus 256. The IT container gets the imported text.

The Hypertalk script in listing 1 performs the above steps for importing up to 16K bytes of text from a Word file. I use Steisplay WORD files only in the GetFile dialog and to get the full pathname of the file from the user. This script reads in the text without any looping so an XCMD may not speed things up enough to be warranted.

on mouseup
  put filename(“WDBN”)into filename
  if filename is not empty then
     open file filename -- filename is the full pathname of a WORD file
     read from file filename for 16 -- move file mark to the text length 
     read from file filename for 1  -- read the upper half of the length
     put chartonum( it ) * 256 into filesize -- shift up by 8 bits
     read from file filename for 1  -- get the lower half of the length
     add chartonum( it ) mod 256 to filesize
     read from file filename for 238-- move to start of text in the file
     read from file filename for filesize-256 -- read in the text
     close file filename -- IT now contains the imported data.    
  end if
end mouseup

Listing 1. Script to Import Text from a Microsoft Word File

Not all file formats can be imported quite so simply. Macwrite uses a packed text format, storing one or two characters per byte using a simple compression scheme.

Because the text is compressed, we can’t just read the file into a container and return the result to Hypercard. We must first decompress the file a byte at a time. Such a process suggests looping and loops, as we know, are not particularly fast in HyperTalk. Although the decompression can be performed in a hypertalk script, we can write an xcmd that performs the decompression faster.

The key to reading in a MacWrite file is understanding that Macwrite stores its data by paragraph. Whereas Word files are clearly divided between the text and formatting information, Macwrite stores formatting information for each paragraph at the end of the text for that paragraph. Hypercard doesn’t do formatted text; we want to ignore the formatting information at the end of each paragraph. Our algorithm then becomes a loop that reads in a paragraph at a time, decompresses the text for that paragraph ignoring the formatting information. This process is repeated for each paragraph in the file.

One small “gotcha” to this approach stems from the fact that Rulers and pictures are also considered paragraphs. When we encounter either of these objects, we just move on to the next paragraph.

Listing 2 depicts the code for this XFCN. I chose “C” because pointer arithmetic is easier to perform in “C” and because last month’s example was written in Pascal. I made every attempt to keep the “C” isomorphic to a Pascal program so that you can easily convert the code to Pascal.

Finding the paragraph information in the file requires a little arithmetic. Bytes 2-3 in the file tell us how many paragraphs the main document contains (MacWrite makes a distinction between the main document, the header document and the footer document. For our purposes, we only want to read in the main body of text) If bytes 2-3 contain a 5 then there are 5 paragraphs in the main document.

For each paragraph, MacWrite stores an information array. We start reading the information arrays at the file position pointed to in file offset $108. An information array is an array of 16-byte elements that tell us something about each paragraph. The first two bytes in the information array tell us whether the paragraph contains text, a ruler or a picture. If this value is positive the paragraph contains text, if this value is 0 or negative the paragraph is a ruler or a picture respectively and we can ignore it.

Offset 8 in the information array contains a status byte that provides some information about the text. If bit 3 is set, the text in this paragraph is compressed. Bytes 9-11 tell us the absolute file offset for the start of the data in the paragraph and bytes 12-13 contain the length of the data (paragraph addressing is 24 bits and each paragraph contains up to 64K of characters or data). The trick is to read in the number of characters indicated in the information array, determine if the paragraph contains text and, if so, decompress the text if it’s compressed.

Once we read in the paragraph, we get some more information. The first two bytes of the paragraph tell us how many characters of text will appear in the decompressed paragraph. Following the text on an even word boundary is the formatting information for the paragraph which we ignore in this example.

MacWrite’s text compression is based on a letter frequency scheme stored as STR resource #700 in MacWrite’s resource fork. For English, this string contains “ etnroaisdlhcfp”. Macwrite maps these characters onto the array [$0..$F]. The space character ($20) has a value of 0, letter “e” has a value of 1, “t” a value of 2 and so on. Since any number less than $F can fit into a nibble, the word “eels” can be represented as “$11A8” rather than the byte-wide representation of $65656C73. In this example, we realize a 50% space saving (the best case for this algorithm).

This compression scheme only works for lower-case letters since 4 bits is not enough information to code for word frequency and case for the 14 most popular letters. This scheme also doesn’t compress non-alphabetic characters such as numerals and punctuation marks. In these cases, the 16th array element, $F, is used as a flag to tell indicate that the next 2 nibbles represent one character. “Then” would be coded as $F55906. Note that the letter “T” crosses byte boundaries, the top nibble is in byte 0 and the lower nibble is in byte 1. This is of no consequence to the algorithm.

Armed with this information, you should have little trouble understanding the XFCN. In fact, I hope you find it useful and informative! (Next month: printing from XCMDs).

*file:  MWRead.c *
*  *
* an XFCN that imports text *
* directly from a MacWrite file  *
* whose full pathname is passed  *
* as an input parameter.  *
* -------------------------------- *
* To Build this file:*
*C -q2 -g MWRead.c *
*link -sn Main=MWRead    *
*-sn STDIO=MWRead *
*-sn INTENV=MWRead  *
*-rt XFCN=301   *
*-m MWREAD MWRead.c.o    *
*“{CLibraries}”CInterface.o  *
*-o “your stack name”*
* -------------------------------- *
* By: Donald Koscheka*
* Date: 2-July-1988*
* ©1988, Donald Koscheka  *
*All Rights Reserved *
* -------------------------------- *

#include  “HyperXCmd.h”

#define INFOPOS  0x00000108
#define PPOS0x00000002
#define COMP0x0008

/* -------------------------- */
/* Define the structure of an */
/* information array element*/
/* */
/* pHite is positive if this*/
/* info array points to text, */
/* ignored otherwise.*/
/* */
/* fPos is the absolute file*/
/* position of the start of */
/* the paragraph in the file*/
/* */
/* fLen is the total length of*/
/* the file including formats */
/* -------------------------- */

typedef struct infoArr {
 short  pHite;/* parag hite */
 short  pixels;/* ignore this */ 
 long pHand;/* ignore this*/
 char status;/* chk comprsn */
 char hiMark;/* msw of mark */
 short  loMark;/* lsw of mark */
 short  fLen;  /* parag. len*/
 short  fmat;  /* ignore this */ 

/* ------------------------ */

short   ReadFile();
Handle  DeCompress();

pascal void MWRead( paramPtr )
 XCmdBlockPtr  paramPtr;
* In:ParamPtr:
*pointer to XCMD param
*block. params[0] is the
*name of the macwrite file
*  to open.
* Out:ParamPtr->returnValue
*empty if data could not
*be read, text portion 
*of a   Macwrite document.
 ref,   /* file reference */
 err,   /* io error  */
 vRef,  /* vol reference  */
 pcnt,  /* #  paragraphs  */
 tSiz,  /* text length  */
 loop;  /* loop counter */
 fSiz,  /* data size */
 iSiz,  /* out data size  */
 iMark,/* iarr file pos */
 fPos;  /* para. offset */
 decomp,/* decompressed*/

ImportText = nil;

if( paramPtr->params[0] != nil ){
HLock( paramPtr->params[0] );

GetVol( vName, &vRef );
fName = *(paramPtr->params[0]);
err = FSOpen( fName, vRef, &ref );
HUnlock( paramPtr->params[0] );

if( err == noErr ){
 ImportText = NewHandle( 0 );
 /* get paragraph count   */
 fSiz = sizeof( short );
 /* get infoArray position*/
 fSiz = sizeof( long );
 /* read in the paragraphs*/
 for( loop = 0; loop < pcnt; loop++){
 fSiz = sizeof( infoArr );
 if( info.pHite > 0 ){
 /* paragraph is text*/
 /* calc text position  */
 /* get the char count  */
 fSiz = sizeof( short );
 err = ReadFile(ref,fSiz,&tSiz,fPos);
 /* read in the text */
 temp = NewHandle((long)tSiz);
 HLock( temp );
 fPos += 2; 
 fSiz = (long)tSiz;
 err = ReadFile(ref,fSiz,*temp,fPos);
 if( info.status & COMP ){
 /* paragraph is compressed */
 HLock( temp );
 decomp = DeCompress( *temp, tSiz );
 HUnlock( temp );
 DisposHandle( temp );
 temp = decomp;
 tSiz = (short)GetHandleSize( decomp ); 
 }/* if( info.status & COMP ) */
 iSiz = GetHandleSize( ImportText );
 fSiz = (long)tSiz;
 SetHandleSize( ImportText, iSiz+fSiz );
 HUnlock( temp );
 DisposHandle( temp );
 }/* if( info.pHite > 0 ) */
 iMark = iMark + sizeof( infoArr );
 }/* FOR paragraph count  */

 iSiz = GetHandleSize( ImportText );
 SetHandleSize( ImportText, iSiz+1 );
 *((*ImportText)+iSiz) = ‘\0’;
 FSClose( ref );
 FlushVol( nil, vRef );
}/* if file opened ok     */

paramPtr->returnValue = ImportText;


short   ReadFile(ioRef,siz,buf,from)
short   ioRef;
long    siz;
char    *buf;
long    from;
* read cnt bytes from the file specified by parms and put 
* the data into the buffer pointed to by buf 
* ioRef = file reference number
* siz = number of bytes to read
* buf = where to read in to
* from  = where in file to read from
* from is the file mark relative to the start of the file from
* which the read is to start.
 short  err;
 err = SetFPos( ioRef, fsFromStart, from );
 if( err == noErr )
 err = FSRead( ioRef, &siz, buf ); 
 return( err);

Handle DeCompress( inp, expcnt )
 char *inp;
 short  expcnt;
* Decompress the input handle’s data (inH) and put the result
* in the output Handle (outH).  outH is sized properly and 
* we use the following scheme:
*0 1 2 3 4 5 6 7 8 9 A B C D E F
*_ e t n r o a i s d l h c f p !
* where _ = SPACE
*! = not compressed
* Consult MacWrite resource str #700 for the decompression
* string in your file (different for other languages).
* cycle through until the decompressed string count 
* matches the expected count
 short  chcnt;
 register char *op;
 register char hiNib;
 register char loNib;
 char   dc[16];
 Handle outH;
 outH = NewHandle( (long)expcnt );

 dc[0]  = 0x020;
 dc[1]  = ‘e’;
 dc[2]  = ‘t’;
 dc[3]  = ‘n’;
 dc[4]  = ‘r’;
 dc[5]  = ‘o’;
 dc[6]  = ‘a’;
 dc[7]  = ‘i’;
 dc[8]  = ‘s’;
 dc[9]  = ‘d’;
 dc[10] = ‘l’;
 dc[11] = ‘h’;
 dc[12] = ‘c’;
 dc[13] = ‘f’;
 dc[14] = ‘p’;
 HLock( outH );
 op = *outH;
 chcnt = 0;
 while( chcnt < expcnt ){
 hiNib = loNib = *inp++;
 hiNib = hiNib >> 0x04;
 hiNib &=  0x000F;
 loNib &=  0x000F;
 if( hiNib < 0x0F ){
 *op++ = dc[hiNib];
 if( loNib < 0x0F ){
 *op++ = dc[loNib];
 else{ /* next BYTE is a char */
 *op++ = *inp++;
 /* next 2 nibbles represent*/
 /* a complete char which */
 /* is on odd-nibble bounds */
 *op  = loNib << 0x04;
 hiNib  = *inp++;
 loNib = hiNib & 0x000F;
 hiNib = hiNib >> 0x04;
 hiNib &= 0x0F;
 *op  = *op | hiNib;
 if( loNib < 0x0F )
 *op++ = dc[loNib];
 *op++ = *inp++;
 HUnlock( outH );
 return( outH );

#include <>

Listing 2. XFCN to import trxt from a MacWrite Document


Community Search:
MacTech Search:

Software Updates via MacUpdate

Dragon Dictate 6.0 - Premium voice-recog...
With Dragon Dictate speech recognition software, you can use your voice to create and edit text or interact with your favorite Mac applications. Far more than just speech-to-text, Dragon Dictate lets... Read more
OmniFocus 3.11.7 - GTD task manager with...
OmniFocus is an organizer app. It uses projects to organize tasks naturally, and then add tags to organize across projects. Easily enter tasks when you’re on the go, and process them when you have... Read more
rekordbox - Professional DJ m...
rekordbox is the best way of preparing and managing your tracks, be it at home, in the studio, or even on the plane! It allows you to import music from other music-management software using the... Read more
1Password 7.8.1 - Powerful password mana...
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more
Ableton Live 10.1.35 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Microsoft Office 365, 2019 16.48 - Popul...
Microsoft Office 365. The essentials to get it all done. Unmistakably Office, designed for Mac Get started quickly with new, modern versions of Word, Excel, PowerPoint, Outlook and OneNote-... Read more
Adobe After Effects 18.1 - Create profes...
After Effects is available as part of Adobe Creative Cloud for $52.99/month (or $20.99/month for a single app license). The new, more connected After Effects can make the impossible possible. Get... Read more
Adobe Audition 14.1 - Professional post-...
Audition is available as part of Adobe Creative Cloud for as little as $20.99/month (or $9.99/month if you're a previous Audition customer). Adobe Audition empowers you to create and deliver... Read more
Adobe Animate 21.0.5 - Animation authori...
Animate is available as part of Adobe Creative Cloud for as little as $20.99/month (or $9.99/month if you're a previous Flash Professional customer). Animate (was Flash CC) lets you share work... Read more
Adobe Photoshop 22.3.1 - Professional im...
You can download Photoshop for Mac as a part of Creative Cloud for only $20.99/month (or $9.99/month if you have purchased an earlier software version). Adobe Photoshop is a recognized classic of... Read more

Latest Forum Discussions

See All

Moonlight Sculptor is an upcoming MMORPG...
Kakao Games and XL Games – who you might be familiar with from their previous game ArcheAge – have announced that their MMORPG Moonlight Sculptor is now available to pre-order for iOS and Android devices. Moonlight Sculptor has previously launched... | Read more »
MU Archangel is now open for pre-registr...
MU Archangel is now open for pre-registration in Southeast Asia following its massive success in other territories. Players from Singapore, Thailand, Malaysia, Indonesia, and the Philippines (except Vietnam) can now join in on the fun by applying... | Read more »
Compete, a new social media app you can...
Whoever told you you can’t get rich making videos has obviously never heard of Compete, Competitive Media Technologies Limited’s hot new social media app where you can rake in all the dough just by doing what you love. Video monetization that... | Read more »
Bethesda has released a new DOOM mobile...
Bethesda Softworks has released a new DOOM game out of the blue exclusively for mobile devices. It’s called Mighty DOOM and is currently only available as an early access title on Android but will be expanding to more users in the future. [Read... | Read more »
Anagraphs is a word puzzle game with a t...
Cinq-Mars Media has released its word puzzle game Anagraphs for iOS and Android devices. The game released last week after a short delay in getting it onto the appropriate platforms. [Read more] | Read more »
These are the top 5 best iPhone games li...
Fortnite has been the big hitter in mobile gaming this year, and it's not hard to see why. Thanks to some excellent marketing, and a polished experience that almost anyone can enjoy, it's really taken the App Store by storm. But there are other... | Read more »
The top 5 best iPhone games like Pokemon...
Pokemon GO is still the, if you'll excuse the pun, go-to game if you want some AR action on your phone. But it's not the only choice out there, and if you've got a hankering for something a bit different, then your eyes might already have started... | Read more »
The top 5 best iPhone games like Starcra...
Starcraft sits at the top of the RTS tree for a number of very good reasons. It also isn't on mobile, again, for a number of very good reasons. But that doesn't mean you can't find a way to indulge your sci-fi, competitive, massive, or engaging RTS... | Read more »
Apple Arcade: Ranked - Top 25 [Updated 4...
In case you missed it, I am on a quest to rank every Apple Arcade game there is. [Read more] | Read more »
The top 5 best iPhone games like The Roo...
The Room has had a massive impact on the world of mobile gaming. Not only is it a brilliant adventure, it also shows how the touchscreen controls on your iPhone can be turned into something far more elegant and tactile than just a bunch of buttons... | Read more »

Price Scanner via

B&H is offering clearance prices on lefto...
Apple reseller B&H Photo has clearance 2020 13″ 1.4GHz Intel-based MacBook Pros on sale today for $200-$300 off Apple’s original MSRP with prices starting at only $1099. Expedited shipping is... Read more
Roundup of Today’s Best MacBook Deals: M1 Mac...
Apple resellers are offering sale prices on Apple’s M1-powered 13″ MacBook Airs ranging up to $190 off MSRP. Here’s where to pick one up today, and as always, keep an eye on our 13″ MacBook Air Price... Read more
Apple AirPods Pro drop to new low price of on...
Amazon has Apple’s AirPods Pro on sale today for a new low price of only $197 shipped. That’s $52 off MSRP and the lowest price currently available for a set of AirPods Pro from any Apple reseller.... Read more
Apple restocks clearance 13″ Intel-based MacB...
Apple has clearance, Certified Refurbished, 2020 13″ Intel-based MacBook Airs available starting at only $809 and up to $280 off original MSRP. Each MacBook features a new outer case, comes with a... Read more
OWC drops prices on 2020 Intel multi-core Mac...
Other World Computing has clearance 2020 Intel-based Mac minis on sale starting at only $499. Both 4-core and 6-core models are in stock today. These are new, unopened, factory-sealed minis: – 3.6GHz... Read more
Save $50 off Apple’s 10.9″ iPad Air today at...
B&H Photo has new 10.9″ Apple iPad Airs in stock and on sale today for up to $50 off MSRP. Expedited shipping is free to most addresses in the US. Note that some sale prices may be restricted to... Read more
Rare Apple sale: Get a HomePod mini for $10 o...
Apple reseller Expercom has the Space Gray HomePod mini on sale today for $89 shipped. Their price is $10 off Apple’s MSRP, and it’s currently the only sale price available for a HomePod mini among... Read more
Apple has M1 Mac minis available starting at...
Apple has a full line of standard configuration M1 Mac minis available in their Certified Refurbished section starting at only $589 and up to $140 off MSRP. Each mini comes with Apple’s one-year... Read more
New sale at Amazon: $55-$70 discounts on Appl...
Amazon has 7.9″ iPad minis on sale today for up to $70 off Apple’s MSRP, each including free shipping. Prices start at $344. These are the same iPad minis sold by Apple in their retail and online... Read more
Apple offering 13″ M1 MacBook Airs for as lit...
Apple has a full line of 2020 13″ M1 MacBook Airs available, Certified Refurbished, starting at only $849 and up to $190 off original MSRP. These are the cheapest M1 MacBook Airs for sale today at... Read more

Jobs Board

*Apple* Valley 20hr Teller - Wells Fargo (Un...
…or scheduled + Ability to stand for extended periods of time **Street Address** **MN- Apple Valley:** 14325 Cedar Ave - Apple Valley, MN **Disclaimer** All offers Read more
*Apple* Valley 20hr Teller - Wells Fargo (Un...
…or scheduled + Ability to stand for extended periods of time **Street Address** **MN- Apple Valley:** 14325 Cedar Ave - Apple Valley, MN **Disclaimer** All offers Read more
Desktop Support Technician - *Apple* / Mac...
…infrastructure and internal desktop systems. Must have an IT background that includes Apple / Mac support. **Overview:** + Responds to routine technical questions or Read more
Geek Squad *Apple* Consultation Professiona...
**801042BR** **Job Title:** Geek Squad Apple Consultation Professional **Job Category:** Store Associates **Store Number or Department:** 000214-Willowbrook-Store Read more
*Apple* Mobility Specialist - Best Buy (Unit...
**800879BR** **Job Title:** Apple Mobility Specialist **Job Category:** Store Associates **Store Number or Department:** 000803-Lansing-Store **Job Description:** Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.