123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631 |
- //
- // XLFormDescriptor.m
- // XLForm ( https://github.com/xmartlabs/XLForm )
- //
- // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com )
- //
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- #import "NSObject+XLFormAdditions.h"
- #import "XLFormDescriptor.h"
- #import "NSPredicate+XLFormAdditions.h"
- #import "NSString+XLFormAdditions.h"
- NSString * const XLFormErrorDomain = @"XLFormErrorDomain";
- NSString * const XLValidationStatusErrorKey = @"XLValidationStatusErrorKey";
- @interface XLFormSectionDescriptor (_XLFormDescriptor)
- @property NSArray * allRows;
- -(BOOL)evaluateIsHidden;
- @end
- @interface XLFormRowDescriptor(_XLFormDescriptor)
- -(BOOL)evaluateIsDisabled;
- -(BOOL)evaluateIsHidden;
- @end
- @interface XLFormDescriptor()
- @property NSMutableArray * formSections;
- @property (readonly) NSMutableArray * allSections;
- @property NSString * title;
- @property (readonly) NSMutableDictionary* allRowsByTag;
- @property NSMutableDictionary* rowObservers;
- @end
- @implementation XLFormDescriptor
- -(instancetype)init
- {
- return [self initWithTitle:nil];
- }
- -(instancetype)initWithTitle:(NSString *)title;
- {
- self = [super init];
- if (self){
- _formSections = [NSMutableArray array];
- _allSections = [NSMutableArray array];
- _allRowsByTag = [NSMutableDictionary dictionary];
- _rowObservers = [NSMutableDictionary dictionary];
- _title = title;
- _addAsteriskToRequiredRowsTitle = NO;
- _disabled = NO;
- _endEditingTableViewOnScroll = YES;
- _rowNavigationOptions = XLFormRowNavigationOptionEnabled;
- [self addObserver:self forKeyPath:@"formSections" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:0];
- }
- return self;
- }
- +(instancetype)formDescriptor
- {
- return [[self class] formDescriptorWithTitle:nil];
- }
- +(instancetype)formDescriptorWithTitle:(NSString *)title
- {
- return [[[self class] alloc] initWithTitle:title];
- }
- -(void)addFormSection:(XLFormSectionDescriptor *)formSection
- {
- [self insertObject:formSection inAllSectionsAtIndex:[self.allSections count]];
- }
- -(void)addFormSection:(XLFormSectionDescriptor *)formSection atIndex:(NSUInteger)index
- {
- if (index == 0){
- [self insertObject:formSection inAllSectionsAtIndex:0];
- }
- else{
- XLFormSectionDescriptor* previousSection = [self.formSections objectAtIndex:MIN(self.formSections.count, index-1)];
- [self addFormSection:formSection afterSection:previousSection];
- }
- }
- -(void)addFormSection:(XLFormSectionDescriptor *)formSection afterSection:(XLFormSectionDescriptor *)afterSection
- {
- NSUInteger sectionIndex;
- NSUInteger allSectionIndex;
- if ((sectionIndex = [self.allSections indexOfObject:formSection]) == NSNotFound){
- allSectionIndex = [self.allSections indexOfObject:afterSection];
- if (allSectionIndex != NSNotFound) {
- [self insertObject:formSection inAllSectionsAtIndex:(allSectionIndex + 1)];
- }
- else { //case when afterSection does not exist. Just insert at the end.
- [self addFormSection:formSection];
- return;
- }
- }
- formSection.hidden = formSection.hidden;
- }
- -(void)addFormRow:(XLFormRowDescriptor *)formRow beforeRow:(XLFormRowDescriptor *)beforeRow
- {
- if (beforeRow.sectionDescriptor){
- [beforeRow.sectionDescriptor addFormRow:formRow beforeRow:beforeRow];
- }
- else{
- [[self.allSections lastObject] addFormRow:formRow beforeRow:beforeRow];
- }
- }
- -(void)addFormRow:(XLFormRowDescriptor *)formRow beforeRowTag:(NSString *)beforeRowTag
- {
- XLFormRowDescriptor * beforeRowForm = [self formRowWithTag:beforeRowTag];
- [self addFormRow:formRow beforeRow:beforeRowForm];
- }
- -(void)addFormRow:(XLFormRowDescriptor *)formRow afterRow:(XLFormRowDescriptor *)afterRow
- {
- if (afterRow.sectionDescriptor){
- [afterRow.sectionDescriptor addFormRow:formRow afterRow:afterRow];
- }
- else{
- [[self.allSections lastObject] addFormRow:formRow afterRow:afterRow];
- }
- }
- -(void)addFormRow:(XLFormRowDescriptor *)formRow afterRowTag:(NSString *)afterRowTag
- {
- XLFormRowDescriptor * afterRowForm = [self formRowWithTag:afterRowTag];
- [self addFormRow:formRow afterRow:afterRowForm];
- }
- -(void)removeFormSectionAtIndex:(NSUInteger)index
- {
- if (self.formSections.count > index){
- XLFormSectionDescriptor *formSection = [self.formSections objectAtIndex:index];
- [self removeObjectFromFormSectionsAtIndex:index];
- NSUInteger allSectionIndex = [self.allSections indexOfObject:formSection];
- [self removeObjectFromAllSectionsAtIndex:allSectionIndex];
- }
- }
- -(void)removeFormSection:(XLFormSectionDescriptor *)formSection
- {
- NSUInteger index = NSNotFound;
- if ((index = [self.formSections indexOfObject:formSection]) != NSNotFound){
- [self removeFormSectionAtIndex:index];
- }
- else if ((index = [self.allSections indexOfObject:formSection]) != NSNotFound){
- [self removeObjectFromAllSectionsAtIndex:index];
- };
- }
- -(void)removeFormRow:(XLFormRowDescriptor *)formRow
- {
- for (XLFormSectionDescriptor * section in self.formSections){
- if ([section.formRows containsObject:formRow]){
- [section removeFormRow:formRow];
- }
- }
- }
- -(void)showFormSection:(XLFormSectionDescriptor*)formSection
- {
- NSUInteger formIndex = [self.formSections indexOfObject:formSection];
- if (formIndex != NSNotFound) {
- return;
- }
- NSUInteger index = [self.allSections indexOfObject:formSection];
- if (index != NSNotFound){
- while (formIndex == NSNotFound && index > 0) {
- XLFormSectionDescriptor* previous = [self.allSections objectAtIndex:--index];
- formIndex = [self.formSections indexOfObject:previous];
- }
- [self insertObject:formSection inFormSectionsAtIndex:(formIndex == NSNotFound ? 0 : ++formIndex)];
- }
- }
- -(void)hideFormSection:(XLFormSectionDescriptor*)formSection
- {
- NSUInteger index = [self.formSections indexOfObject:formSection];
- if (index != NSNotFound){
- [self removeObjectFromFormSectionsAtIndex:index];
- }
- }
- -(XLFormRowDescriptor *)formRowWithTag:(NSString *)tag
- {
- return self.allRowsByTag[tag];
- }
- -(XLFormRowDescriptor *)formRowWithHash:(NSUInteger)hash
- {
- for (XLFormSectionDescriptor * section in self.allSections){
- for (XLFormRowDescriptor * row in section.allRows) {
- if ([row hash] == hash){
- return row;
- }
- }
- }
- return nil;
- }
- -(void)removeFormRowWithTag:(NSString *)tag
- {
- XLFormRowDescriptor * formRow = [self formRowWithTag:tag];
- [self removeFormRow:formRow];
- }
- -(XLFormRowDescriptor *)formRowAtIndex:(NSIndexPath *)indexPath
- {
- if ((self.formSections.count > indexPath.section) && [[self.formSections objectAtIndex:indexPath.section] formRows].count > indexPath.row){
- return [[[self.formSections objectAtIndex:indexPath.section] formRows] objectAtIndex:indexPath.row];
- }
- return nil;
- }
- -(XLFormSectionDescriptor *)formSectionAtIndex:(NSUInteger)index
- {
- return [self objectInFormSectionsAtIndex:index];
- }
- -(NSIndexPath *)indexPathOfFormRow:(XLFormRowDescriptor *)formRow
- {
- XLFormSectionDescriptor * section = formRow.sectionDescriptor;
- if (section){
- NSUInteger sectionIndex = [self.formSections indexOfObject:section];
- if (sectionIndex != NSNotFound){
- NSUInteger rowIndex = [section.formRows indexOfObject:formRow];
- if (rowIndex != NSNotFound){
- return [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
- }
- }
- }
- return nil;
- }
- -(NSIndexPath *)globalIndexPathOfFormRow:(XLFormRowDescriptor *)formRow
- {
- XLFormSectionDescriptor * section = formRow.sectionDescriptor;
- if (section){
- NSUInteger sectionIndex = [self.allSections indexOfObject:section];
- if (sectionIndex != NSNotFound){
- NSUInteger rowIndex = [section.allRows indexOfObject:formRow];
- if (rowIndex != NSNotFound){
- return [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
- }
- }
- }
- return nil;
- }
- -(NSDictionary *)formValues
- {
- NSMutableDictionary * result = [NSMutableDictionary dictionary];
- for (XLFormSectionDescriptor * section in self.formSections) {
- if (section.multivaluedTag.length > 0){
- NSMutableArray * multiValuedValuesArray = [NSMutableArray new];
- for (XLFormRowDescriptor * row in section.formRows) {
- if (row.value){
- [multiValuedValuesArray addObject:row.value];
- }
- }
- [result setObject:multiValuedValuesArray forKey:section.multivaluedTag];
- }
- else{
- for (XLFormRowDescriptor * row in section.formRows) {
- if (row.tag.length > 0){
- [result setObject:(row.value ?: [NSNull null]) forKey:row.tag];
- }
- }
- }
- }
- return result;
- }
- -(NSDictionary *)httpParameters:(XLFormViewController *)formViewController
- {
- NSMutableDictionary * result = [NSMutableDictionary dictionary];
- for (XLFormSectionDescriptor * section in self.formSections) {
- if (section.multivaluedTag.length > 0){
- NSMutableArray * multiValuedValuesArray = [NSMutableArray new];
- for (XLFormRowDescriptor * row in section.formRows) {
- if ([row.value valueData]){
- [multiValuedValuesArray addObject:[row.value valueData]];
- }
- }
- [result setObject:multiValuedValuesArray forKey:section.multivaluedTag];
- }
- else{
- for (XLFormRowDescriptor * row in section.formRows) {
- NSString * httpParameterKey = nil;
- if ((httpParameterKey = [self httpParameterKeyForRow:row cell:[row cellForFormController:formViewController]])){
- id parameterValue = [row.value valueData] ?: [NSNull null];
- [result setObject:parameterValue forKey:httpParameterKey];
- }
- }
- }
- }
- return result;
- }
- -(NSString *)httpParameterKeyForRow:(XLFormRowDescriptor *)row cell:(UITableViewCell<XLFormDescriptorCell> *)descriptorCell
- {
- if ([descriptorCell respondsToSelector:@selector(formDescriptorHttpParameterName)]){
- return [descriptorCell formDescriptorHttpParameterName];
- }
- if (row.tag.length > 0){
- return row.tag;
- }
- return nil;
- }
- -(NSArray *)localValidationErrors:(XLFormViewController *)formViewController {
- NSMutableArray * result = [NSMutableArray array];
- for (XLFormSectionDescriptor * section in self.formSections) {
- for (XLFormRowDescriptor * row in section.formRows) {
- XLFormValidationStatus* status = [row doValidation];
- if (status != nil && (![status isValid])) {
- NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: status.msg,
- XLValidationStatusErrorKey: status };
- NSError * error = [[NSError alloc] initWithDomain:XLFormErrorDomain code:XLFormErrorCodeGen userInfo:userInfo];
- if (error){
- [result addObject:error];
- }
- }
- }
- }
-
- return result;
- }
- - (void)setFirstResponder:(XLFormViewController *)formViewController
- {
- for (XLFormSectionDescriptor * formSection in self.formSections) {
- for (XLFormRowDescriptor * row in formSection.formRows) {
- UITableViewCell<XLFormDescriptorCell> * cell = [row cellForFormController:formViewController];
- if ([cell formDescriptorCellCanBecomeFirstResponder]){
- if ([cell formDescriptorCellBecomeFirstResponder]){
- return;
- }
- }
- }
- }
- }
- #pragma mark - KVO
- -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
- {
- if (!self.delegate) return;
- if ([keyPath isEqualToString:@"formSections"]){
- if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeInsertion)]){
- NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey];
- XLFormSectionDescriptor * section = [self.formSections objectAtIndex:indexSet.firstIndex];
- [self.delegate formSectionHasBeenAdded:section atIndex:indexSet.firstIndex];
- }
- else if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeRemoval)]){
- NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey];
- XLFormSectionDescriptor * removedSection = [[change objectForKey:NSKeyValueChangeOldKey] objectAtIndex:0];
- [self.delegate formSectionHasBeenRemoved:removedSection atIndex:indexSet.firstIndex];
- }
- }
- }
- -(void)dealloc
- {
- @try {
- [self removeObserver:self forKeyPath:@"formSections"];
- }
- @catch (NSException * __unused exception) {}
- }
- #pragma mark - KVC
- -(NSUInteger)countOfFormSections
- {
- return self.formSections.count;
- }
- - (id)objectInFormSectionsAtIndex:(NSUInteger)index {
- return [self.formSections objectAtIndex:index];
- }
- - (NSArray *)formSectionsAtIndexes:(NSIndexSet *)indexes {
- return [self.formSections objectsAtIndexes:indexes];
- }
- - (void)insertObject:(XLFormSectionDescriptor *)formSection inFormSectionsAtIndex:(NSUInteger)index {
- [self.formSections insertObject:formSection atIndex:index];
- }
- - (void)removeObjectFromFormSectionsAtIndex:(NSUInteger)index {
- [self.formSections removeObjectAtIndex:index];
- }
- #pragma mark - allSections KVO
- -(NSUInteger)countOfAllSections
- {
- return self.allSections.count;
- }
- - (id)objectInAllSectionsAtIndex:(NSUInteger)index {
- return [self.allSections objectAtIndex:index];
- }
- - (NSArray *)allSectionsAtIndexes:(NSIndexSet *)indexes {
- return [self.allSections objectsAtIndexes:indexes];
- }
- - (void)removeObjectFromAllSectionsAtIndex:(NSUInteger)index {
- XLFormSectionDescriptor* section = [self.allSections objectAtIndex:index];
- [section.allRows enumerateObjectsUsingBlock:^(id obj, NSUInteger __unused idx, BOOL *stop) {
- XLFormRowDescriptor * row = (id)obj;
- [self removeObserversOfObject:row predicateType:XLPredicateTypeDisabled];
- [self removeObserversOfObject:row predicateType:XLPredicateTypeHidden];
- }];
- [self removeObserversOfObject:section predicateType:XLPredicateTypeHidden];
- [self.allSections removeObjectAtIndex:index];
- }
- - (void)insertObject:(XLFormSectionDescriptor *)section inAllSectionsAtIndex:(NSUInteger)index {
- section.formDescriptor = self;
- [self.allSections insertObject:section atIndex:index];
- section.hidden = section.hidden;
- [section.allRows enumerateObjectsUsingBlock:^(id obj, NSUInteger __unused idx, BOOL * __unused stop) {
- XLFormRowDescriptor * row = (id)obj;
- [self addRowToTagCollection:obj];
- row.hidden = row.hidden;
- row.disabled = row.disabled;
- }];
-
- }
- #pragma mark - EvaluateForm
- -(void)forceEvaluate
- {
- for (XLFormSectionDescriptor* section in self.allSections){
- for (XLFormRowDescriptor* row in section.allRows) {
- [self addRowToTagCollection:row];
- }
- }
- for (XLFormSectionDescriptor* section in self.allSections){
- for (XLFormRowDescriptor* row in section.allRows) {
- [row evaluateIsDisabled];
- [row evaluateIsHidden];
- }
- [section evaluateIsHidden];
- }
- }
- #pragma mark - private
- -(NSMutableArray *)formSections
- {
- return _formSections;
- }
- #pragma mark - Helpers
- -(XLFormRowDescriptor *)nextRowDescriptorForRow:(XLFormRowDescriptor *)row
- {
- NSUInteger indexOfRow = [row.sectionDescriptor.formRows indexOfObject:row];
- if (indexOfRow != NSNotFound){
- if (indexOfRow + 1 < row.sectionDescriptor.formRows.count){
- return [row.sectionDescriptor.formRows objectAtIndex:++indexOfRow];
- }
- else{
- NSUInteger sectionIndex = [self.formSections indexOfObject:row.sectionDescriptor];
- NSUInteger numberOfSections = [self.formSections count];
- if (sectionIndex != NSNotFound && sectionIndex < numberOfSections - 1){
- sectionIndex++;
- XLFormSectionDescriptor * sectionDescriptor;
- while ([[(sectionDescriptor = [row.sectionDescriptor.formDescriptor.formSections objectAtIndex:sectionIndex]) formRows] count] == 0 && sectionIndex < numberOfSections - 1){
- sectionIndex++;
- }
- return [sectionDescriptor.formRows firstObject];
- }
- }
- }
- return nil;
- }
- -(XLFormRowDescriptor *)previousRowDescriptorForRow:(XLFormRowDescriptor *)row
- {
- NSUInteger indexOfRow = [row.sectionDescriptor.formRows indexOfObject:row];
- if (indexOfRow != NSNotFound){
- if (indexOfRow > 0 ){
- return [row.sectionDescriptor.formRows objectAtIndex:--indexOfRow];
- }
- else{
- NSUInteger sectionIndex = [self.formSections indexOfObject:row.sectionDescriptor];
- if (sectionIndex != NSNotFound && sectionIndex > 0){
- sectionIndex--;
- XLFormSectionDescriptor * sectionDescriptor;
- while ([[(sectionDescriptor = [row.sectionDescriptor.formDescriptor.formSections objectAtIndex:sectionIndex]) formRows] count] == 0 && sectionIndex > 0 ){
- sectionIndex--;
- }
- return [sectionDescriptor.formRows lastObject];
- }
- }
- }
- return nil;
- }
- -(void)addRowToTagCollection:(XLFormRowDescriptor*) rowDescriptor
- {
- if (rowDescriptor.tag) {
- self.allRowsByTag[rowDescriptor.tag] = rowDescriptor;
- }
- }
- -(void)removeRowFromTagCollection:(XLFormRowDescriptor *)rowDescriptor
- {
- if (rowDescriptor.tag){
- [self.allRowsByTag removeObjectForKey:rowDescriptor.tag];
- }
- }
- -(void)addObserversOfObject:(id)sectionOrRow predicateType:(XLPredicateType)predicateType
- {
- NSPredicate* predicate;
- id descriptor;
- switch(predicateType){
- case XLPredicateTypeHidden:
- if ([sectionOrRow isKindOfClass:([XLFormRowDescriptor class])]) {
- descriptor = ((XLFormRowDescriptor*)sectionOrRow).tag;
- predicate = ((XLFormRowDescriptor*)sectionOrRow).hidden;
- }
- else if ([sectionOrRow isKindOfClass:([XLFormSectionDescriptor class])]) {
- descriptor = sectionOrRow;
- predicate = ((XLFormSectionDescriptor*)sectionOrRow).hidden;
- }
- break;
- case XLPredicateTypeDisabled:
- if ([sectionOrRow isKindOfClass:([XLFormRowDescriptor class])]) {
- descriptor = ((XLFormRowDescriptor*)sectionOrRow).tag;
- predicate = ((XLFormRowDescriptor*)sectionOrRow).disabled;
- }
- else return;
-
- break;
- }
- NSMutableArray* tags = [predicate getPredicateVars];
- for (NSString* tag in tags) {
- NSString* auxTag = [tag formKeyForPredicateType:predicateType];
- if (!self.rowObservers[auxTag]){
- self.rowObservers[auxTag] = [NSMutableArray array];
- }
- if (![self.rowObservers[auxTag] containsObject:descriptor])
- [self.rowObservers[auxTag] addObject:descriptor];
- }
-
- }
- -(void)removeObserversOfObject:(id)sectionOrRow predicateType:(XLPredicateType)predicateType
- {
- NSPredicate* predicate;
- id descriptor;
- switch(predicateType){
- case XLPredicateTypeHidden:
- if ([sectionOrRow isKindOfClass:([XLFormRowDescriptor class])]) {
- descriptor = ((XLFormRowDescriptor*)sectionOrRow).tag;
- predicate = ((XLFormRowDescriptor*)sectionOrRow).hidden;
- }
- else if ([sectionOrRow isKindOfClass:([XLFormSectionDescriptor class])]) {
- descriptor = sectionOrRow;
- predicate = ((XLFormSectionDescriptor*)sectionOrRow).hidden;
- }
- break;
- case XLPredicateTypeDisabled:
- if ([sectionOrRow isKindOfClass:([XLFormRowDescriptor class])]) {
- descriptor = ((XLFormRowDescriptor*)sectionOrRow).tag;
- predicate = ((XLFormRowDescriptor*)sectionOrRow).disabled;
- }
- break;
- }
- if (descriptor && [predicate isKindOfClass:[NSPredicate class] ]) {
- NSMutableArray* tags = [predicate getPredicateVars];
- for (NSString* tag in tags) {
- NSString* auxTag = [tag formKeyForPredicateType:predicateType];
- if (self.rowObservers[auxTag]){
- [self.rowObservers[auxTag] removeObject:descriptor];
- }
- }
- }
- }
- @end
|