A Fast Method of Identifying Plain Text Files ============================================= Introduction ------------ Given a file coming from an unknown source, it is generally impossible to conclude automatically, and with 100% accuracy, whether that file is a plain text file, without performing a heavy-duty semantic analysis on the file contents. It is, however, possible to obtain a fairly high degree of accuracy, by employing various simple heuristics. Previous versions of the zip tools were using a crude detection scheme, originally used by PKWare in its PKZip programs: if more than 80% (4/5) of the bytes are within the range [7..127], the file is labeled as plain text, otherwise it is labeled as binary. A prominent limitation of this scheme is the restriction to Latin-based alphabets. Other alphabets, like Greek, Cyrillic or Asian, make extensive use of the bytes within the range [128..255], and texts using these alphabets are most often mis-identified by this scheme; in other words, the rate of false negatives is sometimes too high, which means that the recall is low. Another weakness of this scheme is a reduced precision, due to the false positives that may occur when binary files containing a large amount of textual characters are mis-identified as plain text. In this article we propose a new detection scheme, with a much increased accuracy and precision, and a near-100% recall. This scheme is designed to work on ASCII and ASCII-derived alphabets, and it handles single-byte alphabets (ISO-8859, OEM, KOI-8, etc.), and variable-sized alphabets (DBCS, UTF-8, etc.). However, it cannot handle fixed-sized, multi-byte alphabets (UCS-2, UCS-4), nor UTF-16. The principle used by this scheme can easily be adapted to non-ASCII alphabets like EBCDIC. The Algorithm ------------- The algorithm works by dividing the set of bytes [0..255] into three categories: - The white list of textual bytecodes: 9 (TAB), 10 (LF), 13 (CR), 20 (SPACE) to 255 - The gray list of tolerated bytecodes: 7 (BEL), 8 (BS), 11 (VT), 12 (FF), 26 (SUB), 27 (ESC) - The black list of undesired, non-textual bytecodes: 0 (NUL) to 6, 14 to 31. If a file contains at least one byte that belongs to the white list, and no byte that belongs to the black list, then the file is categorized as plain text. Otherwise, it is categorized as binary. Rationale --------- The idea behind this algorithm relies on two observations. The first observation is that, although the full range of 7-bit codes (0..127) is properly specified by the ASCII standard, most control characters in the range 0..31 are not used in practice. The only widely-used, almost universally-portable control codes are 9 (TAB), 10 (LF), and 13 (CR). There are a few more control codes that are recognized on a reduced range of platforms and text viewers/editors: 7 (BEL), 8 (BS), 11 (VT), 12 (FF), 26 (SUB), and 27 (ESC); but these codes are rarely (if ever) used alone, without being accompanied by some printable text. Even the newer, portable text formats, such as XML, avoid using control characters outside the list mentioned here. The second observation is that most of the binary files tend to contain control characters, especially 0 (NUL); even though the older text detection schemes observe the presence of non-ASCII codes from the range [128..255], the precision rarely has to suffer if this upper range is labeled as textual, because the files that are genuinely binary tend to contain both control characters, and codes from the upper range. On the other hand, the upper range needs to be labeled as textual, because it is being used by virtually all ASCII extensions. In particular, this range is being heavily used to encode non-Latin scripts. Given the two observations, the plain text detection algorithm becomes straightforward. There must be at least some printable material, or some portable whitespace such as TAB, CR or LF, otherwise the file is not labeled as plain text. (The boundary case, when the file is empty, automatically falls into this category.) However, there must be no non-portable control characters, otherwise it's very likely that the intended reader of that file is a machine, rather than a human. Since there is no counting involved, other than simply observing the presence or the absence of some byte values, the algorithm produces uniform results on any particular text file, no matter what alphabet encoding is being used for that text. (In contrast, if counting were involved, it could be possible to obtain different results on a text encoded, say, using ISO-8859-2 versus UTF-8.) There is the category of plain text files that are "polluted" with one or a few black-listed codes, either by mistake, or by peculiar design considerations. In such cases, a scheme that tolerates a small percentage of black-listed codes would provide an increased recall (i.e. more true positives). This, however, incurs a reduced precision, since false positives are also more likely to appear in binary files that contain large chunks of textual data. "Polluted" plain text may, in fact, be regarded as binary, on which text conversions should not be performed. Under this premise, it is safe to say that the detection method provides a near-100% recall. Experiments have been run on a large set of files of various categories, including plain old texts, system logs, source code, formatted office documents, compiled object code, etcetera. The results confirm the optimistic assumptions about the high accuracy, precision and recall offered by this algorithm. -- Cosmin Truta Last updated: 2005-Feb-27