package edu.vt.marian.Document;

import java.io.*;
import java.net.*;
import java.util.*;

import edu.vt.marian.common.*;


/**
    A USMARC record interpreted as a MARIAN Document.

*/

public class MarcDocument extends MarcRecord implements Document
{
    /** ID of this MARC object in MARIAN.  Set by extractCharacteristics().
    */
    private int classID;
    private FullID id;

    /** Is this a conference proceedings?  Set by extractCharacteristics().
    */
    private boolean isConference = false;

    /** Language (USMARC 3-letter code).  Set by extractCharacteristics().
    */
    private String language;

    /** Publication year (more or less:  see comments in extractCharacteristics()).
    */
    private int pubYear;


    /** Designators for sections in the "long" presentation.
    */
    private final static int authorSection = 0;
    private final static int titleSection = 1;
    private final static int noteSection = 2;
    private final static int subjectSection = 3;

    /**
       Set instance variables to default values.
       <P>
       <B>NOTE:</B>  Called in constructors and in setFrom*().  Note call to
             superclass to initialize MarcRecord variables.
    */
    protected void init()
    {
        super.init();

     // Give local variables invalid but non-null values.
        id = new FullID(debug);
        isConference = false;
        language = new String();
        pubYear = 0;
    }


   /** those variable field ids are used to get long description of author
    */
    private final static int[] author_ids = {100, 110, 111, 400, 410, 411,
        700, 710, 711, 800, 810, 811};

    /** those variable field ids are used to get long description of title
    */
    private final static int[] title_ids = {130, 240, 241, 242, 243, 245,
        246, 440, 730, 740, 830, 840};

    /** those variable field ids are used to get long description of subject
    */
    private final static int[] subject_ids = {600, 610, 611, 630, 650, 651,
        653, 690, 691, 693};

    /** those variable field ids are used to get long description of notes
    */
    private final static int[] note_ids = {500, 501, 502, 504, 508, 511,
        515, 533, 550, 580, 590, 705, 715};

    /** those variable field ids are used to get long description of 
        person name
    */
    private final static int[] person_name_ids = {100, 400, 600, 700, 800};
        
    /** those variable field ids are used to get long description of 
        conference name
    */
    private final static int[] conference_name_ids = {111, 411, 611, 
        711, 811};

    /** those variable field ids are used to get long description of 
        corprate name
    */
    private final static int[] corp_name_ids = {110, 410, 610, 710, 810};

    
    protected boolean hasSectionType(MarcVarField mvf, int section)
    {
        int[] sectionIDs;
        switch (section)
        {
         case authorSection:
            sectionIDs = author_ids;
            break;
         case titleSection:
            sectionIDs = title_ids;
            break;
         case noteSection:
            sectionIDs = note_ids;
            break;
        case subjectSection:
            sectionIDs = subject_ids;
            break;

         default:
            return(false);
        }
        
        for (int i=0; i<sectionIDs.length; i++)
        {
            if (mvf.getID() == sectionIDs[i])
                return true;
        }
        return(false);
    }
    

    /**
       Create the correct sort of field object.
       <P>
       <B>NOTE:</B>  This may not seem very sensible at this level, but in
            the MarcDocument subclass, where variable fields with different
            semantics get created as different subclasses of MarcVarField,
            it will become indispensable.
    */
    protected MarcVarField newVarField(int id)
    {
        switch( id )
        {
         case 130:  case 240:  case 241:  case 242:    // Title fields.
         case 243:  case 245:  case 246:  case 440:
         case 730:  case 740:  case 830:  case 840:
            return( new MarcTitleField(id, xmlMap, debug) );

         case 600:  case 610:  case 611:  case 630:    // Subject fields.
         case 650:  case 651:  case 653:  case 690:
         case 691:  case 693:
            return( new MarcSubjectField(id, xmlMap, debug) );

         /* case 500:  case 501:  case 502:  case 504:    // Note fields.
         case 508:  case 511:  case 515:  case 533:
         case 550:  case 580:  case 590:  case 705:  case 715:
            return( new MarcNoteField(id, xmlMap, debug) ); */

         case 100:  case 400:  case 700:    // Personal name fields.
         case 800:
            return( new MarcPersNameField(id, xmlMap, debug) );

         case 110:  case 410:  case 710:    // Corporate name
         case 810:                    //  fields.
            return( new MarcCorpNameField(id, xmlMap, debug) );

         case 111:  case 411:  case 711:    // Conference name
         case 811:                    //  fields.
            return( new MarcConfNameField(id, xmlMap, debug) );

         case 260:                    // Imprint field.
            return( new MarcImprintField(id, xmlMap, debug) );

         case 856:                    // Electronic access field.
            return( new Marc856Field(id, xmlMap, debug) );

         default:
            return( new PresentableMarcVarField(id, xmlMap, debug) );
        }
    }


    /**
        extract the characteristics of
            this object from it's MarcRecord, characteristics includes
            level, encodingLevel,...
        @return    OK -- the characteristics have been extracted successfully
        <BR>    other --
    */      
    private int extractCharacteristics()
    {
        // extract isConference, language and publish year
        MarcFixField field8 = getFixFieldById(8);
        if ((field8 == null) || (field8.getData() == null) || 
            (field8.getData().length() != 40))
        {
            // invalid field 8
            debug.dumpTrace("MarcDocument.extractCharacteristics(): field 8 or its data is invalid.");
            return( ReturnCodes.PARSE_ERROR );
        }

        // field 8 is valid, get isConference
        String data = field8.getData();
        isConference = (data.charAt(29) == '1');
        
        // get language
        language = data.substring(35, 38);

        // get publish year
        try
        {
            switch (data.charAt(6))
            {
             case 'd':
             case 's':
                pubYear = Integer.parseInt(data.substring(7, 11));
                break;
            
             case 'r':
                pubYear = Integer.parseInt(data.substring(11, 15));
                if (pubYear <= 0)
                {
                    pubYear = Integer.parseInt(data.substring(7, 11));
                }
                break;
            
             case 'c':
             case 'p':
             case 'u':
                pubYear = Integer.parseInt(data.substring(11, 15));
                break;
            
             case 'i':
             case 'k':
             case 'm':
             case 'q':
                pubYear = ( Integer.parseInt(data.substring(7, 11)) +
                            Integer.parseInt(data.substring(11, 15)) ) / 2;
                break;
            
             case 'b':
             case 'n':    
             default:
                pubYear = 0;
            } // end -- switch
        } catch (Exception e)
            {
                debug.dumpTrace("MarcDocument.extractCharacteristics(): parse year error");
                pubYear = 0;
//              return PARSE_YEAR_ERROR;
            }
        if (pubYear < 0)
        {
            pubYear = 0;
        }

        // get id
        Vector mvfs = getVarFieldsById(35);
        if (mvfs.size() != 1)
        {
            // variable field 35 should be uniq
            debug.dumpTrace("MarcDocument.extractCharateristics():  variable field 35 is not uniq");
            return( ReturnCodes.PARSE_ERROR );
        }
        PresentableMarcVarField mvf = (PresentableMarcVarField) mvfs.elementAt(0);
        Vector msfs = mvf.getSubfieldsByLabel('a');
        if (msfs.size() != 1)
        {
            // subfield 'a' should be uniq here too
            debug.dumpTrace("MarcDocument.extractCharacteristics(): subfield 'a' in var field 35 is not uniq");
            return( ReturnCodes.PARSE_ERROR );
        }
        String rawId = ((MarcSubField) msfs.elementAt(0)).getData();
        if ((rawId == null) || (rawId.length() < 10))
        {
            // invalid rawId
            debug.dumpTrace("MarcDocument.extractCharacteristics(): rawId is not valid");
            return( ReturnCodes.PARSE_ERROR );
        }
        int instID = 0;
        try
        {
            instID = Integer.parseInt(rawId.substring(0, 4) + 
                         rawId.substring(5, 10));
        }
        catch (Exception e)
        {
            debug.dumpTrace("MarcDocument.extractCharacteristics(): error when parsing '" +
                            rawId + "' for instance ID.");
            return( ReturnCodes.PARSE_ERROR );
        }
        if (instID <= 0)
        {
            debug.dumpTrace("MarcDocument.extractCharacteristics(): instance ID '" +
                            rawId + "' is invalid.");
            return( ReturnCodes.PARSE_ERROR );
        }
        id = new FullID(classID, instID, debug);

        // everything is fine here
        return( ReturnCodes.OK );
    }


