Packages
suexcxine_exoffice
0.3.3
File parser for popular excel formats: xls (Excel 2003), csv, xlsx (Excel 2007). Stores data in ets (except for csv, which uses stream).
Current section
Files
Jump to
Current section
Files
lib/exoffice/parser/excel_2003/string.ex
defmodule Exoffice.Parser.Excel2003.String do
alias Exoffice.Parser.Excel2003.OLE
use Bitwise, only_operators: true
defstruct [:value, :size]
def read_unicode_string_short(data) do
# offset: 0: size: 1; length of the string (character count)
character_count = OLE.decoded_binary_at(data, 0)
string = binary_part(data, 1, byte_size(data) - 1) |> read_unicode_string(character_count)
# add 1 for the string length
%{string | size: string.size + 1}
end
def read_unicode_string(data, character_count) do
# offset: 0: size: 1; option flags
# bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
is_compressed = !((0x01 &&& OLE.decoded_binary_at(data, 0)) >>> 0)
# bit: 2; mask: 0x04; Asian phonetic settings
has_asian = 0x04 &&& OLE.decoded_binary_at(data, 0) >>> 2
# bit: 3; mask: 0x08; Rich-Text settings
has_rich_text = 0x08 &&& OLE.decoded_binary_at(data, 0) >>> 3
# offset: 1: size: var; character array
# this offset assumes richtext and Asian phonetic settings are off which is generally wrong
# needs to be fixed
length = if is_compressed, do: character_count * 2, else: character_count
value = encode_utf_16(binary_part(data, 1, length), is_compressed)
size = if is_compressed, do: 1 + character_count, else: 1 + 2 * character_count
%__MODULE__{
value: value,
size: size
}
end
def encode_utf_16(string, false) do
convert_encoding(string, "UTF-16LE", "UTF-8")
end
def encode_utf_16(string, true) do
string
|> uncompress_byte_string
|> encode_utf_16(false)
end
defp uncompress_byte_string(string) do
str_len = byte_size(string)
if str_len > 0 do
Enum.reduce(0..(str_len - 1), <<>>, fn i, acc ->
acc <> binary_part(string, i, 1) <> "\0"
end)
else
""
end
end
def read_byte_string_short(string, codepage) do
# offset: 0; size: 1; length of the string (character count)
ln = OLE.decoded_binary_at(string, 0)
# offset: 1: size: var; character array (8-bit characters)
value = decode_codepage(binary_part(string, 1, ln), codepage)
%__MODULE__{
value: value,
# size in bytes of data structure
size: 1 + ln
}
end
defp decode_codepage(string, codepage) do
convert_encoding(string, codepage, "UTF-8")
end
def convert_encoding(value, from, to) do
:iconv.convert(from, to, value)
end
defp decode_utf_16(str, bom_be \\ true) do
case byte_size(str) < 2 do
true ->
str
false ->
c0 = OLE.decoded_binary_at(str, 0)
c1 = OLE.decoded_binary_at(str, 1)
{str, bom_be} =
cond do
c0 == 0xFE && c1 == 0xFF -> {binary_part(str, 2, byte_size(str) - 2), bom_be}
c0 == 0xFF && c1 == 0xFE -> {binary_part(str, 2, byte_size(str) - 2), false}
true -> {str, bom_be}
end
len = byte_size(str)
Enum.reduce(0..(len - 1), "", fn i, acc ->
val =
case bom_be do
true -> (OLE.decoded_binary_at(str, i) <<< 4) <> OLE.decoded_binary_at(str, i + 1)
false -> (OLE.decoded_binary_at(str, i + 1) <<< 4) <> OLE.decoded_binary_at(str, i)
end
acc <> if val == 0x228, do: "\n", else: val
end)
end
end
end