// Copyright 1997-2000 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OHHTMLDisplayProcessor.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OWF/OWF.h>

#import "OHForm.h"
#import "OHFormActionButton.h"
#import "OHFormFile.h"
#import "OHFormHidden.h"
#import "OHFormImage.h"
#import "OHFormInputButton.h"
#import "OHFormSelect.h"
#import "OHFormSelectMultiple.h"
#import "OHFormSelectOption.h"
#import "OHFormText.h"
#import "OHFormTextArea.h"
#import "OHHTMLDocument.h"
#import "OHHTMLPageView.h"
#import "OWSGMLDTD-OHHTMLDTD.h"
#import "OHTextBuilder.h"
#import "OWScriptContext.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Forms.subproj/OHHTMLDisplayProcessor-Forms.m,v 1.15 2000/01/20 00:27:53 kc Exp $")

@interface OHHTMLDisplayProcessor (FormInputTags)
- (void)processTextInputTag:(OWSGMLTag *)tag;
@end

@interface OHHTMLDisplayProcessor (FormsPrivate)
+ (OWSGMLMethods *)formInputMethods;
@end

@implementation OHHTMLDisplayProcessor (Forms)

#define CHECK_ELEMENT_HANDLER(element, index, eventtype) \
    if ((handler = sgmlTagValueForAttributeAtIndex(tag, index))) \
        [[self hintedScriptContext] setHandler:eventtype onObject:element parented:currentForm toString:handler];

static OWSGMLTagType *formTagType;
static OWSGMLTagType *inputTagType;
static OWSGMLTagType *isindexTagType;
static OWSGMLTagType *optionTagType;
static OWSGMLTagType *selectTagType;
static OWSGMLTagType *textareaTagType;

static unsigned int formActionAttributeIndex;
static unsigned int formEnctypeAttributeIndex;
static unsigned int formMethodAttributeIndex;
static unsigned int formNameAttributeIndex;
static unsigned int formTargetAttributeIndex;
static unsigned int formOnResetAttributeIndex;
static unsigned int formOnSubmitAttributeIndex;
static unsigned int inputAcceptAttributeIndex;
static unsigned int inputAlignAttributeIndex;
static unsigned int inputHSpaceAttributeIndex;
static unsigned int inputVSpaceAttributeIndex;
static unsigned int inputBgcolorAttributeIndex;
static unsigned int inputBorderAttributeIndex;
static unsigned int inputCheckedAttributeIndex;
static unsigned int inputDisabledAttributeIndex;
static unsigned int inputMaxLengthAttributeIndex;
static unsigned int inputNameAttributeIndex;
static unsigned int inputSizeAttributeIndex;
static unsigned int inputSrcAttributeIndex;
static unsigned int inputWidthAttributeIndex;
static unsigned int inputHeightAttributeIndex;
static unsigned int inputTypeAttributeIndex;
static unsigned int inputValueAttributeIndex;
static unsigned int inputOnFocusAttributeIndex;
static unsigned int inputOnClickAttributeIndex;
static unsigned int inputOnChangeAttributeIndex;
static unsigned int inputOnBlurAttributeIndex;
static unsigned int isindexActionAttributeIndex;
static unsigned int isindexPromptAttributeIndex;
static unsigned int optionValueAttributeIndex;
static unsigned int optionSelectedAttributeIndex;
static unsigned int selectAlignAttributeIndex;
static unsigned int selectHSpaceAttributeIndex;
static unsigned int selectMultipleAttributeIndex;
static unsigned int selectNameAttributeIndex;
static unsigned int selectSizeAttributeIndex;
static unsigned int selectVSpaceAttributeIndex;
static unsigned int selectOnFocusAttributeIndex;
static unsigned int selectOnBlurAttributeIndex;
static unsigned int selectOnChangeAttributeIndex;
static unsigned int textareaAlignAttributeIndex;
static unsigned int textareaColsAttributeIndex;
static unsigned int textareaNameAttributeIndex;
static unsigned int textareaRowsAttributeIndex;
static unsigned int textareaOnFocusAttributeIndex;
static unsigned int textareaOnChangeAttributeIndex;
static unsigned int textareaOnBlurAttributeIndex;

