/*
|
* Copyright 2006 Jeremias Maerki in part, and ZXing Authors in part
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
#import "ZXByteArray.h"
|
#import "ZXCharacterSetECI.h"
|
#import "ZXErrors.h"
|
#import "ZXPDF417HighLevelEncoder.h"
|
|
/**
|
* code for Text compaction
|
*/
|
const int ZX_PDF417_TEXT_COMPACTION = 0;
|
|
/**
|
* code for Byte compaction
|
*/
|
const int ZX_PDF417_BYTE_COMPACTION = 1;
|
|
/**
|
* code for Numeric compaction
|
*/
|
const int ZX_PDF417_NUMERIC_COMPACTION = 2;
|
|
/**
|
* Text compaction submode Alpha
|
*/
|
const int ZX_PDF417_SUBMODE_ALPHA = 0;
|
|
/**
|
* Text compaction submode Lower
|
*/
|
const int ZX_PDF417_SUBMODE_LOWER = 1;
|
|
/**
|
* Text compaction submode Mixed
|
*/
|
const int ZX_PDF417_SUBMODE_MIXED = 2;
|
|
/**
|
* Text compaction submode Punctuation
|
*/
|
const int ZX_PDF417_SUBMODE_PUNCTUATION = 3;
|
|
/**
|
* mode latch to Text Compaction mode
|
*/
|
const int ZX_PDF417_LATCH_TO_TEXT = 900;
|
|
/**
|
* mode latch to Byte Compaction mode (number of characters NOT a multiple of 6)
|
*/
|
const int ZX_PDF417_LATCH_TO_BYTE_PADDED = 901;
|
|
/**
|
* mode latch to Numeric Compaction mode
|
*/
|
const int ZX_PDF417_LATCH_TO_NUMERIC = 902;
|
|
/**
|
* mode shift to Byte Compaction mode
|
*/
|
const int ZX_PDF417_SHIFT_TO_BYTE = 913;
|
|
/**
|
* mode latch to Byte Compaction mode (number of characters a multiple of 6)
|
*/
|
const int ZX_PDF417_LATCH_TO_BYTE = 924;
|
|
/**
|
* identifier for a user defined Extended Channel Interpretation (ECI)
|
*/
|
const int ZX_PDF417_HIGH_LEVEL_ECI_USER_DEFINED = 925;
|
|
/**
|
* identifier for a general purpose ECO format
|
*/
|
const int ZX_PDF417_HIGH_LEVEL_ECI_GENERAL_PURPOSE = 926;
|
|
/**
|
* identifier for an ECI of a character set of code page
|
*/
|
const int ZX_PDF417_HIGH_LEVEL_ECI_CHARSET = 927;
|
|
/**
|
* Raw code table for text compaction Mixed sub-mode
|
*/
|
const int8_t ZX_PDF417_TEXT_MIXED_RAW[] = {
|
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 38, 13, 9, 44, 58,
|
35, 45, 46, 36, 47, 43, 37, 42, 61, 94, 0, 32, 0, 0, 0};
|
|
/**
|
* Raw code table for text compaction: Punctuation sub-mode
|
*/
|
const int8_t ZX_PDF417_TEXT_PUNCTUATION_RAW[] = {
|
59, 60, 62, 64, 91, 92, 93, 95, 96, 126, 33, 13, 9, 44, 58,
|
10, 45, 46, 36, 47, 34, 124, 42, 40, 41, 63, 123, 125, 39, 0};
|
|
const int ZX_PDF417_MIXED_TABLE_LEN = 128;
|
unichar ZX_PDF417_MIXED_TABLE[ZX_PDF417_MIXED_TABLE_LEN];
|
|
const int ZX_PDF417_PUNCTUATION_LEN = 128;
|
unichar ZX_PDF417_PUNCTUATION[ZX_PDF417_PUNCTUATION_LEN];
|
|
const NSStringEncoding ZX_PDF417_DEFAULT_ENCODING = NSISOLatin1StringEncoding;
|
|
@implementation ZXPDF417HighLevelEncoder
|
|
+ (void)initialize {
|
if ([self class] != [ZXPDF417HighLevelEncoder class]) return;
|
|
//Construct inverse lookups
|
for (int i = 0; i < ZX_PDF417_MIXED_TABLE_LEN; i++) {
|
ZX_PDF417_MIXED_TABLE[i] = 0xFF;
|
}
|
for (int8_t i = 0; i < sizeof(ZX_PDF417_TEXT_MIXED_RAW) / sizeof(int8_t); i++) {
|
int8_t b = ZX_PDF417_TEXT_MIXED_RAW[i];
|
if (b > 0) {
|
ZX_PDF417_MIXED_TABLE[b] = i;
|
}
|
}
|
for (int i = 0; i < ZX_PDF417_PUNCTUATION_LEN; i++) {
|
ZX_PDF417_PUNCTUATION[i] = 0xFF;
|
}
|
for (int8_t i = 0; i < sizeof(ZX_PDF417_TEXT_PUNCTUATION_RAW) / sizeof(int8_t); i++) {
|
int8_t b = ZX_PDF417_TEXT_PUNCTUATION_RAW[i];
|
if (b > 0) {
|
ZX_PDF417_PUNCTUATION[b] = i;
|
}
|
}
|
}
|
|
+ (NSString *)encodeHighLevel:(NSString *)msg compaction:(ZXPDF417Compaction)compaction encoding:(NSStringEncoding)encoding error:(NSError **)error {
|
//the codewords 0..928 are encoded as Unicode characters
|
NSMutableString *sb = [NSMutableString stringWithCapacity:msg.length];
|
|
if (encoding == 0) {
|
encoding = ZX_PDF417_DEFAULT_ENCODING;
|
} else if (ZX_PDF417_DEFAULT_ENCODING != encoding) {
|
ZXCharacterSetECI *eci = [ZXCharacterSetECI characterSetECIByEncoding:encoding];
|
if (![self encodingECI:eci.value sb:sb error:error]) {
|
return nil;
|
}
|
}
|
|
NSUInteger len = msg.length;
|
int p = 0;
|
int textSubMode = ZX_PDF417_SUBMODE_ALPHA;
|
|
// User selected encoding mode
|
ZXByteArray *bytes = nil; //Fill later and only if needed
|
if (compaction == ZXPDF417CompactionText) {
|
[self encodeText:msg startpos:p count:(int)len buffer:sb initialSubmode:textSubMode];
|
} else if (compaction == ZXPDF417CompactionByte) {
|
bytes = [self bytesForMessage:msg encoding:encoding];
|
[self encodeBinary:bytes startpos:p count:(int)msg.length startmode:ZX_PDF417_BYTE_COMPACTION buffer:sb];
|
} else if (compaction == ZXPDF417CompactionNumeric) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_LATCH_TO_NUMERIC];
|
[self encodeNumeric:msg startpos:p count:(int)len buffer:sb];
|
} else {
|
int encodingMode = ZX_PDF417_TEXT_COMPACTION; //Default mode, see 4.4.2.1
|
while (p < len) {
|
int n = [self determineConsecutiveDigitCount:msg startpos:p];
|
if (n >= 13) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_LATCH_TO_NUMERIC];
|
encodingMode = ZX_PDF417_NUMERIC_COMPACTION;
|
textSubMode = ZX_PDF417_SUBMODE_ALPHA; //Reset after latch
|
[self encodeNumeric:msg startpos:p count:n buffer:sb];
|
p += n;
|
} else {
|
int t = [self determineConsecutiveTextCount:msg startpos:p];
|
if (t >= 5 || n == len) {
|
if (encodingMode != ZX_PDF417_TEXT_COMPACTION) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_LATCH_TO_TEXT];
|
encodingMode = ZX_PDF417_TEXT_COMPACTION;
|
textSubMode = ZX_PDF417_SUBMODE_ALPHA; //start with submode alpha after latch
|
}
|
textSubMode = [self encodeText:msg startpos:p count:t buffer:sb initialSubmode:textSubMode];
|
p += t;
|
} else {
|
if (bytes == NULL) {
|
bytes = [self bytesForMessage:msg encoding:encoding];
|
}
|
int b = [self determineConsecutiveBinaryCount:msg bytes:bytes startpos:p error:error];
|
if (b == -1) {
|
return nil;
|
} else if (b == 0) {
|
b = 1;
|
}
|
if (b == 1 && encodingMode == ZX_PDF417_TEXT_COMPACTION) {
|
//Switch for one byte (instead of latch)
|
[self encodeBinary:bytes startpos:p count:1 startmode:ZX_PDF417_TEXT_COMPACTION buffer:sb];
|
} else {
|
//Mode latch performed by encodeBinary
|
[self encodeBinary:bytes startpos:p count:b startmode:encodingMode buffer:sb];
|
encodingMode = ZX_PDF417_BYTE_COMPACTION;
|
textSubMode = ZX_PDF417_SUBMODE_ALPHA; //Reset after latch
|
}
|
p += b;
|
}
|
}
|
}
|
}
|
|
return sb;
|
}
|
|
/**
|
* Encode parts of the message using Text Compaction as described in ISO/IEC 15438:2001(E),
|
* chapter 4.4.2.
|
*
|
* @param msg the message
|
* @param startpos the start position within the message
|
* @param count the number of characters to encode
|
* @param sb receives the encoded codewords
|
* @param initialSubmode should normally be SUBMODE_ALPHA
|
* @return the text submode in which this method ends
|
*/
|
+ (int)encodeText:(NSString *)msg startpos:(int)startpos count:(int)count buffer:(NSMutableString *)sb initialSubmode:(int)initialSubmode {
|
NSMutableString *tmp = [NSMutableString stringWithCapacity:count];
|
int submode = initialSubmode;
|
int idx = 0;
|
while (true) {
|
unichar ch = [msg characterAtIndex:startpos + idx];
|
switch (submode) {
|
case ZX_PDF417_SUBMODE_ALPHA:
|
if ([self isAlphaUpper:ch]) {
|
if (ch == ' ') {
|
[tmp appendFormat:@"%C", (unichar) 26]; //space
|
} else {
|
[tmp appendFormat:@"%C", (unichar) (ch - 65)];
|
}
|
} else {
|
if ([self isAlphaLower:ch]) {
|
submode = ZX_PDF417_SUBMODE_LOWER;
|
[tmp appendFormat:@"%C", (unichar) 27]; //ll
|
continue;
|
} else if ([self isMixed:ch]) {
|
submode = ZX_PDF417_SUBMODE_MIXED;
|
[tmp appendFormat:@"%C", (unichar) 28]; //ml
|
continue;
|
} else {
|
[tmp appendFormat:@"%C", (unichar) 29]; //ps
|
[tmp appendFormat:@"%C", ZX_PDF417_PUNCTUATION[ch]];
|
break;
|
}
|
}
|
break;
|
case ZX_PDF417_SUBMODE_LOWER:
|
if ([self isAlphaLower:ch]) {
|
if (ch == ' ') {
|
[tmp appendFormat:@"%C", (unichar) 26]; //space
|
} else {
|
[tmp appendFormat:@"%C", (unichar) (ch - 97)];
|
}
|
} else {
|
if ([self isAlphaUpper:ch]) {
|
[tmp appendFormat:@"%C", (unichar) 27]; //as
|
[tmp appendFormat:@"%C", (unichar) (ch - 65)];
|
//space cannot happen here, it is also in "Lower"
|
break;
|
} else if ([self isMixed:ch]) {
|
submode = ZX_PDF417_SUBMODE_MIXED;
|
[tmp appendFormat:@"%C", (unichar) 28]; //ml
|
continue;
|
} else {
|
[tmp appendFormat:@"%C", (unichar) 29]; //ps
|
[tmp appendFormat:@"%C", ZX_PDF417_PUNCTUATION[ch]];
|
break;
|
}
|
}
|
break;
|
case ZX_PDF417_SUBMODE_MIXED:
|
if ([self isMixed:ch]) {
|
[tmp appendFormat:@"%C", ZX_PDF417_MIXED_TABLE[ch]]; //as
|
} else {
|
if ([self isAlphaUpper:ch]) {
|
submode = ZX_PDF417_SUBMODE_ALPHA;
|
[tmp appendFormat:@"%C", (unichar) 28]; //al
|
continue;
|
} else if ([self isAlphaLower:ch]) {
|
submode = ZX_PDF417_SUBMODE_LOWER;
|
[tmp appendFormat:@"%C", (unichar) 27]; //ll
|
continue;
|
} else {
|
if (startpos + idx + 1 < count) {
|
char next = [msg characterAtIndex:startpos + idx + 1];
|
if ([self isPunctuation:next]) {
|
submode = ZX_PDF417_SUBMODE_PUNCTUATION;
|
[tmp appendFormat:@"%C", (unichar) 25]; //pl
|
continue;
|
}
|
}
|
[tmp appendFormat:@"%C", (unichar) 29]; //ps
|
[tmp appendFormat:@"%C", ZX_PDF417_PUNCTUATION[ch]];
|
}
|
}
|
break;
|
default: //ZX_PDF417_SUBMODE_PUNCTUATION
|
if ([self isPunctuation:ch]) {
|
[tmp appendFormat:@"%C", ZX_PDF417_PUNCTUATION[ch]];
|
} else {
|
submode = ZX_PDF417_SUBMODE_ALPHA;
|
[tmp appendFormat:@"%C", (unichar) 29]; //al
|
continue;
|
}
|
}
|
idx++;
|
if (idx >= count) {
|
break;
|
}
|
}
|
unichar h = 0;
|
NSUInteger len = tmp.length;
|
for (int i = 0; i < len; i++) {
|
BOOL odd = (i % 2) != 0;
|
if (odd) {
|
h = (unichar) ((h * 30) + [tmp characterAtIndex:i]);
|
[sb appendFormat:@"%C", h];
|
} else {
|
h = [tmp characterAtIndex:i];
|
}
|
}
|
if ((len % 2) != 0) {
|
[sb appendFormat:@"%C", (unichar) ((h * 30) + 29)]; //ps
|
}
|
return submode;
|
}
|
|
/**
|
* Encode parts of the message using Byte Compaction as described in ISO/IEC 15438:2001(E),
|
* chapter 4.4.3. The Unicode characters will be converted to binary using the cp437
|
* codepage.
|
*
|
* @param bytes the message converted to a byte array
|
* @param startpos the start position within the message
|
* @param count the number of bytes to encode
|
* @param startmode the mode from which this method starts
|
* @param sb receives the encoded codewords
|
*/
|
+ (void)encodeBinary:(ZXByteArray *)bytes startpos:(int)startpos count:(int)count startmode:(int)startmode buffer:(NSMutableString *)sb {
|
if (count == 1 && startmode == ZX_PDF417_TEXT_COMPACTION) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_SHIFT_TO_BYTE];
|
} else {
|
BOOL sixpack = ((count % 6) == 0);
|
if (sixpack) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_LATCH_TO_BYTE];
|
} else {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_LATCH_TO_BYTE_PADDED];
|
}
|
}
|
|
int idx = startpos;
|
// Encode sixpacks
|
if (count >= 6) {
|
const int charsLen = 5;
|
unichar chars[charsLen];
|
memset(chars, 0, charsLen * sizeof(unichar));
|
while ((startpos + count - idx) >= 6) {
|
long long t = 0;
|
for (int i = 0; i < 6; i++) {
|
t <<= 8;
|
t += bytes.array[idx + i] & 0xff;
|
}
|
for (int i = 0; i < 5; i++) {
|
chars[i] = (unichar) (t % 900);
|
t /= 900;
|
}
|
for (int i = charsLen - 1; i >= 0; i--) {
|
[sb appendFormat:@"%C", chars[i]];
|
}
|
idx += 6;
|
}
|
}
|
//Encode rest (remaining n<5 bytes if any)
|
for (int i = idx; i < startpos + count; i++) {
|
int ch = bytes.array[i] & 0xff;
|
[sb appendFormat:@"%C", (unichar)ch];
|
}
|
}
|
|
+ (void)encodeNumeric:(NSString *)msg startpos:(int)startpos count:(int)count buffer:(NSMutableString *)sb {
|
int idx = 0;
|
NSMutableString *tmp = [NSMutableString stringWithCapacity:count / 3 + 1];
|
NSDecimalNumber *num900 = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithInt:900] decimalValue]];
|
NSDecimalNumber *num0 = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithInt:0] decimalValue]];
|
while (idx < count) {
|
[tmp setString:@""];
|
int len = MIN(44, count - idx);
|
NSString *part = [@"1" stringByAppendingString:[msg substringWithRange:NSMakeRange(startpos + idx, len)]];
|
NSDecimalNumber *bigint = [NSDecimalNumber decimalNumberWithString:part];
|
do {
|
NSRoundingMode roundingMode = ((bigint.floatValue < 0) ^ (num900.floatValue < 0)) ? NSRoundUp : NSRoundDown;
|
NSDecimalNumber *quotient = [bigint decimalNumberByDividingBy:num900
|
withBehavior:[NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:roundingMode
|
scale:0
|
raiseOnExactness:NO
|
raiseOnOverflow:NO
|
raiseOnUnderflow:NO
|
raiseOnDivideByZero:NO]];
|
|
NSDecimalNumber *subtractAmount = [quotient decimalNumberByMultiplyingBy:num900];
|
NSDecimalNumber *remainder = [bigint decimalNumberBySubtracting:subtractAmount];
|
|
[tmp appendFormat:@"%C", (unichar)[remainder longValue]];
|
bigint = quotient;
|
} while (![bigint isEqualToNumber:num0]);
|
|
//Reverse temporary string
|
for (int i = (int)tmp.length - 1; i >= 0; i--) {
|
[sb appendFormat:@"%C", [tmp characterAtIndex:i]];
|
}
|
idx += len;
|
}
|
}
|
|
+ (BOOL)isDigit:(unichar)ch {
|
return ch >= '0' && ch <= '9';
|
}
|
|
+ (BOOL)isAlphaUpper:(unichar)ch {
|
return ch == ' ' || (ch >= 'A' && ch <= 'Z');
|
}
|
|
+ (BOOL)isAlphaLower:(unichar)ch {
|
return ch == ' ' || (ch >= 'a' && ch <= 'z');
|
}
|
|
+ (BOOL)isMixed:(unichar)ch {
|
return ZX_PDF417_MIXED_TABLE[ch] != 0xFF;
|
}
|
|
+ (BOOL)isPunctuation:(unichar)ch {
|
return ZX_PDF417_PUNCTUATION[ch] != 0xFF;
|
}
|
|
+ (BOOL)isText:(unichar)ch {
|
return ch == '\t' || ch == '\n' || ch == '\r' || (ch >= 32 && ch <= 126);
|
}
|
|
/**
|
* Determines the number of consecutive characters that are encodable using numeric compaction.
|
*
|
* @param msg the message
|
* @param startpos the start position within the message
|
* @return the requested character count
|
*/
|
+ (int)determineConsecutiveDigitCount:(NSString *)msg startpos:(int)startpos {
|
int count = 0;
|
NSUInteger len = msg.length;
|
int idx = startpos;
|
if (idx < len) {
|
char ch = [msg characterAtIndex:idx];
|
while ([self isDigit:ch] && idx < len) {
|
count++;
|
idx++;
|
if (idx < len) {
|
ch = [msg characterAtIndex:idx];
|
}
|
}
|
}
|
return count;
|
}
|
|
/**
|
* Determines the number of consecutive characters that are encodable using text compaction.
|
*
|
* @param msg the message
|
* @param startpos the start position within the message
|
* @return the requested character count
|
*/
|
+ (int)determineConsecutiveTextCount:(NSString *)msg startpos:(int)startpos {
|
NSUInteger len = msg.length;
|
int idx = startpos;
|
while (idx < len) {
|
char ch = [msg characterAtIndex:idx];
|
int numericCount = 0;
|
while (numericCount < 13 && [self isDigit:ch] && idx < len) {
|
numericCount++;
|
idx++;
|
if (idx < len) {
|
ch = [msg characterAtIndex:idx];
|
}
|
}
|
if (numericCount >= 13) {
|
return idx - startpos - numericCount;
|
}
|
if (numericCount > 0) {
|
//Heuristic: All text-encodable chars or digits are binary encodable
|
continue;
|
}
|
ch = [msg characterAtIndex:idx];
|
|
//Check if character is encodable
|
if (![self isText:ch]) {
|
break;
|
}
|
idx++;
|
}
|
return idx - startpos;
|
}
|
|
/**
|
* Determines the number of consecutive characters that are encodable using binary compaction.
|
*
|
* @param msg the message
|
* @param bytes the message converted to a byte array
|
* @param startpos the start position within the message
|
* @return the requested character count
|
*/
|
+ (int)determineConsecutiveBinaryCount:(NSString *)msg bytes:(ZXByteArray *)bytes startpos:(int)startpos error:(NSError **)error {
|
NSUInteger len = msg.length;
|
int idx = startpos;
|
while (idx < len) {
|
char ch = [msg characterAtIndex:idx];
|
int numericCount = 0;
|
|
while (numericCount < 13 && [self isDigit:ch]) {
|
numericCount++;
|
//textCount++;
|
int i = idx + numericCount;
|
if (i >= len) {
|
break;
|
}
|
ch = [msg characterAtIndex:i];
|
}
|
if (numericCount >= 13) {
|
return idx - startpos;
|
}
|
ch = [msg characterAtIndex:idx];
|
|
//Check if character is encodable
|
//Sun returns a ASCII 63 (?) for a character that cannot be mapped. Let's hope all
|
//other VMs do the same
|
if (bytes.array[idx] == 63 && ch != '?') {
|
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Non-encodable character detected: %c (Unicode: %C)", ch, (unichar)ch]};
|
|
if (error) *error = [[NSError alloc] initWithDomain:ZXErrorDomain code:ZXWriterError userInfo:userInfo];
|
return -1;
|
}
|
idx++;
|
}
|
return idx - startpos;
|
}
|
|
+ (BOOL)encodingECI:(int)eci sb:(NSMutableString *)sb error:(NSError **)error {
|
if (eci >= 0 && eci < 900) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_HIGH_LEVEL_ECI_CHARSET];
|
[sb appendFormat:@"%C", (unichar) eci];
|
} else if (eci < 810900) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_HIGH_LEVEL_ECI_GENERAL_PURPOSE];
|
[sb appendFormat:@"%C", (unichar) (eci / 900 - 1)];
|
[sb appendFormat:@"%C", (unichar) (eci % 900)];
|
} else if (eci < 811800) {
|
[sb appendFormat:@"%C", (unichar) ZX_PDF417_HIGH_LEVEL_ECI_USER_DEFINED];
|
[sb appendFormat:@"%C", (unichar) (810900 - eci)];
|
} else {
|
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"ECI number not in valid range from 0..811799, but was %d", eci]};
|
|
if (error) *error = [[NSError alloc] initWithDomain:ZXErrorDomain code:ZXWriterError userInfo:userInfo];
|
return NO;
|
}
|
return YES;
|
}
|
|
+ (ZXByteArray *)bytesForMessage:(NSString *)msg encoding:(NSStringEncoding)encoding {
|
NSData *data = [msg dataUsingEncoding:encoding];
|
int8_t *bytes = (int8_t *)[data bytes];
|
ZXByteArray *byteArray = [[ZXByteArray alloc] initWithLength:(unsigned int)[data length]];
|
memcpy(byteArray.array, bytes, [data length] * sizeof(int8_t));
|
return byteArray;
|
}
|
|
@end
|