diff --git a/data-discovery/src/main/java/vlkb/output/ObscoreExt.java b/data-discovery/src/main/java/vlkb/output/ObscoreExt.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e84f582818ef6d03a22992e28e4175c684977ed
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/output/ObscoreExt.java
@@ -0,0 +1,191 @@
+
+
+import uk.ac.starlink.table.*;// ColumnInfo needed
+
+class ObscoreExt
+{
+   public static final ColumnInfo[] OBSCORE_COLINFO =
+   {
+      new ColumnInfo( "dataproduct_type",  String.class,  "Dataproduct Type (image|cube)" ),
+      new ColumnInfo( "calib_level",       Integer.class, "Calibration level" ),
+      new ColumnInfo( "obs_collection",    String.class,  "Collection" ),
+      new ColumnInfo( "obs_id",            String.class,  "Observation Id" ),
+      new ColumnInfo( "obs_publisher_did", String.class,  "Publisher Did" ),
+
+      new ColumnInfo( "access_url",     String.class, "Access URL" ),
+      new ColumnInfo( "access_format",  String.class, "Format (MIME type)" ),
+      new ColumnInfo( "access_estsize", Long.class,   "Extimated size (KB)" ),
+
+      new ColumnInfo( "target_name", String.class,  "Target" ),
+
+      new ColumnInfo( "s_ra",         Double.class, "Right Ascention" ),
+      new ColumnInfo( "s_dec",        Double.class, "Declination" ),
+      new ColumnInfo( "s_fov",        Double.class, "Field of view" ),
+      new ColumnInfo( "s_region",     String.class, "Region" ),
+      new ColumnInfo( "s_xel1",       Long.class,   "Pixels axis1" ),
+      new ColumnInfo( "s_xel2",       Long.class,   "Pixels axis2" ),
+      new ColumnInfo( "s_resolution", Double.class, "Spatial resolution" ),
+
+      new ColumnInfo( "t_min",        Double.class, "Time min" ),
+      new ColumnInfo( "t_max",        Double.class, "Time max" ),
+      new ColumnInfo( "t_exptime",    Double.class, "Exposure time" ),
+      new ColumnInfo( "t_resolution", Double.class, "Time resolution" ),
+      new ColumnInfo( "t_xel",        Long.class,   "Time pixels" ),
+
+      new ColumnInfo( "em_min",       Double.class, "Spectrum min" ),
+      new ColumnInfo( "em_max",       Double.class, "Spectrum max" ),
+      new ColumnInfo( "em_res_power", Double.class, "Spectrum resolution power" ),
+      new ColumnInfo( "em_xel",       Long.class,   "Spectrum pixels" ),
+
+      new ColumnInfo( "o_ucd", String.class, "Observable UCD" ),
+
+      new ColumnInfo( "pol_states", String.class, "Polarization states" ),
+      new ColumnInfo( "pol_xel",    Long.class,   "Polarization pixels" ),
+
+      new ColumnInfo( "facility_name", String.class, "Facility" ),
+      new ColumnInfo( "instrument_name", String.class, "Instrument" ),
+   };
+
+   public static Object[] obscoreRow( Dataset dataset )
+   {
+      return new Object[]
+      {
+         dataset.obsCore.dataproduct_type,//dataset.dataType, 
+            Integer.valueOf( dataset.obsCore.calib_level ),
+            dataset.obsCore.obs_collection,
+            dataset.obsCore.obs_id,
+            dataset.obsCore.obs_publisher_did,
+
+            dataset.obsCore.access_url,//dataset.access.accessCutoutUrl,
+            dataset.obsCore.access_format,
+            Long.valueOf( dataset.obsCore.access_estsize ),
+
+            dataset.obsCore.target_name,
+
+            Double.valueOf(dataset.obsCore.s_ra),Double.valueOf(dataset.obsCore.s_dec),
+            Double.valueOf(dataset.obsCore.s_fov),
+
+            dataset.obsCore.s_region,
+            Long.valueOf( dataset.obsCore.s_xel1 ), Long.valueOf( dataset.obsCore.s_xel2 ),
+            Double.valueOf(dataset.obsCore.s_resolution),
+
+            Double.valueOf(dataset.obsCore.t_min), Double.valueOf(dataset.obsCore.t_max),
+            Double.valueOf(dataset.obsCore.t_exptime), Double.valueOf(dataset.obsCore.t_resolution),
+            Long.valueOf( dataset.obsCore.t_xel ),
+
+            Double.valueOf(dataset.obsCore.em_min), Double.valueOf(dataset.obsCore.em_max),
+            Double.valueOf(dataset.obsCore.em_res_power),
+            Long.valueOf( dataset.obsCore.em_xel ),
+
+            dataset.obsCore.o_ucd,
+
+            dataset.obsCore.pol_states,
+            Long.valueOf( dataset.obsCore.pol_xel ),
+
+            dataset.obsCore.facility_name,
+            dataset.obsCore.instrument_name,
+      };
+   }
+
+
+
+   public static final ColumnInfo[] VLKB_COLINFO =
+   {
+      new ColumnInfo( "overlap",     Integer.class, "Overlap Code" ),
+      new ColumnInfo( "overlapSky",  Integer.class, "Overlap Code for Sky axes" ),
+      new ColumnInfo( "overlapSpec", Integer.class, "Overlap Code for Spectral axis" ),
+
+      new ColumnInfo( "P1lon", Double.class, "P1 longitude" ),
+      new ColumnInfo( "P1lat", Double.class, "P1 latitude" ),
+      new ColumnInfo( "P2lon", Double.class, "P2 longitude" ),
+      new ColumnInfo( "P2lat", Double.class, "P2 latitude" ),
+      new ColumnInfo( "P3lon", Double.class, "P3 longitude" ),
+      new ColumnInfo( "P3lat", Double.class, "P3 latitude" ),
+      new ColumnInfo( "P4lon", Double.class, "P4 longitude" ),
+      new ColumnInfo( "P4lat", Double.class, "P4 latitude" ),
+
+      new ColumnInfo( "file_url",   String.class, "Access URL: all file" ),
+      new ColumnInfo( "cutout_url", String.class, "Access URL: cut file" ),
+      new ColumnInfo( "merge_url",  String.class, "Access URL: demosaicing files" ),
+
+   };
+
+   public static Object[] vlkbRow( Dataset dataset )
+   {
+      return new Object[]
+      {
+         Integer.valueOf( dataset.overlapCode ),
+            Integer.valueOf( dataset.overlapCodeSky ),
+            Integer.valueOf( dataset.overlapCodeVel ),
+
+            Double.valueOf(dataset.vertices_deg.lon[0]), Double.valueOf(dataset.vertices_deg.lat[0]),
+            Double.valueOf(dataset.vertices_deg.lon[1]), Double.valueOf(dataset.vertices_deg.lat[1]),
+            Double.valueOf(dataset.vertices_deg.lon[2]), Double.valueOf(dataset.vertices_deg.lat[2]),
+            Double.valueOf(dataset.vertices_deg.lon[3]), Double.valueOf(dataset.vertices_deg.lat[3]),
+
+            dataset.access.accessFileUrl,
+            dataset.access.accessCutoutUrl,
+            dataset.access.accessMosaicUrl,
+      };
+   }
+
+
+
+   public static final ColumnInfo[] SUBSURVEY_COLINFO =
+   {
+      new ColumnInfo( "velocity_unit", String.class, "Velocity Unit" ),
+      new ColumnInfo( "survey",        String.class, "Survey name" ),
+      new ColumnInfo( "species",       String.class, "Species" ),
+      new ColumnInfo( "transition",    String.class, "Transition" ),
+      new ColumnInfo( "description",   String.class, "Descritpion" )
+   };
+
+   public static Object[] subsurveyRow( Subsurvey subsurvey )
+   {
+      return new Object[]
+      {
+         subsurvey.vel_unit,
+            subsurvey.surveyname,
+            subsurvey.species,
+            subsurvey.transition,
+            subsurvey.description
+      };
+   }
+
+
+
+   public static final ColumnInfo[] OBSCORE_VLKB_COLINFO = concat(OBSCORE_COLINFO, VLKB_COLINFO);
+
+   public static Object[] obscoreVlkbRow( Dataset dataset )
+   {
+      return concat(obscoreRow(dataset),vlkbRow(dataset));
+   }
+
+
+   public static final ColumnInfo[] OBSCORE_VLKB_SUBSURVEY_COLINFO = concat(concat(OBSCORE_COLINFO, VLKB_COLINFO), SUBSURVEY_COLINFO);
+
+   public static Object[] obscoreVlkbSubsurveyRow( Dataset dataset, Subsurvey subsurvey )
+   {
+      return concat(concat(obscoreRow(dataset),vlkbRow(dataset)), subsurveyRow(subsurvey));
+   }
+
+
+
+
+   private static ColumnInfo[] concat(ColumnInfo[] arr1, ColumnInfo[] arr2)
+   {
+      ColumnInfo[] oc = new ColumnInfo[arr1.length + arr2.length];
+      System.arraycopy(arr1, 0, oc, 0,           arr1.length);
+      System.arraycopy(arr2, 0, oc, arr1.length, arr2.length);
+      return oc;
+   };
+
+   private static Object[] concat(Object[] arr1, Object[] arr2)
+   {
+      Object[] oc = new Object[arr1.length + arr2.length];
+      System.arraycopy(arr1, 0, oc, 0,           arr1.length);
+      System.arraycopy(arr2, 0, oc, arr1.length, arr2.length);
+      return oc;
+   };
+}
+
diff --git a/data-discovery/src/main/java/vlkb/output/XmlSerializer.java b/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
index 0c495622d49fc7ebeb28991193966c3d9a88a0c7..aa039c1abed41ac803f188603a9dd6aa2a2a4350 100644
--- a/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
+++ b/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
@@ -1,5 +1,4 @@
 