    protected int presentCallNumber(int markupType, BufferedWriter out)
         throws IOException
    {
        // Look for an 099 field, or if none an 090 field, or if none an 050.
        // Search backwards through var field vector to achieve this.
        PresentableMarcVarField mvf;
        for (int i = varFields.size()-1; i>=0; i--)
        {
            mvf = (PresentableMarcVarField) varFields.elementAt(i);
            if ( (mvf.getID() == 99) || (mvf.getID() == 90) ||
                 (mvf.getID() == 50) )
            {
                // we found the corresponding variable field
                return( mvf.presentLong(markupType, out) );
            }
        }

        // we couldn't find any corresponding variable field
        return( ReturnCodes.NOT_FOUND );
    }


    /**
        format the publishing information of this MarcRecord
        @param    markupType    specifies which markup type the result should be
                only ASCII is supported now
        @return    the formated publish information of this object as a string
    */      
    protected int presentPublishingInfoSection(int markupType, BufferedWriter out)
         throws IOException
    {
        int Err;

        String lineBreak;
        String indent;
        switch ( markupType )
        {
         case DigInfObj.HTML:
            lineBreak = new String("<BR>" + System.getProperty("line.separator"));
            indent = "&nbsp;&nbsp;&nbsp;";
            break;

         default:
            debug.dumpTrace("MarcDocument.presentLong(): unsupported markup type:  using ASCII.");
            //  FALL THROUGH.

         case DigInfObj.ASCII:
         case DigInfObj.ANSEL:
            lineBreak = System.getProperty("line.separator");
            indent = "   ";
            break;
        }

        PresentableMarcVarField mvf;
        PresentableMarcVarField editionField = null;
        Enumeration varfld = varFields.elements();
        try { while ( true )
        {
            mvf = (PresentableMarcVarField) varfld.nextElement();
            switch (mvf.getID())
            {
             case 250:    // Save for the end.
                editionField = mvf;
                break;
             case 260:
                if (markupType == HTML)
                    out.write("<B>Imprint:  </B>");
                else
                    out.write("Imprint:  ");
                if ( (Err = mvf.presentLong(markupType, out))
                          != ReturnCodes.OK )
                    return(Err);
                out.write(lineBreak);
                break;
             case 300:
                out.write(indent);
                out.write("Description:  " );
                if ( (Err = mvf.presentLong(markupType, out))
                          != ReturnCodes.OK )
                    return(Err);
                out.write(lineBreak);
                break;
             case 310:
                out.write(indent);
                out.write("Frequency:  ");
                if ( (Err = mvf.presentLong(markupType, out))
                          != ReturnCodes.OK )
                    return(Err);
                out.write(lineBreak);
                break;
             case 362:
                out.write(indent);
                out.write("Published:  ");
                if ( (Err = mvf.presentLong(markupType, out))
                          != ReturnCodes.OK )
                    return(Err);
                out.write(lineBreak);
             case 490:
                out.write(indent);
                out.write("Series:  ");
                if ( (Err = mvf.presentLong(markupType, out))
                          != ReturnCodes.OK )
                    return(Err);
                out.write(lineBreak);
             default:
            }
        } } catch(NoSuchElementException e) {}

        if ( editionField != null )
        {
            out.write(indent);
            out.write("Edition:  ");
            if ( (Err = editionField.presentLong(markupType, out))
                      != ReturnCodes.OK )
                return(Err);
            out.write(lineBreak);
        }

        return( ReturnCodes.OK );
    }


