L'uso di questo sito
autorizza anche l'uso dei cookie
necessari al suo funzionamento.
(Altre informazioni)

Wednesday, December 30, 2009

Unicode and the encoding problem

Italian version

Note:

This post is complemented by a later one, concerning especially encoding problems in databases used for web sites

Introduction



Over the course of (too many) years, I have struggled with computer representation of text. Each tiem I had to solve what I call the fundamental problem o text representation I resorted to instainct and ad hoc methods. Only recently did I force myself to systematically gain a clear understanding of the theorical underpinnings of the incantations I have been using. Having done this, I thought I had to write what I'd learned (lest I forgot); once written (in Italian), I thought that, had I published it, I might have helped some poor schmuck struggling with the same problems; once published, I thought: why italians only? Hence this article. The root motivation, though, is that the subject matter is obscure (or obscured by decades of committee-generated specs) and much of the information that circulates about it is either incomprehensible, partial, or plain wrong.


Intended Audience



The core subject is (should be) comprehensible by anybody possessing basic computer knowledge. Its ramifications, though lie in programming techniques that are rather arkane even for software engineers. I have tried to treat the various subjects in order of increasing obscurity: when what you'llbe reading stops making any sense, most of what follows will be of the same ilk.


Conversely, if you feel that the stuff you're reading is trivial, have a look at the section headings: something juicier may be in the works. I do not advise skipping around, though. Most of this article usefulness (if any) lies in a clear definition of the encoding/decoding process, an apparently trivial pursuit: after all, everybody thinks hat he knows what encoding and decoding are about. Read on and you may find out that you were mistaken...


The fundamental problem



The fundamental problem is posed by a perplexed, yet angry, user that wlaks up to your desk saying:


"On X the text Y displays all right. On W, the same text Y, is displayed in the wrong Z-way"


Here X may be a computer or a program or anything else somebody might use to display text. Y may be coming from an actual text file, or a message blurted out by some program, or a page coming from a web server. Z ranges from "Some charachters are funny" to "It's a jumble of totally garbled, random characters"


With some luck, the original text and its displayed representation are somewhat readable, and are on accessible machines. On a bad day, it's a Dewanagari text, translated in Tokyo and displayed in Brisbane.


At any rate you, the IT professional, mutter knowingly "Oh yeah, an encoding problem. I'll be calling you" and pretend to be absorbed in deep meditation, hoping the user leaves. All the while you are cursing under your breath.


Let's see why.


A character issue



Characters are the elementary components of the written word (here 'written' i importante: by contrast, the elementary components of the spoken word are called phonemes, a different kettle of fish)


It is essential to keep a character and its visual representation distinct: the character "LATIN SMALL LETTER A" ('a') must not be confused with any of the shapes it assumes when printed or displayed on a screen, a newspaper and so on. While the character is the idea of an elementary component of a word, its realization is called grapheme. To be even more precise, a grapheme is a the specification of a character shape abstracted from its typographic font (a foggy concept that I won't explore): a printed character, font and all, should be called a glyph. A single character may have more graphemes (like the Greek "SIGMA", who has a different shapes at the end and at the boeginning/middle of a word), while more characters, can be combined in a same grafeme/glyph (like the syllable "fi", whose typographic representation ligates the two letters and omits the dot on the 'i')


At this point a "character" is already an idea much wider than "alphabet letter": it includes punctuations, diacritics and several varieties of white space. I will call "alphabet" the collection of characters used by a written language.


The stupid computer



Up to the first half of the 20th century, most of the preceding notions were basically subsumed in tha activity of learning the alphabet of a language. The coming of age of computers did change a few things about text representation and posed radically new problems.


The Turing/Von Neumann computers we're all familiar with are - at their core - quite stupid when it comes to symbolic representations. The only 'alphabet' they can cope directly with is, in fact, composed by a total of two symbols, namely, 0 and 1 (this is known as a bit). In other words, the only information a computer can directly manipulate consists of binary integer numbers. Since bits are too small to be used as a practical data unit, most computers deal with data through the exchange of 8 bit packages almost universally known as use a larger bytes (sometimes also octets).We may think to a byte as the smallest data unit that a computer can exchange through an input/output device (or from/to memory) with its central processing unit. This means that, to get at the 20th bit, a computer will have to access the entire third byte - which contains it.


Computers can process non-numerical information by converting it to numbers. If we need to process musical notation - for instance - we must beforehand devise a method that converts musical notation symbols to numbers and viceversa. This is normally done by numbering the repertoir of symbols (the alphabet) that will be used.


In what follows, I will call this correspondence between symbols and numbers a code. This is a somewhat non-standard terminology: most of the times, the same concept is called a codepage or - in a somewhat ambiguous fashion - an encoding. The reason for this change in verbiage is that the encoding process - discussed later - is actually a rather different from the code, and grasping this difference is 90% of the rules of thi particular game, while the term "codepage" is used rather inconsistently, often with reference to a particular brand of computers, or operating systems.


I will restrict my scope to alphabetic notation, neglecting more specialistic fields such as mathematical and musical notation for which several codes have been developed over the years. Suffice it to say the most important code (Unicode) can be used to process a repertoire of symbols that spans the non-textual alphabets that have been mentioned, as well as several others, such as the alphabets and writing systems of ancient languages.


I will also neglect all the problems involving text layout,
writing direction, word collation, date and currency format. Most of
these are - however - taken into consideration by "http://www.unicode.org">Unicode.


Codes



What we have said to this point can be summarized as follows:


"Computer-mediated text processing requires a procedure of character numbering called a code."


The number that - in a code - represents a given character is called codepoint.


A code can represent any given collection of symbols: an entire alphabet, just a part of it, or even several alphabets. Thus the adoption of a code limits the repertoir of characters that can be used in a given application, which can severely handicap - for instance - the processing of multilingual text. As an example, an interlinear italian translation of K'ung-tzu's work cannot be represented by using the venerable ASCII code or a large number of the national codes (like Latin-1). This was ne of the reasons of the invention of Unicode, which tries to include the entire repertoire of ll the human languages and which, to this writing, actually includes all the characters used by the principal modenr languages.


Representations



We must now distinguish between the internal and external representations of a character. I call internal representation the representation which a computer program uses to identify a given character. For our purposes ths can be regarded as completely abstract. I call external representation of a charachter the one that is used to write it to disk, or to send it to some other program.


While the internal representation is used, the code is used as a pure reference: it's there, it is (somehow) used, but it remains mostly invisible.


By contrast, the code plays a paramount role when the internal representation is converted to external, or viceversa. When transforming the internal represtation to external the following happens:


i) the character is converted to a number (codepoint) according to the code;


ii) the codepoint must be converted to a byte sequence: this is called encoding;


iii) the byte sequence is transmitted.


When going from the external represtation to internal, the prcedure is reversed:


i) the byte sequence (external representation) is received.


ii) the byte sequence is converted to a sequnce of numbers/codepoint: this is called decoding;


iii) the codepoints are converted to internal representation by using the code.


As will be showed, the procedures of converting a codepoint to a byte sequence and viceversa are not uniquely determined. If they were, converting to/from the external and internal representation would be possible by just knowing the code that is been used, and the "fundamental problem" would be much easier.


The decoding and encoding concepts are so central to the definition of our problem that they deserve to be clearly defined as follows:


The encoding process associates a codepoint to a character in tis internla representation and then converts the codepoint to a concrete numerical representation expressed as a byte sequence (external representation.


The decoding process converts a byte sequence (a character in external representation) to a codepoint and then (by the application of a suitable code) to its internal representation.


How text is displayed



So, how does a byte sequence becomes readable text on a screen (or on a printed page)? And how can it happen that the same byte sequence can instead be displayed as an unreadable jumble of cruft?


The process of actually displaying sequnce of characters can be summarized as follows.


1) The text is received by the software environment as a byte sequence (numbers in the range 0-255).


2) The decoding process converts the byte stream to a sequence of codepoints


3) The codepoint sequence is identified as a character sequence by applying a code.


4) After text processing is completed, character are associated to glyphs that can be sent to the display device (say a screen, or a printer).


The last step of this process - important though it may be for the actual representation of text - is seldom relevant to the problem at hand. At its core it consists in determining what shape the character is given within a given font. This is a well defined task, once the code is known. The only thing that can go wrong (rendering the text unreadable) happens when a wrong font (a font which does not contain the desired shapes). This may be a non-alphabetic font, or a font that does not contain the alphabet we need. This type of error is easily solved by using one of the standard (Unicode) alphabetic fonts which are found on modern systems (such as Arial, Helvetica, Courier...).


Errors that happens in phases (1) through (3) are more common, and harder to solve. Any mismatch between code and encoding will render the entire sequence unintelligible, and may even result in software errors.


So why do we have to cope with more than one (several, actually) code and encoding? To understand it, we need to look back at history?


Once upon a time



...computers were uniquely dedicate to numerical computing. Numerical results require a fairly limited character set (ten digits, a few latin letters, the '+', '-', ',' and '.' signs) which are well understood in most human languages. Besides most results were printed (or punched) for human consumption. Our problem did not exist.


This happy situation changed over the course of few years, as the computers' symbolic capabilities were understood and exploited. Codes were invented to satisfy the need to represent symbols. Anarchy set in as each manufacturer devised its own code: exchanging data among machines quickly became a problem, and the industry eventually settled on two codes: EBCDIC (1963) and ASCII (1963).


The ASCII code was - and is - an actual standard, devised by an industry comittee. EBCDIC had been independently developed by IBM and had been, over the years, adopted by many other mainframe manufacturers. Today its use is basically limited to IBM mainframes, and is waning.


The root of all evil



The goals that were set for the first codes were, by today's standards, extremely limited:


1) they were designed to be used in a single-language environment, the single language being very often English;


2) they needed to be space efficient (by using the least possible amount of bits per character) so that their transmission could be fast.


Because of (1), the set of coded symbols was cut down to the minimum: ASCII, for instance, dealt only with the characters and punctuation used in the English language.


Because of (2) a number of (unprintable) symbols that were only used for transmission control (things like Start-Of-Message, End-Of-Message) were crammed in the same code, reducing the number of codepoint available to actual characters.( This happened also because the most popular transmission methods used a serial line, so the control signalling was forcibly interleaved with the character stream.)


Mostly for these reasons ASCII is a 7 bit code. The availble number of codepoints is therefore 128 of which the first 32 are control characters: the reamining (printable) codepoint can represent mosto of the characters found on the keyboard of an (english language) typewriter keyboard. The remaining (eighth) bit was reserved for checksumming (a process that allows the detection of transmission errors)


What about the rest of the world?



Ninetysix character are not enough to represent all the alphabets of the world. They are not even nearly enough to represent all the alphabets in use for european western languages.


This was obviously quite unsatisfactory and it became ever more so as computing was becoming commonplace in countries whose alphabet was completely different from the one coded by ASCII.


The codepage



The situation was initially solved by patching the existing standards (which, alas, were not designed to tolerate much patching. A first step was seizing the eighth bit that ASCII had reseved to checksumming. This expanded the codepoint space of 128 units. Every manufacturer then proceded to create codes mapping these newly available codepoints to additional alphabetic characters (sometimes, to graphic symbols used for drawing). Borrowing an IBM word, these codes were (and are) called codepages./p>
Information Technology Babel quickly ensued: correct interpretation of a received text now required the knowledge of the (vendor dependent) codepage: if the text had to be used on a different system, a compatible codepage had to exist on the system. The situation was, however somewhat mitigated by the existence of e de-facto-standard setting Behemoth (IBM) that dictated most of the practices for the industry, and by the fact that computer communcations were still mediated by highly specialized professionals which were capable of making sense of sentences like: "I am sending over a .5 inch ANSI tape in the 443 codepage".


LATIN-1 & Co.



Standardization was obviously called for and this time the task was assumed by ISO towards the end of the 1980s.


A uniform naming system was established for the codes that had become a de-facto standard. As an (important) instance, a 256 codepoint set describing the alphabets of several western european languages was named iso-8859-1 (or, Latin-1). The names iso-8859-x (from iso-8859-2 to iso-8859-16) were chosen to designate codes for european languages with special alphabets (such as Greek and Cyrillic).


Codes forAsian languages (essentially japanese, chinese, korean) were similarly grouped under the ISO/IEC 2022 set of specifications.


Most, if not all, the ISO codepages map the first 127 codepoints in the same way ASCII does, making them (almost) ASCII compatible.


Some regional and special purpose codes were left behind in the process, and some monsters came into existence: Latin-1, for instance is to this day almost, but not quite, identical to the widely used windows-1252, codepage.


One of the outcomes of this process was underlining the need for a unified code, capable of cataloguing all the alphabets used by humanity: this resulted in the creation of Unicode.


Encodings



We defined a byte as the minimal transmission unit fora computer. Because a byte is an can represent 256 integers, any code that uses less than this amount of codepoints can have an external representation tht fits within a single byte. For these codes, internal and external representation may be made coincident, by mapping each codepoint to its single-byte value. For this codes, code and encoding are one and the same.


As we all know, several languages use more than 256 symbols (Chinese and Japanese, as well as several other asian languages, use up to tens of thousands of symbols). Codes for these languages do not have a single byte per character: some characters must be represented by more than one byte, a feat that may be accomplished in at least two ways.


Wide-char encodings



The most natural choice is using the same number of bytes to encode all the codepoints.For instance an alphabet having more than 256, but less than 65536, symbols is amenable to a two byte (00000000-00000000 to 11111111-11111111) encoding. Such encodings are called "wide-char" encodings. In spite of their being quite intuitive, wide-char encodings suffer from a number of shortcomings, that I will discuss later.


An example: UCS-2 (UTF-16)



Let us conider a U encoding, having the following properties (I am essentially describing - save a few, minor details - the UNICODE encoding known as UCS-2).


1) U is a wide-char, two byte per codepoint encoding