-//import java.util.logging.Logger;
 import java.io.PrintWriter;
 
 // VOTable
@@ -12,16 +11,15 @@ import java.io.BufferedWriter;
 
 
 
-public final class XmlSerializer
+final class XmlSerializer
 {
-   //private static final Logger LOGGER = Logger.getLogger(ServletCutout.class.getName());
-
-   private XmlSerializer() {} // disables instatiation
-
+   private XmlSerializer() {}
 
    // VOTable
 
-   public static void serializeToVoTable(PrintWriter writer, String charEncoding, SearchOutputData searchOutputData,
+   public static void serializeToVoTable(
+         PrintWriter writer, String charEncoding,
+         SearchOutputData searchOutputData,
          boolean showDuration, long startTime_msec) throws IOException
    {
       StarTable dstable = makeSearchResultsTable( searchOutputData.subsurveyArr );
@@ -34,173 +32,113 @@ public final class XmlSerializer
                new DefaultValueInfo( "datacubeCount",  Integer.class, "Count of all datacubes from VLKB-search" ),
                searchOutputData.datacubeCount ) );
 
-      StarTable[] tables = {dstable};
-
       BufferedWriter out = new BufferedWriter( writer );
 
       out.write("<?xml-stylesheet type='text/xsl' href='VOTable2XHTML.xsl'?>");
       out.write( "<VOTABLE version='1.1'>" );
       out.write( "<RESOURCE>" );
       out.write( "<DESCRIPTION> " + searchOutputData.versionString + " </DESCRIPTION>" );
-      for ( int i = 0; i < tables.length; i++ )
-      {
-         VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[ i ] ).writeInlineTableElement( out );
-      }
+
+      VOSerializer.makeSerializer( DataFormat.TABLEDATA, dstable ).writeInlineTableElement( out );
+
       out.write( "</RESOURCE>" );
       out.write( "</VOTABLE>" );
       out.flush();
    }
 