    /**
        Prsent the notes information of this object.
        @param    markupType    specifies which markup type the result should be
        @return    the formated notes information of this object as a string
    */      
    protected int presentNotesSection(int markupType, BufferedWriter out) throws IOException 
    {
        int Err;

        // First, pull together all the undifferentiated note fields:
        if ( (Err = presentSection(noteSection, markupType, out))
                  != ReturnCodes.OK )
             return( Err );

        String lineBreak;
        String indent;
        switch ( markupType )
        {
         case DigInfObj.HTML:
            lineBreak = new String("<BR>" + System.getProperty("line.separator"));
            indent = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
            break;

         default:
            debug.dumpTrace("MarcDocument.presentLong(): unsupported markup type:  using ASCII.");
            //  FALL THROUGH.

         case DigInfObj.ASCII:
         case DigInfObj.ANSEL:
            lineBreak = System.getProperty("line.separator");
            indent = "   ";
            break;
        }

        PresentableMarcVarField mvf = null;
        for (int i=0; i<getNumberVarFields(); i++)
        {
            mvf = (PresentableMarcVarField) getVarFieldByIndex(i);
            switch (mvf.getID())
            {
             case 505:
                out.write(indent);
                out.write("Contents:  ");
                if ( (Err = mvf.presentLong(markupType, out)) != ReturnCodes.OK)
                    return( Err );
                out.write(lineBreak);
                break;
             case 520:
                out.write(indent);
                out.write("Summary:  ");
                if ( (Err = mvf.presentLong(markupType, out)) != ReturnCodes.OK)
                    return( Err );
                out.write(lineBreak);
                break;
             case 525:
                out.write(indent);
                out.write("Supplements:  ");
                if ( (Err = mvf.presentLong(markupType, out)) != ReturnCodes.OK)
                    return( Err );
                out.write(lineBreak);
                break;
             case 856:
                out.write(indent);
                out.write("Access:  ");
                if ( (Err = mvf.presentLong(markupType, out)) != ReturnCodes.OK)
                    return( Err );
                out.write(lineBreak);
                break;
             default:
            }
        }

        return( ReturnCodes.OK );
    }


    /**
        format the specified variable
            fields of this object from the MarcRecord
        @param    markupType    see edu.vt.marian.common.DigInfObj
        @return    the formated information of this object from the specified 
            variable fields as a string
    */      
    protected int presentSection(int fieldType, int markupType, BufferedWriter out)
        throws IOException 
    {
        int Err;

        // first count the actual variable fields that need to be formated and
        // record down the first of them
        int numberToFormat = 0;
        int firstField = 0;
        PresentableMarcVarField mvf = null;
        int i;
        for (i = getNumberVarFields() - 1; i >= 0; i--)
        {
            mvf = (PresentableMarcVarField) getVarFieldByIndex(i);
            if ( hasSectionType(mvf, fieldType) )
            {
                numberToFormat++;
                firstField = i;
            }
        }
        
        if (numberToFormat == 0)
        {
            // there is no variable field to format
            return( ReturnCodes.OK );
        }

        // we have at least one variable field to format
        // Set up the formatting strings.
        String label;
        switch( fieldType )
        {
         case authorSection:
            if (numberToFormat > 1)
                label = "Authors:  ";
            else
                label = "Author:  ";
            break;
         case titleSection:
            if (numberToFormat > 1)
                label = "Titles:  ";
            else
                label = "Title:  ";
            break;
         case subjectSection:
            if (numberToFormat > 1)
                label = "Subjects:  ";
            else
                label = "Subject:  ";
            break;
         case noteSection:
            if (numberToFormat > 1)
                label = "Notes:  ";
            else
                label = "Note:  ";
            break;
         default:
            label = "";
        }
        String lineBreak;
        StringBuffer indentBuf = new StringBuffer();
        switch ( markupType )
        {
         case DigInfObj.HTML:
            lineBreak = new String("<BR>" + System.getProperty("line.separator"));
            for (i=0; i<label.length(); i++) // Prepare spacing string.
            {
                indentBuf.append("&nbsp;&nbsp;");
            }
            label = "<B>" + label + "</B>";
            break;

         default:
            debug.dumpTrace("MarcDocument.presentLong(): unsupported markup type:  using ASCII.");
            //  FALL THROUGH.

         case DigInfObj.ASCII:
         case DigInfObj.ANSEL:
            lineBreak = System.getProperty("line.separator");
            for (i=0; i<label.length(); i++) // Prepare spacing string.
            {
                indentBuf.append(" ");
            }
            break;
        }
        String indent = new String(indentBuf);

        out.write(label);
        // format the first variable field
        mvf = (PresentableMarcVarField) getVarFieldByIndex(firstField);
        if ( (Err = mvf.presentLong(markupType, out)) != ReturnCodes.OK )
            return( Err );
        out.write(lineBreak);

        if (numberToFormat > 1)
        {
            // format the remaining variable fields
            for (i = firstField + 1; i < getNumberVarFields(); i++)
            {
                mvf = (PresentableMarcVarField) getVarFieldByIndex(i);
                if ( hasSectionType(mvf, fieldType) )
                {
                    out.write(indent);
                    if ( (Err = mvf.presentLong(markupType, out)) != ReturnCodes.OK )
                        return( Err );
                    out.write(lineBreak);
                } 
            } 
        }

        return( ReturnCodes.OK );
    }