2) U uses the first 256 codepoints in the same order and meaning as the Latin-1 codepage. This means that all the alphabets of the principal western european language fit in the first byte of this encoding.


The first problem with U us that it is spatially inefficient. U containst 511 symbols encoded by sequences with at least a null byte (all the bits of the byte are zero). When U is used for texts using Western Europeans alphabets (fitting int he first byte of the encoding), every other byte is null - so basically half of the space (and of transmission time) is wasted.


A second problem of U relates to endianness. (The word comes from the inhabitants of the legendary islands oof the mythical islands of Lilliput and Blefuscu, who - as related by Swift in the novel "Gulliver's Travels" - could not agree on which end of an egg should be broken first. Lilliput's inhabitants - by royal decree - used the largest (big endians),Blefuscu's, who opposed the King, used the smallest (little endians). Because of this disagreement, the two peoples fought a bloody war.per protesta contro il re: little endians).


Even though the basic transmission uniti, for computers is the byte, the need of larger data units was soon felt. Among these a certain regard is attached to the so called word, adjacent pair of bytes. Internally, computers often manipulates words as a whole: integer numbers, for instance, are represented by one, two or four words.


A word, however, is never seen as basic (unsplittable). So when a word leaves the computer memory it can be sent (externally represented) in one of two ways:


1) the Most Significant Byte is sent first, followed by the Least Significant Byte (big endian)


2) the Least Significant Byte is sent first, followed by the Most Significant Byte (big endian)


(Similar differences may arise in the representation of pair of words, but fortunately they are far rarer)


If we picture bytes as decimal digits, and given the number "ninety-one", we can see that big endian machine would write/memorize it as "9" "1", whereas a little endian machine would write/memorize it as "1" "9".


Unbelievable (or stupid) as it may seem, for years nobody mandated the word order in external representation, so either order has been used with comparable frequency. This obviously made endianness (AKA byte-ordering) another stumbling block on the way towards computer communication. So pesky a problem, in fact, that at some point it was actually solved with a blitz operated by da Sun by deciding that, over a TCPI/IP network, a network byte order existed, to which all computers must submit (the network byte order is big endian, the same that Sun machine used at the time). While that fixed for network communication, no such fix exists for files, which are still being written with different endianness on different machines.


For our U encoding this means that its correct decoding will be possible only after its endianness is known.


A last problem with U is apparent to programmers only. We have seen that a U encoded character stream can contain null bytes (indeed up to half of the bytes may be null). Traditionally though (traditionally meaning from circa 1960 until sometime around the year 2000) a null byte had a almost universal meaning of "end of string" for a large body of software, including software devoted to text manipulation in Western European countries. This also means that U is not compatible with the above mentioned software, which will behave unpredictably when handed a U-encoded string.


Multibyte encodings



We have multibyte encodings if we translate different codepoints using a different number of bytes.


An example: UTF-8




Let us consider a F encoding, having the following properties (I am essentially describing - save a few, minor details - the UNICODE encoding known as UTF-8).


1) F is a multibyte encoding, an uses the first 127 codepoints in the same order and meaning as the ASCII codepage. F is therefore ASCII compatible


2) When the most significant bit of a given byte (in F) is set, the byte is part of the encoding of a codepoint which translates to a multiple byte sequence. If one (or more) bits following the most significant bit are set, followed by a zero ((110xyyzz, 1110yyzz, ...) the byte starts a sequence composed by one, two... bytes. Bytes of the form (10xxyyzz) follow the first in a multibyte sequence for a codepoint.


While F solves some of the problems of the wide-char encodings, it introduces some of its own. Let us compare it to the encoding F, above


1) F is much more space efficient than U when coding the first 127 codepoints (ASCII). F becomes less efficient than U for codepoints requiring three- or more bytes to represent (the majority of the asian languages), which have a space penalty of about 30% on the average.


2) F is not affected by endianness: codepoints are written as sequences of bytes (not words - a concept that is intrinsically ordered)


3) F does not contain null-bytes and is ASCII compatible, making it possible its processing with "traditional" software tools.


4) F is not latin-1 compatible.


5) Decoding F is harder then decoding U. F makes also harder performing tasks such as "finding the fifth character of a string". If U (or a similar wide-char encoding) is in use, the task can be effected by fetching the fifth "word" (pair of bytes) of the string. With F, one has to read all the bytes starting from the first and convert them to characters, until the fifth codepoint is reached.


6) F does not allow arbitrary byte sequences (for instanceo 110xyyzz-0qxxyyzz is an illegal F sequence). Therefore, upon encountering a byte stream containing a an illegal sequence it is possible to estblish, with certainty, that it has not been encoded using F. This may seem a trivial property, but it is not: many single-byte and wide char encodings ofer no such validation method. In particular, any random byte sequence can be interpreted as a valid ISO-8859-X encoded char sequence. This is an important part of the fundamental problem.


Over there years, several multibyte encodings were devised and describing them is byond the scope of this paper. It is worth mentioning, though the "shift" family of encodings in which the appearance of a particular byte sequence (the "upshifit" sequence) changes the meaning of all the upcoming sequences until a corresponding "downshift" sequence is met. Many such encodings are grouped under the ISO/IEC-2022, specification which deals with the encoding of several east Asia languages.


It must be said that, for most of the codes/codepages defined by the ISO specifications, the coupling between code and encoding is actually unique, meaning that, if the code is known so is the encoding (and viceversa). Unicode differs on this, as we will see shorly.


Unicode



The often mentioned Unicode standard, as specified from the Unicode consortium, is an initiative whose purpose is creating a unified repertoire (a code) of all the characters used by humanity including contemporary languages, antique languages, invented languages (there is a codepoint space dedicated to the Klingon alphabet) and with enough space to include yet-to-be-coded languages. The most recent Unicode formulation also includes math and music symbols as well as other more specialistic symbol catalogs. More than half of the codepoint space (10 planes) is currently unassigned (i.e. it contains no characters) and it is unlikely it will be in the near future.


In other words Unicode aims to be the code to end all codes: it, for instance, the creation of multilingual text without switching codepage - actually Unicode makes the existence of other codepages irrelevant (assuming all world could be persuaded to use Unicode).


As of today the standard includes1 114 112 (one milion one hudred fourteen thousand one hundred and twelve) codepoints, divided in 17 planes; every plane comprises 56 536 codepoints ordered on 256 rows of 256 columns)


Plan 0, which includes the first 65536 codepoints, is more often called Basic Multilingual Plane (BMP) and it contains most of the repertoire for modern languagese. In order to stay compatible with legacy practices, the BMP maps its first 127 codepoints in the exact same way as ASCII.



An important part of Unicode is the definition of the "Unicode transformation format" (UTF) and the "Universal character set" (UCS): these are the encodings used for Unicode's external representation.


Of these the most important (and the ones that are met with most often) are just two:


UTF-8: a multibyte encoding maximizing the ASCII retro-compatibility (I have already described this as the "F" encoding). UTF-8 encodes every character with a byte sequence whose lenght varies between one an four octects (bytes).


UTF-16 (formerly UCS-2: I have already described this as the "U" encoding): a multibyte encoding of the entire Unicode repertoire. UTF-16 represnts the entire BMP as a wide-char two-byte encoding: this was the original definition of UCS-2 -a no longer used encoding - which could only represent the BMP. UTF-16 encodes every codepoint with a byte sequence whose length varies between two and four octets. The four-byte encoding is reserved to very rare codepoints lying outside the BMP.


UTF 16 also defines a particular value(Byte-Order-Mark or BOM) which is used to start a text and that is used to infer the text's endianness.The BOM is represented by the hexadeciimal codepoint U+FEFF which on a big-endian machine is presented as the 0xFE,0xFF sequence and as the sequence 0xFF,0xFE on a little endian machine. Because the U+FEFF codepoint (Zero-Width No-Break Space) may never start an encodend sequence, while the U+FFFE codepoint is not (and never will be) assigned to a valid character, finding one of these codepoints at the start of a codified sequnec allows to determine the endianness of the entire sequence.


UTF-32/UCS-4: a "wide" fixed length encoding: every codepoint is encoded in a 4 byte long sequence. BOM is applied as seen in UTF-16. This encoding is - in practice - seldom used.


Because of the stated advantages of the F encoding over U, UTF-8 is the most used encoding for the external representation of Unicode texts, while UTF-16 is often used for the internal representation of text. In particular, UTF-16 is used in the Microsoft operating systems.


The fundamental problem, revisited.



At this point we have the background needed to understand the causes of the fundamental problem, as stated a few sections ago.


The root cause of the problem is that a text (file) that was prepared to be displayd using a given triad (code, enconding, endianness) is actually being displayed with the wrong triad (i.e. at least one of the three components is wrong).


There is another possibility, namely, a font mismatch (for instance no japanese characters in the selected font). This error source is easily elimintaed by installing (and directing the application to use) a complete set of fonts (often termed Unicode fonts).


The fundamental problem is solved if we can somehow infer the source triad, the target triad, and determin the correct translation between the two.


Theorem of the uncomputability of the transcoding.



What we know about the subject is also sufficient to proof what I (pompously) call "the theorem of the uncomputability of the transcoding":


"There is no algorithmic procedure capable to uniquely determine the encoding/codepage of an arbitrary byte sequence."


The proof is easy: it is sufficient to observe that any arbitrary byte sequence is a "correct" sequence in the Latin-1 encoding (actually in any Latin-X encoding). This means that any byte sequence "could" be a Latin-1 encoded character sequence regardless of the encoding that was actually used to produce it. Therefore the original sequence cannot be determined with certainty.


This unfortunate result means that any technique to solve the fundamental problem will have to rely on euristics and/or statistics.


Let's see some of this techniques.


Wrong endianness (for a wide char encoding).



These days, this error seldom happens, except when exchanging tapes between systems. UTF-16 encoded files must start with the BOM, and are therefore easily detected (this should actually be done for you by the application). Othe encodings may need that both possible ordering be tested, with the correct one being inferred by inspection.


Wrong code or encoding



The file was generated by using a code/encoding different from the one expected on the target machine. This is the type of errors that happen more often.


The first thing to ascertain is the (code,encoing,endianness) expected on the target system. This may not be easy as it seems. Web browsers, for instance, try to guess code and encoding of web pages, and frequently guess wrong. Operating systems assume a codepage: on windows systems the ANSI CP_ACP codepage must be examined, as well as the regional setings; on Linux systems the LOCALE family of environment variables must be taken in account. While a complete list of indicators is hardo to come by, some inferences can be made: an italian speaking Windows machine, for instance, is often working under the windows-1252 locale.


We must then turn our attention to the source triad. because of the uncomputability theorem we know that we have no sure-fire procedure, but we can still try to take a good guess.


First off, if the representation on the source machine shows a high percentage of graphical and/or control characters, we may assume that the encoding in use is wrong. Unless of course, the file we are looking at is not a binary file to start with. This may seem trivial, but, if in doubt, using the output of the Linux command "file <nomefile>" can save you many a frustrating hours, especially if the oputput turns out to be something like "PDF file" or "compressed file".


The easy case



For systems in the italian language (but the same shouldgo for most european languages), and in my experience, the most common occurence is having Unicode - encoded text on a iso-8859-1 system, or viceversa. On the Unicode side, you can exepct the encoding to be UTF-8 9 times out of 10, and UTF-16 for the rest. UTF-16 is easily detected (for european languages) by looking at the file with a binary editor (odump on linux) and noting extensive areas of the file where null and non-null bytes alternate (this method is worthless for CJK languages). Also the presence of the BOM will give UTF-16 away.


A strong evidence for UTF-8 encoding is gined by observing the way in which accented letters are modified (this is mostly useful for languages where accented letters are common, such as Italian and French). If a UTF-8 encoded file is transmitted to a ISO-8859-x (with Latin-1 or windows-1252 instances being the most common occurences) most of the non-accented characters will be displayed correctly, while accented letters (which are encoded as a pair of bytes which translate to two charaters in Latin-x encodings) will display as a pair of characters, the first being a capital "A" capped by a tilde (Ã). Diacritic marks of other western european languages (e.g. the German tilde) will behave similarly, as will most currency symbols (the S dollar being the exception) and other semi-graphic characters, such as the copyright (©) mark.


The opposite case (ISO-8859-X on a UTF-8 system) is a little thornier. All accented letters and diacritics will be mistaken as the beginning of a multibyte sequence, which will be either a valid or invalid UTF-8 mulitbyte sequence. In either case, one (or more) of the subsequent characters will disappear; what will be displayed will either be a random "exotic" character - often a white question mark on a black diamond - or an error message. Sequences of ASCII characters will display normally.



The general case



In the most general case, a more systematic approach must be taken. The following checklist may help here.


1) Collect as much information as possible on the file origin. If possible, found out which application produced it and look for its manual. Use Google. If possible, speak with the human originator of the file.