-   static final ColumnInfo[] OBSCORE_COLINFO =
-   {
-      new ColumnInfo( "dataproduct_type",  String.class,  "Dataproduct Type (image|cube)" ),
-      new ColumnInfo( "calib_level",       Integer.class, "Calibration level" ),
-      new ColumnInfo( "obs_collection",    String.class,  "Collection" ),
-      new ColumnInfo( "obs_id",            String.class,  "Observation Id" ),
-      new ColumnInfo( "obs_publisher_did", String.class,  "Publisher Did" ),
-
-      new ColumnInfo( "access_url",     String.class, "Access URL" ),
-      new ColumnInfo( "access_format",  String.class, "Format (MIME type)" ),
-      new ColumnInfo( "access_estsize", Long.class,   "Extimated size (KB)" ),
-
-      new ColumnInfo( "target_name", String.class,  "Target" ),
-
-      new ColumnInfo( "s_ra",         Double.class, "Right Ascention" ),
-      new ColumnInfo( "s_dec",        Double.class, "Declination" ),
-      new ColumnInfo( "s_fov",        Double.class, "Field of view" ),
-      new ColumnInfo( "s_region",     String.class, "Region" ),
-      new ColumnInfo( "s_xel1",       Long.class,   "Pixels axis1" ),
-      new ColumnInfo( "s_xel2",       Long.class,   "Pixels axis2" ),
-      new ColumnInfo( "s_resolution", Double.class, "Spatial resolution" ),
-
-      new ColumnInfo( "t_min",        Double.class, "Time min" ),
-      new ColumnInfo( "t_max",        Double.class, "Time max" ),
-      new ColumnInfo( "t_exptime",    Double.class, "Exposure time" ),
-      new ColumnInfo( "t_resolution", Double.class, "Time resolution" ),
-      new ColumnInfo( "t_xel",        Long.class,   "Time pixels" ),
-
-      new ColumnInfo( "em_min",       Double.class, "Spectrum min" ),
-      new ColumnInfo( "em_max",       Double.class, "Spectrum max" ),
-      new ColumnInfo( "em_res_power", Double.class, "Spectrum resolution power" ),
-      new ColumnInfo( "em_xel",       Long.class,   "Spectrum pixels" ),
-
-      new ColumnInfo( "o_ucd", String.class, "Observable UCD" ),
-
-      new ColumnInfo( "pol_states", String.class, "Polarization states" ),
-      new ColumnInfo( "pol_xel",    Long.class,   "Polarization pixels" ),
-
-      new ColumnInfo( "facility_name", String.class, "Facility" ),
-      new ColumnInfo( "instrument_name", String.class, "Instrument" ),
-
-
-      // VLKB extensions
-      new ColumnInfo( "overlap",     Integer.class, "Overlap Code" ),
-      new ColumnInfo( "overlapSky",  Integer.class, "Overlap Code for Sky axes" ),
-      new ColumnInfo( "overlapSpec", Integer.class, "Overlap Code for Spectral axis" ),
-
-      new ColumnInfo( "P1lon", Double.class, "P1 longitude" ),
-      new ColumnInfo( "P1lat", Double.class, "P1 latitude" ),
-      new ColumnInfo( "P2lon", Double.class, "P2 longitude" ),
-      new ColumnInfo( "P2lat", Double.class, "P2 latitude" ),
-      new ColumnInfo( "P3lon", Double.class, "P3 longitude" ),
-      new ColumnInfo( "P3lat", Double.class, "P3 latitude" ),
-      new ColumnInfo( "P4lon", Double.class, "P4 longitude" ),
-      new ColumnInfo( "P4lat", Double.class, "P4 latitude" ),
-
-      new ColumnInfo( "file_url",   String.class, "Access URL: all file" ),
-      new ColumnInfo( "cutout_url", String.class, "Access URL: cut file" ),
-      new ColumnInfo( "merge_url",  String.class, "Access URL: demosaicing files" ),
-
-      // subsurvey metadata
-      new ColumnInfo( "velocity_unit", String.class, "Velocity Unit" ),
-      new ColumnInfo( "survey",        String.class, "Survey name" ),
-      new ColumnInfo( "species",       String.class, "Species" ),
-      new ColumnInfo( "transition",    String.class, "Transition" ),
-      new ColumnInfo( "description",   String.class, "Descritpion" )
-   };
-
-   private static Object[] obscoreRow( Dataset dataset, Subsurvey subsurvey ) 
+   private static StarTable makeSearchResultsTable(Subsurvey[] ssurv)
    {
-      return new Object[]
+      RowListStarTable astro = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_SUBSURVEY_COLINFO );
+
+      for(Subsurvey subsurvey : ssurv)
       {
-         // ObsCore
-         dataset.obsCore.dataproduct_type,//dataset.dataType, 
-            Integer.valueOf( dataset.obsCore.calib_level ),
-            dataset.obsCore.obs_collection,
-            dataset.obsCore.obs_id,
-            dataset.obsCore.obs_publisher_did,
+         for(Dataset dataset : subsurvey.datasetArr)
+         {
+            if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets
 
-            dataset.obsCore.access_url,//dataset.access.accessCutoutUrl,
-            dataset.obsCore.access_format,
-            Long.valueOf( dataset.obsCore.access_estsize ),
+            astro.addRow( ObscoreExt.obscoreVlkbSubsurveyRow(dataset, subsurvey) );
+         }
+      }
 