    /**
        Create a null MarcDocument.
        @param    debug -- used for debugging
    */      
    public MarcDocument(Debug dbg)
    {
        super(dbg);
        classID = ClassIDs.CLASS_VT_MARC;
        // Do we need to call the method at this level?:  init();
    }


    /**
        Create a null MarcRecord, with a minimal xmlMap (one with only &amp; and &lt; in it).
        @param    clID -- class ID of this record.
        @param    debug -- used for debugging
    */      
    public MarcDocument(int clID, Debug dbg)
    {
        super(dbg);
        classID = clID;
    }


    /**
        Create a MarcDocuemnt object using an explicit EntityMap.
        @param    xMap -- an EntityMap used when reading or writing the record
                        in XML.
        @param    debug -- used for debugging
    */      
    public MarcDocument(EntityMap xMap, Debug dbg)
    {
        super(xMap, dbg);
        classID = ClassIDs.CLASS_VT_MARC;
    }

    
    /**
        Create a MarcDocuemnt object using an explicit EntityMap.
        @param    xMap -- an EntityMap used when reading or writing the record
                        in XML.
        @param    debug -- used for debugging
    */      
    public MarcDocument(int clID, EntityMap xMap, Debug dbg)
    {
        super(xMap, dbg);
        classID = clID;
    }


    /**
        Does this object represent the same document as parameter object?
        <P><B>NOTE:</B>  At this point we are using String compare on the
            raw strings to determine equality.  This obviously leaves
            something to be desired.
        @param    d    the document used to compare with this object
        @return    true / false
    */      
    public boolean equals(MarcDocument d)
    {
        if (d == null)
        {
            debug.dumpTrace("MarcDocument.equals(): d is null");
            return false;
        }
        if ( (! isInstantiated ) || ( ! d.isInstantiated ) )
        {
            debug.dumpTrace("MarcDocument.equals(): uninstantiated record.");
            return false;
        }

        return( id.equals(d.id) );
    }


    /**
        Make a new MarcDocument just like this one.
        <BR>(Substitutes for public Object clone() until we straighten things out.)
        @return    another MarcDocument object just like this one.
    */
    public DigInfObj copy()
    {
        MarcDocument retDoc = null;
        try 
        {
            StringWriter sw = new StringWriter();
            BufferedWriter out = new BufferedWriter( sw );
            if ( presentAsXml(out) != ReturnCodes.OK )
                return( null );
            StringReader sr = new StringReader( out.toString() );
            BufferedReader in = new BufferedReader( sr );
            retDoc = new MarcDocument(debug);
            if ( retDoc.setFromXml(in) != ReturnCodes.OK )
               return( null );
        } catch (Exception e)
        {
            debug.dumpTrace("MarcDocument.copy(): expection " + e.toString() +
                            " raised:  bailing out.");
            return( null );
        }
        return( retDoc );
    }

    
    /**
        Return a short description 
            (probably only one sentence) of the document this represents.
        @param    markupType -- specifies the charater set type need to be returned
        @return    the short description of this object as a string
    */      
    public String presentShort(int markupType)
    {
        StringWriter sw = new StringWriter();
        BufferedWriter out = new BufferedWriter( sw );
        int Err;
        try {
            if ( (Err = presentShort(markupType, out)) != ReturnCodes.OK )
                return( null );
			out.flush();
        } catch( Exception e ) { return( null ); }
        return(sw.toString());
    }