2) Examine the file through other applications. A good text editor is of great help (the one I use - emacs, has very good multilingual support from release 23 - sometimes opening the culprit in emacs is enough to solve the problem)
3) Remember the obvious. The file intended purpose is often enough to solve the problem. XML files, for instance are UTF-8 enacoded unless they explicitely declare an alternative encoding.


4) Have a good (software) toolbox and use it to try all the possible transcodings in order of decreasing probability (for example, if the file should contain japanese tex, the JIS encoding is a likely starting point; for chinese, Big5 would be more likely). If you can, make your experiments on a sample of the file that also contains recognizable sequences of western characters (things like addresses or personal names), because english/ASCII characters tend to be encoding-invariant. Some tools exist that help in automating this process (e.g. the Universal Encoding Detector uses the same euristics most browsers use).


See also appendix A for a couple of (python) functions which may be handy in this activity.


Having a clear mental picture of the purpose of your actions and of the intended behavior of your tools is an important part of this puzzle solving activity. This (python oriented) link may shed further light on the subject http://code.activestate.com/recipes/466341/


Some programming tips



The brute force approach to cracking the "fundamental problem" almost invariably requires some programming. Most programming languages, these days, claim "Unicode support", but what is meant with this is far from clear. To the best of my understanding, a programming language with "Unicode support" is a language that can represent text objects (i.e. strings) as Unicode codepoints, and that can convert a sequence of Unicode codepoints to a text object. You will notice that this tells us nothing about the process by which one can convert the language internal representation of textual objects to a suitable external representation (which is what mostly concerns us). What happens is that basically every language has its own way of doing it, which is, to say the least, confusing.


The purist approach can be found in the C language. Here external and internal representation are one and the same, because C represents strings as byte sequences. Encodings are handled through external libraries (if memory serves, IBM distributes a free ICU mulitilingual library). I believ that C++ behaves similarly. This is not for the weak, and, unless you already spend your days with Developer Studio or automake, I'd suggest a less purist aproach.


Unicode and Dynamic Languages



In ths section I will focus mostly on one dynamic language, namely, python. Even though I am normally a satisfied perl user, python appears to have better - at least where consistency and stability are concerned - UNICODE support. Once the basic concepts have been grasped, however, I'd say that the two languages' capability is almost on par.


Python, has internal supprto for two types of text objects: Unicode and ordinary (i.e. encoded) strings. Unicode objects can be thought of as sequnces of codepoints; ordinary strings can be thought of as byte sequences.


Creating a unicode string is easy:


us=u'\u00e8\u00e1'


us, represents the "èá" sequnce: 00e8 (232 hex) e 00e1 (225 hex) are the relevant codepoints.


If the encoding of an ordinary string is known, the Unicode string can be created, by decoding the string itself:

    us=cs.decode('string_encoding')
  


For instance from "èá":


    cs='\xe8\xe1'
    us=cs.decode('Latin-1')
  


There is a formally different - yet equivalent - way of obtaining the same result:


    us=unicode(cs,'Latin-1') # or the string encoding
  


Because I find this confusing, I mentally read this as "build a Unicode string by decoding cs from the 'Latin-1' encoding".


What above will work correctly if and only if the right encoding is specified. A wrong encoding will either result in a runtime exception or in wrong string, depending on the content of the ordinary string and on the encoding specified.


A python Unicode object is rather abstract. It cannot be directly saved, printed or represented without applying an encoding: attempts to do so will trigger a run-time exception. It may come as a surprise that a Unicode object can be encoded using any supported encoding, rather than the canonical, UTF and UCS encodings defined by Unicode.


It is in fact perfectly possible - and proper - to encode a sequence of Unicode codepoints in the (say) Latin-1 encoding provided that the codepoints are representable in the target encoding. It is for instance possible to encode as 'Latin-1' the 'U+00e8' codepoint, whereas the same cannot be done for the Kanji codepoint 'U+4e01'. Both codepoints in the preceding example, however, can be represented in the shift-jis-2004 encoding, as well as in UTF8 or UTF16. A partial list of the encodings supported by a standard python installation can be found in appendix B. UTF8 and UTF16 are special, because they are the only encodings that can always be safely specified as targets (as they are capable of represent the entire Unicode repertoire)


Going from a Unicode string (us) to a coded string is also easy:

    cs=us.encode(desired_encoding)


for instance:

    us=u'u'\u00e8'
    cs=us.encode('Latin-1') #contains '\xe8'

    us=u'\u00e8\u4e01'      #contains an ideographic char: è丁
    cs=us.encode('Latin-1') #runtime error

  UnicodeEncodeError: 'latin-1' codec can't encode character
  u'\u4e01' in position 1: ordinal not in range(256)

    cs=us.encode('shift-jis-2004') # contains '\x85}\x92\x9a'
    cs=us.encode('utf8') #contains '\xc3\xa8\xe4\xb8\x81'
    cs=us.encode('utf16') #contains '\xff\xfe\xe8\x00\x01N'


By putting the two steps together, we can translate from an encoding to another (transcoding):

    us=cs_source.encode(source_encoding)
    cs_target=us.encode(target_encoding)


Again, this is only possible for compatible encodings (target can represent all the codepoints present in source).


In particular, transcoding to UTF8 is always possible, if the codec for the source encoding is installed (Python's standard codecs are listed in appendix B):


    us=cs_source.encode(source_encoding)
    cs_target=us.encode('utf8')
  


As mentioned, unencoded Unicode strings cannot be sent to output streams:

    cs=u'u'\u00e8' f=file('/tmp/ciccio','a') f.write(cs)

    UnicodeEncodeError: 'ascii' codec can't encode characters in
    position 0-1: ordinal not in range(128)
  


Here we can see that the python interpreter tries to apply a default encoding to us (ASCII, in this case) and fails because us contains an accented character that is not part of the ASCII specs.


So the pythonic way of working with Unicode requires that we 1) decode strings coming from input and 2) encode strings going to output.


The 'codecs' module can, alternatively, decorate our I/O handles for us with suitable encodings: :


    import codecs

    f=codecs.open('/tmp/ciccio','UTF-8','r')
    g=codecs.open('/tmp/ciccia','latin-1','w') 
    us=f.read()
    g.write(us)
  


Anything we read from 'f' is decoded as UTF-8, while any Unicode object we write to 'g' is encoded in Latin-1. (So we may receive a runtime error if 'f' contained korean text, for instance). One should also refrain from writing ordinary - encoded - strings to g because, at this point, the interpreter would implicitely decode the original string applying a default codec (normally ASCII) which is probably not what one would expect, or desire.


It should be obvious that, for regular python programming - outside of multilingual text processing - Unicode objects are not normally used, as ordinary strings are perfectly suited to most tasks.


A different kind of "Unicode support" is the interpreter capability of processing source files containing non-ASCII characters. This is doable, by inserting a directive like:

#-*- coding: iso-8859-1 -*-


- (or other encoding) towards the beginning of the file. I advise against this, as a practice that will end up annoying you and your coworkers, as well as any other perspective user of the file. Stick to ASCII for source code.


The Curse of Implicit Encodings



Most I/O peripherals, these days, try to "help" their user by taking a guess on the encodings of the strings that are sent to them. This is good for normal use, atrocious if your aim is solving problems akin to those we have been tackling so far. Relationships between string types and encodings are confusing enough even without layering on top of them other encodings implicitely brought on by I/O devices.


This is a sample interaction, on my current system (emacs 23.1, Fedora Core 11, IPython):

    In [270]: import sys
    In [270]: sys.stdin.encoding
    Out[271]:
  'UTF-8'
    In [272]: cs='è'
    In [273]: repr(cs)
    Out[273]:
  "'\\xc3\\xa8'"


this can be translated as "writing the sequence 'è' on this interpreters console, which is using the implicit input encoding UTF-8, results in a coded string whose content is '\xc3\xa8'"


The same interaction on a different system is:

    In [270]: import sys
    In [270]: sys.stdin.encoding
    Out[271]:
  'latin_1'
    In [272]: cs='è'
    In [273]: repr(cs)
    Out[273]:
  "'\\xe8'"


this can be translated as "writing the sequence 'è' on this interpreters console, which is using the implicit input encoding Latin-1, results in a coded string whose content is '\xe8'"


Seems harmless? It is not. If we want a Unicode string, on system (1) we have to do:

    us=cs.decode('utf-8')
  


on system 2:

    us=cs.decode('latin-1')
  


I don't know you, but I find it bewildering.


My point: in source code -and outside the ASCII domain - stick to codepoint, even if writing literal characters may seem more convenient.


Unicode, encodings and HTML



Like XML, HTML had early awareness of multilingual environments. Too bad that the permissive attitude of prevalent browsers spoiled the fun for everybody.


Waht follows is my laundry list of multilingual HTML facts - check with the W£ consortium if you need complete assessments.


Named entities



In HTML, a (limited) number of national characters can be specified by using the so called 'named entitites': for instance the sequence "&agrave;" is displayed as "à".


Numeric entitities



In HTML, the entire Unicode codepoint repertoire cna be represented through numeric entities, which are written by preceding the decimal codpeoint identifier withj the sequence &# and following it with ";", like this:


&#8212; displays as: '—'


or,if you favore HEX codes:


&#x2014; displays as: '—'


Obviously, no sane Japanese will ever want to write a novel this way (unless her word processor takes care of this for her). Also, all this makes for quite unreadable HTML source.


HTML content declaration



A simpler way for multilingual HTML is declaring the document charset ("charset" is HTML-speak for encoding) like this:


<meta http-equiv="content-type" content="text-html; charset=utf-8">


this way of specifying the charset is rather safe, provided the reader's browser supports the charset and that the web server does not spoil everything by slapping a different charset on your document, overwriting the one speciied by you (This happened to me when I first published this document).


Explicit charset specification, however, seldom happens so what we have instead is:


1) Assumed charset (by the editing tools, or by the server). This is seldom correct, besides, it will easily be a national codepage (i.e. windows-1252, for italians) rather than the preferable UTF-8.


2) No charset at all. Pages like this should contain only ASCII characters and named or numeric entities. But this does not happen because:


3) Browsers attempt to infer charset for a document's content, besides


4) ...several webservers "help" by providing a default charset.


All of this makes solving HTML display problems no fun. My advice follows closely the advice already given for simple text.


Database



Multilingual text in databases is a thorny issue, and I am not really up to give copentent advice on it. So I'm just going to give my very limited take of the issue, without taking any responsibility for it. Caveat emptor.


Databases, as a family, are among the oldest pieces of software. This means that they started to tackle multilingual text in the wild frontier years, when men were men and databases encoded text every which way they damn well pleased. Fun, but hardly conducive to consistency.


This said, if the amount of your interaction is going to concern the encoding of a report output, then not to worry: that's a simple text file conversion.


If on the other hand, you are going to build a DBMS system from scratch, I advise you to make everything UTF-8 and to test the entire toolchain with multilanguge strings. Here the tooolchain includes the DBMS, the relevant OSes and communication protocols, programming laguages and libraries, and command line tools.


Lastly if you are going to convert a legacy system to multilingual: stick to the manufacturers' manual(s), and may God help you.


While most databases do document the encoding used for text data (which is often a propriety of the entire database or even of the instance), this is basically ruined by the fact that (for old databases) several architects devised their own encoding schemes that they then crammed inside one of the available data types. Another difficulty is that many databases use(d) non-standard naming for encodings.


Some moderns DBMSs (among them - to my knowledge - is SQL Server since tits 2005 release), atoned for a taking a too rigid encoding stance in the past by allowing to have different encodings for the database, each table contained therein, and even for each column of every table. My opinion is that this is a recipe for disaster, insanity, or both



copyryght © Alessandro Forghieri

tutti i diritti riservati

Modena, 14 Dicembre 2009

$Id: Unicode_en.html,v 1.4 2009/12/30 11:20:34 alf Exp $


Appendice A


import unicodedata

def unilist(u):
    """ prints the unicde description of a string """
    for i, c in enumerate(u):
        print i, '%04x' % ord(c), unicodedata.category(c),
        print unicodedata.name(c)

def safe_unicode(obj, *args):
    """ return the unicode representation of obj """
    try:
        return unicode(obj, *args)
    except UnicodeDecodeError:
        # obj is byte string
        ascii_text = str(obj).encode('string_escape')
        return unicode(ascii_text)

def safe_str(obj):
    """ return the byte string representation of obj """
    try:
        return str(obj)
    except UnicodeEncodeError:
        # obj is unicode
        return unicode(obj).encode('unicode_escape')


Wednesday, December 16, 2009

Unicode e il problema della codifica

Introduzione


Nel corso di troppi anni, ho spesso dovuto lottare con la rappresentazione elettronica del testo. Ogni volta in cui sono stato confrontato con quello che chiamo il problema fondamentale della rappresentazione del testo (vedi oltre) ho finito per risolverlo più o meno in maniera ad hoc, istintiva, insomma a naso. Solo poco tempo fa mi sono costretto a cercare di fare un po' di chiarezza nella pletora di nozioni "pratiche" che ho nel tempo utilizzato per cavarmi d'impaccio. Alla fine, ho pensato che, magari, scrivere quello che ho imparato su questo tema potesse servire a dare una mano a qualche fratello d'arme impelagato nello stesso tipo di problema, soprattutto se l'avessi scritto in italiano, visto che d'informazioni di questo tipo, in inglese, se ne trovano tante (anche se magari parziali, poco organiche o proprio sbagliate).