-            dataset.obsCore.target_name,
+      return astro;
+   }
 
-            Double.valueOf(dataset.obsCore.s_ra),Double.valueOf(dataset.obsCore.s_dec),
-            Double.valueOf(dataset.obsCore.s_fov),
 
-            dataset.obsCore.s_region,
-            Long.valueOf( dataset.obsCore.s_xel1 ), Long.valueOf( dataset.obsCore.s_xel2 ),
-            Double.valueOf(dataset.obsCore.s_resolution),
+   public static void serializeToVoTableBySubsurveys(
+         PrintWriter writer, String charEncoding,
+         SearchOutputData searchOutputData,
+         boolean showDuration, long startTime_msec) throws IOException
+   {
+      StarTable[] tables = makeSubsurveyTables( searchOutputData.subsurveyArr );
+      BufferedWriter out = new BufferedWriter( writer );
 
-            Double.valueOf(dataset.obsCore.t_min), Double.valueOf(dataset.obsCore.t_max),
-            Double.valueOf(dataset.obsCore.t_exptime), Double.valueOf(dataset.obsCore.t_resolution),
-            Long.valueOf( dataset.obsCore.t_xel ),
+      out.write( "<VOTABLE version='1.1'>" );
+      out.write( "<RESOURCE>" );
+      out.write( "<DESCRIPTION> " + searchOutputData.versionString + " </DESCRIPTION>" );
+      for ( int i = 0; i < tables.length; i++ )
+      {
+         VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[ i ] ).writeInlineTableElement( out );
+      }
+      out.write( "</RESOURCE>" );
+      out.write( "</VOTABLE>" );
+      out.flush();
+   } 
 