    public int presentShort(int markupType, BufferedWriter out) throws IOException
    {
        PresentableMarcVarField mvf = null;
        int Err;
        switch ( markupType )
        {
         default:
            // only ASCII and XML are supported now.  ASCII produces the sort
            //  of single-line description used in results lists.  XML produces
            //  an OAMS description of the document.
            debug.dumpTrace("MarcDocument.presentShort(): unsupported markup type:  using ASCII.");
            //  FALL THROUGH:
            
         case DigInfObj.ASCII:
            boolean seenAuthor = false;
            for (int i = 0; i < getNumberVarFields(); i++)
            {
                mvf = (PresentableMarcVarField) getVarFieldByIndex(i);
                switch (mvf.getID())
                {
                 case 100:
                 case 110:
                 case 111:
                    if (seenAuthor)
                    {
                        // this is not the first author, so add
                        // separator between them
                        out.write(", ");
                    }
                    else
                    {
                        // this is the first author, no separator
                        // before it
                        seenAuthor = true;
                    }
                    if ( (Err = mvf.presentShort(markupType, out))
                              != ReturnCodes.OK )
                        return( Err );
                    break;

                 case 245:    // Main title.
                    if (seenAuthor)
                    {
                        // add separator
                        out.write(": ");
                    }
                    if ( (Err = mvf.presentShort(markupType, out))
                              != ReturnCodes.OK )
                        return( Err );
                    mvf.presentShort(markupType, out);
                    out.write(". ");
                    break;

                 case 260:    // Publication info.
                    if ( (Err = mvf.presentShort(markupType, out))
                              != ReturnCodes.OK )
                        return( Err );
                    break;

                 default: 
                } // end -- switch
            } // end -- for(...)
            break;

         case DigInfObj.XML:	// Produce OAMS record.
            out.write("<oams xmlns=\"http://www.openarchives.org/sfc/sfc_oams.htm\">");
            out.newLine();
            // Present last transaction date for accession date (if available).
            MarcFixField field5 = getFixFieldById(5);
            if ( (field5 != null) && (field5.getData() != null) && 
                 (field5.getData().length() == 16) )
            {
                String marcDate = field5.getData();
                // Re-format to ISO 8901 standard and output.
                out.write("<accession date=\"" +
                            marcDate.substring(0, 4) + "-" +
                            marcDate.substring(4, 6) + "-" +
                            marcDate.substring(6, 8) + "\"/>");
                out.newLine();
            }
			else // No 005 field.  Try 502 (another special case for VT-ETDs only).
			{
				Vector f502s = getVarFieldsById(502);
				if (f502s.size() != 1)
				{	 // 502 should be unique
					debug.dumpTrace("MarcDocument.presentShort():  variable field 502 is not uniq");
					// continue;
                }
				else
				{
				    PresentableMarcVarField f502 = (PresentableMarcVarField) f502s.elementAt(0);
				    Vector s502As = f502.getSubfieldsByLabel('a');
                    if (s502As.size() != 1)
                    {	// subfield 'a' should be unique too
                        debug.dumpTrace("MarcDocument.presentShort(): subfield 'a' in var field 35 is not uniq");
                        // continue;
                    }
					else
					{
                        String thesisDesc = ((MarcSubField) s502As.elementAt(0)).getData();
				        int startIndex;
                        if ( (thesisDesc == null) || ((startIndex = thesisDesc.indexOf(", ")) < 0) )
                        {	// This is not much of a cue, but it's standard across VT-ETDs.
                            debug.dumpTrace("MarcDocument.presentShort(): 502a field has no recognizable date.");
                            // continue;
                        }
					    else
					    {
				            startIndex += 2;
				            // Already in ISO 8901 standard; just output.
                            out.write("<accession date=\"" + thesisDesc.substring(startIndex) + "\"/>");
                            out.newLine();				
						}
					}
				}
			}	// end else try for a 502 field.
			
            for (int i = 0; i < getNumberVarFields(); i++)
            {
                mvf = (PresentableMarcVarField) getVarFieldByIndex(i);
                if ( hasSectionType(mvf, authorSection) )
                {
                    out.write("<author><name>");
                    if ( (Err = mvf.presentShort(DigInfObj.HTML, out))
                              != ReturnCodes.OK )
                        return( Err );
                    out.write("</name></author>");
                    out.newLine();
                }
                else if ( hasSectionType(mvf, subjectSection) )
                {
                    out.write("<subject>");
                    if ( (Err = mvf.presentShort(DigInfObj.HTML, out))
                              != ReturnCodes.OK )
                        return( Err );
                    out.write("</subject>");
                    out.newLine();
                }
		//**KLUDGE:  And if the record *has* no 245?  We really should
		//**  go looking for the next best title field.
                else if ( mvf.getID() == 245 )
                {
                    out.write("<title>");
                    if ( (Err = mvf.presentShort(DigInfObj.HTML, out))
                              != ReturnCodes.OK )
                        return( Err );
                    out.write("</title>");
                    out.newLine();
                }
                else if ( mvf.getID() == 520 )
                {
                    out.write("<abstract>");
                    if ( (Err = mvf.presentShort(DigInfObj.HTML, out))
                              != ReturnCodes.OK )
                        return( Err );
                    out.write("</abstract>");
                    out.newLine();
                }
                else if (mvf.getID() == 856)
                {
                    StringWriter sw = new StringWriter();
                    BufferedWriter bsw = new BufferedWriter( sw );
					String urnString = null;
					// Take the (presumably) unique URN and try to create an OAMS fullId
					//  (not a MARIAN FullID; see OAMS documentation) from it.  If no URN,
					//  look for a URL of the right format and use that.  NOTE:  This
					//  works for VT-ETDs, maybe for other NDTLD ETDs, and probably for
					//  nothing else.
					Vector gSubFields = mvf.getSubfieldsByLabel('g');
                    if (gSubFields.size() < 1)
					{
						debug.dumpTrace("MarcDocument.presentShort(XML):  **NOTE:  No URN; attempting to use URL.");
						Vector uSubFields = mvf.getSubfieldsByLabel('u');
						Enumeration uEnum = uSubFields.elements();
						try { while (true)
						{
							MarcSubField uSubField = (MarcSubField) uEnum.nextElement();
							if ( (Err = uSubField.present(DigInfObj.HTML, bsw))
									 != ReturnCodes.OK )
								return( Err );
    						bsw.flush();
						    urnString = sw.toString();
						} } catch (NoSuchElementException e) {}
					}
					else	// A 'g' subfield!  Use it.
                    {
                        if (gSubFields.size() > 1)
                        {
                            // URN should be unique, but don't die if it's not.
                            debug.dumpTrace("MarcDocument.presentShort(XML):  URN is not unique!");
					    }
						MarcSubField gSubField = (MarcSubField) gSubFields.firstElement();
                        if ( (Err = gSubField.present(DigInfObj.HTML, bsw))
                                  != ReturnCodes.OK )
                            return( Err );
						bsw.flush();
						urnString = sw.toString();
                    }
					if ( urnString == null)	// Nothing found.
					{
						debug.dumpTrace("MarcDocument.presentShort(XML):  **WARNING:  No URN ot URL; record will have no fullId.");
						//**DEVEL:  should we return(ReturnCodes.NO_CAN_DO); ?
					}
					else
					{
                        out.write("<fullId>");
						out.write("NDLTD:VT/");
						int firstIdPos = urnString.indexOf("etd-") + 4;	//**DEVEL:
						out.write(urnString.substring(firstIdPos));		//** what
                        out.write("</fullId>");							//** do the
                        out.newLine();					//** ETD URNs really look like?
					}
					

					// Each URL becomes a DisplayID.
					Vector uSubFields = mvf.getSubfieldsByLabel('u');
					Enumeration uEnum = uSubFields.elements();
					try { while (true)
					{
						MarcSubField uSubField = (MarcSubField) uEnum.nextElement();
                        out.write("<displayId>");
                        if ( (Err = uSubField.present(DigInfObj.HTML, out))
                                  != ReturnCodes.OK )
                            return( Err );
                        out.write("</displayId>");
                        out.newLine();
				    } } catch (NoSuchElementException e) {}
                }
            } // end -- for(...)
			
            out.write("</oams>");
            out.newLine();
            break;
        } // end -- switch

        return( ReturnCodes.OK );
    }