+ (void)didLoad;
{
    OWSGMLMethods *methods;
    OWSGMLDTD *dtd;

    dtd = [self dtd];

    formTagType = [dtd tagTypeNamed:@"form"];
    inputTagType = [dtd tagTypeNamed:@"input"];
    isindexTagType = [dtd tagTypeNamed:@"isindex"];
    optionTagType = [dtd tagTypeNamed:@"option"];
    selectTagType = [dtd tagTypeNamed:@"select"];
    textareaTagType = [dtd tagTypeNamed:@"textarea"];
    [textareaTagType setContentIsNotValidSGML:YES];

    formMethodAttributeIndex = [formTagType addAttributeNamed:@"method"];
    formNameAttributeIndex = [formTagType addAttributeNamed:@"name"];
    formActionAttributeIndex = [formTagType addAttributeNamed:@"action"];
    formEnctypeAttributeIndex = [formTagType addAttributeNamed:@"enctype"];
    formTargetAttributeIndex = [formTagType addAttributeNamed:@"target"];
    formOnResetAttributeIndex = [formTagType addAttributeNamed:@"onreset"];
    formOnSubmitAttributeIndex = [formTagType addAttributeNamed:@"onsubmit"];

    isindexPromptAttributeIndex = [isindexTagType addAttributeNamed:@"prompt"];
    isindexActionAttributeIndex = [isindexTagType addAttributeNamed:@"action"];

    selectNameAttributeIndex = [selectTagType addAttributeNamed:@"name"];
    selectSizeAttributeIndex = [selectTagType addAttributeNamed:@"size"];
    selectMultipleAttributeIndex = [selectTagType addAttributeNamed:@"multiple"];
    selectAlignAttributeIndex = [selectTagType addAttributeNamed:@"align"];
    selectHSpaceAttributeIndex = [selectTagType addAttributeNamed:@"hspace"];
    selectVSpaceAttributeIndex = [selectTagType addAttributeNamed:@"vspace"];
    selectOnFocusAttributeIndex = [selectTagType addAttributeNamed:@"onfocus"];
    selectOnBlurAttributeIndex = [selectTagType addAttributeNamed:@"onblur"];
    selectOnChangeAttributeIndex = [selectTagType addAttributeNamed:@"onchange"];

    optionValueAttributeIndex = [optionTagType addAttributeNamed:@"value"];
    optionSelectedAttributeIndex = [optionTagType addAttributeNamed:@"selected"];
    
    textareaNameAttributeIndex = [textareaTagType addAttributeNamed:@"name"];
    textareaRowsAttributeIndex = [textareaTagType addAttributeNamed:@"rows"];
    textareaColsAttributeIndex = [textareaTagType addAttributeNamed:@"cols"];
    textareaAlignAttributeIndex = [textareaTagType addAttributeNamed:@"align"];
    textareaOnFocusAttributeIndex = [textareaTagType addAttributeNamed:@"onfocus"];
    textareaOnChangeAttributeIndex = [textareaTagType addAttributeNamed:@"onchange"];
    textareaOnBlurAttributeIndex = [textareaTagType addAttributeNamed:@"onblur"];
    
    inputTypeAttributeIndex = [inputTagType addAttributeNamed:@"type"];
    inputNameAttributeIndex = [inputTagType addAttributeNamed:@"name"];
    inputValueAttributeIndex = [inputTagType addAttributeNamed:@"value"];
    inputCheckedAttributeIndex = [inputTagType addAttributeNamed:@"checked"];
    inputAlignAttributeIndex = [inputTagType addAttributeNamed:@"align"];
    inputHSpaceAttributeIndex = [inputTagType addAttributeNamed:@"hspace"];
    inputVSpaceAttributeIndex = [inputTagType addAttributeNamed:@"vspace"];
    inputBorderAttributeIndex = [inputTagType addAttributeNamed:@"border"];
    inputSrcAttributeIndex = [inputTagType addAttributeNamed:@"src"];
    inputWidthAttributeIndex = [inputTagType addAttributeNamed:@"width"];
    inputHeightAttributeIndex = [inputTagType addAttributeNamed:@"height"];
    inputMaxLengthAttributeIndex = [inputTagType addAttributeNamed:@"maxlength"];
    inputSizeAttributeIndex = [inputTagType addAttributeNamed:@"size"];
    inputAcceptAttributeIndex = [inputTagType addAttributeNamed:@"accept"];
    inputBgcolorAttributeIndex = [inputTagType addAttributeNamed:@"bgcolor"];
    inputDisabledAttributeIndex = [inputTagType addAttributeNamed:@"disabled"];
    inputOnFocusAttributeIndex = [inputTagType addAttributeNamed:@"onfocus"];
    inputOnClickAttributeIndex = [inputTagType addAttributeNamed:@"onclick"];
    inputOnChangeAttributeIndex = [inputTagType addAttributeNamed:@"onchange"];
    inputOnBlurAttributeIndex = [inputTagType addAttributeNamed:@"onblur"];

    methods = [self sgmlMethods];

    [methods registerMethod:@"Form" forTagName:@"form"];
    [methods registerMethod:@"EndForm" forEndTagName:@"form"];
    [methods registerMethod:@"Input" forTagName:@"input"];
    [methods registerMethod:@"IsIndex" forTagName:@"isindex"];
    [methods registerMethod:@"Option" forTagName:@"option"];
    [methods registerMethod:@"Select" forTagName:@"select"];
    [methods registerMethod:@"TextArea" forTagName:@"textarea"];
}