-            Double.valueOf(dataset.obsCore.em_min), Double.valueOf(dataset.obsCore.em_max),
-            Double.valueOf(dataset.obsCore.em_res_power),
-            Long.valueOf( dataset.obsCore.em_xel ),
+   private static StarTable[] makeSubsurveyTables(Subsurvey[] subsurveyArr)
+   {
+      StarTable[] ssurvTableArr = new StarTable[subsurveyArr.length]; 
+      int ix = 0;
 
-            dataset.obsCore.o_ucd,
+      for(Subsurvey subsurvey : subsurveyArr)
+      {
+         RowListStarTable table = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_COLINFO );
 
-            dataset.obsCore.pol_states,
-            Long.valueOf( dataset.obsCore.pol_xel ),
+         table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "subsurveyCount", Integer.class, "Count of subsurveys with found datacube(s)" ),
+                  subsurveyArr.length ) );
 
-            dataset.obsCore.facility_name,
-            dataset.obsCore.instrument_name,
+         table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "datacubeCount",  Integer.class, "Count of all datacubes from VLKB-search" ),
+                  subsurvey.datasetArr.length ) );
 
-            // VLKB extensions
-            Integer.valueOf( dataset.overlapCode ),
-            Integer.valueOf( dataset.overlapCodeSky ),
-            Integer.valueOf( dataset.overlapCodeVel ),
+         table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "velocity_unit",  String.class, "Unit of velocity in FITS header" ),
+                  subsurvey.vel_unit ) );
 
-            Double.valueOf(dataset.vertices_deg.lon[0]), Double.valueOf(dataset.vertices_deg.lat[0]),
-            Double.valueOf(dataset.vertices_deg.lon[1]), Double.valueOf(dataset.vertices_deg.lat[1]),
-            Double.valueOf(dataset.vertices_deg.lon[2]), Double.valueOf(dataset.vertices_deg.lat[2]),
-            Double.valueOf(dataset.vertices_deg.lon[3]), Double.valueOf(dataset.vertices_deg.lat[3]),
+         table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "survey",  String.class, "Survey name" ),
+                  subsurvey.surveyname ) );
 
-            dataset.access.accessFileUrl,
-            dataset.access.accessCutoutUrl,
-            dataset.access.accessMosaicUrl,
+         table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "species",  String.class, "Species" ),
+                  subsurvey.species ) );
 
-            subsurvey.vel_unit,
-            subsurvey.surveyname,
-            subsurvey.species,
-            subsurvey.transition,
-            subsurvey.description
-      };
-   }
+         table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "transition",  String.class, "Transition" ),
+                  subsurvey.transition ) );
 