Non avrei mai scritto questo articolo se l'industria informatica avesse collettivamente più buon senso di quello che dimostra quotidianamente e avesse pensato - per tempo - a definire un modo di dedurre il tipo di caratteri utilizzato da un documento di testo.


Questa è una storia di pochi standard, troppi standard e di pezze multicolori applicate su buchi così grandi che nessuno pensava si potessero turare. E' altresì una storia che fa capire quanto siamo fortunati che non sia toccato all'industria informatica decidere come siano fatte le prese e le spine elettriche, perché altrimenti nessuno potrebbe infilare una spina B-Ticino in una presa fatta da un altro produttore, o magari, dalla stessa B-Ticino, ma in un anno differente. Ci penso tutte le volte che ho in mano una spina Shucko e mi manca un adattatore....


Per chi legge



La materia di questo articolo varia da nozioni alla portata di quasi chiunque abbia una informatizzazione di base a tecniche di programmazione che sono abbastanza oscure anche per chi fa del software la propria professione. Ho cercato di esporle all'incirca in questo ordine, do modo che, se ci si accorge che da un certo punto in avanti non si capisce più nulla, si sa già che si può tranquillamente smettere di leggere, perché il livello di comprensibilità non è destinato a migliorare.


D'altra parte, chi si accorge di stare leggendo informazioni abbastanza banali, può sbirciare avanti, sperando di trovare (se ci sono) le cose che ancora non sa sull'argomento. Siccome però la (mia) maggiore difficoltà nell'arrivare a capire il tema è stata per lungo tempo la mancanza di una riflessione organica sul tema - mancanza sempre dovuta al fatto di dover risolvere un particolare problema in poco tempo, situazione che da sempre si concilia male con l'apprendimento riflessivo - io sconsiglio di leggere questo articolo saltando qua e là. Metà dell'utilità di questo articolo - nella mia opinione - sta nella definizione esplicita di alcuni termini (carattere, codepoint, codifica, decodifica) il cui significato è dato spesso per scontato, mentre non lo è affatto (tanto da confondere spesso codifica e decodifica).


Il problema fondamentale



Il problema tipico è in genere posto da un utente perplesso (e contemporaneamente irritato) che lo formula più o meno in questi termini:


"Sul sistema X il testo Y (dove ) si vede perfettamente. Sul sistema Z lo stesso testo Y si vede nella maniera scorretta Z."


Qui X è un calcolatore, programma, o qualunque altra cosa faccia parte del contesto utilizzato per mostrare un testo, Y può essere testo puro e semplice, ovvero testo mostrato da una particolare applicazione, a volte come messaggio che proviene da un programma, a volte come pagina html, Z può variare da: "alcuni caratteri sono sostituiti da altri" a "tutti i caratteri sono rimpiazzati da caratteri stranissimi, scelti apparentemente a caso".


Se si ha fortuna, il testo di partenza e la sua rappresentazione sono espressi in un alfabeto vagamente intellegibile (per chi scrive, europeo), su calcolatori accessibili. Se è una brutta giornata, il testo in oggetto è in cinese, è stato tradotto a Tokyo e il calcolatore incriminato è a Seoul (benefici della globalizzazione).


In ogni caso, il professionista d'Information Technology borbotta "Ah, sì, è un problema di codifica, adesso ci guardo, ti chiamo io.", assume un'aria meditativa e spera che l'utente se ne vada (invece di restare nei dintorni sperando che la soluzione si materializzi). Dentro di sé, il nostro professionista IT sta bestemmiando come un turco.


Per capire il perché, bisogna prenderla alla lontana.


Una questione di carattere



I caratteri, si sa, sono i componenti elementari delle parole scritte. Dico parole scritte, perché, ad esempio, i componenti elementari delle parole pronunciate si chiamano fonemi, e sono una cosa abbastanza diversa, di cui qui non parlerò.


In questa sede, bisogna distinguere il carattere come concetto (ad esempio: la lettera minuscola "a") dalla sua rappresentazione visibile, che può assumere moltissime forme, a seconda del mezzo che si usa per visualizzare il carattere (un foglio di carta, lo schermo di un calcolatore, un cartellone pubblicitario) e della forma che si sceglie di dargli (il cosiddetto tipo - o font - in tipografia, o la rappresentazione calligrafica se si sta scrivendo a mano).


In questo senso, un carattere è l'idea (astratta dalla sua rappresentazione concreta) di uno dei componenti elementari della parola scritta. La sua realizzazione, o rappresentazione, dovrebbe essere chiamata grafema, una parola che sta ad indicare il segno grafico che viene interpretato come carattere. In effetti, bisogna distinguere ulteriormente tra il grafema, inteso come "idea della forma di un carattere" - che ad esempio, non incorpora il particolare tipo/font usato nella rappresentazione - dal segno fisico vero e proprio, che viene spesso chiamato glifo. Si noti che ad uno stesso carattere possono corrispondere più grafemi: ad esempio, in greco antico, la "sigma" in finale di parola ha un grafema diverso da quella ad inizio o in mezzo ad una parola. D'altra parte, a più caratteri può corrispondere uno stesso grafema e/o glifo: ad esempio la rappresentazione tipografica della sillaba "fi" è spesso un unico simbolo in cui 'f' e 'i' sono fuse e la 'i' perde il punto.


Bisogna tenere presente che, già a questo punto, stiamo indicando come "carattere" un assieme più esteso di quello che si presenta quasi automaticamente alla nostra intuizione. Ad esempio, in questa definizione, si distingue la lettera "e" dalla lettera "e con accento acuto" ("é") dalla lettera "con accento grave" ("è"). In più, si suole dare dignità di carattere a tutta una famiglia più esotica di segni che comprendono i segni d'interpunzione (apostrofi, virgole...) segni diacritici (accenti, dieresi...) e varie forme di spazio bianco (interruzioni di linea, spazi, tabulazioni...).


La collezione di caratteri (nel senso esteso definito poc'anzi) utilizzati da una lingua si chiama alfabeto. L'inventiva umana ha fatto sì che ad ogni lingua parlata, scritta o anche solo immaginata corrispondesse almeno un alfabeto proprio (spesso, più di uno).


Fino a non molto tempo fa, questo significava semplicemente che, mentre uno imparava una lingua, ne imparava anche l'alfabeto. Se si era fortunati, si trattava di un'alfabeto affine (questo succede con le lingue europee occidentali, i cui alfabeti sono in larga parte sovrapposti). Chi si cimentava con altre lingue poteva essere meno fortunato e gli capitava di doversi cimentare con un alfabeto totalmente diverso da quello nativo, magari composto da alcune migliaia di caratteri.


Lo stupido calcolatore



Come è capitato per tante altre cose, anche per quello che riguarda il testo e la sua rappresentazione, l'avvento dei calcolatore ha posto problemi nuovi.


Alla radice del problema (che è poi quello del trattamento informatico dei simboli) sta il fatto ben noto che il calcolatore (almeno il calcolatore binario di tipo Turing/Von Neumann - quello a cui siamo abituati) è in grado di manipolare senza mediazione un alfabeto (mi si passi la semplificazione) composto da due soli simboli: 0 e 1 (un bit). Questo è un modo un po' diverso di dire che un calcolatore è in grado di trattare, senza mediazione, solo informazione di tipo numerico, anzi, solo di tipo numerico, intero, binario. In effetti, siccome operare sui bit tende a rendere le cose ancora più difficili di quello che già non siano, la totalità dei calcolatori moderni utilizza come unità di calcolo, un "pacchetto" di 8 bit universalmente chiamato byte (tranne per i francesi, che lo chiamano "octet": la poco usata versione italiana è "ottetto"). Con un byte, un calcolatore è in grado di contare da zero a 255 (o da -127 a 127, ma in questa sede non c'interessa). Per la precisione il byte è l'unità minima che un calcolatore (moderno) può trasferire da un dispositivo di memorizzazione, o di input/output, all'unità di elaborazione: in altre parole, se un calcolatore vuole operare sul bit numero 20, prima di farlo dovrà andare a prendere l'intero byte numero 3, in cui il bit numero 20 è contenuto.


Per poter svolgere qualunque operazione simbolica non-numerica, il calcolatore deve usare un qualche metodo di traduzione che converta i simboli in numeri, e viceversa. Un programma che manipoli, ad esempio, note musicali, dovrà appoggiarsi ad un metodo che permetta di convertire le note (e tutti i simboli che costituiscono la notazione musicale) in numeri, e all'inverso di questo metodo per poter fare il tragitto inverso.


In buona sostanza, si tratta di numerare (spesso in modo astratto, ma la differenza non è essenziale in questa sede) i simboli che si ha intenzione di usare (l'alfabeto, o repertorio), utilizzando poi i numeri come se fossero i simboli stessi.


In quanto segue, chiamerò un'associazione di questo tra numeri e simboli codice.


Si tratta di una denominazione non standard: normalmente quello che qui indicherò con codice viene chiamato codepage o - in maniera molto ambigua - codifica. Siccome il processo di codifica, di cui parlerò tra poco, è una cosa abbastanza diversa ,che è cruciale comprendere con precisione per non perdersi, mentre il termine "codepage" è usato in maniera poco uniforme e spesso con riferimento ad un particolare fabbricante di calcolatori o sistemi operativi, ho deciso di adottare questa variante del gergo "normale".


Poiché qui si parla solo di testo, trascurerò l'importante campo dei codici relativi ad applicazioni più specialistiche, come la già citata notazione musicale, la notazione matematica e molte altre. In questa nota mi interessano soprattutto i codici il cui scopo è numerare i caratteri. D'altra parte il più importante di questi codici (Unicode) può essere utilizzato (e viene utilizzato) anche per catalogare tutti i simboli non testuali di cui ho accennato e molti altri di interesse ancora più specialistico (come gli alfabeti e i sistemi di scrittura delle lingue morte)


Sottolineo anche che, nel concentrarmi sui codici testuali, trascuro anche quasi tutto quello che riguarda l'impaginazione del testo, fatto salvo per i suoi aspetti più elementari (spazio tra le parole, interruzioni di riga e - raramente - di pagina e di colonna). Si tratta di fattori che sono invece molto importanti per chi si occupa della rappresentazione del testo (che non può trascurare il fatto che l'arabo venga scritto da sinistra a destra, o il cinese tradizionale dall'alto in basso). Anche qui vale la pena di ricordare che si tratta di fattori che sono pienamente recepiti da Unicode.


I codici



Il primo punto fermo che abbiamo raggiunto, quindi, si può formulare come segue:


"Nell'elaborazione testuale, i calcolatori e i programmi si affidano - implicitamente o esplicitamente - ad una procedura di numerazione dei caratteri, detta codice."


Un codice è quindi un'associazione tra numeri e caratteri. Il numero che in un determinato codice viene associato ad un dato carattere è detto codepoint.


Un codice può essere associato ad un qualsivoglia insieme di caratteri, non necessariamente a quelli in uso in un particolare alfabeto; un unico codice può codificare un solo alfabeto, un insieme di alfabeti o anche solo parte di un dato alfabeto.


A questo punto vale la pena di sottolineare che l'adozione di un codice delimita, in maniera implicita o esplicita, il repertorio di caratteri che si è in grado di elaborare. Questo è specialmente importante (e può costituire un grave handicap) per i testi che contengono parti scritte in lingue diverse (ad esempio la traduzione interlineare italiana di un testo di Confucio).Se, ad esempio, il codice in uso è il venerando ASCII, il repertorio, fra le altre cose, esclude tutte le lettere accentate; se il codice in uso è Latin-1, non sarà possibile rappresentare (fra l'altro) la lettera greca "ALFA", e così via. Ancora una volta, fa eccezione Unicode, che ha l'ambizione di comprendere il repertorio dei caratteri di tutte le lingue umane, e che - al momento - ha effettivamente catalogato i caratteri di tutte le principali lingue moderne.


Tipi di rappresentazione



Per arrivare a capire completamente il problema che ho chiamato fondamentale dobbiamo complicarci ancora un po' la vita, distinguendo tra la rappresentazione esterna e quella interna di un carattere. Con rappresentazione interna intendo il modo in cui un carattere "vive" in un programma mentre viene elaborato: si tratta di una rappresentazione che può essere concepita come astratta (non lo è, ma i suoi dettagli sono inessenziali). Con rappresentazione esterna intendo la forma assunta dal carattere nel momento in cui viene trascritto su disco, o spedito ad un altro programma.


Nell'ambito della rappresentazione interna - e al livello di dettaglio che m'interessa - il codice gioca una funzione di puro riferimento. Ovvero si sa che c'è e viene usato ma, posto che ne venga fatto un utilizzo corretto, esso è praticamente invisibile.


Dove il codice gioca una funzione essenziale è nella transizione tra rappresentazione interna ed esterna (e viceversa). Infatti, passando da rappresentazione interna a rappresentazione esterna, ogni carattere deve:


i) prima essere identificato come numero (codepoint) secondo le specifiche del codice;


ii) successivamente, questo numero deve essere tradotto in una sequenza di byte (codifica);


iii) infine, la sequenza di byte deve essere trasmessa.


Per passare dalla rappresentazione esterna, il procedimento è invertito:


i) la sequenza di byte (rappresentazione esterna) è ricevuta dall'ambiente di elaborazione.


ii) la sequenza di byte dev' essere trasformata in una sequenza di numeri/codepoint (decodifica)


iii) il codice identifica ogni numero come carattere (rappresentazione interna)


Se trasformare un codepoint (numero) in una sequenza di byte (o viceversa) fosse un procedimento che può essere fatto in unico modo, quello che ho chiamato "problema fondamentale" sarebbe molto semplificato - basterebbe sapere quale codice viene usato per la rappresentazione interna. Come si vedrà, le cose sono più complicate (come se non lo fossero già abbastanza).


Il processo di decodifica e codifica sono fondamentali per l'inquadramento del nostro problema e vale la pena di metterle in bella evidenza:


Codificare (encoding) è il processo per cui un carattere, in rappresentazione interna viene prima associato ad un codepoint (attraverso l'uso di un codice) e poi convertito in una rappresentazione numerica concreta o, per dire meglio, in una sequenza di byte (rappresentazione esterna).


Decodificare (decoding) è il processo inverso della codifica: una sequenza di byte (rappresentazione esterna) viene prelevata e convertita prima in sequenza numerica (sequenza di codepoint), poi - applicando un codice - in sequenza di caratteri.


Il processo di rappresentazione elettronica del testo



A questo punto il mio lettore (ammettendo che ce ne sia almeno uno) è più che giustificato ad essere confuso.


Cosa sta succedendo? Come avviene che una sequenza di byte diventa un testo leggibile sul mio schermo (a su una pagina di stampante)? E, come avviene che la stessa sequenza di byte possa trasformarsi in pagine e pagine di porcherie illeggibili su un altro schermo?


Riassumiamo e integriamo quello che abbiamo visto finora, cercando di renderlo comprensibile.


1) Il testo viene prelevato (dal disco, da internet, o da qualche altra sorgente esterna), sotto forma di sequenza di byte (numeri da 0 a 255).


2) Attraverso il procedimento di decodifica (q.v.) la sequenza di byte viene trasformata in sequenza di codepoint


3) Applicando un codice (q.v.) la sequenza di codepoint di cui al punto precedente è identificata come sequenza di caratteri.


4) Al termine della elaborazione, ad ogni carattere della sequenza è associato un glifo che viene rappresentato sul dispositivo di visualizzazione (schermo, stampante).


Il punto (4), di cui finora non si era parlato, che è importantissimo per poter vedere e interpretare il testo, non è invece particolarmente rilevante per quello che riguarda il nostro problema. Semplificando un po' le cose, infatti, quello che accade è che ad ogni carattere, in rappresentazione interna, deve essere associata una forma del tipo/font in uso (glifo). Ora, l'associazione tra un carattere e un font è ben definita una volta che sia noto il codice, e il codice è normalmente definito in maniera univoca a livello di macchina. L'unica cosa che può andare storta (rendendo il testo illeggibile) è che per qualche motivo venga usato un font sbagliato (non alfabetico, o relativo ad un alfabeto diverso). Questo tipo di errore, oltre ad essere relativamente raro, si corregge rapidamente utilizzando uno dei font alfabetici standard (Per le lingue occidentali: Arial, Helvetica, Courier...).


Incidenti nelle prime tre fasi, invece, sono di soluzione più difficile. Infatti, ogni errore nel determinare il codice o la codifica usati nella fase di trasmissione rende il testo decodificato parzialmente (più spesso, completamente) incomprensibile.


Questo è "Il problema di codifica" di cui mugugna il professionista IT di cui si è parlato qualche paragrafo fa.


E' abbastanza naturale a questo punto chiedersi come mai possano sorgere questi equivoci: come mai non c'è un solo codice e una sola codifica? Per saperlo bisogna studiarsi un pochino di storia del calcolo elettronico.


Un milione di anni fa



...o poco meno, i calcolatori facevano una sola cosa: calcolavano, appunto. Per leggere e scrivere i risultati di procedure di calcolo numerico, non servono molti simboli (le cifre decimali, qualche lettera latina, i segni +,-,. e ",") e questi sono ben noti a quasi tutti gli esseri umani. In più i calcolatori comunicavano quasi esclusivamente tramite stampati, e quasi esclusivamente con esseri umani. Il nostro problema, in pratica, non esisteva.


Questa felice situazione durò pochi anni, sia perché un'interazione esclusivamente numerica era molto faticosa anche per gli utenti tecnici, sia perché furono presto comprese le potenzialità di elaborazione "simbolica" dei calcolatori. La necessità di rappresentare informazione testuale portò quindi alla formulazione dei primi codici. Dopo un periodo di anarchia, durante il quale ogni fabbricante utilizzava un proprio codice, rendendo estremamente problematico qualunque scambio di dati tra macchine diverse, emersero due codici destinati a costituire standard di fatto: EBCDIC (1963) e ASCII (1963).


In effetti, il solo codice ASCII era un vero e proprio standard. EBCDIC è un codice sviluppato in maniera indipendente da IBM (e adottato da alcuni altri fabbricanti di mainframe suoi concorrenti). Il suo uso, oggi, è essenzialmente relegato all'ambito IBM, ed è probabilmente destinato a cadere in disuso.


La radice di tutti i mali



O almeno la radice di molti mali, è che i primi codici (e qui ci concentriamo essenzialmente su ASCII) cercavano di realizzare di un insieme di obiettivi che oggi consideriamo estremamente ristretto: odierni):


1) creazione di un codice per un ambiente sostanzialmente monolingua (inglese)


2) avere un codice che rendesse efficiente (in termini di spazio e di tempo) le comunicazioni tra apparati.


Il primo obiettivo fece sì che ci si concentrasse sulla codifica di un set di simboli molto limitato (quelli compresi nell'alfabeto inglese più i relativi segni diacritici).


La soluzione adottata per il secondo obiettivo fece sì che - nello stesso codice - si dovesse trovare spazio anche per tutta una serie di simboli utilizzati solo per la trasmissione di dati (ad esempio Start-Of-Message, End-Of-Message...) riducendo in questo modo il numero di codepoint disponibili per i caratteri. (Questo fu dovuto al fatto che le comunicazioni tra apparati si svolgessero su linea seriale, e che quindi i caratteri di controllo fossero obbligatoriamente mescolati a quelli costituenti il messaggio stesso) Inoltre i requisiti di efficienza spingevano a creare un codice il più ridotto possibile, cioè un codice che utilizzasse il minor numero possibile di caratteri.


Fu così che il codice ASCII fu pubblicato come un codice a 7 bit, il che permette la rappresentazione di 128 caratteri, di cui i primi 32 sono caratteri di controllo. I caratteri stampabili non alfanumerici furono presi dalla tastiera standard di una macchina da scrivere (inglese).


Poiché il codice ASCII usa solo sette bit degli otto disponibili in un byte, l'ottavo bit rimase libero per essere utilizzato come bit di verifica della correttezza della trasmissione.


E il resto del mondo?



Naturalmente, 96 caratteri non bastano per rappresentare tutti gli alfabeti del mondo. Non bastano neanche per rappresentare gli alfabeti di tutte le lingue europee, né quelli delle sole lingue occidentali.


Man mano che l'uso del calcolo elettronico si espandeva, la situazione diventava sempre meno accettabile, soprattutto per gli abitanti delle nazioni il cui linguaggio utilizza un set di caratteri completamente diverso da quello previsto dall' ASCII. A dire la verità, già gli scandinavi erano abbastanza ostacolati nel rappresentare la propria lingua; tutto sommato gli italiani erano fra i più avvantaggiati, dovendo solo risolvere il problema delle lettere accentate, normalmente risolto con l'uso degli apici.


I codepage



Come è tipico del mondo dell'informatica (e forse del mondo in generale) la soluzione del problema venne tentata da più parti applicando pezze su uno standard che non poteva, per sua natura, tollerarne molte.


Il primo passo fu appropriarsi dell'ottavo bit che ASCII riservava a compiti di controllo, aggiungendo in questo modo altri 128 caratteri ai codepoint disponibili. Ogni fabbricante creò poi una serie di codici detti - prendendo a prestito un termine IBM - codepage. Ognuno di questi codici utilizzava questo nuovo spazio di 128 codepoint per associarvi altri alfabeti, o - non di rado - caratteri grafici utili per disegnare cose come tabelle, report e così via.


Questo fu l'inizio della Babele informatica. La corretta interpretazione di un testo ricevuto da un'altra macchina richiedeva che il ricevente fosse in grado di determinare il codice/codepage (dipendente dal venditore) che era stato usato. La Babele, tuttavia, era abbastanza mascherata dal fatto che gli scambi di dati tra calcolatori di norma coinvolgevano calcolatori fatti dallo stesso produttore (o al limite calcolatori che aderivano ad uno standard de facto imposto dal maggior produttore del settore - IBM, in genere), spesso chiamando in causa personaggi in camice bianco che al telefono si dicevano cose tipo "Ti mando un nastro ANSI da 0,5 pollici nel codepage IBM 443", e per loro avevano un senso.


LATIN-1 e compagnia bella



La situazione era più che matura (quasi marcia, in effetti) per fare qualche tentativo di standardizzazione, tentativo che ad un certo punto fu intrapreso dalla ISO (l'organizzazione per gli standard internazionali) verso la fine degli anni 1980.


Intanto vennero definite denominazioni uniformi per una serie di (varianti di) codifiche entrate nell'uso. Ad esempio (un esempio importante) venne definita la denominazione iso-8859-1 (anche detta Latin-1) per un codice di 256 codepoint che descrive gli alfabeti di molte lingue europee occidentali. Vennero predisposti altri codici iso-8859-x (da iso-8859-2 a iso-8859-16) per le lingue europee con altri alfabeti (ad esempio greco, cirillico...).


Le codifiche per gli alfabeti orientali (essenzialmente giapponese, cinese, coreano) vennero (in maniera simile) raggruppate sotto la famiglia di denominazioni ISO/IEC 2022.


Per quasi tutte le codifiche ISO, si fece in modo che i primi 127 codepoint corrispondessero ai codici ASCII, in modo da conservare un qualche tipo di compatibilità con quest'ultimo.


Il processo fece alcune vittime (codifiche nazionali e industriali di varia denominazione che non vennero recepite) e creò alcuni orrori (ad esempio, la codifica Latin-1 è quasi uguale, ma non identica, al codepage windows-1252, un'ambiguità che persiste ancora oggi).


Uno degli effetti di questo processo fu sottolineare la necessità di unificare i codici esistenti in un unico repertorio in grado di rappresentare tutti i caratteri usati dall'uomo. Il risultato dello studio di un catalogo di questo tipo fu la creazione di Unicode (e ne parleremo fra un po')


Le codifiche



Come abbiamo detto più sopra, i calcolatori trasmettono l'informazione in unità minime chiamate byte (mentre sono in grado di elaborarla facendo riferimento ad un'unità ancora minore detta bit: un cifra binaria che può valere 0 o 1)


Siccome un byte può rappresentare i numeri interi nell'intervallo 0-256, qualunque codice contente un massimo di 256 codepoint può essere codificato (messo in forma esterna) utilizzando un byte per carattere. Per questi codici è quindi possibile far coincidere rappresentazione interna ed esterna, facendo corrispondere ad ogni codepoint la sua rappresentazione come singolo byte. In buona sostanza, codice e codifica sono indistinguibili.


Esistono però lingue che hanno (molti) più caratteri dei 256 rappresentabili con un singolo byte: il cinese e il giapponese sono due fra le più importanti. Le codifiche dei codici/codepage creati per queste lingue presenta quindi la necessità di usare più di un byte per carattere cosa che può essere fatta in almeno due modi - ed entrambi sono stati usati in diversi codici e codifiche.


Codifiche wide-char.



La scelta apparentemente più naturale è quella di usare lo stesso numero di byte per la codifica di ogni codepoint. Ad esempio, Per un alfabeto che abbia più di 256 ma meno di 65536 simboli, questo significa che ogni carattere sarà codificato con due byte, da 00000000-00000000 a 11111111-11111111. Codifiche di questo genere si chiamano "wide-char" (caratteri larghi). Benché facilmente e immediatamente comprensibili, queste codifiche hanno un problema evidente, uno latente e uno che interessa principalmente i programmatori.


Un esempio: UCS-2 (UTF-16)



Consideriamo, come esempio tutt'altro che teorico, una codifica U così fatta (questa codifica è essenzialmente quella che, in UNICODE, è chiamata UCS-2).


1) U è wide-char, con due byte per codepoint


2) U utilizza i primi 256 codepoint nello stesso ordine e con lo stesso significato del codepage latin-1. Questo significa che tutte le lettere delle principali lingue europee occidentali sono contenute in un solo byte, il primo dei due.


Il primo problema (quello evidente) è l'inefficienza di U. U infatti contiene 511 simboli che vengono codificati in sequenze che hanno almeno un byte nullo. Tuttavia, quando U viene utilizzata per codificare testi costituiti da soli caratteri occidentali, questi risultano occupare il doppio dello spazio (e vengono trasmessi nel doppio del tempo) che sarebbe necessario, perché tutti i caratteri occidentali hanno una codifica in cui il byte più significativo è nullo.