+ (OWSGMLMethods *)formInputMethods;
{
    static OWSGMLMethods *formInputMethods = nil;

    if (!formInputMethods)
	formInputMethods = [[OWSGMLMethods alloc] init];
    return formInputMethods;
}

- (void)processFormTag:(OWSGMLTag *)tag;
{
    OWAddress *address;
    NSString *method, *action, *name, *encodingType, *target;
    NSNumber *dominantStringEncoding;

    [self endForm];

    method = [sgmlTagValueForAttributeAtIndex(tag, formMethodAttributeIndex) uppercaseString];
    action = sgmlTagValueForAttributeAtIndex(tag, formActionAttributeIndex);
    if (!(encodingType = sgmlTagValueForAttributeAtIndex(tag, formEnctypeAttributeIndex)))
	encodingType = @"application/x-www-form-urlencoded";
    if (!(target = sgmlTagValueForAttributeAtIndex(tag, formTargetAttributeIndex)))
	target = [baseAddress target];
    name = sgmlTagValueForAttributeAtIndex(tag, formNameAttributeIndex);

    address = [[OWAddress alloc] initWithURL:[[baseAddress url] urlFromRelativeString:action] target:target methodString:method methodDictionary:nil effect:OWAddressEffectFollowInWindow forceAlwaysUnique:NO];
    
    currentForm = [[OHForm allocWithZone:[htmlDocument zone]] init];
    [currentForm setAddress:address];
    [currentForm setEncodingType:encodingType];
    [currentForm setName:name];
    dominantStringEncoding = [pipeline contextObjectForKey:@"dominantCharacterEncoding"];
    if (dominantStringEncoding && [dominantStringEncoding intValue] != 0)
        [currentForm setStringEncoding:[dominantStringEncoding intValue]];

    [address release];

    [textBuilder beginBlock];

    /* We need to set the onReset and onSubmit event handlers, but we can't talk to JavaScript about this form until it's connected to the document, which happens in -endForm. So we have to stash the handlers (if any) in instance variables. */
    currentFormSubmitHandler = [sgmlTagValueForAttributeAtIndex(tag, formOnSubmitAttributeIndex) retain];
    currentFormResetHandler = [sgmlTagValueForAttributeAtIndex(tag, formOnResetAttributeIndex) retain];
}

- (void)processEndFormTag:(OWSGMLTag *)tag;
{
    [self endForm];
}

- (void)endForm;
{
    if (!currentForm)
	return;

    [textBuilder endBlock];
    [htmlDocument addForm:currentForm];
    [currentForm setHTMLDocument:htmlDocument];

    /* Set the event handlers on this form, if any */
    if (currentFormSubmitHandler) {
    [[self hintedScriptContext] setHandler:OWSE_Submit onObject:currentForm
 toString:currentFormSubmitHandler];
        [currentFormSubmitHandler release];
        currentFormSubmitHandler = nil;
    }
   if (currentFormResetHandler) {
   [[self hintedScriptContext] setHandler:OWSE_Reset onObject:currentForm toString:currentFormResetHandler];
        [currentFormResetHandler release];
        currentFormResetHandler = nil;
    }

    [currentForm release];
    currentForm = nil;
}

- (void)processUnknownInputTag:(OWSGMLTag *)tag;
{
#ifdef DEBUG
    NSLog(@"HTML: Unknown INPUT type encountered: %@", tag);
#endif
    [self processTextInputTag:tag];
}