-   private static StarTable makeSearchResultsTable(Subsurvey[] ssurv)
-   {
-      RowListStarTable astro = new RowListStarTable( OBSCORE_COLINFO );
+         table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "description",  String.class, "Reference description" ),
+                  subsurvey.description ) );
 
-      for(Subsurvey subsurvey : ssurv)
-      {
          for(Dataset dataset : subsurvey.datasetArr)
          {
             if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets
 
-            astro.addRow( obscoreRow(dataset, subsurvey) );
+            table.addRow( ObscoreExt.obscoreVlkbRow(dataset) );
          }
+
+         ssurvTableArr[ix++] = table;
       }
 
-      return astro;
+      return ssurvTableArr;
    }
 
+
    // legacy
 
-   public static void serializeToLegacyResults(PrintWriter writer, String charEncoding,
+   public static void serializeToLegacyResults(
+         PrintWriter writer, String charEncoding,
          AuthPolicy inputAuth, Coord inputCoord, SubsurveyId inputSubsurveyId,
          SearchOutputData searchOutputData,
          boolean showDuration, long startTime_msec)
@@ -226,7 +164,7 @@ public final class XmlSerializer
       writer.println("</results>");
    }
 
-   public static String serialize(Coord coord)
+   private static String serialize(Coord coord)
    {
       StringBuilder xml = new StringBuilder();
       xml.append("<SkySystem>"+coord.skySystem+"</SkySystem>");
@@ -251,7 +189,7 @@ public final class XmlSerializer
       return xml.toString();
    }
 
-   public static String serialize(SubsurveyId subsurveyId)
+   private static String serialize(SubsurveyId subsurveyId)
    {
       StringBuilder xml = new StringBuilder();
       if(subsurveyId.surveyName  != null) xml.append("<SurveyName>"+subsurveyId.surveyName+"</SurveyName>");
@@ -260,7 +198,7 @@ public final class XmlSerializer
       return xml.toString();
    }
 
-   public static String serialize(AuthPolicy auth)
+   private static String serialize(AuthPolicy auth)
    {
       StringBuilder xml = new StringBuilder();
       xml.append("<AccessPolicy>" + auth.getAccessPolicy() + "</AccessPolicy>");
@@ -271,7 +209,7 @@ public final class XmlSerializer
    }
 
 
-   public static void serialize(PrintWriter writer, Subsurvey subsurvey)
+   private static void serialize(PrintWriter writer, Subsurvey subsurvey)
    {
       writer.println("<survey>");
       // replace with escape the XML-predefined entities:
@@ -301,7 +239,7 @@ public final class XmlSerializer
       writer.println("</survey>");
    }
 
-   public static String serialize(Dataset.Access access)
+   private static String serialize(Dataset.Access access)
    {
       StringBuilder xml = new StringBuilder();
 
@@ -321,7 +259,7 @@ public final class XmlSerializer
       return xml.toString();
    }
 
-   public static String serialize(Dataset.Vertices vertices)
+   private static String serialize(Dataset.Vertices vertices)
    {
       StringBuilder xml = new StringBuilder();
       xml.append("<vertices>");
@@ -340,7 +278,7 @@ public final class XmlSerializer
    }
 
 
-   public static String serialize(Dataset dataset)
+   private static String serialize(Dataset dataset)
    {
       StringBuilder xml = new StringBuilder();
 
@@ -364,7 +302,7 @@ public final class XmlSerializer
    }
 
 
-   public static String serializeOverlapCode(String tagName, int ovCode)
+   private static String serializeOverlapCode(String tagName, int ovCode)
    {
       final String[] overString =
       {
diff --git a/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java b/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
index 832593f6727fd129c1743ae06d1a5e38afe98b8d..c6b48993b532cdb6a28ce10e1a223d7e691a7c68 100644
--- a/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
+++ b/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
@@ -137,11 +137,15 @@ public class FormatResponseFilter implements Filter
                   searchOutputData,
                   showDuration,startTime_msec);
          }
-         else if(respFormat.equals("application/x-votable+xml"))
+         else if(respFormat.startsWith("application/x-votable+xml"))
          {
             response.setContentType("application/xml");
             boolean showDuration = false;
-            XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
+
+            if(respFormat.contains("mode=bysubsurveys"))
+               XmlSerializer.serializeToVoTableBySubsurveys(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
+            else
+               XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
          }
          else
          {