    /**
        return a long description of the document this object represents
        @return    the long description of this document as a string
    */      
    public String presentLong(int markupType)
    {
        StringWriter sw = new StringWriter();
        BufferedWriter out = new BufferedWriter( sw );
         int Err;
        try {
            if ( (Err = presentLong(markupType, out)) != ReturnCodes.OK )
                return( null );
			out.flush();
        } catch( Exception e ) { return( null ); }
        return(sw.toString());
    }


    public int presentLong(int markupType, BufferedWriter out) throws IOException
    {
        String lineBreak;
        String paraBreak;
        int Err;
        switch ( markupType )
        {
         case DigInfObj.HTML:
            lineBreak = new String("<BR>" + System.getProperty("line.separator"));
            paraBreak = new String("<P>" + System.getProperty("line.separator"));
            out.write("<B>Call Number:  </B>");
            break;

         default:
            debug.dumpTrace("MarcDocument.presentLong(): unsupported markup type:  using ASCII.");
            //  FALL THROUGH.

         case DigInfObj.ASCII:
         case DigInfObj.ANSEL:
            lineBreak = System.getProperty("line.separator");
            paraBreak = new String(System.getProperty("line.separator") +
                                   System.getProperty("line.separator"));
            out.write("Call Number:  ");
            break;
        }

        if ( (Err = presentCallNumber(markupType, out)) != ReturnCodes.OK)
            return( Err );
        out.write(paraBreak);
        if ( (Err = presentSection(authorSection, markupType, out))
                  != ReturnCodes.OK)
            return( Err );
        out.write(lineBreak);
        if ( (Err = presentSection(titleSection, markupType, out))
                  != ReturnCodes.OK)
            return( Err );
        out.write(lineBreak);
        if ( (Err = presentPublishingInfoSection(markupType, out))
                  != ReturnCodes.OK)
            return( Err );
        out.write(lineBreak);
        if ( (Err = presentNotesSection(markupType, out)) != ReturnCodes.OK)
            return( Err );
        out.write(lineBreak);
        if ( (Err = presentSection(subjectSection, markupType, out))
                  != ReturnCodes.OK)
            return( Err );

        return( ReturnCodes.OK );
    }


