Partager via


Copier une feuille de calcul à l’aide de SAX (API simple pour XML)

Cette rubrique montre comment utiliser le Kit de développement logiciel (SDK) Open XML pour Office pour copier par programmation une grande feuille de calcul à l’aide de SAX (API simple pour XML). Pour plus d’informations sur la structure de base d’un SpreadsheetML document, consultez Structure d’un document SpreadsheetML.


Pourquoi utiliser l’approche SAX ?

Le Kit de développement logiciel (SDK) Open XML offre deux façons d’analyser les fichiers Office Open XML : le modèle DOM (Document Object Model) et l’API simple pour XML (SAX). L’approche DOM est conçue pour faciliter l’interrogation et l’analyse des fichiers Open XML à l’aide de classes fortement typées. Toutefois, l’approche DOM nécessite le chargement de parties Open XML entières dans la mémoire, ce qui peut entraîner un traitement plus lent et Out of Memory des exceptions lors de l’utilisation de parties très volumineuses. L’approche SAX lit dans le xml d’un élément Open XML partie un élément à la fois sans lire l’intégralité de la partie en mémoire, ce qui donne un accès non mis en cache, avant uniquement aux données XML, ce qui en fait un meilleur choix lors de la lecture de très grandes parties, comme un WorksheetPart avec des centaines de milliers de lignes.

Utilisation de l’approche DOM

À l’aide de l’approche DOM, nous pouvons tirer parti des classes fortement typées du Kit de développement logiciel (SDK) Open XML. La première étape consiste à accéder au du package et à vérifier qu’il WorksheetPart n’est pas null.

using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
{
    // Get the first sheet
    WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

    if (worksheetPart is not null)

Une fois qu’il est déterminé que le WorksheetPart à copier n’est pas null, ajoutez un nouveau WorksheetPart pour le copier. Ensuite, clonez le WorksheetWorksheetPartet affectez le cloné Worksheet à la nouvelle WorksheetPartpropriété Worksheet de .

// Add a new WorksheetPart
WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

// Make a copy of the original worksheet
Worksheet newWorksheet = (Worksheet)worksheetPart.Worksheet.Clone();

// Add the new worksheet to the new worksheet part
newWorksheetPart.Worksheet = newWorksheet;

À ce stade, le nouveau WorksheetPart a été ajouté, mais un nouvel Sheet élément doit être ajouté aux WorkbookPartéléments enfants de Sheetspour qu’il s’affiche. Pour ce faire, recherchez d’abord l’ID du nouveau WorksheetPartet créez un ID de feuille en incrémentant le Sheets nombre d’un, puis ajoutez un nouvel Sheet enfant à l’élément Sheets . Avec cela, la feuille de calcul copiée est ajoutée au fichier.

// Find the new WorksheetPart's Id and create a new sheet id
string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

// Append a new Sheet with the WorksheetPart's Id and sheet id to the Sheets element
sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });

Utilisation de l’approche SAX

L’approche SAX fonctionne sur les parties. Par conséquent, la première étape est la même en utilisant l’approche SAX. Accédez au package et assurez-vous qu’il WorksheetPart n’est pas null.

using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
{
    // Get the first sheet
    WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

    if (worksheetPart is not null)

Avec SAX, nous n’avons pas accès à la Clone méthode . Au lieu de cela, commencez par ajouter un nouveau WorksheetPart à .WorkbookPart

WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

Créez ensuite une instance de avec OpenXmlPartReader le composant de feuille de calcul d’origine et une instance du OpenXmlPartWriter avec le composant de feuille de calcul nouvellement créé.

using (OpenXmlReader reader = OpenXmlPartReader.Create(worksheetPart))
using (OpenXmlWriter writer = OpenXmlPartWriter.Create(newWorksheetPart))

Ensuite, lisez les éléments un par un avec la Read méthode . Si l’élément est un CellValue , le texte interne doit être explicitement ajouté à l’aide de la GetText méthode pour lire le texte, car le WriteStartElement n’écrit pas le texte interne d’un élément. Pour les autres éléments, nous devons uniquement utiliser la WriteStartElement méthode , car nous n’avons pas besoin du texte interne de l’autre élément.

// Write the XML declaration with the version "1.0".
writer.WriteStartDocument();

// Read the elements from the original worksheet part
while (reader.Read())
{
    // If the ElementType is CellValue it's necessary to explicitly add the inner text of the element
    // or the CellValue element will be empty
    if (reader.ElementType == typeof(CellValue))
    {
        if (reader.IsStartElement)
        {
            writer.WriteStartElement(reader);
            writer.WriteString(reader.GetText());
        }
        else if (reader.IsEndElement)
        {
            writer.WriteEndElement();
        }
    }
    // For other elements write the start and end elements
    else
    {
        if (reader.IsStartElement)
        {
            writer.WriteStartElement(reader);
        }
        else if (reader.IsEndElement)
        {
            writer.WriteEndElement();
        }
    }
}

À ce stade, la partie feuille de calcul a été copiée dans le composant qui vient d’être ajouté, mais comme avec l’approche DOM, nous devons toujours ajouter un Sheet à l’élément Workbookde Sheets . Étant donné que l’approche SAX donne un accès non mis en cache et avant uniquement aux données XML, il est uniquement possible d’ajouter des enfants d’éléments, ce qui, dans ce cas, ajouterait la nouvelle feuille de calcul au début plutôt qu’à la fin, en modifiant l’ordre des feuilles de calcul. L’approche DOM est donc nécessaire ici, car nous voulons ajouter sans ajouter le nouveau Sheet et comme le WorkbookPart n’est généralement pas une grande partie, les gains de performances seraient minimes.

Sheets? sheets = spreadsheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>();

if (sheets is null)
{
    spreadsheetDocument.WorkbookPart.Workbook.AddChild(new Sheets());
}

string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });

Exemple de code

Vous trouverez ci-dessous l’exemple de code pour les approches DOM et SAX pour copier les données d’une feuille vers une nouvelle feuille et les ajouter au document de feuille de calcul. Bien que l’approche DOM soit plus simple et dans de nombreux cas le choix préféré, avec des documents très volumineux, l’approche SAX est préférable, car elle est plus rapide et peut éviter les Out of Memory exceptions. Pour voir la différence, créez un document de feuille de calcul avec plusieurs lignes (10 000 et plus) et case activée les résultats de Stopwatch pour case activée la différence de temps d’exécution. Augmentez le nombre de lignes à plus de 100 000 pour obtenir des gains de performances encore plus significatifs.

Approche DOM

void CopySheetDOM(string path)
{
    Console.WriteLine("Starting DOM method");

    Stopwatch sw = new();
    sw.Start();
    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
    {
        // Get the first sheet
        WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

        if (worksheetPart is not null)
        {
            // Add a new WorksheetPart
            WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

            // Make a copy of the original worksheet
            Worksheet newWorksheet = (Worksheet)worksheetPart.Worksheet.Clone();

            // Add the new worksheet to the new worksheet part
            newWorksheetPart.Worksheet = newWorksheet;

            Sheets? sheets = spreadsheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>();

            if (sheets is null)
            {
                spreadsheetDocument.WorkbookPart.Workbook.AddChild(new Sheets());
            }

            // Find the new WorksheetPart's Id and create a new sheet id
            string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
            uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

            // Append a new Sheet with the WorksheetPart's Id and sheet id to the Sheets element
            sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });
        }
    }

    sw.Stop();

    Console.WriteLine($"DOM method took {sw.Elapsed.TotalSeconds} seconds");
}

Approche SAX

void CopySheetSAX(string path)
{
    Console.WriteLine("Starting SAX method");

    Stopwatch sw = new();
    sw.Start();
    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
    {
        // Get the first sheet
        WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

        if (worksheetPart is not null)
        {
            WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

            using (OpenXmlReader reader = OpenXmlPartReader.Create(worksheetPart))
            using (OpenXmlWriter writer = OpenXmlPartWriter.Create(newWorksheetPart))
            {
                // Write the XML declaration with the version "1.0".
                writer.WriteStartDocument();

                // Read the elements from the original worksheet part
                while (reader.Read())
                {
                    // If the ElementType is CellValue it's necessary to explicitly add the inner text of the element
                    // or the CellValue element will be empty
                    if (reader.ElementType == typeof(CellValue))
                    {
                        if (reader.IsStartElement)
                        {
                            writer.WriteStartElement(reader);
                            writer.WriteString(reader.GetText());
                        }
                        else if (reader.IsEndElement)
                        {
                            writer.WriteEndElement();
                        }
                    }
                    // For other elements write the start and end elements
                    else
                    {
                        if (reader.IsStartElement)
                        {
                            writer.WriteStartElement(reader);
                        }
                        else if (reader.IsEndElement)
                        {
                            writer.WriteEndElement();
                        }
                    }
                }
            }

            Sheets? sheets = spreadsheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>();

            if (sheets is null)
            {
                spreadsheetDocument.WorkbookPart.Workbook.AddChild(new Sheets());
            }

            string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
            uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

            sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });

            sw.Stop();

            Console.WriteLine($"SAX method took {sw.Elapsed.TotalSeconds} seconds");
        }
    }
}