- (void)processInputTag:(OWSGMLTag *)tag;
{
    OFImplementationHolder *implementation;
    NSString *inputType;

    inputType = sgmlTagValueForAttributeAtIndex(tag, inputTypeAttributeIndex);
    if (!inputType)
	inputType = @"text";
    implementation = [[[isa formInputMethods] implementationForTagDictionary] objectForKey:[inputType lowercaseString]];
    if (implementation)
	[implementation executeOnObject:self withObject:tag];
    else
	[self processUnknownInputTag:tag];
}

- (void)processIsIndexTag:(OWSGMLTag *)tag;
{
    OWAddress *address;
    NSString *prompt;
    NSString *action;
    OHForm *oldForm;
    OHFormText *formText;

    prompt = sgmlTagValueForAttributeAtIndex(tag, isindexPromptAttributeIndex);
    if (!prompt)
	prompt = @"This is a searchable index. Enter search keywords: ";

    action = sgmlTagValueForAttributeAtIndex(tag, isindexActionAttributeIndex);
    address = [baseAddress addressForRelativeString:action];

    oldForm = currentForm;
    currentForm = [[OHForm allocWithZone:[htmlDocument zone]] init];
    [currentForm setAddress:address];
    [currentForm setEncodingType:@"application/x-www-form-urlencoded"];

    [textBuilder beginBlock];
    [textBuilder writeUnprocessedString:prompt];

    formText = [OHFormText formTextWithForm:currentForm name:@"ISINDEX" value:nil alignment:NSLeftTextAlignment disabled:NO invisibleInput:NO width:20 height:1 maximumLength:0];

    // Add element to form
    [currentForm addFormElement:formText];

    // Add to text
    [textBuilder writeFormCellObject:formText];

    [self endForm]; // Calls [textBuilder endBlock] for us.
    
    currentForm = oldForm;
}

- (void)processOptionTag:(OWSGMLTag *)tag;
{
#ifdef DEBUG
    NSLog(@"<OPTION> outside of <SELECT>");
#endif
}

#define CLOSE_OPTION(option, optionText) {\
    if (option) {\
	[option setText:[optionText stringByCollapsingWhitespaceAndRemovingSurroundingWhitespace]];\
	option = nil;\
	optionText = nil;\
    }\
}

- (void)processSelectTag:(OWSGMLTag *)tag;
{
    OHFormSelect *select;
    NSString *name, *size, *handler;
    BOOL multiple;
    id <OWSGMLToken> sgmlToken;
    OHFormSelectOption *option;
    NSMutableString *optionText;
    NSMutableArray *options;

    name = sgmlTagValueForAttributeAtIndex(tag, selectNameAttributeIndex);
    size = sgmlTagValueForAttributeAtIndex(tag, selectSizeAttributeIndex);
    multiple = sgmlTagAttributePresentAtIndex(tag, selectMultipleAttributeIndex);

    options = [[NSMutableArray alloc] init];
    option = nil;
    optionText = nil;
    while ((sgmlToken = [objectCursor readObject])) {
        OWSGMLTag *newTag;
        OWSGMLTagType *newTagType;

        switch ([sgmlToken tokenType]) {
            case OWSGMLTokenTypeCData:
                [optionText appendString:[sgmlToken string]];
                break;
            case OWSGMLTokenTypeStartTag:
                newTag = (OWSGMLTag *)sgmlToken;
                newTagType = [newTag tagType];
                if (newTagType == optionTagType) {
                    CLOSE_OPTION(option, optionText);

                    option = [[OHFormSelectOption alloc] init];
                    if (sgmlTagAttributePresentAtIndex(newTag, optionSelectedAttributeIndex))
                        [option setInitiallySelected:YES];
                    [option setTag:sgmlTagValueForAttributeAtIndex(newTag, optionValueAttributeIndex)];
                    [options addObject:option];
                    [option release];
                    optionText = [NSMutableString stringWithCapacity:16];
                } else if (newTagType == selectTagType) {
                    CLOSE_OPTION(option, optionText);
                    [objectCursor ungetObject:newTag];
                    goto exit;
                }
                break;
            case OWSGMLTokenTypeEndTag:
                newTag = (OWSGMLTag *)sgmlToken;
                newTagType = [newTag tagType];
                if (newTagType == optionTagType) {
                    CLOSE_OPTION(option, optionText);
                } else if (newTagType == selectTagType) {
                    CLOSE_OPTION(option, optionText);
                    goto exit;
                }
                break;
            default:
                break;
        }
    }

exit:
    select = [OHFormSelect selectWithForm:currentForm name:name options:options visibleRows:[size intValue] allowMultiple:multiple];
    [options release];

    // Alignment
    [select setAlignmentString:sgmlTagValueForAttributeAtIndex(tag, selectAlignAttributeIndex)];

    // Inset size
    [select setInsetSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, selectHSpaceAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, selectVSpaceAttributeIndex) defaultWidth:([select isMarginalCell] ? 1.0 : 0.0)];

    // Add element to form
    [currentForm addFormElement:select];

    // Add to text
    [textBuilder writeFormCellObject:select];

    // Event handlers
    CHECK_ELEMENT_HANDLER(select, selectOnBlurAttributeIndex, OWSE_Blur);
    CHECK_ELEMENT_HANDLER(select, selectOnChangeAttributeIndex, OWSE_Change);
    CHECK_ELEMENT_HANDLER(select, selectOnFocusAttributeIndex, OWSE_Focus);
}

- (void)processTextAreaTag:(OWSGMLTag *)tag;
{
    NSMutableString *textAreaContent = nil;
    id <OWSGMLToken> sgmlToken;
    NSString *inputName, *rows, *cols, *alignmentString, *handler;
    NSTextAlignment alignment;
    int width = 80, height = 4, maxLen = 0;
    OHFormTextArea *formTextArea;

    inputName = sgmlTagValueForAttributeAtIndex(tag, textareaNameAttributeIndex);
    rows = sgmlTagValueForAttributeAtIndex(tag, textareaRowsAttributeIndex);
    cols = sgmlTagValueForAttributeAtIndex(tag, textareaColsAttributeIndex);
    alignmentString = sgmlTagValueForAttributeAtIndex(tag, textareaAlignAttributeIndex);

    if (rows)
	height = [rows intValue];
    if (cols)
	width = [cols intValue];
    switch ([alignmentString firstCharacter]) {
        default:
        case 'L':
        case 'l':
            alignment = NSLeftTextAlignment;
            break;
        case 'C':
        case 'c':
            alignment = NSCenterTextAlignment;
            break;
        case 'R':
        case 'r':
            alignment = NSRightTextAlignment;
            break;
    }

    while ((sgmlToken = [objectCursor readObject])) {
	NSString *tokenString;
	OWSGMLTokenType tokenType;

	tokenType = [sgmlToken tokenType];
	if (tokenType != OWSGMLTokenTypeCData &&
	    [(OWSGMLTag *)sgmlToken tagType] == textareaTagType) {
	    if (tokenType != OWSGMLTokenTypeEndTag)
		[objectCursor ungetObject:sgmlToken];
	    break;
	}
	tokenString = [sgmlToken string];
	if (textAreaContent) {
	    [textAreaContent appendString:tokenString];
	    continue;
	}
	if ([tokenString hasPrefix:@"\r"])
	    tokenString = [tokenString substringFromIndex:1];
	if ([tokenString hasPrefix:@"\n"])
	    tokenString = [tokenString substringFromIndex:1];
	textAreaContent = [tokenString mutableCopy];
    }

    formTextArea = [[OHFormTextArea allocWithZone:[currentForm zone]] initWithForm:currentForm name:inputName value:textAreaContent alignment:alignment disabled:NO invisibleInput:NO width:width height:height maximumLength:maxLen];

    // Add element to form
    [currentForm addFormElement:formTextArea];

    // Add to text
    [textBuilder writeFormCellObject:formTextArea];

    // Event handlers
    CHECK_ELEMENT_HANDLER(formTextArea, textareaOnBlurAttributeIndex, OWSE_Blur);
    CHECK_ELEMENT_HANDLER(formTextArea, textareaOnChangeAttributeIndex, OWSE_Change);
    CHECK_ELEMENT_HANDLER(formTextArea, textareaOnFocusAttributeIndex, OWSE_Focus);

    [formTextArea release];
}

@end

@interface OHHTMLDisplayProcessor (FormInput)

- (void)processButtonInputTag:(OWSGMLTag *)tag;
- (void)processHiddenInputTag:(OWSGMLTag *)tag;
- (void)processImageInputTag:(OWSGMLTag *)tag;
- (void)processRadioInputTag:(OWSGMLTag *)tag;
- (void)processResetInputTag:(OWSGMLTag *)tag;
- (void)processFileInputTag:(OWSGMLTag *)tag;
- (void)processSubmitInputTag:(OWSGMLTag *)tag;
- (void)processTextInputTag:(OWSGMLTag *)tag;
- (void)processPasswordInputTag:(OWSGMLTag *)tag;

//

- (void)processButtonInputTag:(OWSGMLTag *)tag isRadio:(BOOL)isRadio;
- (void)processTextInputTag:(OWSGMLTag *)tag invisible:(BOOL)isInvisible;

@end

@implementation OHHTMLDisplayProcessor (FormInput)

+ (void)didLoad;
{
    OWSGMLMethods *methods;

    methods = [self formInputMethods];

    [methods registerMethod:@"ButtonInput" forTagName:@"checkbox"];
    [methods registerMethod:@"FileInput" forTagName:@"file"];
    [methods registerMethod:@"HiddenInput" forTagName:@"hidden"];
    [methods registerMethod:@"ImageInput" forTagName:@"image"];
    [methods registerMethod:@"PasswordInput" forTagName:@"password"];
    [methods registerMethod:@"RadioInput" forTagName:@"radio"];
    [methods registerMethod:@"ResetInput" forTagName:@"reset"];
    [methods registerMethod:@"SubmitInput" forTagName:@"submit"];
    [methods registerMethod:@"SubmitInput" forTagName:@"button"];
    [methods registerMethod:@"TextInput" forTagName:@"text"];
}

- (void)processButtonInputTag:(OWSGMLTag *)tag;
{
    [self processButtonInputTag:tag isRadio:NO];
}

- (void)processHiddenInputTag:(OWSGMLTag *)tag;
{
    NSString *name, *value;
    OHFormHidden *hiddenElement;

    name = sgmlTagValueForAttributeAtIndex(tag, inputNameAttributeIndex);
    value = sgmlTagValueForAttributeAtIndex(tag, inputValueAttributeIndex);

    hiddenElement = [[OHFormHidden allocWithZone:[currentForm zone]] initWithName:name value:value];
    [currentForm addFormElement:hiddenElement];
    [hiddenElement release];
}

- (void)processImageInputTag:(OWSGMLTag *)tag;
{
    OHFormImage *formImage;
    NSString *source, *border;
    OWAddress *sourceAddress;
    BOOL oldUnderlined;

    // Source address
    if ((source = sgmlTagValueForAttributeAtIndex(tag, inputSrcAttributeIndex)))
	sourceAddress = [baseAddress addressForRelativeString:source];
    else
        sourceAddress = nil;

    formImage = [[OHFormImage allocWithZone:[currentForm zone]] initWithAddress:sourceAddress referringAddress:baseAddress htmlDocument:htmlDocument];

    // Size
    [formImage setRequestedSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, inputWidthAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, inputHeightAttributeIndex)];

    // Alignment
    [formImage setAlignmentString:sgmlTagValueForAttributeAtIndex(tag, inputAlignAttributeIndex)];

    // Inset size
    [formImage setInsetSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, inputHSpaceAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, inputVSpaceAttributeIndex) defaultWidth:([formImage isMarginalCell] ? 3.0 : 0.0)];

    // Form setup
    [formImage setForm:currentForm];
    [formImage setName:sgmlTagValueForAttributeAtIndex(tag, inputNameAttributeIndex)];

    // Border width
    if ((border = sgmlTagValueForAttributeAtIndex(tag, inputBorderAttributeIndex)))
	[formImage setBorderWidth:[border intValue]];

    // Add element to form
    [currentForm addFormElement:formImage];

    // Add to text (underlines off)
    oldUnderlined = [textBuilder setUnderlined:NO];
    [textBuilder writeNonWhitespaceCellObject:formImage];
    [textBuilder setUnderlined:oldUnderlined];

    // Add to the document's list of images
    [htmlDocument addImage:formImage];

    // Release the image
    [formImage release];
}

- (void)processRadioInputTag:(OWSGMLTag *)tag;
{
    [self processButtonInputTag:tag isRadio:YES];
}

- (void)processResetInputTag:(OWSGMLTag *)tag;
{
    OHFormActionButton *formButton;
    NSString *value, *handler;

    value = sgmlTagValueForAttributeAtIndex(tag, inputValueAttributeIndex);
    formButton = [[OHFormActionButton allocWithZone:[currentForm zone]] initWithForm:currentForm name:sgmlTagValueForAttributeAtIndex(tag, inputNameAttributeIndex) value:nil action:@selector(resetForm) title:value ? value : @"Reset"];

    // Alignment
    [formButton setAlignmentString:sgmlTagValueForAttributeAtIndex(tag, inputAlignAttributeIndex)];

    // Inset size
    [formButton setInsetSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, inputHSpaceAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, inputVSpaceAttributeIndex) defaultWidth:([formButton isMarginalCell] ? 1.0 : 0.0)];

    // Add element to form
    [currentForm addFormElement:formButton];

    // Add to text
    [textBuilder writeFormCellObject:formButton];
    
    // Event handlers
    CHECK_ELEMENT_HANDLER(formButton, inputOnBlurAttributeIndex, OWSE_Blur);
    CHECK_ELEMENT_HANDLER(formButton, inputOnClickAttributeIndex, OWSE_Click);
    CHECK_ELEMENT_HANDLER(formButton, inputOnFocusAttributeIndex, OWSE_Focus);
    
    [formButton release];
}

- (void)processFileInputTag:(OWSGMLTag *)tag;
{
    NSString *inputName, *inputValue, *maxLength, *size, *handler;
    NSString *acceptTypes;
    int width = 20, height = 1, maxLen = 0;
    NSTextAlignment alignment;
    NSString *alignmentString;
    OHFormFile *formFile;
    OHFormActionButton *formButton;

    inputName = sgmlTagValueForAttributeAtIndex(tag, inputNameAttributeIndex);
    inputValue = sgmlTagValueForAttributeAtIndex(tag, inputValueAttributeIndex);
    maxLength = sgmlTagValueForAttributeAtIndex(tag, inputMaxLengthAttributeIndex);
    size = sgmlTagValueForAttributeAtIndex(tag, inputSizeAttributeIndex);
    alignmentString = sgmlTagValueForAttributeAtIndex(tag, inputAlignAttributeIndex);
    acceptTypes = sgmlTagValueForAttributeAtIndex(tag, inputAcceptAttributeIndex);

    if (maxLength)
	maxLen = [maxLength intValue];
    if (size)
	sscanf([size cString], "%d,%d", &width, &height);
    switch ([alignmentString firstCharacter]) {
        default:
        case 'L':
        case 'l':
            alignment = NSLeftTextAlignment;
            break;
        case 'C':
        case 'c':
            alignment = NSCenterTextAlignment;
            break;
        case 'R':
        case 'r':
            alignment = NSRightTextAlignment;
            break;
    }

    formFile = [[OHFormFile allocWithZone:[currentForm zone]] initWithForm:currentForm name:inputName types:acceptTypes value:inputValue alignment:alignment width:width height:height maximumLength:maxLen];
    formButton = [[OHFormActionButton allocWithZone:[currentForm zone]] initWithForm:currentForm name:inputName value:@"Find..." action:@selector(findFile) title:@"Find..."];
    [formButton setTarget:formFile];

    // Add elements to form
    [currentForm addFormElement:formFile];
    [currentForm addFormElement:formButton];

    // Add to text
    [textBuilder writeFormCellObject:formFile];
    [textBuilder writeProcessedCharacter:NO_BREAK_SPACE];
    [textBuilder writeFormCellObject:formButton];

    // Event handlers
    CHECK_ELEMENT_HANDLER(formFile, inputOnBlurAttributeIndex, OWSE_Blur);
    CHECK_ELEMENT_HANDLER(formFile, inputOnChangeAttributeIndex, OWSE_Change);
    CHECK_ELEMENT_HANDLER(formFile, inputOnFocusAttributeIndex, OWSE_Focus);

    [formFile release];
    [formButton release];
}

- (void)processSubmitInputTag:(OWSGMLTag *)tag;
{
    OHFormButton *formButton;
    NSString *value, *handler;

    value = sgmlTagValueForAttributeAtIndex(tag, inputValueAttributeIndex);
    if (!value)
	value = @"Submit"; // IE 4.0 for Mac has just 'Submit'
    formButton = [[OHFormActionButton allocWithZone:[currentForm zone]] initWithForm:currentForm name:sgmlTagValueForAttributeAtIndex(tag, inputNameAttributeIndex) value:value action:@selector(submitForm) title:value];

    // Alignment
    [formButton setAlignmentString:sgmlTagValueForAttributeAtIndex(tag, inputAlignAttributeIndex)];

    // Inset size
    [formButton setInsetSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, inputHSpaceAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, inputVSpaceAttributeIndex) defaultWidth:([formButton isMarginalCell] ? 1.0 : 0.0)];

    // Add element to form
    [currentForm addFormElement:formButton];

    // Add to text
    [textBuilder writeFormCellObject:formButton];

    // Event handlers
    CHECK_ELEMENT_HANDLER(formButton, inputOnBlurAttributeIndex, OWSE_Blur);
    CHECK_ELEMENT_HANDLER(formButton, inputOnClickAttributeIndex, OWSE_Click);
    CHECK_ELEMENT_HANDLER(formButton, inputOnFocusAttributeIndex, OWSE_Focus);
    
    [formButton release];
}