    /**
        Return a full description of the document this object represents.
        <BR><B>NOTE:</B>  At the risk of conflating two uses -- presentaion
        of the complete object for tranport to another system and
        presentation to a sophisticated data administrator (e.g.,
        a library cataloger) -- we are using the transport
        methods defined in MarcRecord to implement this.
        @param    markupType    specifies the character set type need to be returned
        @return    the full description of this document as a string
    */      
    public String presentFull(int markupType)
    {
        StringWriter sw = new StringWriter();
        BufferedWriter out = new BufferedWriter( sw );
        int Err;
        try {
            if ( (Err = presentFull(markupType, out)) != ReturnCodes.OK )
                return( null );
			out.flush();
       } catch( Exception e ) { return( null ); }
        return(sw.toString());
    }


    public int presentFull(int markupType, BufferedWriter out) throws IOException
    {
        switch ( markupType )
        {
         case DigInfObj.XML:
         case DigInfObj.SGML:
            return( presentAsXml(out) );

        default:
            debug.dumpTrace("MarcDocument.presentFull(): Unexpected markup type" +
                            markupType + ":  treating as ASCII.");
            // Fall through:

        case DigInfObj.ASCII:
        case DigInfObj.ANSEL:
            return( presentAsTapeFormat(out) );
        }
    }


    /**
        return a Vector of metadata attributes for this document.
        @param    markupType    how to mark up the string returned (e.g., HTML or ASCII).
        @return    a Vector of triples [attrName, attrType, attrValue].
    */
    public Vector presentAttributes(int markupType)
    {
        debug.dumpTrace("MarcDocument.presentAttributes(): not yet implemented");
        return null;
    }


    /**
        Return an Object (almost certainly a String) in some markupType for the given
            attribute.
        @param    markupType    how to mark up the string returned (e.g., HTML or ASCII).
        @return    a Vector of triples [attrName, attrType, attrValue].
    */
    public Object presentAttribute(int attrID, int markupType)
    {
        debug.dumpTrace("MarcDocument.presentAttributes(): not yet implemented");
        return null;
    }


    /**
        return a Vector of metadata attributes for this document.
        @param    markupType    how to mark up the string returned (e.g., HTML or ASCII).
        @return    a Vector of triples [attrName, attrType, attrValue].
    */
    public Vector attributes()
    {
        debug.dumpTrace("SOIFDocument.presentAttributes(): not yet implemented");
        return null;
    }

}