Il secondo problema (quello meno apparente) è noto come problema dell'endianness. La parola endianness e la terminologia associata derivano dai nomi di due fazioni politiche che esistevano nelle favolose isole di Lilliput e Blefuscu (come racconta Swift ne i "Viaggi di Gulliver") i cui membri si distinguevano per l'estremità da cui iniziavano ad aprire le uova: quella grande (a Lilliput, per editto del re che una volta si era tagliato aprendo un uovo dall'estremità più sottile: big endians) o quella piccola (a Blefuscu, per protesta contro il re: little endians). Su questa differenza (e sulla sua legittimazione regale), era scoppiata tra le due isole una guerra sanguinosa in cui bravi lillipuziani e blefuscudiani si scannavano in gran numero. In campo informatico, l'endianness ha dato origine a grattacapi meno sanguinosi, ma anche più idioti di quelli provocati a Lilliput.


Ho più volte detto che, per i calcolatori moderni, l'unità basilare di trasmissione e manipolazione dei dati è il byte. Molto presto, comunque, i calcolatori cominciarono ad assegnare un posto di riguardo alle coppie di byte adiacenti (dette parole, o word) che vengono spesso trattate come un tutto unico. Ad esempio i numeri interi sono di norma rappresentati da una, due o quattro word (due, quattro o otto byte adiacenti).


Siccome una word non è, come il byte, un'unità indivisibile, essa è suscettibile di essere rappresentata esternamente (o memorizzata, o scritta, o trasmessa: in fondo è la stessa cosa) in due modi diversi:


1) scrivendo prima il byte più significativo, poi quello meno significativo (big endian)


2) scrivendo prima il byte meno significativo, poi quello più significativo (little endian)


(Per completezza, dirò che possono esistere - ma sono rare - analoghe differenze nella rappresentazione di coppie di word.)


In altre parole, se immaginiamo che i byte siano cifre decimali, e dato il numero "novantuno", una macchina big-endian lo memorizzerebbe/scriverebbe come "9" "1" e una macchina little endian come "1" "9".


Il problema dell'endianness nasce dal fatto che, per incredibile/stupido che possa sembrare, nessuno ha mai pensato di stabilire come vadano scritte le word (in rappresentazione esterna). In informatica questo comportamento ufficialmente "non definito" (o in alternativa "definito dall'implementazione") ha il significato ufficioso "ognuno può fare l'accidenti che gli pare, e l'IT pensa a raccogliere i cocci".


Cosa che infatti è puntualmente successa, inserendo anche l'endianness (o byte-ordering) tra le incognite da risolvere nello stabilire la comunicazione tra due calcolatori diversi. Questo problema divenne talmente scocciante da venire infine risolto "manu militari" da Sun che, per quello che riguarda le comunicazioni tra calcolatori in rete, che riuscì a fare accettare l'idea che esistesse un network byte order a cui tutti dovevano conformarsi nelle comunicazioni. (Il "network byte order" è il big endian, non a caso quello usato da Sun). Peccato che la stessa saggezza non abbia prevalso per quello che riguarda la memorizzazione dei dati: i file vengono tuttora scritti, da macchine diverse, con endianness diversa.


Per la nostra codifica U tutto questo significa che essa potrà essere interpretata correttamente solo dopo che chi la vuole decodificare abbia in qualche modo determinato l'endianness con cui è stata scritta. Spesso, il modo è provare entrambe le endianness e vedere quale delle due sembra giusta.


L'ultimo problema (evidente solo ai programmatori) è che, come già detto, la codifica U contiene per forza un certo numero di byte nulli (anzi, per un testo occidentale big endian, sono nulli tutti i byte pari). Ma, tradizionalmente (qui tradizionalmente significa: dall'inizio degli anni 1960 fino ad una qualche data prima del 2000) il byte nullo ha avuto il significato di "fine stringa" per una grande quantità di software - in particolare per tutto quello utilizzato per manipolare direttamente testo nei paesi occidentali (gli orientali se ne erano fatto di ad hoc per le loro codifiche o avevano messo pezze su quello usato in occidente facendo leva sulla loro proverbiale pazienza).


Quello che questo significa, per la codifica U, è che la maggior parte degli strumenti tradizionali per la manipolazione del testo non sono in grado di utilizzarla o lo fanno solo con grande difficoltà.


Codifiche multibyte



Un'altra famiglia di codifiche si ottiene se si ammette la possibilità di codificare codepoint diversi con un numero variabile di byte.


Un esempio: UTF-8



Consideriamo ad esempio una codifica F (come vedremo, questa codifica è essenzialmente quella chiamata UTF-8) così concepita:


1) I primi 127 codepoint sono gli stessi - e nello stesso ordine - di quelli utilizzati dalla codifica ASCII e vengono scritti con unico byte il cui bit più significativo è posto a zero. La codifica dei primi 127 codepoint è quindi uguale alla codifica ASCII.


2) Quando il bit più significativo di un dato byte è uguale a 1, il byte fa parte della codifica di un codepoint che viene codificato in più byte. Se uno o più bit successivi a quello più significativo sono pari a uno e seguiti da uno zero (110xyyzz, 1110yyzz, ...) si è in presenza del primo bit della codifica, e il numero di bit iniziali pari ad uno indica quanti byte sono usati per codificare il codepoint in esame. Se invece il bit successivo a quello più significativo è pari a zero (10xxyyzz) il byte in esame è il secondo, terzo... della codifica di un dato codepoint.


La codifica F risolve alcuni problemi delle codifiche "wide", introducendo comunque altri inconvenienti. Confrontiamola con la codifica U descritta nel paragrafo precedente.


1) La parte di F che riguarda i primi 127 codepoint è molto più compatta della corrispondente codifica U. Per contro F è meno compatta di U nella codifica di tutti i codepoint che richiedono più di due byte (guarda caso questa è la zona riservata alla maggior parte degli alfabeti orientali), che pagano un'inefficienza di circa il 30%.


2) F è indipendente dall'endianness: ogni codepoint è concepito come una sequenza di byte (non di word!) ordinata intrinsecamente.


3) F non contiene byte nulli, ed è compatibile con la codifica ASCII: quindi i file di testo codificati in F possono essere manipolati con strumenti "tradizionali".


4) F non è invece compatibile con la codifica latin-1 (e ne riparleremo)


5) Decodificare F è più difficile che decodificare U. In particolare, una codifica come F rende difficile fare cose come "trovare l'ottavo carattere di una parola". Usando una codifica come U posso infatti compiere questa operazione semplicemente estraendo l'ottava "word" della sequenza (in una codifica a byte singolo, questo si fa estraendo l'ottavo byte). Se invece la codifica in uso è F, per poter trovare il carattere richiesto devo prima leggere i byte della sequenza di ingresso e decodificarli fino ad arrivare all'ottavo codepoint.


6) F contiene alcune sequenze di byte che sono vietate (ad esempio: 110xyyzz-0qxxyyzz). Questo rende possibile stabilire con certezza che un sequenza contenente una sotto-sequenza proibita non usa la codifica F. Questa sembra una banalità ma è il caso di far notare che questa proprietà non è condivisa da molte codifiche a byte singolo o wide: in particolare, qualunque sequenza, anche casuale, di byte può essere interpretata come corretta per una delle codifiche ISO-8859-x. Questa circostanza fa parte integrante del problema fondamentale.


Esistono molte altre possibili codifiche multibyte di cui non parlerò: in particolare esistono codifiche di tipo "shift" in cui la comparsa di una particolare sequenza di byte (upshift) cambia il significato di tutti i byte successivi fino alla ricezione di un'altra sequenza di byte definita (downshift) che ripristina la codifica precedente. Una vasta famiglia di codifiche di questo tipo è raggruppata nello standard ISO/IEC-2022, dedicato alla codifica di varie lingue orientali.


A questo punto è necessario dire che, per la maggior parte dei codici/codepage definiti dalle specifiche ISO, la codifica è univocamente determinata. Questo significa che, se si è nella condizione di sapere quale codice è utilizzato, si sa anche quale codifica è stata utilizzata. Questo però non è più vero là dove si prende in considerazione il codice noto come UNICODE, che è l'argomento del prossimo paragrafo.


Unicode



