1/*
2 * Copyright 2009, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "KeyboardLayout.h"
8
9#include <ctype.h>
10#include <stdarg.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <vector>
14
15#include <File.h>
16#include <InterfaceDefs.h>
17
18
19#undef TRACE
20
21//#define TRACE_LAYOUT
22#ifdef TRACE_LAYOUT
23#	define TRACE(x...) printf(x)
24#else
25#	define TRACE(x...)
26#endif
27
28
29KeyboardLayout::KeyboardLayout()
30	:
31	fKeys(NULL),
32	fKeyCount(0),
33	fKeyCapacity(0),
34	fIndicators(5, true),
35	fIsDefault(true)
36{
37	SetDefault();
38}
39
40
41KeyboardLayout::~KeyboardLayout()
42{
43	free(fKeys);
44}
45
46
47const char*
48KeyboardLayout::Name()
49{
50	return fName.String();
51}
52
53
54int32
55KeyboardLayout::CountKeys()
56{
57	return fKeyCount;
58}
59
60
61Key*
62KeyboardLayout::KeyAt(int32 index)
63{
64	if (index < 0 || index >= fKeyCount)
65		return NULL;
66
67	return &fKeys[index];
68}
69
70
71int32
72KeyboardLayout::CountIndicators()
73{
74	return fIndicators.CountItems();
75}
76
77
78Indicator*
79KeyboardLayout::IndicatorAt(int32 index)
80{
81	return fIndicators.ItemAt(index);
82}
83
84
85BRect
86KeyboardLayout::Bounds()
87{
88	return fBounds;
89}
90
91
92BSize
93KeyboardLayout::DefaultKeySize()
94{
95	return fDefaultKeySize;
96}
97
98
99int32
100KeyboardLayout::IndexForModifier(int32 modifier)
101{
102	switch (modifier) {
103		case B_CAPS_LOCK:
104			return 58;
105		case B_NUM_LOCK:
106			return 33;
107		case B_SCROLL_LOCK:
108			return 14;
109		case B_LEFT_SHIFT_KEY:
110			return 74;
111		case B_RIGHT_SHIFT_KEY:
112			return 85;
113		case B_LEFT_CONTROL_KEY:
114			return 91;
115		case B_RIGHT_CONTROL_KEY:
116			return 98;
117		case B_LEFT_OPTION_KEY:
118			return 92;
119		case B_RIGHT_OPTION_KEY:
120			return 96;
121		case B_LEFT_COMMAND_KEY:
122			return 93;
123		case B_RIGHT_COMMAND_KEY:
124			return 95;
125		case B_MENU_KEY:
126			return 97;
127	}
128
129	return 0;
130}
131
132
133status_t
134KeyboardLayout::Load(const char* path)
135{
136	entry_ref ref;
137	get_ref_for_path(path, &ref);
138
139	return Load(ref);
140}
141
142
143status_t
144KeyboardLayout::Load(entry_ref& ref)
145{
146	BFile file;
147	status_t status = file.SetTo(&ref, B_READ_ONLY);
148	if (status != B_OK)
149		return status;
150
151	off_t size;
152	status = file.GetSize(&size);
153	if (status != B_OK)
154		return status;
155
156	if (size > 65536) {
157		// We don't accept files larger than this
158		return B_BAD_VALUE;
159	}
160
161	char* data = (char*)malloc(size + 1);
162	if (data == NULL)
163		return B_NO_MEMORY;
164
165	ssize_t bytesRead = file.Read(data, size);
166	if (bytesRead != size) {
167		free(data);
168
169		if (bytesRead < 0)
170			return bytesRead;
171
172		return B_IO_ERROR;
173	}
174
175	data[size] = '\0';
176
177	status = _InitFrom(data);
178	if (status == B_OK)
179		fIsDefault = false;
180
181	free(data);
182
183	return status;
184}
185
186
187void
188KeyboardLayout::SetDefault()
189{
190	// The keyboard layout description language defines the position,
191	// size, shape, and scancodes of the keys on the keyboard, row by
192	// row.
193	// You can define variables that are substituted in the row
194	// descriptions. Variables are always prefixed with a '$' symbol.
195
196	// On top level, only two different terms are accepted: value pairs,
197	// and row descriptions.
198	// Value pairs are in the form "<name> = <value>". There are only two
199	// predefined names at this moment "name" that specifies the name of
200	// a layout, and "default-size" that specifies the size of keys when
201	// no specific size is given. Also, variables can be specified this
202	// way.
203
204	// Row descriptions are embedded in the '[' and ']' brackets.
205	// The first term within a row is the position of the row, written as
206	// "<x>,<y>;" - the delimiter between terms/keys is usually the
207	// semi-colon. After the initial position, key descriptions are expected
208	// until the row is closed with a ']'.
209
210	// A key description is of the form "<shape>:<scancodes>", where <shape>
211	// is combined of the shape of the character, and its size, written as
212	// "<kind><width>,<height>[,<second-row-width>]".
213	// The kind can either be 'r' (the default, can also be omitted) for
214	// rectangular keys, or 'l' for the enter key. Additionally, you can use
215	// the 'd' character to mark dark keys. The size can be omitted completely,
216	// in which case the defined "default-size" is used. The default size is
217	// also used to determine the height of the first row of the enter key;
218	// the "<second-row-width>" specifier is only valid for enter keys, too.
219
220	// The scancodes can be written in different ways:
221	// 1) "+<count>": adds <count> keys, the scancodes will be used relative
222	//    to the previous keys.
223	// 2) "<first>-<last>": keys are added for scancode <first> to scancode
224	//    <last>.
225	// 3) "<first>+<count>": one key with the <first> scancode is added, plus
226	//    <count> ones with relative scancode from that one.
227
228	// Finally, you can also define LED indicator. Those can be made instead
229	// of a key, it accepts a size, but no shape modifiers. Also, instead of
230	// a scancode, the name of the modifer is given, currently only the
231	// following are allowed: "led-num", "led-caps", and "led-scroll".
232
233	// See for examples in the default layout below.
234#if 1
235	static const char* kDefaultLayout105 =
236		"name = Generic 105-key International\n"
237		// Size shortcuts
238		"default-size = 10,10\n"
239		"$b = 5,10\n"
240		"$c = 20,10\n"
241		"$d = 15,10\n"
242		"$e = l15,20,9\n"
243		"$f = 10,20\n"
244		"$g = 13,10\n"
245		// Key rows
246		"[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; "
247			"$g:led-num; $g:led-caps; $g:led-scroll ]\n"
248		"[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n"
249		"[ 0,30; d$d:0x26; :+12; d$e:0x47; $b:-; d:0x34-0x36; $b:-; :+3; "
250			"d$f:+1 ]\n"
251		"[ 0,40; d19,10:0x3b; :+11; :0x33; 51,10:-; :0x48-0x4a ]\n"
252		"[ 0,50; d$g:0x4b; :0x69; :0x4c+9; d27,10:+1; 15,10:-; d:+1; "
253		"	15,10:-; :+3; d$f:+1 ]\n"
254		"[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; "
255			"d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n";
256
257	_InitFrom(kDefaultLayout105);
258#endif
259#if 0
260	static const char* kDefaultLayout104 = "name = Generic 104-key\n"
261		// Size shortcuts
262		"default-size = 10,10\n"
263		"$b = 5,10\n"
264		"$c = 20,10\n"
265		"$d = 15,10\n"
266		"$enter = d20,10\n"
267		"$f = 10,20\n"
268		"$g = 13,10\n"
269		// Key rows
270		"[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; "
271			"$g:led-num; $g:led-caps; $g:led-scroll ]\n"
272		"[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n"
273		"[ 0,30; d$d:0x26; :+12; $d:+1; $b:-; d:+3; $b:-; :+3; "
274			"d$f:+1 ]\n"
275		"[ 0,40; d$c:0x3b; :+11; $enter:+1; 40,10:-; :+3 ]\n"
276		"[ 0,50; d24,10:0x4b; :+10; d26,10:+1; 15,10:-; d:+1; "
277		"	15,10:-; :+3; d$f:+1 ]\n"
278		"[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; "
279			"d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n";
280
281	_InitFrom(kDefaultLayout104);
282#endif
283#if 0
284	static const char* kIBMLaptop = "name = IBM Laptop International\n"
285		// Size shortcuts
286		"default-size = 18,18\n"
287		"$s = 17,10\n"
288		"$gap = 6,10\n"
289		"$sgap = 5,10\n"
290		"$backspace = 38,18\n"
291		"$tab = 28,18\n"
292		"$caps = 32,18\n"
293		"$enter = l28,36,22\n"
294		"$l-shift-ctrl = 23,18\n"
295		"$r-shift = 51,18\n"
296		"$option = 13,18\n"
297		"$space = 95,18\n"
298		// Key rows
299		"[ 0,0; $s:0x01; 148,10:-; $s:0x0e+2; $sgap:-; $s:0x1f+2; ]\n"
300		"[ 0,10; $s:0x02+3; $gap:-; $s:+4; $gap:-; $s:+4; $sgap:-; "
301			"$s:0x34+2; ]\n"
302		"[ 0,20; :0x11+12; $backspace:+1 ]\n"
303		"[ 0,38; $tab:0x26; :+12; $enter:0x47; ]\n"
304		"[ 0,56; $caps:0x3b; :+11; :0x33 ]\n"
305		"[ 0,74; $l-shift-ctrl:0x4b; :0x69; :0x4c+9; $r-shift:+1 ]\n"
306		"[ 0,92; :0x99; $l-shift-ctrl:0x5c; $option:0x66; :0x5d; $space:+1; "
307			":+1; :0x68; :0x60; $s:0x9a; $s:0x57; $s:0x9b ]\n"
308		"[ 221,102; $s:0x61+2; ]\n";
309
310	_InitFrom(kIBMLaptop);
311#endif
312	fIsDefault = true;
313}
314
315
316void
317KeyboardLayout::_FreeKeys()
318{
319	free(fKeys);
320	fKeys = NULL;
321	fKeyCount = 0;
322	fKeyCapacity = 0;
323	fBounds = BRect();
324
325	fIndicators.MakeEmpty();
326}
327
328
329void
330KeyboardLayout::_Error(const parse_state& state, const char* reason, ...)
331{
332	va_list args;
333	va_start(args, reason);
334
335	fprintf(stderr, "Syntax error in line %" B_PRId32 ": ", state.line);
336	vfprintf(stderr, reason, args);
337	fprintf(stderr, "\n");
338
339	va_end(args);
340}
341
342
343void
344KeyboardLayout::_AddAlternateKeyCode(Key* key, int32 modifier, int32 code)
345{
346	if (key == NULL)
347		return;
348
349	// TODO: search for free spot
350}
351
352
353bool
354KeyboardLayout::_AddKey(const Key& key)
355{
356	TRACE("    add %ld (%g,%g)\n", key.code, key.frame.left, key.frame.top);
357	if (fKeyCount + 1 > fKeyCapacity) {
358		// enlarge array
359		int32 newCapacity = fKeyCapacity + 32;
360		Key* newKeys = (Key*)realloc((void*)fKeys, newCapacity * sizeof(Key));
361		if (newKeys == NULL)
362			return false;
363
364		fKeys = newKeys;
365		fKeyCapacity = newCapacity;
366	}
367
368	fKeys[fKeyCount] = key;
369	fKeyCount++;
370
371	fBounds = key.frame | fBounds;
372
373	return true;
374}
375
376
377void
378KeyboardLayout::_SkipCommentsAndSpace(parse_state& state, const char*& data)
379{
380	while (data[0] != '\0') {
381		while (isspace(data[0])) {
382			if (data[0] == '\n')
383				state.line++;
384			data++;
385		}
386
387		if (data[0] == '#') {
388			// skip comment
389			while (data[0] != '\0' && data[0] != '\n') {
390				data++;
391			}
392		} else
393			break;
394	}
395}
396
397
398void
399KeyboardLayout::_Trim(BString& string, bool stripComments)
400{
401	// Strip leading spaces
402	int32 i = 0;
403	while (isspace(string[i])) {
404		i++;
405	}
406	if (i > 0)
407		string.Remove(0, i);
408
409	// Remove comments
410	if (stripComments) {
411		i = string.FindFirst('#');
412		if (i >= 0)
413			string.Truncate(i);
414	}
415
416	// Strip trailing spaces
417	i = string.Length() - 1;
418	while (i > 0 && isspace(string[i])) {
419		i--;
420	}
421	string.Truncate(i + 1);
422}
423
424
425bool
426KeyboardLayout::_GetPair(const parse_state& state, const char*& data,
427	BString& name, BString& value)
428{
429	// Get name
430	name = "";
431	while (data[0] != '\0' && data[0] != '=') {
432		name += data[0];
433		data++;
434	}
435
436	if (data[0] != '=') {
437		_Error(state, "no valid pair");
438		return false;
439	}
440
441	// Skip sign
442	data++;
443
444	// Get value
445	value = "";
446	while (data[0] != '\0' && data[0] != '\n') {
447		value += data[0];
448		data++;
449	}
450
451	_Trim(name, false);
452	_Trim(value, true);
453
454	return true;
455}
456
457
458bool
459KeyboardLayout::_AddKeyCodes(const parse_state& state, BPoint& rowLeftTop,
460	Key& key, const char* data, int32& lastCount)
461{
462	if (data[0] == '-') {
463		// no key, just free space
464		int32 num = strtoul(data + 1, NULL, 0);
465		if (num < 1)
466			num = 1;
467		else if (num > 32) {
468			_Error(state, "empty key count too large");
469			return false;
470		}
471
472		key.frame.OffsetTo(rowLeftTop);
473		rowLeftTop.x = key.frame.left + key.frame.Width() * num;
474		return true;
475	}
476
477	int32 modifier = 0;
478
479	if (isalpha(data[0])) {
480		bool led = false;
481		if (!strcmp("led-caps", data)) {
482			modifier = B_CAPS_LOCK;
483			led = true;
484		} else if (!strcmp("led-num", data)) {
485			modifier = B_NUM_LOCK;
486			led = true;
487		} else if (!strcmp("led-scroll", data)) {
488			modifier = B_SCROLL_LOCK;
489			led = true;
490		} else {
491			// TODO: get modifier (ie. "num")
492		}
493
494		if (led) {
495			key.frame.OffsetTo(rowLeftTop);
496			rowLeftTop.x = key.frame.right;
497			fBounds = key.frame | fBounds;
498
499			Indicator* indicator = new(std::nothrow) Indicator;
500			if (indicator != NULL) {
501				indicator->modifier = modifier;
502				indicator->frame = key.frame;
503
504				fIndicators.AddItem(indicator);
505			}
506			return true;
507		}
508	}
509
510	int32 first;
511	int32 last;
512	int32 num = 1;
513
514	if (data[0] == '+') {
515		num = strtoul(data + 1, NULL, 0);
516		if (num < 1)
517			num = 1;
518		else if (num > 32) {
519			_Error(state, "key count too large");
520			return false;
521		}
522
523		if (fKeyCount > 0)
524			first = fKeys[fKeyCount - 1].code + 1;
525		else
526			first = 1;
527
528		last = first + num - 1;
529	} else {
530		char* end;
531		first = strtoul(data, &end, 0);
532		last = first;
533
534		if (end[0] == '-') {
535			last = strtoul(end + 1, NULL, 0);
536			if (first > last) {
537				_Error(state, "invalid key code specifier");
538				return false;
539			}
540
541			num = last - first;
542		} else if (end[0] == '+') {
543			num = strtoul(end + 1, NULL, 0) + 1;
544			last = first + num - 1;
545		} else if (end[0] != '\0') {
546			_Error(state, "invalid key range");
547			return false;
548		}
549	}
550
551	if (lastCount != 0) {
552		// update existing keys
553		if (lastCount != num) {
554			_Error(state, "modifier key mismatch");
555			return false;
556		}
557
558		for (int32 i = fKeyCount - num; i < fKeyCount; i++, first++) {
559			Key* key = KeyAt(i);
560
561			_AddAlternateKeyCode(key, modifier, first);
562		}
563	} else {
564		// add new keys
565		for (int32 i = first; i <= last; i++) {
566			key.code = i;
567
568			// "layout"
569			key.frame.OffsetTo(rowLeftTop);
570			rowLeftTop.x = key.frame.right;
571
572			_AddKey(key);
573		}
574
575		lastCount = num;
576	}
577
578	return true;
579}
580
581
582bool
583KeyboardLayout::_GetSize(const parse_state& state, const char* data,
584	float& x, float& y, float* _secondRow)
585{
586	if (data[0] == '\0') {
587		// default size
588		x = fDefaultKeySize.width;
589		y = fDefaultKeySize.height;
590		return true;
591	}
592
593	float secondRow = 0;
594	int num = sscanf(data, "%g,%g,%g", &x, &y, &secondRow);
595	if (num < 2) {
596		_Error(state, "invalid size");
597		return false;
598	}
599
600	if (_secondRow != NULL)
601		*_secondRow = secondRow;
602	return true;
603}
604
605
606bool
607KeyboardLayout::_GetShape(const parse_state& state, const char* data, Key& key)
608{
609	// the default
610	key.shape = kRectangleKeyShape;
611	key.dark = false;
612
613	while (isalpha(data[0])) {
614		switch (tolower(data[0])) {
615			case 'r':
616				key.shape = kRectangleKeyShape;
617				break;
618			case 'c':
619				key.shape = kCircleKeyShape;
620				break;
621			case 'l':
622				key.shape = kEnterKeyShape;
623				break;
624			case 'd':
625				key.dark = true;
626				break;
627
628			default:
629				_Error(state, "unknown shape specifier '%c'", data[0]);
630				return false;
631		}
632
633		data++;
634	}
635
636	float width, height;
637	if (!_GetSize(state, data, width, height, &key.second_row))
638		return false;
639
640	// don't accept second row with anything but kEnterKeyShape
641	if ((key.shape != kEnterKeyShape && key.second_row != 0)
642		|| (key.shape == kEnterKeyShape && key.second_row == 0)) {
643		_Error(state, "shape size mismatch");
644		return false;
645	}
646
647	key.frame.left = 0;
648	key.frame.top = 0;
649	key.frame.right = width;
650	key.frame.bottom = height;
651
652	return true;
653}
654
655
656/*!	Returns the term delimiter expected in a certain parse mode. */
657const char*
658KeyboardLayout::_Delimiter(parse_mode mode)
659{
660	switch (mode) {
661		default:
662		case kSize:
663			return "";
664		case kRowStart:
665			return ";";
666
667		case kKeyShape:
668			return ":";
669		case kKeyCodes:
670			return ";:";
671	}
672}
673
674
675bool
676KeyboardLayout::_GetTerm(const char*& data, const char* delimiter,
677	BString& term, bool closingBracketAllowed)
678{
679	// Get term
680	term = "";
681	while (data[0] != '\0' && strchr(delimiter, data[0]) == NULL
682		&& data[0] != '\n' && data[0] != '#'
683		&& (!closingBracketAllowed || data[0] != ']')) {
684		term += data[0];
685		data++;
686	}
687
688	if (data[0] == '\0' && delimiter[0])
689		return false;
690
691	_Trim(term, true);
692	return true;
693}
694
695
696bool
697KeyboardLayout::_SubstituteVariables(BString& term, VariableMap& variables,
698	BString& unknown)
699{
700	while (true) {
701		int32 index = term.FindFirst('$');
702		if (index < 0)
703			break;
704
705		// find variable name
706
707		VariableMap::iterator iterator = variables.begin();
708		VariableMap::iterator best = variables.end();
709		int32 bestLength = 0;
710
711		for (; iterator != variables.end(); iterator++) {
712			const BString& name = iterator->first;
713			if (!term.CompareAt(index, name, name.Length())
714				&& name.Length() > bestLength) {
715				best = iterator;
716				bestLength = name.Length();
717			}
718		}
719
720		if (best != variables.end()) {
721			// got one, replace it
722			term.Remove(index, bestLength);
723			term.Insert(best->second.String(), index);
724		} else {
725			// variable has not been found
726			int32 length = 1;
727			while (isalpha(term[length + index])) {
728				length++;
729			}
730			term.Truncate(length + index);
731			return false;
732		}
733	}
734
735	return true;
736}
737
738
739bool
740KeyboardLayout::_ParseTerm(const parse_state& state, const char*& data,
741	BString& term, VariableMap& variables)
742{
743	if (!_GetTerm(data, _Delimiter(state.mode), term,
744			state.mode == kKeyCodes)) {
745		_Error(state, state.mode == kRowStart
746			? "no valid row start" : "invalid term");
747		return false;
748	}
749
750	BString unknown;
751	if (!_SubstituteVariables(term, variables, unknown)) {
752		_Error(state, "Unknown variable \"%s\"", unknown.String());
753		return false;
754	}
755
756	return true;
757}
758
759
760/*!	Initializes the keyboard layout from the data given.
761	The string has to be a valid keyboard layout description, otherwise
762	an error is returned.
763*/
764status_t
765KeyboardLayout::_InitFrom(const char* data)
766{
767	_FreeKeys();
768
769	VariableMap variables;
770	BPoint rowLeftTop;
771	int32 lastKeyCount = 0;
772	Key key;
773
774	parse_state state = {kPairs, 1};
775
776	while (data[0] != '\0') {
777		_SkipCommentsAndSpace(state, data);
778
779		if (data[0] == '[') {
780			state.mode = kRowStart;
781
782			rowLeftTop = BPoint(0, 0);
783			data++;
784			continue;
785		} else if (data[0] == '\0')
786			break;
787
788		switch (state.mode) {
789			case kPairs:
790			{
791				BString name;
792				BString value;
793				if (!_GetPair(state, data, name, value))
794					return B_BAD_VALUE;
795
796				TRACE("<%s> = <%s>\n", name.String(), value.String());
797				if (name == "name")
798					fName = value;
799				else if (name == "default-size") {
800					const char* valueString = value.String();
801					parse_state tempState = {kSize, state.line};
802					BString term;
803					if (!_ParseTerm(tempState, valueString, term, variables))
804						return B_BAD_VALUE;
805
806					TRACE("  size = %s\n", term.String());
807					if (!_GetSize(state, term.String(), fDefaultKeySize.width,
808							fDefaultKeySize.height))
809						return B_BAD_VALUE;
810				} else if (name[0] == '$')
811					variables[name] = value;
812				break;
813			}
814
815			case kRowStart:
816			case kKeyShape:
817			case kKeyCodes:
818			{
819				if (data[0] == ']') {
820					if (state.mode == kKeyShape) {
821						state.mode = kPairs;
822						data++;
823						continue;
824					}
825					_Error(state, "unexpected row closing bracket");
826					return B_BAD_VALUE;
827				}
828
829				BString term;
830				if (!_ParseTerm(state, data, term, variables))
831					return B_BAD_VALUE;
832
833				switch (state.mode) {
834					case kRowStart:
835						if (!_GetSize(state, term.String(), rowLeftTop.x,
836								rowLeftTop.y))
837							return B_BAD_VALUE;
838
839						TRACE("row: %s (%g:%g)\n", term.String(), rowLeftTop.x,
840							rowLeftTop.y);
841
842						state.mode = kKeyShape;
843						break;
844					case kKeyShape:
845						memset((void*)&key, 0, sizeof(Key));
846						if (!_GetShape(state, term.String(), key))
847							return B_BAD_VALUE;
848
849						TRACE("  shape: %s (%g:%g:%g)\n", term.String(),
850							key.frame.Width(), key.frame.Height(),
851							key.second_row);
852
853						lastKeyCount = 0;
854						state.mode = kKeyCodes;
855						break;
856					case kKeyCodes:
857						TRACE("   raw key: %s\n", term.String());
858
859						if (!_AddKeyCodes(state, rowLeftTop, key, term.String(),
860								lastKeyCount))
861							return B_BAD_VALUE;
862
863						if (data[0] != ':')
864							state.mode = kKeyShape;
865						break;
866
867					default:
868						break;
869				}
870				if (data[0] != ']' && data[0] != '\0')
871					data++;
872				break;
873			}
874
875			default:
876				return B_BAD_VALUE;
877		}
878	}
879
880	return B_OK;
881}
882
883