- (void)processTextInputTag:(OWSGMLTag *)tag;
{
    [self processTextInputTag:tag invisible:NO];
}

- (void)processPasswordInputTag:(OWSGMLTag *)tag;
{
    [self processTextInputTag:tag invisible:YES];
}

//

- (void)processTextInputTag:(OWSGMLTag *)tag invisible:(BOOL)isInvisible;
{
    NSString *inputName, *inputValue, *maxLength, *size, *handler;
    int width = 20, height = 1, maxLen = 0;
    NSTextAlignment alignment;
    NSString *alignmentString;
    OHFormText *formText;

    inputName = sgmlTagValueForAttributeAtIndex(tag, inputNameAttributeIndex);
    inputValue = sgmlTagValueForAttributeAtIndex(tag, inputValueAttributeIndex);
    maxLength = sgmlTagValueForAttributeAtIndex(tag, inputMaxLengthAttributeIndex);
    size = sgmlTagValueForAttributeAtIndex(tag, inputSizeAttributeIndex);
    alignmentString = sgmlTagValueForAttributeAtIndex(tag, inputAlignAttributeIndex);

    if (maxLength)
	maxLen = [maxLength intValue];
    if (size)
	sscanf([size cString], "%d,%d", &width, &height);
    switch ([alignmentString firstCharacter]) {
        default:
        case 'L':
        case 'l':
            alignment = NSLeftTextAlignment;
            break;
        case 'C':
        case 'c':
            alignment = NSCenterTextAlignment;
            break;
        case 'R':
        case 'r':
            alignment = NSRightTextAlignment;
            break;
    }

    formText = [OHFormText formTextWithForm:currentForm name:inputName value:inputValue alignment:alignment disabled:sgmlTagAttributePresentAtIndex(tag, inputDisabledAttributeIndex) invisibleInput:isInvisible width:width height:height maximumLength:maxLen];

    // Add element to form
    [currentForm addFormElement:formText];

    // Add to text
    [textBuilder writeFormCellObject:formText];

    // Event handlers
    CHECK_ELEMENT_HANDLER(formText, inputOnBlurAttributeIndex, OWSE_Blur);
    CHECK_ELEMENT_HANDLER(formText, inputOnChangeAttributeIndex, OWSE_Change);
    CHECK_ELEMENT_HANDLER(formText, inputOnFocusAttributeIndex, OWSE_Focus);
}

- (void)processButtonInputTag:(OWSGMLTag *)tag isRadio:(BOOL)isRadio;
{
    OHFormInputButton *formButton;
    NSString *handler;

    formButton = [[OHFormInputButton allocWithZone:[currentForm zone]] initWithForm:currentForm name:sgmlTagValueForAttributeAtIndex(tag, inputNameAttributeIndex) value:sgmlTagValueForAttributeAtIndex(tag, inputValueAttributeIndex) radio:isRadio checked:sgmlTagAttributePresentAtIndex(tag, inputCheckedAttributeIndex)];

    // Alignment
    [formButton setAlignmentString:sgmlTagValueForAttributeAtIndex(tag, inputAlignAttributeIndex)];

    // Inset size
    [formButton setInsetSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, inputHSpaceAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, inputVSpaceAttributeIndex) defaultWidth:([formButton isMarginalCell] ? 1.0 : 0.0)];

    // Add element to form
    [currentForm addFormElement:formButton];

    // Add to text
    [textBuilder writeFormCellObject:formButton];

    // Event handlers
    CHECK_ELEMENT_HANDLER(formButton, inputOnBlurAttributeIndex, OWSE_Blur);
    CHECK_ELEMENT_HANDLER(formButton, inputOnClickAttributeIndex, OWSE_Click);
    CHECK_ELEMENT_HANDLER(formButton, inputOnFocusAttributeIndex, OWSE_Focus);

    [formButton release];
}

@end