Lo standard Unicode (specificato dallo Unicode consortium) è essenzialmente un'iniziativa il cui scopo è la creazione di un repertorio unificato di tutti i caratteri usati dall'umanità, comprendendo quelli delle lingue scritte contemporanee, quelle del passato, qualche lingua immaginaria (Unicode riserva un insieme di codepoint per l'alfabeto Klingon), e con abbastanza spazio per incorporare lingue non ancora codificate.


L'esistenza di un repertorio di questo tipo, e delle relative codifiche, può permettere - ad esempio - l'utilizzo di testo multilingua senza dover identificare e cambiare codepage. Unicode insomma sarebbe il codice dei codici: se fosse usato dappertutto porrebbe fine al "problema centrale" come enunciato più sopra, senza che si dovesse rinunciare alla rappresentazione di qualche carattere..


Sorvolando sulla storia delle varie versioni di Unicode, dirò che lo standard attuale contiene 1 114 112 (un milione centoquattordicimila centododici) codepoint, suddivisi in 17 piani, ognuno composto di 65 536 codepoint, cioè 256 righe contenenti 256 codepoint ciascuna.


Il piano 0, costituito dai primi 65536 codepoint, è chiamato Basic Multilingual Plane (BMP) e contiene la maggior parte del repertorio di caratteri oggi in uso. Per assicurare la retro-compatibilità con ASCII, è previsto che i primi 127 codepoint coincidano con quelli definiti dalle specifiche ASCII.


La più recente formulazione di UNICODE contiene gran parte di tutte le lingue in uso e del passato,i loro diacritici, simboli matematici, simboli musicali e molte altre simbologie. Inoltre più di 10 piani non sono assegnati (cioè i codepoint in essi contenuti non corrispondono ad alcun carattere) né è probabile che vengano assegnati in un futuro prossimo.


Oltre a catalogare un enorme repertorio di caratteri, Unicode definisce tutta una serie di informazioni accessorie (ordinamento dei vari set di caratteri, regole per assicurare la "multi-direzionalità" del testo...) che non hanno una diretta influenza sul problema fondamentale sopra definito.


Inoltre Unicode definisce anche ciò che chiama "Unicode transformation format" (UTF) e "Universal character set" (UCS): questi non sono altre che le codifiche necessarie per la rappresentazione esterna di Unicode.


Delle diverse codifiche definite e usate nella storia di Unicode, mi limiterò a citare le più importanti (che sono anche quelle usate in più del 90% dei casi).


UTF-8: una codifica multibyte che massimizza la compatibilità con ASCII (parzialmente descritta nel materiale precedente come codifica F). In UTF-8 ogni carattere viene codificato in una sequenza di lunghezza variabile da 1 a quattro ottetti (byte)


UTF-16 (ex UCS-2, descritta nel materiale precedente come codifica U): una codifica multibyte che permette la rappresentazione dell'intero repertorio Unicode e che rappresenta l'intero BMP (65536 codepoint) con una codifica di tipo "wide" costituita da due byte (questa era l'originale codifica UCS-2, che era in grado di rappresentare il solo BMP). Mentre UTF-16 e UCS-2 sono spesso confuse, UTF-16 è l'unica di uso corrente. In UTF-16 ogni carattere viene codificato in una sequenza di lunghezza variabile da 2 a quattro ottetti (byte), riservando le codifiche a quattro byte per codepoint rarissimi gestiti tramite "codepoint surrogati".


UTF 16 definisce anche un particolare valore (Byte-Order-Mark o BOM) che si può usare per capire l'endianness usata nella codifica del testo. Il BOM è rappresentato dal codepoint (esadecimale) U+FEFF che su una macchina big-endian viene rappresentato dalla sequenza 0xFE,0xFF e dalla sequenza 0xFF,0xFE su una macchina little endian. Poiché il codepoint U+FEFF (Zero-Width No-Break Space : Spazio di ampiezza zero che non consente interruzioni) non può mai essere il primo carattere di una sequenza codificata mentre il codepoint U+FFFE non è - né sarà - mai assegnato ad un carattere valido, l'apparire di uno di questi due codepoint all' inizio di una sequenza codificata permette di dedurre la endianness dell'intera sequenza.


In UTF-8 non esiste un BOM (per motivi già spiegati) anche se alcuni programmi (soprattutto operanti in ambiente windows) ne inseriscono uno (xEF,0xBB,0xBF) equivalente a quello usato in UTF-16. Questo è permesso, ma sconsigliato, dallo standard, e in essenza non fa che rompere le scatole.


UTF-32/UCS-4: una codifica "wide" a lunghezza fissa: ogni codepoint di Unicode è rappresentato da una sequenza di 4 byte. Si applicano le considerazioni sul BOM già viste per UTF-16. Questa codifica è usata, in pratica, molto di rado.


A causa dei vantaggi illustrati della codifica F sulla codifica U, UTF-8 è oggi la codifica più usata per la rappresentazione esterna di testi e testi multilingua. UTF-16 è per contro molto usata nella rappresentazione interna delle stringhe (in particolari è quella in uso in tutti i sistemi operativi Microsoft posteriori a Windows 2000)


Il problema fondamentale, rivisitato



Giunti praticamente alla fine del nostro esame (semplificato) dei codici e codifiche associate, siamo pronti per cercare di capire quali inconvenienti possono provocare il problema fondamentale che ho enunciato qualche paragrafo fa.


Quello che succede è che un testo (file) preparato per essere visualizzato con una data tripletta (codice, codifica, endianness) va a finire su di un sistema in cui uno dei tre componenti viene applicato in maniera erronea.


Esiste un'altra possibilità, cioè che sul sistema obiettivo - quello su cui viene visualizzato il testo - non esista il font necessario per la visualizzazione (ad esempio, mancano i caratteri Giapponesi). Questo errore si elimina semplicemente installando un set di font completi (spesso chiamati font Unicode).


Il problema fondamentale è risolto quando si riescono a ricostruire la tripletta di partenza, quella di arrivo, e a determinare la tecnica corretta di traduzione tra le due.


Il teorema di non calcolabilità della codifica



Purtroppo, quello che ho detto in precedenza è sufficiente anche per enunciare quello che io (e io solo, per quel che ne so) chiamo "il principio di non calcolabilità della transcodifica":


Non esiste un metodo algoritmico per determinare con esattezza la codifica/codepage di un dato file di testo.


La dimostrazione è semplicissima, basta osservare che una qualsiasi sequenza di byte costituisce una "corretta" sequenza nel codepage iso-8859-1 (Latin-1) - in realtà, costituisce una sequenza corretta in molte tra le codifiche non-Unicode. Quindi, da un punto di vista logico, ogni file di testo potrebbe essere stato prodotto almeno con codifica 'Latin-1', e quindi, non è possibile stabilire con certezza la codifica effettivamente usata.


Questo significa che tutte le tecniche di soluzione del problema fondamentale sono procedurali, probabilistiche, o euristiche (e sono quindi tecniche solo in senso lato).


Esaminiamo ora i casi più frequenti e le "tecniche" di soluzione che si possono adottare.


Endianness errata (per una codifica multibyte).



Questo tipo di errore in pratica si verifica di rado, e l'occorrenza più frequente si ha quando si legge un nastro prodotto su di un altro sistema (cosa che da qualche anno è abbastanza rara). Se si tratta di file codificati in UTF-16, questo errore può essere risolto esaminando il BOM all'inizio del file. Per altri encoding, se non è nota la endianness della macchina su cui il file è stato creato, è spesso necessario provare a cambiare l'ordine dei byte, cercando di inferire la correttezza del file risultante dall'esame diretto.


Codice/codifica errati



Ovvero: il codice/codifica per cui il file è stato generato non è quello atteso sulla macchina obiettivo. Questo è il caso che si verifica più di frequente.


La prima cosa da fare è accertarsi su quale tipo di (codice, codifica, endianness) stia usando il sistema obiettivo. Questo può essere sorprendentemente complicato da una serie abbastanza lunga di circostanze. Ad esempio, i browser web cercano di dedurre, speso in maniera euristica, il codice e la codifica delle pagine web, e di adattarvisi; non di rado l'euristica è errata, e questo procedimento va ricostruito a ritroso prima di iniziare l'analisi del file di partenza. Se sono assenti fattori legati alla particolare applicazione in uso, le condizioni attese sono determinate dal codepage della macchina obiettivo, che ad esempio, per un sistema windows, comprendono il codepage ANSI CP_ACP e i regional settings, per un sistema Linux il LOCALE (e le variabili d'ambiente correlate). Anche in questo caso, è difficile fare un elenco esaustivo. Fortunatamente è possibile in molti casi fare qualche deduzione di massima probabilità: ad esempio, se il linguaggio della macchina è italiano, e il sistema operativo è windows si può presumere di essere nel codepage windows-1252.


Quando si sia determinato con un certo gradi di sicurezza il codice atteso sulla macchina obiettivo, bisogna cercare di determinare quale fosse il codice utilizzato in partenza. Il teorema di non computabilità esclude che questo possa essere fatto con certezza, ma non è (ancora) il caso di disperarsi.


In ogni caso, tutte le volte che la rappresentazione del testo contiene un'elevata percentuale di caratteri grafici e/o di controllo, si può essere abbastanza sicuri che la codifica in uso sia scorretta. Può sembrare banale, ma questo vale solo se si è sicuri che il file di partenza fosse un file di testo, o assimilabile - cosa che può non sempre essere vera. Sui sistemi Linux/Unix, conviene perciò consultare l'output del comando "file <nomefile>": se il risultato è ad esempio "file PDF", oppure "file compresso", questo è un indizio che le nostre ipotesi di partenza erano errate.


Il caso "facile"



Prima di affrontare il caso più generale, vediamo qualche esempio che si presenta di frequente sui sistemi in lingua italiana, per testi italiani o europei occidentali. Per questi casi, nella mia esperienza il problema più frequente di questi tempi è quello in cui un testo Unicode viene interpretato su una macchina che si aspetta iso-8859-x, o viceversa. Per la parte Unicode, la codifica sarà al 99.99% dei casi UTF-8 o UTF-16. Per i linguaggi europei è relativamente facile distinguere il secondo caso: basta esaminare il file con un editor binario (o odump su Linux) e vedere se ci sono zone estese in cui byte nulli e non nulli si alternano: in questo caso siamo in presenza di un file Unicode con codifica UTF-16 (vale la pena ribadire che, se il file in questione contiene, ad esempio, un romanzo giapponese, questo metodo NON funziona, visto che le codifiche dei codepoint giapponesi non prevedono byte nulli).


Il caso in cui la codifica è UTF-8 si può individuare osservando le modifiche che vengono fatte alle accentate. Questo è più facile quando un file UTF-8 arriva su una macchina iso-8859-x, che in Italia è praticamente sempre una macchina iso-8859-1 o CP windows-1252. In questo caso si osserva che tutte le lettere non accentate vengono tradotte correttamente, mentre le accentate vengono tradotte con due caratteri "esotici", il primo dei quali è una A maiuscola sormontata da una tilde (Ã). Se la lingua in cui è scritto il file non è l'italiano, si osserverà lo stesso fenomeno sui diacritici tipici della lingua stessa (ad esempio, per il tedesco, le dieresi) o sui segni di interpunzione "rari" (certi tipi di virgolette) o su simboli semi-grafici (simbolo dell'euro o comunque di valuta - dollaro escluso - simbolo di copyright). Questo comportamento è dovuto al fatto che tutti questi caratteri hanno una codifica UTF-8 pari a due byte, mentre i sistemi iso-8859-x decodificano un carattere per ogni byte.


Quando ci si trova nel caso inverso, (codifica attesa UTF-8, codifica effettiva iso-8859-x) si hanno sintomi un po' più vari che dipendono dall'applicativo in uso. I casi normali sono quelli in cui non viene segnalato nessun errore, ma le accentate mancano e sono sostituite, assieme al carattere successivo, da caratteri diversi (spesso un punto interrogativo bianco in campo nero). In alternativa, il programma che si usa per visualizzare il testo segnala un errore: quando questo errore è sufficientemente esplicativo (caso più raro di quanto non si creda) è possibile risalire al carattere che lo ha provocato: tabelle alla mano, si può poi vedere a quale carattere esso dovrebbe corrispondere. Il motivo di questo comportamento è che i segni diacritici, che in ISO-8859-x occupano i codepoint 128-255, avendo il bit più significativo a 1, vengono interpretati come l'inizio di una sequenza multibyte UTF-8 e il più delle volte, la sequenza ottenuta "mangiando" il byte successivo non è una codifica UTF-8 valida.


Altro fattore rivelatore è che laddove sia possibile esaminare il testo (UTF-8 o ISO-8859-x) con un editor (magari binario) è che le parti - se ce ne sono - contenenti sequenze di caratteri occidentali anglosassoni (cioè caratteri ASCII) sono invariate.


Il caso generale



Se quanto sopra non è di aiuto, l'unica cosa che resta da fare è prepararsi ad andare per tentativi. A questo fine è utile la seguente checklist:


1) Procurarsi quante più informazioni possibili sulla provenienza del file. Se possibile bisogna individuare l'applicazione che l'ha prodotto, consultare la documentazione che può essere disponibile e/o il sito del produttore, consultare Google ed altri motori di ricerca. Spesso è possibile - ed utile - parlare con la persona che ha prodotto il file.


2) Esaminare il file con altri mezzi. Un buon editor di testo è utilissimo (io direi indispensabile). Io utilizzo emacs, che dalla release 23 offre un ottimo supporto a molti codici e codifiche: a volte mi basta aprire un file con emacs per dedurre codice e codifica.


3) Non dimenticarsi dell'ovvio. La destinazione del file (se si può determinare) spesso fornisce tutte le informazioni che servono per dedurre codice e codifica. Ad esempio i file XML (sempre riconoscibili a causa dell'intestazione che deve essere presente nella prima riga) devono dichiarare esplicitamente l'encoding usato: se non lo fanno, il loro encoding deve essere UTF-8


4) Procurarsi una cassetta degli attrezzi per la transcodifica il più munita ed agguerrita possibile e utilizzarla per provare tutte le transcodifiche plausibili in ordine di probabilità decrescente secondo quanto si è determinato nei passi precedenti (ad esempio, per un file giapponese si inizierà provando le codifiche JIS). Prima di cominciare è utile - usando un editor - isolare un piccolo segmento di testo da analizzare, sfruttando il fatto che caratteri come gli spazi sono invarianti tra le varie codifiche: idealmente si dovrebbe identificare e isolare un segmento di testo contenente anche una porzione di caratteri occidentali (ad esempio un indirizzo: si ricordi che i caratteri occidentali anglosassoni sono invarianti per la maggior parte delle codifiche). E' anche possibile (e forse consigliabile) usare strumenti che automatizzano il procedimento per tentativi - anche se sempre usando un approccio euristico/probabilistico. Ad esempio lo Universal Encoding Detector utilizza la stessa euristica utilizzata nei browser.


In appendice riporto un paio di funzioni (python) che sono abbastanza utili per la manipolazione di dati multilingua.


Parte della difficoltà di questa fase della ricerca della soluzione è avere una chiara immagine mentale di quello che si sta cercando di ottenere e interpretare correttamente quello che stanno facendo i propri attrezzi. Io personalmente trovai a suo tempo illuminanti (riguardo al linguaggio di programmazione python, che uso abbastanza di frequente) le considerazioni e i metodi esposti in questa URL: http://code.activestate.com/recipes/466341/


Piccoli temi di programmazione



Quando si arriva a cercare di risolvere il "problema fondamentale" per tentativi, si deve quasi per forza ricorrere all'uso di qualche tipo di programmazione. La frase ricorrente in questo frangente è: 'il linguaggio "X" supporta Unicode'. Cosa questo significhi in generale è tutt'altro che chiaro. Io sono arrivato ad una spiegazione di questa frase che mi pare abbastanza vicino al vero, anche se non posso garantire che questa valga per tutti i linguaggi di programmazione.


La mia interpretazione è:


"Il linguaggio 'X' è in grado di rappresentare i suoi oggetti testuali (stringhe) come sequenza di codepoint Unicode ed è - viceversa - in grado di interpretare correttamente una sequenza di codepoint Unicode come un oggetto testuale."


Quello che è egregiamente assente da questa definizione è la menzione del processo di codifica/decodifica che sposta le stringhe tra le rappresentazioni interna (al linguaggio) ed esterna (sistema operativo, resto del mondo etc.)


Su questa, infatti, ogni linguaggio ha da dire la sua, e non è detto che il coro che ne risulta sia consonante.


L'approccio duro e puro è quello del C, in cui rappresentazione interna ed esterna coincidono, il che significa che le stringhe C riflettono esattamente le sequenza di byte ricevute dal mondo esterno. Lavorare sulla loro codifica richiede l'uso di librerie esterne (IBM m pare abbia un ICU multilingual library che è gratuita). Se non mi sono perso qualcosa, il C++ adotta un approccio simile. Niente di male se avete Developer Studio o automake in esecuzione dal mattino alla sera. Se invece il vostro profilo professionale è un po' diverso, suggerirei di lasciar perdere durezza e purezza e cercare qualcosa di meglio.


Unicode e Linguaggi dinamici



Il titolo di questa sezione è abbastanza esagerato. Ho intenzione di parlare di due linguaggi dinamici (perl e python) e dare dettagli su uno solo (python).


Il motivo per cui accantonerei perl in prima battuta è che (e lo dico da programmatore perl convinto) python mi pare avere un supporto UNICODE migliore di quello di perl, se non altro dal punto di vista della terminologia (che è quello che interessa di più in questa sede). Una volta che acquisita familiarità con la terminologia, e dal punto di vista di questa trattazione, direi che la funzionalità dei due linguaggi in questo campo è simile.


Python, internamente, supporta due tipi di stringa: Unicode e stringhe ordinarie o codificate. Si può pensare che le stringhe Unicode siano composte di una sequenza di codepoint, e che le stringhe ordinarie siano composte da una sequenza di byte.


Creare una stringa Unicode è semplice:


us=u'u'\u00e8\u00e1'


us, così definita, rappresenta la sequenza "èá": 00e8 (232 in esadecimale) e 00e1 (225 in esadecimale) sono i codepoint relativi.


Data una stringa codificata (e vedremo dopo come ottenerla) è possibile ottenere la relativa stringa Unicode posto che si conosca l'encoding della stringa codificata. Basta infatti eseguire la decodifica:



us=cs.decode('encoding_della_stringa')


Ad esempio per la consueta sequenza "èá":




cs='\xe8\xe1' us=cs.decode('Latin-1')


Sfortunatamente (dal punto di vista della chiarezza) esiste un altro modo (che è normalmente citato per primo) per fare la stessa conversione:



us=unicode(cs,'Latin-1') # or the string encoding


Per evitare confusioni, io leggo mentalmente questa istruzione come "costruisci una stringa Unicode decodificando cs dall'encoding 'Latin-1')".


