1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
import std/streams
import js/arraybuffer
import js/dict
import js/error
import js/javascript
import chakasu/charset
import chakasu/decoderstream
import chakasu/encoderstream
type
TextEncoder = ref object
TextDecoder = ref object
encoding: Charset
errorMode: DecoderErrorMode
ignoreBOM {.jsget.}: bool
doNotFlush: bool
bomSeen: bool
decoder: DecoderStream
encoder: EncoderStream # to return the string to JS
istream: StringStream
jsDestructor(TextDecoder)
jsDestructor(TextEncoder)
type TextDecoderOptions = object of JSDict
fatal: bool
ignoreBOM: bool
func newTextDecoder(label = "utf-8", options = TextDecoderOptions()):
JSResult[TextDecoder] {.jsctor.} =
let errorMode = if options.fatal:
DECODER_ERROR_MODE_FATAL
else:
DECODER_ERROR_MODE_REPLACEMENT
let encoding = getCharset(label)
if encoding in {CHARSET_UNKNOWN, CHARSET_REPLACEMENT}:
return err(newRangeError("Invalid encoding label"))
return ok(TextDecoder(
errorMode: errorMode,
ignoreBOM: options.ignoreBOM,
encoding: encoding
))
type TextDecodeOptions = object of JSDict
stream: bool
#TODO AllowSharedBufferSource
proc decode(this: TextDecoder, input = none(JSArrayBufferView),
options = TextDecodeOptions()): string {.jsfunc.} =
if not this.doNotFlush:
if this.istream != nil:
this.istream.close()
if this.decoder != nil:
this.decoder.close()
if this.encoder != nil:
this.encoder.close()
this.istream = newStringStream()
this.decoder = newDecoderStream(this.istream, cs = this.encoding,
errormode = this.errorMode)
this.encoder = newEncoderStream(this.decoder, cs = CHARSET_UTF_8)
this.bomSeen = false
if this.doNotFlush != options.stream:
this.doNotFlush = options.stream
this.decoder.setInhibitCheckEnd(options.stream)
if input.isSome:
let input = input.get
let pos = this.istream.getPosition()
#TODO input offset?
this.istream.writeData(input.abuf.p, int(input.abuf.len))
this.istream.setPosition(pos)
#TODO this should return a JSString, so we do not needlessly re-encode
# the output. (Right now we do, implicitly through toJS.)
return this.encoder.readAll()
func jencoding(this: TextDecoder): string {.jsfget: "encoding".} =
return $this.encoding
func fatal(this: TextDecoder): bool {.jsfget.} =
return this.errorMode == DECODER_ERROR_MODE_FATAL
func newTextEncoder(): TextEncoder {.jsctor.} =
return TextEncoder()
func jencoding(this: TextEncoder): string {.jsfget: "encoding".} =
return "utf-8"
proc dealloc_wrap(rt: JSRuntime, opaque, p: pointer) {.cdecl.} =
dealloc(p)
proc encode(this: TextEncoder, input = ""): JSUint8Array {.jsfunc.} =
# input is already UTF-8 here :P
let buf = cast[ptr UncheckedArray[uint8]](alloc(input.len))
copyMem(buf, unsafeAddr input[0], input.len)
let abuf = JSArrayBuffer(
p: buf,
len: csize_t(input.len),
dealloc: dealloc_wrap
)
return JSUint8Array(
abuf: abuf,
offset: 0,
nmemb: csize_t(input.len)
)
#TODO encodeInto
proc addEncodingModule*(ctx: JSContext) =
ctx.registerType(TextDecoder)
ctx.registerType(TextEncoder)
|