2007-07-06 Jungshik Shin <jungshik.shin@gmail.com>
[WebKit-https.git] / WebCore / platform / TextEncoding.cpp
index 022c334..d440b9d 100644 (file)
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2004, 2006 Apple Computer, Inc.  All rights reserved.
+ * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Alexey Proskuryakov <ap@nypop.com>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "config.h"
 #include "TextEncoding.h"
 
-#include "CharsetNames.h"
-#include <kxmlcore/Assertions.h>
-#include <kxmlcore/HashSet.h>
-#include "StreamingTextDecoder.h"
+#include "CString.h"
+#include "PlatformString.h"
+#include "TextCodec.h"
+#include "TextDecoder.h"
+#include "TextEncodingRegistry.h"
+#if USE(ICU_UNICODE)
+#include <unicode/unorm.h>
+#elif USE(QT4_UNICODE)
+#include <QString>
+#endif
+#include <wtf/HashSet.h>
+#include <wtf/OwnPtr.h>
 
 namespace WebCore {
 
-TextEncoding::TextEncoding(const char* name, bool eightBitOnly)
+static void addEncodingName(HashSet<const char*>& set, const char* name)
 {
-    m_encodingID = textEncodingIDFromCharsetName(name, &m_flags);
-    if (eightBitOnly && m_encodingID == UTF16Encoding)
-        m_encodingID = UTF8Encoding;
+    const char* atomicName = atomicCanonicalTextEncodingName(name);
+    if (atomicName)
+        set.add(atomicName);
 }
 
-const char* TextEncoding::name() const
+TextEncoding::TextEncoding(const char* name)
+    : m_name(atomicCanonicalTextEncodingName(name))
 {
-    return charsetNameFromTextEncodingID(m_encodingID);
 }
 
-QChar TextEncoding::backslashAsCurrencySymbol() const
+TextEncoding::TextEncoding(const String& name)
+    : m_name(atomicCanonicalTextEncodingName(name.characters(), name.length()))
 {
-    if (m_flags & BackslashIsYen)
-        return 0x00A5; // yen sign
-    return '\\';
 }
 
-DeprecatedString TextEncoding::toUnicode(const char *chs, int len) const
+String TextEncoding::decode(const char* data, size_t length) const
 {
-    return StreamingTextDecoder(*this).toUnicode(chs, len, true);
+    if (!m_name)
+        return String();
+
+    return TextDecoder(*this).decode(data, length, true);
 }
 
-DeprecatedString TextEncoding::toUnicode(const DeprecatedByteArray &qba, int len) const
+CString TextEncoding::encode(const UChar* characters, size_t length, bool allowEntities) const
 {
-    return StreamingTextDecoder(*this).toUnicode(qba, len, true);
-}
+    if (!m_name)
+        return CString();
 
-// We'd like to use ICU for this on OS X as well eventually, but we need to make sure
-// it covers all the encodings that we need
-#ifndef __APPLE__
+    if (!length)
+        return "";
 
-static UConverter* cachedConverter;
-static TextEncodingID cachedConverterEncoding = InvalidEncoding;
+#if USE(ICU_UNICODE)
+    // FIXME: What's the right place to do normalization?
+    // It's a little strange to do it inside the encode function.
+    // Perhaps normalization should be an explicit step done before calling encode.
 
-static const int ConversionBufferSize = 16384;
+    const UChar* source = characters;
+    size_t sourceLength = length;
 
-static inline UConverter* getConverter(TextEncodingID encoding, UErrorCode* status)
-{
-    if (cachedConverter && encoding == cachedConverterEncoding) {
-        UConverter* conv = cachedConverter;
-        cachedConverter = 0;
-        return conv;
-    }
+    Vector<UChar> normalizedCharacters;
 
-    const char* encodingName = charsetNameFromTextEncodingID(encoding);
     UErrorCode err = U_ZERO_ERROR;
-    UConverter* conv = ucnv_open(encodingName, &err);
-    if (err == U_AMBIGUOUS_ALIAS_WARNING)
-        LOG_ERROR("ICU ambiguous alias warning for encoding: %s", encodingName);
-
-    if (!conv) {
-        LOG_ERROR("the ICU Converter won't convert to text encoding 0x%X, error %d", encoding, err);
-        *status = err;
-        return 0;
+    if (unorm_quickCheck(source, sourceLength, UNORM_NFC, &err) != UNORM_YES) {
+        // First try using the length of the original string, since normalization to NFC rarely increases length.
+        normalizedCharacters.resize(sourceLength);
+        int32_t normalizedLength = unorm_normalize(source, length, UNORM_NFC, 0, normalizedCharacters.data(), length, &err);
+        if (err == U_BUFFER_OVERFLOW_ERROR) {
+            err = U_ZERO_ERROR;
+            normalizedCharacters.resize(normalizedLength);
+            normalizedLength = unorm_normalize(source, length, UNORM_NFC, 0, normalizedCharacters.data(), normalizedLength, &err);
+        }
+        ASSERT(U_SUCCESS(err));
+
+        source = normalizedCharacters.data();
+        sourceLength = normalizedLength;
     }
+    return newTextCodec(*this)->encode(source, sourceLength, allowEntities);
+#elif USE(QT4_UNICODE)
+    QString str(reinterpret_cast<const QChar*>(characters), length);
+    str = str.normalized(QString::NormalizationForm_C);
+    return newTextCodec(*this)->encode(str.utf16(), str.length(), allowEntities);
+#endif
+}
+
+bool TextEncoding::usesVisualOrdering() const
+{
+    if (noExtendedTextEncodingNameUsed())
+        return false;
 
-    return conv;
+    static const char* const a = atomicCanonicalTextEncodingName("ISO-8859-8");
+    return m_name == a;
 }
 
-static inline void cacheConverter(TextEncodingID id, UConverter* conv)
+bool TextEncoding::isJapanese() const
 {
-    if (conv) {
-        if (cachedConverter)
-            ucnv_close(cachedConverter);
-        cachedConverter = conv;
-        cachedConverterEncoding = id;
+    if (noExtendedTextEncodingNameUsed())
+        return false;
+
+    static HashSet<const char*> set;
+    if (set.isEmpty()) {
+        addEncodingName(set, "x-mac-japanese");
+        addEncodingName(set, "cp932");
+        addEncodingName(set, "JIS_X0201");
+        addEncodingName(set, "JIS_X0208-1983");
+        addEncodingName(set, "JIS_X0208-1990");
+        addEncodingName(set, "JIS_X0212-1990");
+        addEncodingName(set, "JIS_C6226-1978");
+        addEncodingName(set, "Shift_JIS_X0213-2000");
+        addEncodingName(set, "ISO-2022-JP");
+        addEncodingName(set, "ISO-2022-JP-2");
+        addEncodingName(set, "ISO-2022-JP-1");
+        addEncodingName(set, "ISO-2022-JP-3");
+        addEncodingName(set, "EUC-JP");
+        addEncodingName(set, "Shift_JIS");
     }
+    return m_name && set.contains(m_name);
 }
 
-static inline TextEncodingID effectiveEncoding(TextEncodingID encoding)
+UChar TextEncoding::backslashAsCurrencySymbol() const
 {
-    if (encoding == Latin1Encoding || encoding == ASCIIEncoding)
-        return WinLatin1Encoding;
-    return encoding;
+    if (noExtendedTextEncodingNameUsed())
+        return '\\';
+
+    // The text encodings below treat backslash as a currency symbol.
+    // See http://blogs.msdn.com/michkap/archive/2005/09/17/469941.aspx for more information.
+    static const char* const a = atomicCanonicalTextEncodingName("Shift_JIS_X0213-2000");
+    static const char* const b = atomicCanonicalTextEncodingName("EUC-JP");
+    return (m_name == a || m_name == b) ? 0x00A5 : '\\';
 }
 
-DeprecatedCString TextEncoding::fromUnicode(const DeprecatedString &qcs, bool allowEntities) const
+const TextEncoding& TextEncoding::closest8BitEquivalent() const
 {
-    TextEncodingID encoding = effectiveEncoding(m_encodingID);
-
-    if (encoding == WinLatin1Encoding && qcs.isAllLatin1())
-        return qcs.latin1();
-
-    if ((encoding == WinLatin1Encoding || encoding == UTF8Encoding || encoding == ASCIIEncoding) 
-        && qcs.isAllASCII())
-        return qcs.ascii();
-
-    // FIXME: We should see if there is "force ASCII range" mode in ICU;
-    // until then, we change the backslash into a yen sign.
-    // Encoding will change the yen sign back into a backslash.
-    DeprecatedString copy = qcs;
-    copy.replace(QChar('\\'), backslashAsCurrencySymbol());
-
-    UErrorCode err = U_ZERO_ERROR;
-    UConverter* conv = getConverter(encoding, &err);
-    if (!conv && U_FAILURE(err))
-        return DeprecatedCString();
+    if (*this == UTF16BigEndianEncoding() || *this == UTF16LittleEndianEncoding())
+        return UTF8Encoding();
+    return *this;
+}
 
-    ASSERT(conv);
+const TextEncoding& ASCIIEncoding()
+{
+    static TextEncoding globalASCIIEncoding("ASCII");
+    return globalASCIIEncoding;
+}
 
-    // FIXME: when DeprecatedString buffer is latin1, it would be nice to
-    // convert from that w/o having to allocate a unicode buffer
+const TextEncoding& Latin1Encoding()
+{
+    static TextEncoding globalLatin1Encoding("Latin-1");
+    return globalLatin1Encoding;
+}
 
-    char buffer[ConversionBufferSize];
-    const UChar* source = reinterpret_cast<const UChar*>(copy.unicode());
-    const UChar* sourceLimit = source + copy.length();
+const TextEncoding& UTF16BigEndianEncoding()
+{
+    static TextEncoding globalUTF16BigEndianEncoding("UTF-16BE");
+    return globalUTF16BigEndianEncoding;
+}
 
-    DeprecatedCString result(1); // for trailng zero
+const TextEncoding& UTF16LittleEndianEncoding()
+{
+    static TextEncoding globalUTF16LittleEndianEncoding("UTF-16LE");
+    return globalUTF16LittleEndianEncoding;
+}
 
-    if (allowEntities)
-        ucnv_setFromUCallBack(conv, UCNV_FROM_U_CALLBACK_ESCAPE, UCNV_ESCAPE_XML_DEC, 0, 0, &err);
-    else {
-        ucnv_setSubstChars(conv, "?", 1, &err);
-        ucnv_setFromUCallBack(conv, UCNV_FROM_U_CALLBACK_SUBSTITUTE, 0, 0, 0, &err);
-    }
+const TextEncoding& UTF32BigEndianEncoding()
+{
+    static TextEncoding globalUTF32BigEndianEncoding("UTF-32BE");
+    return globalUTF32BigEndianEncoding;
+}
 
-    do {
-        char* target = buffer;
-        char* targetLimit = target + ConversionBufferSize;
-        err = U_ZERO_ERROR;
-        ucnv_fromUnicode(conv, &target, targetLimit, &source, sourceLimit, 0, true,  &err);
-        int count = target - buffer;
-        buffer[count] = 0;
-        result.append(buffer);
-    } while (err == U_BUFFER_OVERFLOW_ERROR);
+const TextEncoding& UTF32LittleEndianEncoding()
+{
+    static TextEncoding globalUTF32LittleEndianEncoding("UTF-32LE");
+    return globalUTF32LittleEndianEncoding;
+}
 
-    cacheConverter(encoding, conv);
 
-    return result;
+const TextEncoding& UTF8Encoding()
+{
+    static TextEncoding globalUTF8Encoding("UTF-8");
+    return globalUTF8Encoding;
 }
-#endif
 
+const TextEncoding& WindowsLatin1Encoding()
+{
+    static TextEncoding globalWindowsLatin1Encoding("WinLatin-1");
+    return globalWindowsLatin1Encoding;
+}
 
 } // namespace WebCore