legacy/src/main/java/thredds/catalog/InvDatasetImpl.java
static public void writeHtmlDescription(StringBuilder buff, InvDatasetImpl ds,
                                          boolean complete, boolean isServer,
                                          boolean datasetEvents, boolean catrefEvents,
                                          boolean resolveRelativeUrls) {

    if (ds == null) return;

    if (complete) {
      buff.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n")
              .append("        \"http://www.w3.org/TR/html4/loose.dtd\">\n")
              .append("<html>\n");
      buff.append("<head>");
      buff.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">");
      buff.append("</head>");
      buff.append("<body>\n");
    }

    buff.append("<h2>Dataset: ").append(ds.getFullName()).append("</h2>\n<ul>\n");
    if ((ds.getDataFormatType() != null) && (ds.getDataFormatType() != DataFormatType.NONE))
      buff.append(" <li><em>Data format: </em>").append(StringUtil2.quoteHtmlContent(ds.getDataFormatType().toString())).append("</li>\n");

    if ((ds.getDataSize() != 0.0) && !Double.isNaN(ds.getDataSize()))
      buff.append(" <li><em>Data size: </em>").append(Format.formatByteSize(ds.getDataSize())).append("</li>\n");

    if ((ds.getDataType() != null) && (ds.getDataType() != FeatureType.ANY))
      buff.append(" <li><em>Data type: </em>").append(StringUtil2.quoteHtmlContent(ds.getDataType().toString())).append("</li>\n");

    if ((ds.getCollectionType() != null) && (ds.getCollectionType() != CollectionType.NONE))
      buff.append(" <li><em>Collection type: </em>").append(StringUtil2.quoteHtmlContent(ds.getCollectionType().toString())).append("</li>\n");

    if (ds.isHarvest())
      buff.append(" <li><em>Harvest: </em>").append(ds.isHarvest()).append("</li>\n");

    if (ds.getAuthority() != null)
      buff.append(" <li><em>Naming Authority: </em>").append(StringUtil2.quoteHtmlContent(ds.getAuthority())).append("</li>\n");

    if (ds.getID() != null)
      buff.append(" <li><em>ID: </em>").append(StringUtil2.quoteHtmlContent(ds.getID())).append("</li>\n");

    if (ds.getRestrictAccess() != null)
      buff.append(" <li><em>RestrictAccess: </em>").append(StringUtil2.quoteHtmlContent(ds.getRestrictAccess())).append("</li>\n");

    if (ds instanceof InvCatalogRef) {
      InvCatalogRef catref = (InvCatalogRef) ds;
      String href = resolveRelativeUrls || catrefEvents
              ? resolve(ds, catref.getXlinkHref())
              : catref.getXlinkHref();
      if (catrefEvents) href = "catref:" + href;
      buff.append(" <li><em>CatalogRef: </em>").append(makeHref(href, null)).append("</li>\n");
    }

    buff.append("</ul>\n");

    java.util.List<InvDocumentation> docs = ds.getDocumentation();
    if (docs.size() > 0) {
      buff.append("<h3>Documentation:</h3>\n<ul>\n");
      for (InvDocumentation doc : docs) {
        String type = (doc.getType() == null) ? "" : "<strong>" + StringUtil2.quoteHtmlContent(doc.getType()) + ":</strong> ";
        String inline = doc.getInlineContent();
        if ((inline != null) && (inline.length() > 0))
          buff.append(" <li>").append(type).append(StringUtil2.quoteHtmlContent(inline)).append("</li>\n");
        if (doc.hasXlink()) {
          // buff.append(" <li>" + type + makeHrefResolve(ds, url.toString(), doc.getXlinkTitle()) + "</a>\n");
          buff.append(" <li>").append(type).append(makeHref(doc.getXlinkHref(), doc.getXlinkTitle())).append("</li>\n");
        }
      }
      buff.append("</ul>\n");
    }

    java.util.List<InvAccess> access = ds.getAccess();
    if (access.size() > 0) {
      buff.append("<h3>Access:</h3>\n<ol>\n");
      for (InvAccess a : access) {
        InvService s = a.getService();
        String urlString = resolveRelativeUrls || datasetEvents
                ? a.getStandardUrlName()
                : a.getUnresolvedUrlName();
        String fullUrlString = urlString;
        if (datasetEvents) fullUrlString = "dataset:" + fullUrlString;
        if (isServer) {
          ServiceType stype = s.getServiceType();
          if ((stype == ServiceType.OPENDAP) || (stype == ServiceType.DODS))
            fullUrlString = fullUrlString + ".html";
          else if (stype == ServiceType.DAP4)
            fullUrlString = fullUrlString + ".dmr.xml";
          else if (stype == ServiceType.WCS)
            fullUrlString = fullUrlString + "?service=WCS&version=1.0.0&request=GetCapabilities";
          else if (stype == ServiceType.WMS)
            fullUrlString = fullUrlString + "?service=WMS&version=1.3.0&request=GetCapabilities";
          //NGDC update 8/18/2011
          else if (stype == ServiceType.NCML || stype == ServiceType.UDDC || stype == ServiceType.ISO) {
            String catalogUrl = ds.getCatalogUrl();
        	  String datasetId = ds.id;
            if ( catalogUrl.indexOf('#') > 0)
              catalogUrl = catalogUrl.substring( 0, catalogUrl.lastIndexOf('#'));
            try {
              catalogUrl = URLEncoder.encode( catalogUrl, "UTF-8" );
              datasetId = URLEncoder.encode( datasetId,"UTF-8");
            } catch ( UnsupportedEncodingException e) {
              e.printStackTrace();
            }
            fullUrlString = fullUrlString + "?catalog=" + catalogUrl +  "&dataset=" + datasetId;
          }
          else if (stype == ServiceType.NetcdfSubset)
            fullUrlString = fullUrlString + "/dataset.html";
          else if ((stype == ServiceType.CdmRemote) || (stype == ServiceType.CdmrFeature))
            fullUrlString = fullUrlString + "?req=form";
        }
        buff.append(" <li> <b>").append(StringUtil2.quoteHtmlContent(s.getServiceType().toString()));
        buff.append(":</b> ").append(makeHref(fullUrlString, urlString)).append("</li>\n");
      }
      buff.append("</ol>\n");
    }

    java.util.List<ThreddsMetadata.Contributor> contributors = ds.getContributors();
    if (contributors.size() > 0) {
      buff.append("<h3>Contributors:</h3>\n<ul>\n");
      for (ThreddsMetadata.Contributor t : contributors) {
        String role = (t.getRole() == null) ? "" : "<strong> (" + StringUtil2.quoteHtmlContent(t.getRole()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(t.getName())).append(role).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Vocab> keywords = ds.getKeywords();
    if (keywords.size() > 0) {
      buff.append("<h3>Keywords:</h3>\n<ul>\n");
      for (ThreddsMetadata.Vocab t : keywords) {
        String vocab = (t.getVocabulary() == null) ? "" : " <strong>(" + StringUtil2.quoteHtmlContent(t.getVocabulary()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(t.getText())).append(vocab).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<DateType> dates = ds.getDates();
    if (dates.size() > 0) {
      buff.append("<h3>Dates:</h3>\n<ul>\n");
      for (DateType d : dates) {
        String type = (d.getType() == null) ? "" : " <strong>(" + StringUtil2.quoteHtmlContent(d.getType()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(d.getText())).append(type).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Vocab> projects = ds.getProjects();
    if (projects.size() > 0) {
      buff.append("<h3>Projects:</h3>\n<ul>\n");
      for (ThreddsMetadata.Vocab t : projects) {
        String vocab = (t.getVocabulary() == null) ? "" : " <strong>(" + StringUtil2.quoteHtmlContent(t.getVocabulary()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(t.getText())).append(vocab).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Source> creators = ds.getCreators();
    if (creators.size() > 0) {
      buff.append("<h3>Creators:</h3>\n<ul>\n");
      for (ThreddsMetadata.Source t : creators) {
        buff.append(" <li><strong>").append(StringUtil2.quoteHtmlContent(t.getName())).append("</strong><ul>\n");
        buff.append(" <li><em>email: </em>").append(StringUtil2.quoteHtmlContent(t.getEmail())).append("</li>\n");
        if (t.getUrl() != null) {
          String newUrl = resolveRelativeUrls
                  ? makeHrefResolve(ds, t.getUrl(), null)
                  : makeHref(t.getUrl(), null);
          buff.append(" <li> <em>").append(newUrl).append("</em></li>\n");
        }
        buff.append(" </ul></li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Source> publishers = ds.getPublishers();
    if (publishers.size() > 0) {
      buff.append("<h3>Publishers:</h3>\n<ul>\n");
      for (ThreddsMetadata.Source t : publishers) {
        buff.append(" <li><strong>").append(StringUtil2.quoteHtmlContent(t.getName())).append("</strong><ul>\n");
        buff.append(" <li><em>email: </em>").append(StringUtil2.quoteHtmlContent(t.getEmail())).append("\n");
        if (t.getUrl() != null) {
          String urlLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, t.getUrl(), null)
                  : makeHref(t.getUrl(), null);
          buff.append(" <li> <em>").append(urlLink).append("</em>\n");
        }
        buff.append(" </ul>\n");
      }
      buff.append("</ul>\n");
    }

    /*
    4.2:
    <h3>Variables:</h3>
    <ul>
    <li><em>Vocabulary</em> [DIF]:
    <ul>
     <li><strong>Reflectivity</strong> =  <i></i> = EARTH SCIENCE &gt; Spectral/Engineering &gt; Radar &gt; Radar Reflectivity (db)
     <li><strong>Velocity</strong> =  <i></i> = EARTH SCIENCE &gt; Spectral/Engineering &gt; Radar &gt; Doppler Velocity (m/s)
     <li><strong>SpectrumWidth</strong> =  <i></i> = EARTH SCIENCE &gt; Spectral/Engineering &gt; Radar &gt; Doppler Spectrum Width (m/s)
     </ul>
     </ul>
    </ul>

    4.3:
    <h3>Variables:</h3>
    <ul>
    <li><em>Vocabulary</em> [CF-1.0]:
    <ul>
     <li><strong>d3d (meters) </strong> =  <i>3D Depth at Nodes
    <p>        </i> = depth_at_nodes
     <li><strong>depth (meters) </strong> =  <i>Bathymetry</i> = depth
     <li><strong>eta (m) </strong> =  <i></i> =
     <li><strong>temp (Celsius) </strong> =  <i>Temperature
    <p>        </i> = sea_water_temperature
     <li><strong>u (m/s) </strong> =  <i>Eastward Water
    <p>          Velocity
    <p>        </i> = eastward_sea_water_velocity
     <li><strong>v (m/s) </strong> =  <i>Northward Water
    <p>          Velocity
    <p>        </i> = northward_sea_water_velocity
    </ul>
    </ul>
     */

    java.util.List<ThreddsMetadata.Variables> vars = ds.getVariables();
    if (vars.size() > 0) {
      buff.append("<h3>Variables:</h3>\n<ul>\n");
      for (ThreddsMetadata.Variables t : vars) {

        buff.append("<li><em>Vocabulary</em> [");
        if (t.getVocabUri() != null) {
          URI uri = t.getVocabUri();
          String vocabLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, uri.toString(), t.getVocabulary())
                  : makeHref(uri.toString(), t.getVocabulary());
          buff.append(vocabLink);
        } else {
          buff.append(StringUtil2.quoteHtmlContent(t.getVocabulary()));
        }
        buff.append("]:\n<ul>\n");

        java.util.List<ThreddsMetadata.Variable> vlist = t.getVariableList();
        if (vlist.size() > 0) {
          for (ThreddsMetadata.Variable v : vlist) {
            String units = (v.getUnits() == null || v.getUnits().length() == 0) ? "" : " (" + v.getUnits() + ") ";
            buff.append(" <li><strong>").append(StringUtil2.quoteHtmlContent(v.getName() + units)).append("</strong> = ");
            String desc = (v.getDescription() == null) ? "" : " <i>" + StringUtil2.quoteHtmlContent(v.getDescription()) + "</i> = ";
            buff.append(desc);
            if (v.getVocabularyName() != null)
              buff.append(StringUtil2.quoteHtmlContent(v.getVocabularyName()));
            buff.append("\n");
          }
        }
        buff.append("</ul>\n");
      }
      buff.append("</ul>\n");
    }
    if (ds.getVariableMapLink() != null) {
      buff.append("<h3>Variables:</h3>\n");
      buff.append("<ul><li>"+makeHref(ds.getVariableMapLink(), "VariableMap")+"</li></ul>\n");
    }

    ThreddsMetadata.GeospatialCoverage gc = ds.getGeospatialCoverage();
    if ((gc != null) && !gc.isEmpty()) {
      buff.append("<h3>GeospatialCoverage:</h3>\n<ul>\n");
      if (gc.isGlobal())
        buff.append(" <li><em> Global </em>\n");

      buff.append(" <li><em> Longitude: </em> ").append(rangeString(gc.getEastWestRange())).append("</li>\n");
      buff.append(" <li><em> Latitude: </em> ").append(rangeString(gc.getNorthSouthRange())).append("</li>\n");
      if (gc.getUpDownRange() != null) {
        buff.append(" <li><em> Altitude: </em> ").append(rangeString(gc.getUpDownRange())).append(" (positive is <strong>").append(StringUtil2.quoteHtmlContent(gc.getZPositive())).append(")</strong></li>\n");
      }

      java.util.List<ThreddsMetadata.Vocab> nlist = gc.getNames();
      if ((nlist != null) && (nlist.size() > 0)) {
        buff.append(" <li><em>  Names: </em> <ul>\n");
        for (ThreddsMetadata.Vocab elem : nlist) {
          buff.append(" <li>").append(StringUtil2.quoteHtmlContent(elem.getText())).append("\n");
        }
        buff.append(" </ul>\n");
      }
      buff.append(" </ul>\n");
    }

    CalendarDateRange tc = ds.getCalendarDateCoverage();
    if (tc != null) {
      buff.append("<h3>TimeCoverage:</h3>\n<ul>\n");
      CalendarDate start = tc.getStart();
      if (start != null)
        buff.append(" <li><em>  Start: </em> ").append(start.toString()).append("\n");
      CalendarDate end = tc.getEnd();
      if (end != null) {
        buff.append(" <li><em>  End: </em> ").append(end.toString()).append("\n");
      }
      CalendarDuration duration = tc.getDuration();
      if (duration != null)
        buff.append(" <li><em>  Duration: </em> ").append(StringUtil2.quoteHtmlContent(duration.toString())).append("\n");
      CalendarDuration resolution = tc.getResolution();
      if (resolution != null) {
        buff.append(" <li><em>  Resolution: </em> ").append(StringUtil2.quoteHtmlContent(resolution.toString())).append("\n");
      }
      buff.append(" </ul>\n");
    }

    java.util.List<InvMetadata> metadata = ds.getMetadata();
    boolean gotSomeMetadata = false;
    for (InvMetadata m : metadata) {
      if (m.hasXlink()) gotSomeMetadata = true;
    }

    if (gotSomeMetadata) {
      buff.append("<h3>Metadata:</h3>\n<ul>\n");
      for (InvMetadata m : metadata) {
        String type = (m.getMetadataType() == null) ? "" : m.getMetadataType();
        if (m.hasXlink()) {
          String title = (m.getXlinkTitle() == null) ? "Type " + type : m.getXlinkTitle();
          String mdLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, m.getXlinkHref(), title)
                  : makeHref(m.getXlinkHref(), title);
          buff.append(" <li> ").append(mdLink).append("\n");
        } //else {
        //buff.append(" <li> <pre>"+m.getMetadataType()+" "+m.getContentObject()+"</pre>\n");
        //}
      }
      buff.append("</ul>\n");
    }

    java.util.List<InvProperty> propsOrg = ds.getProperties();
    java.util.List<InvProperty> props = new ArrayList<>(ds.getProperties().size());
    for (InvProperty p : propsOrg)  {
      if (!p.getName().startsWith("viewer"))  // eliminate the viewer properties from the html view
        props.add(p);
    }
    if (props.size() > 0) {
      buff.append("<h3>Properties:</h3>\n<ul>\n");
      for (InvProperty p : props) {
        if (p.getName().equals("attachments")) // LOOK whats this ?
        {
          String attachLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, p.getValue(), p.getName())
                  : makeHref(p.getValue(), p.getName());
          buff.append(" <li>").append(attachLink).append("\n");
        } else {
          buff.append(" <li>").append(StringUtil2.quoteHtmlContent(p.getName() + " = \"" + p.getValue())).append("\"\n");
        }
      }
      buff.append("</ul>\n");
    }

    if (complete) buff.append("</body></html>");
  }
static public void writeHtmlDescription(StringBuilder buff, InvDatasetImpl ds,
                                          boolean complete, boolean isServer,
                                          boolean datasetEvents, boolean catrefEvents,
                                          boolean resolveRelativeUrls) {

    if (ds == null) return;

    if (complete) {
      buff.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n")
              .append("        \"http://www.w3.org/TR/html4/loose.dtd\">\n")
              .append("<html>\n");
      buff.append("<head>");
      buff.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">");
      buff.append("</head>");
      buff.append("<body>\n");
    }

    buff.append("<h2>Dataset: ").append(ds.getFullName()).append("</h2>\n<ul>\n");
    if ((ds.getDataFormatType() != null) && (ds.getDataFormatType() != DataFormatType.NONE))
      buff.append(" <li><em>Data format: </em>").append(StringUtil2.quoteHtmlContent(ds.getDataFormatType().toString())).append("</li>\n");

    if ((ds.getDataSize() != 0.0) && !Double.isNaN(ds.getDataSize()))
      buff.append(" <li><em>Data size: </em>").append(Format.formatByteSize(ds.getDataSize())).append("</li>\n");

    if ((ds.getDataType() != null) && (ds.getDataType() != FeatureType.ANY))
      buff.append(" <li><em>Data type: </em>").append(StringUtil2.quoteHtmlContent(ds.getDataType().toString())).append("</li>\n");

    if ((ds.getCollectionType() != null) && (ds.getCollectionType() != CollectionType.NONE))
      buff.append(" <li><em>Collection type: </em>").append(StringUtil2.quoteHtmlContent(ds.getCollectionType().toString())).append("</li>\n");

    if (ds.isHarvest())
      buff.append(" <li><em>Harvest: </em>").append(ds.isHarvest()).append("</li>\n");

    if (ds.getAuthority() != null)
      buff.append(" <li><em>Naming Authority: </em>").append(StringUtil2.quoteHtmlContent(ds.getAuthority())).append("</li>\n");

    if (ds.getID() != null)
      buff.append(" <li><em>ID: </em>").append(StringUtil2.quoteHtmlContent(ds.getID())).append("</li>\n");

    if (ds.getRestrictAccess() != null)
      buff.append(" <li><em>RestrictAccess: </em>").append(StringUtil2.quoteHtmlContent(ds.getRestrictAccess())).append("</li>\n");

    if (ds instanceof InvCatalogRef) {
      InvCatalogRef catref = (InvCatalogRef) ds;
      String href = resolveRelativeUrls || catrefEvents
              ? resolve(ds, catref.getXlinkHref())
              : catref.getXlinkHref();
      if (catrefEvents) href = "catref:" + href;
      buff.append(" <li><em>CatalogRef: </em>").append(makeHref(href, null)).append("</li>\n");
    }

    buff.append("</ul>\n");

    java.util.List<InvDocumentation> docs = ds.getDocumentation();
    if (docs.size() > 0) {
      buff.append("<h3>Documentation:</h3>\n<ul>\n");
      for (InvDocumentation doc : docs) {
        String type = (doc.getType() == null) ? "" : "<strong>" + StringUtil2.quoteHtmlContent(doc.getType()) + ":</strong> ";
        String inline = doc.getInlineContent();
        if ((inline != null) && (inline.length() > 0))
          buff.append(" <li>").append(type).append(StringUtil2.quoteHtmlContent(inline)).append("</li>\n");
        if (doc.hasXlink()) {
          // buff.append(" <li>" + type + makeHrefResolve(ds, url.toString(), doc.getXlinkTitle()) + "</a>\n");
          buff.append(" <li>").append(type).append(makeHref(doc.getXlinkHref(), doc.getXlinkTitle())).append("</li>\n");
        }
      }
      buff.append("</ul>\n");
    }

    java.util.List<InvAccess> access = ds.getAccess();
    if (access.size() > 0) {
      buff.append("<h3>Access:</h3>\n<ol>\n");
      for (InvAccess a : access) {
        InvService s = a.getService();
        String urlString = resolveRelativeUrls || datasetEvents
                ? a.getStandardUrlName()
                : a.getUnresolvedUrlName();
        String fullUrlString = urlString;
        if (datasetEvents) fullUrlString = "dataset:" + fullUrlString;
        if (isServer) {
          ServiceType stype = s.getServiceType();
          if ((stype == ServiceType.OPENDAP) || (stype == ServiceType.DODS))
            fullUrlString = fullUrlString + ".html";
          else if (stype == ServiceType.DAP4)
            fullUrlString = fullUrlString + ".dmr.xml";
          else if (stype == ServiceType.WCS)
            fullUrlString = fullUrlString + "?service=WCS&version=1.0.0&request=GetCapabilities";
          else if (stype == ServiceType.WMS)
            fullUrlString = fullUrlString + "?service=WMS&version=1.3.0&request=GetCapabilities";
          //NGDC update 8/18/2011
          else if (stype == ServiceType.NCML || stype == ServiceType.UDDC || stype == ServiceType.ISO) {
            String catalogUrl = ds.getCatalogUrl();
        	  String datasetId = ds.id;
            if ( catalogUrl.indexOf('#') > 0)
              catalogUrl = catalogUrl.substring( 0, catalogUrl.lastIndexOf('#'));
            try {
              catalogUrl = URLEncoder.encode( catalogUrl, "UTF-8" );
              datasetId = URLEncoder.encode( datasetId,"UTF-8");
            } catch ( UnsupportedEncodingException e) {
              e.printStackTrace();
            }
            fullUrlString = fullUrlString + "?catalog=" + catalogUrl +  "&dataset=" + datasetId;
          }
          else if (stype == ServiceType.NetcdfSubset)
            fullUrlString = fullUrlString + "/dataset.html";
          else if ((stype == ServiceType.CdmRemote) || (stype == ServiceType.CdmrFeature))
            fullUrlString = fullUrlString + "?req=form";
        }
        buff.append(" <li> <b>").append(StringUtil2.quoteHtmlContent(s.getServiceType().toString()));
        buff.append(":</b> ").append(makeHref(fullUrlString, urlString)).append("</li>\n");
      }
      buff.append("</ol>\n");
    }

    java.util.List<ThreddsMetadata.Contributor> contributors = ds.getContributors();
    if (contributors.size() > 0) {
      buff.append("<h3>Contributors:</h3>\n<ul>\n");
      for (ThreddsMetadata.Contributor t : contributors) {
        String role = (t.getRole() == null) ? "" : "<strong> (" + StringUtil2.quoteHtmlContent(t.getRole()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(t.getName())).append(role).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Vocab> keywords = ds.getKeywords();
    if (keywords.size() > 0) {
      buff.append("<h3>Keywords:</h3>\n<ul>\n");
      for (ThreddsMetadata.Vocab t : keywords) {
        String vocab = (t.getVocabulary() == null) ? "" : " <strong>(" + StringUtil2.quoteHtmlContent(t.getVocabulary()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(t.getText())).append(vocab).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<DateType> dates = ds.getDates();
    if (dates.size() > 0) {
      buff.append("<h3>Dates:</h3>\n<ul>\n");
      for (DateType d : dates) {
        String type = (d.getType() == null) ? "" : " <strong>(" + StringUtil2.quoteHtmlContent(d.getType()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(d.getText())).append(type).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Vocab> projects = ds.getProjects();
    if (projects.size() > 0) {
      buff.append("<h3>Projects:</h3>\n<ul>\n");
      for (ThreddsMetadata.Vocab t : projects) {
        String vocab = (t.getVocabulary() == null) ? "" : " <strong>(" + StringUtil2.quoteHtmlContent(t.getVocabulary()) + ")</strong> ";
        buff.append(" <li>").append(StringUtil2.quoteHtmlContent(t.getText())).append(vocab).append("</li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Source> creators = ds.getCreators();
    if (creators.size() > 0) {
      buff.append("<h3>Creators:</h3>\n<ul>\n");
      for (ThreddsMetadata.Source t : creators) {
        buff.append(" <li><strong>").append(StringUtil2.quoteHtmlContent(t.getName())).append("</strong><ul>\n");
        buff.append(" <li><em>email: </em>").append(StringUtil2.quoteHtmlContent(t.getEmail())).append("</li>\n");
        if (t.getUrl() != null) {
          String newUrl = resolveRelativeUrls
                  ? makeHrefResolve(ds, t.getUrl(), null)
                  : makeHref(t.getUrl(), null);
          buff.append(" <li> <em>").append(newUrl).append("</em></li>\n");
        }
        buff.append(" </ul></li>\n");
      }
      buff.append("</ul>\n");
    }

    java.util.List<ThreddsMetadata.Source> publishers = ds.getPublishers();
    if (publishers.size() > 0) {
      buff.append("<h3>Publishers:</h3>\n<ul>\n");
      for (ThreddsMetadata.Source t : publishers) {
        buff.append(" <li><strong>").append(StringUtil2.quoteHtmlContent(t.getName())).append("</strong><ul>\n");
        buff.append(" <li><em>email: </em>").append(StringUtil2.quoteHtmlContent(t.getEmail())).append("\n");
        if (t.getUrl() != null) {
          String urlLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, t.getUrl(), null)
                  : makeHref(t.getUrl(), null);
          buff.append(" <li> <em>").append(urlLink).append("</em>\n");
        }
        buff.append(" </ul>\n");
      }
      buff.append("</ul>\n");
    }

    /*
    4.2:
    <h3>Variables:</h3>
    <ul>
    <li><em>Vocabulary</em> [DIF]:
    <ul>
     <li><strong>Reflectivity</strong> =  <i></i> = EARTH SCIENCE &gt; Spectral/Engineering &gt; Radar &gt; Radar Reflectivity (db)
     <li><strong>Velocity</strong> =  <i></i> = EARTH SCIENCE &gt; Spectral/Engineering &gt; Radar &gt; Doppler Velocity (m/s)
     <li><strong>SpectrumWidth</strong> =  <i></i> = EARTH SCIENCE &gt; Spectral/Engineering &gt; Radar &gt; Doppler Spectrum Width (m/s)
     </ul>
     </ul>
    </ul>

    4.3:
    <h3>Variables:</h3>
    <ul>
    <li><em>Vocabulary</em> [CF-1.0]:
    <ul>
     <li><strong>d3d (meters) </strong> =  <i>3D Depth at Nodes
    <p>        </i> = depth_at_nodes
     <li><strong>depth (meters) </strong> =  <i>Bathymetry</i> = depth
     <li><strong>eta (m) </strong> =  <i></i> =
     <li><strong>temp (Celsius) </strong> =  <i>Temperature
    <p>        </i> = sea_water_temperature
     <li><strong>u (m/s) </strong> =  <i>Eastward Water
    <p>          Velocity
    <p>        </i> = eastward_sea_water_velocity
     <li><strong>v (m/s) </strong> =  <i>Northward Water
    <p>          Velocity
    <p>        </i> = northward_sea_water_velocity
    </ul>
    </ul>
     */

    java.util.List<ThreddsMetadata.Variables> vars = ds.getVariables();
    if (vars.size() > 0) {
      buff.append("<h3>Variables:</h3>\n<ul>\n");
      for (ThreddsMetadata.Variables t : vars) {

        buff.append("<li><em>Vocabulary</em> [");
        if (t.getVocabUri() != null) {
          URI uri = t.getVocabUri();
          String vocabLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, uri.toString(), t.getVocabulary())
                  : makeHref(uri.toString(), t.getVocabulary());
          buff.append(vocabLink);
        } else {
          buff.append(StringUtil2.quoteHtmlContent(t.getVocabulary()));
        }
        buff.append("]:\n<ul>\n");

        java.util.List<ThreddsMetadata.Variable> vlist = t.getVariableList();
        if (vlist.size() > 0) {
          for (ThreddsMetadata.Variable v : vlist) {
            String units = (v.getUnits() == null || v.getUnits().length() == 0) ? "" : " (" + v.getUnits() + ") ";
            buff.append(" <li><strong>").append(StringUtil2.quoteHtmlContent(v.getName() + units)).append("</strong> = ");
            String desc = (v.getDescription() == null) ? "" : " <i>" + StringUtil2.quoteHtmlContent(v.getDescription()) + "</i> = ";
            buff.append(desc);
            if (v.getVocabularyName() != null)
              buff.append(StringUtil2.quoteHtmlContent(v.getVocabularyName()));
            buff.append("\n");
          }
        }
        buff.append("</ul>\n");
      }
      buff.append("</ul>\n");
    }
    if (ds.getVariableMapLink() != null) {
      buff.append("<h3>Variables:</h3>\n");
      buff.append("<ul><li>"+makeHref(ds.getVariableMapLink(), "VariableMap")+"</li></ul>\n");
    }

    ThreddsMetadata.GeospatialCoverage gc = ds.getGeospatialCoverage();
    if ((gc != null) && !gc.isEmpty()) {
      buff.append("<h3>GeospatialCoverage:</h3>\n<ul>\n");
      if (gc.isGlobal())
        buff.append(" <li><em> Global </em>\n");

      buff.append(" <li><em> Longitude: </em> ").append(rangeString(gc.getEastWestRange())).append("</li>\n");
      buff.append(" <li><em> Latitude: </em> ").append(rangeString(gc.getNorthSouthRange())).append("</li>\n");
      if (gc.getUpDownRange() != null) {
        buff.append(" <li><em> Altitude: </em> ").append(rangeString(gc.getUpDownRange())).append(" (positive is <strong>").append(StringUtil2.quoteHtmlContent(gc.getZPositive())).append(")</strong></li>\n");
      }

      java.util.List<ThreddsMetadata.Vocab> nlist = gc.getNames();
      if ((nlist != null) && (nlist.size() > 0)) {
        buff.append(" <li><em>  Names: </em> <ul>\n");
        for (ThreddsMetadata.Vocab elem : nlist) {
          buff.append(" <li>").append(StringUtil2.quoteHtmlContent(elem.getText())).append("\n");
        }
        buff.append(" </ul>\n");
      }
      buff.append(" </ul>\n");
    }

    CalendarDateRange tc = ds.getCalendarDateCoverage();
    if (tc != null) {
      buff.append("<h3>TimeCoverage:</h3>\n<ul>\n");
      CalendarDate start = tc.getStart();
      if (start != null)
        buff.append(" <li><em>  Start: </em> ").append(start.toString()).append("\n");
      CalendarDate end = tc.getEnd();
      if (end != null) {
        buff.append(" <li><em>  End: </em> ").append(end.toString()).append("\n");
      }
      CalendarDuration duration = tc.getDuration();
      if (duration != null)
        buff.append(" <li><em>  Duration: </em> ").append(StringUtil2.quoteHtmlContent(duration.toString())).append("\n");
      CalendarDuration resolution = tc.getResolution();
      if (resolution != null) {
        buff.append(" <li><em>  Resolution: </em> ").append(StringUtil2.quoteHtmlContent(resolution.toString())).append("\n");
      }
      buff.append(" </ul>\n");
    }

    java.util.List<InvMetadata> metadata = ds.getMetadata();
    boolean gotSomeMetadata = false;
    for (InvMetadata m : metadata) {
      if (m.hasXlink()) gotSomeMetadata = true;
    }

    if (gotSomeMetadata) {
      buff.append("<h3>Metadata:</h3>\n<ul>\n");
      for (InvMetadata m : metadata) {
        String type = (m.getMetadataType() == null) ? "" : m.getMetadataType();
        if (m.hasXlink()) {
          String title = (m.getXlinkTitle() == null) ? "Type " + type : m.getXlinkTitle();
          String mdLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, m.getXlinkHref(), title)
                  : makeHref(m.getXlinkHref(), title);
          buff.append(" <li> ").append(mdLink).append("\n");
        } //else {
        //buff.append(" <li> <pre>"+m.getMetadataType()+" "+m.getContentObject()+"</pre>\n");
        //}
      }
      buff.append("</ul>\n");
    }

    java.util.List<InvProperty> propsOrg = ds.getProperties();
    java.util.List<InvProperty> props = new ArrayList<>(ds.getProperties().size());
    for (InvProperty p : propsOrg)  {
      if (!p.getName().startsWith("viewer"))  // eliminate the viewer properties from the html view
        props.add(p);
    }
    if (props.size() > 0) {
      buff.append("<h3>Properties:</h3>\n<ul>\n");
      for (InvProperty p : props) {
        if (p.getName().equals("attachments")) // LOOK whats this ?
        {
          String attachLink = resolveRelativeUrls
                  ? makeHrefResolve(ds, p.getValue(), p.getName())
                  : makeHref(p.getValue(), p.getName());
          buff.append(" <li>").append(attachLink).append("\n");
        } else {
          buff.append(" <li>").append(StringUtil2.quoteHtmlContent(p.getName() + " = \"" + p.getValue())).append("\"\n");
        }
      }
      buff.append("</ul>\n");
    }

    if (complete) buff.append("</body></html>");
  }
Write an Html representation of the given dataset.
<p> With datasetEvents, catrefEvents = true, this is used to construct an HTML page on the client
(eg using HtmlPage); the client then detects URL clicks and processes.
<p> With datasetEvents, catrefEvents = false, this is used to construct an HTML page on the server.
(eg using HtmlPage); the client then detects URL clicks and processes.

@param buff          put HTML here.
@param ds            the dataset.
@param complete      if true, add HTML header and ender so its a complete, valid HTML page.
@param isServer      if true, then we are in the thredds data server, so do the following: <ul>
<li> append "html" to DODS Access URLs
</ul>
@param datasetEvents if true, prepend "dataset:" to any dataset access URLS
@param catrefEvents  if true, prepend "catref:" to any catref URLS