Naturalmente le operazioni sopra illustrate funzionano correttamente se e solo se viene specificata la giusta codifica ('Latin-1'). Non so se ho sottolineato a sufficienza il fatto (che è importantissimo tener ben presente) che una stringa Unicode è un oggetto abbastanza astratto: in particolare non è possibile salvarla, stamparla o rappresentarla senza prima applicarle un encoding: e - fatto forse sorprendente - l'encoding da applicare non è necessariamente uno di quelli riservati alla codifica di Unicode (essenzialmente UTF8 o UTF16).


Infatti è perfettamente possibile - e in questo contesto lecito - codificare una sequenza di codepoint Unicode in (ad esempio) Latin-1, posto che il carattere corrispondente esiste in questa codifica. Ad esempio è possibile rappresentare in Latin-1 il codepoint 'U+00e8, ma non il carattere Kanji U+4e01. Allo stesso modo è possibile rappresentare entrambi i caratteri dell'esempio precedente codificandoli in shift-jis-2004 o, ovviamente, in UTF8 o UTF16. (una lista parziale di encoding supportati da una installazione standard di python è in appendice). non ha analoghi in altri codepage.


Ciò detto, passare da una stringa Unicode (us) ad una stringa codificata (cs) è abbastanza semplice:



cs=us.encode(encoding_desiderato)


ad esempio:



us=u'u'\u00e8'
cs=us.encode('Latin-1') #contiene '\xe8'

us=u'\u00e8\u4e01' # contiene un ideogramma: è丁
cs=us.encode('Latin-1') #errore

UnicodeEncodeError: 'latin-1' codec can't encode character u'\u4e01' in position 1: ordinal not in range(256)
cs=us.encode('shift-jis-2004') # contiene '\x85}\x92\x9a'
cs=us.encode('utf8') #contiene '\xc3\xa8\xe4\xb8\x81'
cs=us.encode('utf16') #contiene '\xff\xfe\xe8\x00\x01N'


Componendo le due operazioni, si può tradurre da una codifica ad un'altra (transcodifica):



us=cs_source.encode(source_encoding)
cs_target=us.encode(target_encoding)


questo può essere fatto se e solo se i due encoding sono compatibili (cioè target è in grado di rappresentare tutti i codepoint di source).


In particolare, è sempre possibile transcodificare in UTF-8 (se si ha a disposizione il codec per la codifica di partenza: i codec a disposizione di python sono in appendice):



us=cs_source.encode(source_encoding)
cs_target=us.encode('utf8')



Cosa succede se cerchiamo di scrivere una stringa Unicode senza codificarla?



cs=u'u'\u00e8' f=file('/tmp/ciccio','a') f.write(cs)

UnicodeEncodeError: 'ascii' codec can't encode characters in
position 0-1: ordinal not in range(128)



La risposta è che l'interprete - quando effettua I/O e conversioni di stringhe Unicode - cerca di codificare/decodificare la stringa per noi, utilizzando un encoding di default: in questo caso codifica con l'encoding ascii (in cui le accentate non esistono, da cui l'errore).


Quindi, lavorare con Unicode in python richiede:


1) decodifica delle stringhe in ingresso 2) codifica delle stringhe in uscita


Oppure, attraverso l'uso del modulo codecs si può decorare un filehandle attraverso il codificatore desiderato:



import codecs

f=codecs.open('/tmp/ciccio','UTF-8','r')
g=codecs.open('/tmp/ciccia','latin-1','w') us=f.read()
g.write(us)



a questo punto tutte le stringhe lette da f saranno decodificate con UTF-8 e convertite a stringhe Unicode, mentre tutte le stringhe Unicode scritte su g saranno codificate in latin-1 (quindi sarà bene che da f non arrivino stringhe contenenti caratteri coreani, o la scrittura darà errore). Sarà inoltre bene astenersi dal cercare di scrivere stringhe codificate (byte) su g: in fatti a questo punto ogni scrittura su g di stringhe di byte è preceduta da una codifica implicita, fatta usando il default (ASCII); questo probabilmente non è quello che ci si aspetta, o che si desidera.


Naturalmente, quando non si stanno risolvendo problemi che richiedono l'uso di set di caratteri multilingua, vale a dire nella normale programmazione in python, è molto probabile che le comuni byte string vadano più che bene per ciò che ci serve fare.


Un'altra considerazione riguarda la presenza di caratteri non-ascii all'interno di un file di sorgenti python (questa è un'altra accezione di supporto Unicode). In breve: è possibile farlo, basta specificare:



#-*- coding: iso-8859-1 -*-


- o altro encoding - verso l'inizio del file. Il mio consiglio è, non fatelo: alla lunga è una cosa che romperà le scatole a voi, ai vostri colleghi e soprattutto a chiunque altro dovesse lavorare con voi sullo stesso file.


Encoding impliciti, e la loro maledizione



Lavorare con Unicode e con alfabeti multinazionali è reso più complicato dal fatto che le varie periferiche di I/O tentano di "aiutare" l'utente facendo del loro meglio per interpretare quello che gli viene dato da presentare. Questo è perfetto per l'uso interattivo (specie quando funziona). Per risolvere i problemi di cui abbiamo parlato fin qui, è atroce. Questo è il motivo per cui tutti gli esempi precedenti sono stati scritti utilizzando i caratteri in rappresentazione numerica. Le relazioni tra tipi di stringhe ed encoding sono già abbastanza confuse senza che si debba tenere conto dell' encoding che ogni dispositivo di I/O utilizza implicitamente: questa circostanza è particolarmente perniciosa se si usa un interprete interattivo.


Un esempio a questo punto può essere utile. Sul sistema che sto utilizzano ultimamente per scrivere (emacs 23.1, Fedora Core 11, IPython), la seguente interazione con l'interprete ha i risultati illustrati:



In [270]: import sys
In [270]: sys.stdin.encoding
Out[271]:
'UTF-8'
In [272]: cs='è'
In [273]: repr(cs)
Out[273]:
"'\\xc3\\xa8'"


che tradotto significa: scrivere la sequenza 'è' sulla console di questo interprete, il cui encoding implicito in input è UTF-8 dà una stringa codificata (byte string) il cui contenuto è "'\xc3\xe8'"


La stessa sequenza, su un'altro sistema, diventa:



In [270]: import sys
In [270]: sys.stdin.encoding
Out[271]:
'latin_1'
In [272]: cs='è'
In [273]: repr(cs)
Out[273]:
"'\\xe8'"


che tradotto significa: scrivere la sequenza 'è' sulla console di questo interprete, il cui encoding implicito in input è Latin-1 dà una stringa codificata (byte string) il cui contenuto è "\xe8"


Se questo pare innocuo, si rifletta sul fatto che, per ottenere una stringa Unicode sul sistema (1) bisogna ora impartire l'istruzione:



us=cs.decode('utf-8')



e sul sistema 2:



us=cs.decode('latin-1')



Non so a voi, ma a me fa girare la testa.


Unicode, encoding e HTML



Come XML, anche HTML è un formato che ha preso coscienza abbastanza presto (in teoria, fin dalla nascita) delle questioni relativa all'uso di alfabeti multilingua. Purtroppo, la manica larga che i browser hanno tradizionalmente usato nei confronti delle prescrizioni degli standard relativi ha reso questo campo una delle peggiori babele immaginabili.


Questa è una breve lista di fatti relativa al supporto multilingue in HTML, senza alcuna pretesa di completezza (che lascio volentieri al W3 consortium).


Entità con nome



Indipendentemente da ogni altra circostanza, è possibile specificare un ristretto numero di caratteri nazionali ricorrendo alle 'named entities' di HTML, che comprendono, fra l'altro tutte le accentate (quindi gli italiani sono - quasi - a posto) e diversi simboli di uso comune . Ad esempio l'entità &agrave; viene mostrata come "à".


Entità numeriche



Indipendentemente da ogni altra circostanza, è possibile specificare l'intero set dei codepoint di Unicode esprimendoli come entità numeriche, cioè facendo precedere il numero (decimale) del codepoint da &# e facendolo seguire da ";", così:


&#8212; visualizzato come: '—'


in esadecimale:


&#x2014; visualizzato come: '—'


Chiaramente, nessun giapponese potrà mai scrivere un romanzo così (a meno che non sia il suo word processor a fare questa traduzione in automatico). Se non bastasse , farsi un'idea del contenuto di una pagina html scritta nel formato di cui sopra è quasi impossibile.


Dichiarazione del contenuto HTML



La strada maestra per la creazione di pagine HTML multilingua corrette è dichiarare il charset del documento:


<meta http-equiv="content-type" content="text-html; charset=utf-8">


"charset" è il modo HTML di chiamare l'encoding.


Un documento che specifichi il charset nell'intestazione, e lo usi consistentemente, è al sicuro, almeno se il browser che viene usato dai visitatori supporta l'encoding specificato e se il server web non decide di appiccicare al vostro documento un charset diverso, sovrascrivendo quello da voi dichiarato. (Quest' ultimo incidente è quello che mi è accaduto quando ho pubblicato questo documento sul web.) Potendo, vale comunque la pena di specificare UTF-8, che, di questi tempi, è quello che ha maggior supporto e compatibilità. Naturalmente siamo ben lontani dalla realtà e questo per alcuni fatti storici.


1) Il charset è raramente specificato dall'autore del documento - più spesso è assegnato automaticamente dai tool di editing, che non sempre c'azzeccano. In ogni caso si tratta spesso di un charset o codepage nazionale (windows-1252, per gli italiani) e non del più portabile utf-8.


2) Esistono ancora, e presumibilmente ne vengono prodotte ogni giorno di più, pagine che non specificano il charset. In questo caso, queste pagine dovrebbero contenere solo caratteri ASCII, e tutti gli altri caratteri dovrebbero essere espressi come entità con nome o entità numeriche. Questo in realtà non avviene perché:


3) Molti browser cercano d'inferire il charset dal contenuto del documento, e poi


4) Molti server cercano di "aiutare" i browser fornendo anch'essi un charset d'appoggio.


Non ho bisogno di dire che in queste condizioni risolvere i problemi di display di HTML è un rebus non molto meno difficile (e con l'aggiunta di dover distinguere testo da markup) di quello illustrato nella resto di questo documento. Gli strumenti e i consigli che ho dato più sopra tendono ad essere comunque utili anche in questa circostanza.


Database



Per finire accenno al tema della codifica nel campo delle basi di dati solo per dichiarare la mia più completa inadeguatezza a trattarla, e per dare alcuni consigli che possono tornare utili (ma non sono disposto ad assumermi alcuna responsabilità).


Ciò che rende la questione delle codifiche particolarmente spinoso, nel caso dei database, è dato il fatto che, mentre un approccio sistematico al problema è relativamente recente, esiste una straordinaria varietà di approcci storici che risale all'alba del calcolo elettronico, approcci rigorosamente diversificati secondo il produttore del DBMS, del protocollo di comunicazione, dello strumento di reporting, del sistema operativo e così via. Inoltre nel caso delle basi di dati diventano di fondamentale importanza alcuni dei fattori che ho allegramente trascurato nel caso dei file di testo, tra cui l'ordinamento (collation) delle stringhe, le unità monetarie, i sistemi di datazione, le rappresentazione dei numeri.


Se quello che interessa è la conversione dell'output di un report, siamo nel caso delle conversioni di file di testo,che abbiamo visto in precedenza.


Se quello che serve è costruire un sistema da zero, il mio consiglio è: fate tutto in UTF-8 e provate tutte le componenti con stringhe provenienti da vari linguaggi. In questo caso "tutte" le componenti comprendono il DBMS, i sistemi e i protocolli di comunicazione, i linguaggi e le librerie usate per la programmazione, gli strumenti di reporting e gli strumenti a linea comando.


Se quello che dovete fare è convertire un sistema "legacy" ad un sistema multilingue, non so che dire: leggete la documentazione del produttore (o dei produttori) e che Dio vi aiuti.


Una facilitazione - rispetto al caso della codifica di file di testo arbitrari- è che normalmente le basi di dati documentano la codifica che utilizzano per i dati di tipo testo. Tale codifica è molto spesso una proprietà di tutto il database (o addirittura della particolare installazione). Questa facilitazione, nei casi di database legacy, è però spesso inficiata dal fatto che non di rado gli architetti della base di dati originale si sono inventati modi "originali", spesso non documentati, di accomodare altri linguaggi all'interno della codifica disponibile, soprattutto quando questa coincideva con US-7 (in pratica, ASCII). Altro ostacolo è che alcuni DBMS in passato hanno adottato nomi proprietari per le codifiche in uso, peggiorando il già babelico stato dell'arte.


Alcuni DBMS moderni (per quello che ne so, SQL Server, dalla versione 2005, è tra questi) hanno ritenuto di fare ammenda per aver in passato consentito una configurazione di codifiche estremamente spartana permettendo l'attribuzione di codifiche diverse per il database, per ogni tabella al suo interno e per ogni campo di ogni tabella. La mia opinione è che questa - come il rispondere a lettere provenienti dalla Nigeria che promettono ingenti somme di denaro - è una opportunità da non cogliere assolutamente, se si tiene alla propria salute mentale.



copyryght © Alessandro Forghieri

tutti i diritti riservati

Modena, 14 Dicembre 2009

$Id: Unicode.html,v 1.3 2009/12/16 10:28:51 alf Exp $