简体   繁体   中英

How can I insert an existing Google Worksheet into a Google Spreadsheet?

I'm writing a C# app with some Google Spreadsheets integration. I'm in a situation where I have some data in a worksheet that needs to be moved into a different spreadsheet. This worksheet contains a huge amount of data, so I want to avoid iterating through its contents.

The API guide gives an example of how to create a new worksheet within a spreadsheet. I modified it to add an existing worksheet to the spreadsheet:

using System;
using Google.GData.Client;
using Google.GData.Spreadsheets;

namespace MySpreadsheetIntegration
{
    class Program
    {
        static void Main(string[] args)
        {
            SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");

            SpreadsheetEntry destinationSpreadsheet = fetchGoogleSpreadSheetEntry(service, "some_title");
            SpreadsheetEntry originSpreadsheet = fetchGoogleSpreadSheetEntry(service, "some_other_title");

            // Create a local representation of the new worksheet.
            WorksheetEntry originWorksheet = fetchGoogleWorksheet( originSpreadsheet, "some_worksheet_title" );

            // Send the local representation of the worksheet to the API for
            // creation.  The URL to use here is the worksheet feed URL of our
            // spreadsheet.
            WorksheetFeed wsFeed = destinationSpreadsheet.Worksheets;
            service.Insert(wsFeed, originWorksheet);
        }
    }
}

For clarity, the above code attempts to take the "some_worksheet_title" worksheet in the "some_other_title" spreadsheet, and put it into the "some_title" spreadsheet. Below are the functions referenced in the above code.

public static WorksheetEntry fetchGoogleWorksheet( SpreadsheetEntry spreadsheet, string worksheet_title )
{
    WorksheetFeed wsFeed = spreadsheet.Worksheets;
    WorksheetEntry worksheet = null;

    foreach (WorksheetEntry entry in wsFeed.Entries)
    {
        worksheet = entry;
        if (entry.Title.Text == worksheet_title)
        {
            Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Worksheet found on Google Drive.");
            break;
        }
    }

    if (worksheet.Title.Text != worksheet_title)
    {
        return null;
    }

    return worksheet;
}

public static SpreadsheetEntry fetchGoogleSpreadSheetEntry( SpreadsheetsService service, string spreadsheet_title )
{
    Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Looking for spreadsheet on Google Drive.");
    SpreadsheetQuery query = new SpreadsheetQuery();
    SpreadsheetFeed feed;

    feed = service.Query(query);

    SpreadsheetEntry spreadsheet = null;
    // Iterate through all of the spreadsheets returned
    foreach (SpreadsheetEntry entry in feed.Entries)
    {
        // Print the title of this spreadsheet to the screen
        spreadsheet = entry;
        if (entry.Title.Text == spreadsheet_title)
        {
            Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Spreadsheet found on Google Drive.");
            Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Looking for worksheet in spreadsheet.");
            break;
        }
    }

    if (spreadsheet.Title.Text != spreadsheet_title)
    {
        return null;
    }
    return spreadsheet;

    }   

I expected to be able to fetch to worksheet I want to add to the spreadsheet, and just add it to the spreadsheet. It does not work. The above code creates a (correctly titled) worksheet in the destination spreadsheet, but does not transfer any of the content of the worksheet.

Is there any way to have it transfer the content correctly?

After trying a few different ways of doing this, the most reliable way turned out to be Google Apps Scripting . In general terms, my solution involves a Google Apps script that is being called by my C# application via the execution API . Below are some code samples demonstrating how all of this works together.

So here's the Google Apps script that moves content from one worksheet to another:

function copyWorksheet( destinationSpreadsheetId, destinationWorksheetTitle, originSpreadsheetId, originWorksheetTitle ) {

  // Spreadsheet where new data will go:
  var dss = SpreadsheetApp.openById(destinationSpreadsheetId);

  // Spreadsheet where new data is coming from:
  var oss = SpreadsheetApp.openById(originSpreadsheetId);

  // Worksheet containing new data:
  var dataOriginWorksheet = oss.getSheetByName(originWorksheetTitle);

  // Worksheet whose data will be 'overwritten':
  var expiredWorksheet = dss.getSheetByName(destinationWorksheetTitle);

  // If a spreadsheet only has one worksheet, deleting that worksheet causes an error.
  // Thus we need to know whether the expired worksheet is the only worksheet in it's parent spreadsheet.
  var expiredWorksheetIsAlone = dss.getNumSheets() == 1 && expiredWorksheet != null;

  // Delete the expired worksheet if there are other worksheets:
  if (expiredWorksheet != null && !expiredWorksheetIsAlone)
    dss.deleteSheet(expiredWorksheet);

  // Otherwise, rename it to something guaranteed not to clash with the new sheet's title:
  if(expiredWorksheetIsAlone)
    expiredWorksheet.setName(dataOriginWorksheet.getName() + destinationWorksheetTitle);

  // Copy the new data into it's rightful place, and give it it's rightful name.
  dataOriginWorksheet.copyTo(dss).setName(destinationWorksheetTitle);

  // Since there are now definitely 2 worksheets, it's safe to delete the expired one.
  if(expiredWorksheetIsAlone)
    dss.deleteSheet(expiredWorksheet);

  // Make sure our changes are applied ASAP:
  SpreadsheetApp.flush();

  return "finished";
}

This is a severely stripped down version of the code I ended up using, which is why there are two spreadsheet ID fields. This means that it does not matter whether or not the the two worksheets are in the same spreadsheet.

The C# part of the solution looks like this:

// We need these for the method below
using Google.Apis.Script.v1;
using Google.Apis.Script.v1.Data;

...    

public static bool copyWorksheet(ScriptService scriptService, string destinationSpreadsheetId, string destinationWorksheetTitle, string originSpreadsheetId, string originWorksheetTitle)
  {
    // You can get the script ID by going to the script in the 
    // Google Apps Scripts Editor > Publish > Deploy as API executable... > API ID
    string scriptId = "your-apps-script-id";

    ExecutionRequest request = new ExecutionRequest();
    request.Function = "copyWorksheet";
    IList<object> parameters = new List<object>();

    parameters.Add(destinationSpreadsheetId);
    parameters.Add(destinationWorksheetTitle);
    parameters.Add(originSpreadsheetId);
    parameters.Add(originWorksheetTitle);            

    request.Parameters = parameters;

    ScriptsResource.RunRequest runReq = scriptService.Scripts.Run(request, scriptId);

    try
    {
      Operation op = runReq.Execute();

      if (op.Error != null)
      {
        Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " The Apps script encountered an error");
        // The API executed, but the script returned an error.

        IDictionary<string, object> error = op.Error.Details[0];
        Console.WriteLine( "Script error message: {0}", error["errorMessage"]);
        if ( error.ContainsKey("scriptStackTraceElements") )
        {

          // There may not be a stacktrace if the script didn't
          // start executing.
          Console.WriteLine("Script error stacktrace:");
          Newtonsoft.Json.Linq.JArray st = (Newtonsoft.Json.Linq.JArray)error["scriptStackTraceElements"];
          foreach (var trace in st)
            {
              Console.WriteLine(
                "\t{0}: {1}",
                trace["function"],
                trace["lineNumber"]);
            }

          }
        }
        else
        {
          // The result provided by the API needs to be cast into
          // the correct type, based upon what types the Apps
          // Script function returns. Here, the function returns
          // an Apps Script Object with String keys and values.
          // It is most convenient to cast the return value as a JSON
          // JObject (folderSet).

          return true;

        }
      }
      catch (Google.GoogleApiException e)
      {
        // The API encountered a problem before the script
        // started executing.
        Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " Could not call Apps Script");
      }

      return false;
    }

...

The above 2 pieces of code, when used together, solved the problem perfectly. Execution time does not differ greatly between different volumes of data, and there has been no data corruption with transfers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM