IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PHP & Base de données Discussion :

Exportation d'un volume important de données vers excel [MySQL]


Sujet :

PHP & Base de données

  1. #1
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut Exportation d'un volume important de données vers excel
    Salam ; j'ai le script suivant pour exporté un volume variable de données vers excel.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
     
    <?php
    // exportation des données vers excel 
    include('../mod/cbd.inc.php');
    set_time_limit(0);
    header("Content-type: application/csv");
    header("Content-Disposition: attachment; filename=file.csv");
    header("Pragma: no-cache");
    header("Expires: 0");
     
    //Requete SQL
     
     
    			try {
    			$afflist=$cbd->prepare('SELECT * FROM conteneur 
    									  INNER JOIN com
    									  ON conteneur.cdc=com.cdc
    									  WHERE conteneur.cdet=:cdet 
    									  ORDER BY datedec DESC') or die('erreur de requête');
     
    								$cdet	=trim(htmlspecialchars($_SESSION['cdet']));
     
    								$afflist->bindParam(':cdet', $cdet ,  PDO::PARAM_STR); 
     
    								// On envois la requète
    								$afflist->execute();
    								// On récupère en 1er toutes les données
    								$datalist = $afflist->fetchAll();
    									// libèré la connexion du serveur
     
    								$afflist->closeCursor();	
     
    						// print_r ($datalist); exit;//verrification du résultat
    				}
    			  catch( Exception $e )
    				{
    				 echo 'Erreur d\'affichage : ', $e->getMessage();
    				} 
     
    while ($row = $afflist->fetch(PDO::FETCH_ASSOC)) {
          echo implode(';',$row ) . "\n";
       }
    ?>
    résultat:
    fichier excel qui s'ouvre mais pas sous le bon format?

  2. #2
    Rédacteur

    Avatar de Torgar
    Homme Profil pro
    Développeur Web
    Inscrit en
    Août 2007
    Messages
    2 334
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Août 2007
    Messages : 2 334
    Points : 8 084
    Points
    8 084
    Par défaut
    Salut,

    Que veux-tu dire par : pas le bon format ?

    Parce que tu crées un fichier CSV, donc niveau présentation c'est pas le top c'est sûr.
    Je ne suis pas schizophrène, nous sommes unanime !!!

    ► Pensez à la balise code et au CODE HTML GENERE !!!!
    ► Au si c'est le cas et au à ceux qui vous ont aidé.
    Vous souhaitez participer aux rubriques CSS et (X)HTML ? Contactez-nous !

    Créer des colonnes de même hauteur en CSS
    Glossaire CSS
    Les bordures en CSS3
    Les transitions en CSS3

  3. #3
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    oui désolé : un fichier vide !!!!
    si j'active
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    ....
    print_r ($datalist); exit;//verrification du résultat
    j'ai des array avec des données affichés dans la page excel comme le resultat d'un var_dump

  4. #4
    Rédacteur

    Avatar de Torgar
    Homme Profil pro
    Développeur Web
    Inscrit en
    Août 2007
    Messages
    2 334
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Août 2007
    Messages : 2 334
    Points : 8 084
    Points
    8 084
    Par défaut
    Et si tu parcourais le bon tableau ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    while ($row = $afflist->fetch(PDO::FETCH_ASSOC)) {
        echo implode(';',$row ) . "\n";
    }
    Ca, ça ne fonctionne pas et c'est normal. $afflist n'est plus disponible vu que tu le fermes.

    En plus, tu donnes toi même le nom de la variable qui contient tes données.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    foreach ($datalist as $data) {
        echo implode(';', $data) . "\n";
    }
    Je ne suis pas schizophrène, nous sommes unanime !!!

    ► Pensez à la balise code et au CODE HTML GENERE !!!!
    ► Au si c'est le cas et au à ceux qui vous ont aidé.
    Vous souhaitez participer aux rubriques CSS et (X)HTML ? Contactez-nous !

    Créer des colonnes de même hauteur en CSS
    Glossaire CSS
    Les bordures en CSS3
    Les transitions en CSS3

  5. #5
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    $afflist n'est plus disponible vu que tu le fermes.
    là tu parle de $afflist->closeCursor(); Ok
    sa marche bien , j'ai mes colonnes mais sans les entêtes !!!!

  6. #6
    Expert éminent sénior

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Points : 10 726
    Points
    10 726
    Par défaut
    fputcsv pour faire du CSV

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    header("Content-type: text/csv");
    header("Content-Disposition: attachment; filename=file.csv");
     
    $stmt = $pdo->prepare('
    SELECT * 
    FROM conteneur 
    INNER JOIN com
        ON conteneur.cdc=com.cdc
    WHERE conteneur.cdet=:cdet 
    ORDER BY datedec DESC'
    );
     
    $stmt->execute(array('cdet' => $_SESSION['cdet']));
     
    $output = fopen('php://output', 'wb');
     
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    $headers = array_keys($row);
     
    fputcsv($output, $headers, ';');
     
    do {
        fputcsv($output, $row, ';');
    } while ($row = $stmt->fetch(PDO::FETCH_NUM));

    Ton try .. catrch sert a rien
    Ton or die ne sert à a rien
    Tes trim + htmlspecialchars servent a rien
    Ton PDO::PARAM_STR sert a rien
    Ton fetchAll sert a rien
    Ton closeCursor sert a rien
    Tes headers Pragma et Expires ne servent a rien
    Le MIME du CSV est text/csv

    Mais le mieux est de mettre ça dans un fichier avant

  7. #7
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    Merci stealth35 (pour ne pas t’embêter avec le 36).
    avec ton code sa donne dans le fichier excel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    <font size='1'><table class='xdebug-error' dir='ltr' border='1' cellspacing='0' cellpadding='1'>			
    <tr><th align='left' bgcolor='#f57900' colspan="5"><span style='background-color: #cc0000	 color: #fce94f	 font-size: x-large	'>( ! )</span> Notice: Undefined variable: pdo in C:\wamp\www\s3g\interfaces\exportenreg.php on line <i>79</i></th></tr>
    <tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>			
    <tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>			
    <tr><td bgcolor='#eeeeec' align='center'>1</td><td bgcolor='#eeeeec' align='center'>0.0163</td><td bgcolor='#eeeeec' align='right'>372552</td><td bgcolor='#eeeeec'>{main}(  )</td><td title='C:\wamp\www\s3g\interfaces\exportenreg.php' bgcolor='#eeeeec'>..\exportenreg.php<b>:</b>0</td></tr>			
    </table></font>			
    <br />			
    <font size='1'><table class='xdebug-error' dir='ltr' border='1' cellspacing='0' cellpadding='1'>			
    <tr><th align='left' bgcolor='#f57900' colspan="5"><span style='background-color: #cc0000	 color: #fce94f	 font-size: x-large	'>( ! )</span> Fatal error: Call to a member function prepare() on a non-object in C:\wamp\www\s3g\interfaces\exportenreg.php on line <i>79</i></th></tr>
    <tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>			
    <tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>			
    <tr><td bgcolor='#eeeeec' align='center'>1</td><td bgcolor='#eeeeec' align='center'>0.0163</td><td bgcolor='#eeeeec' align='right'>372552</td><td bgcolor='#eeeeec'>{main}(  )</td><td title='C:\wamp\www\s3g\interfaces\exportenreg.php' bgcolor='#eeeeec'>..\exportenreg.php<b>:</b>0</td></tr>			
    </table></font>

  8. #8
    Expert éminent sénior

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Points : 10 726
    Points
    10 726
    Par défaut
    normale t'as copier bêtement mon code, c'était un exemple met les bonnes variables ...

  9. #9
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    Bêtement :
    alors comment j'ai detecté le ) qui manqué
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $output = fopen('php://output', 'wb');

  10. #10
    Expert éminent sénior

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Points : 10 726
    Points
    10 726
    Par défaut
    Citation Envoyé par redoran Voir le message
    Bêtement :
    alors comment j'ai detecté le ) qui manqué
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $output = fopen('php://output', 'wb');
    qui manque quoi ?

  11. #11
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    Merci stealth35 pour ton aide voila le script donc j'ai mit 46 minutes pour le comprendre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    header("Content-type: text/csv");
    header("Content-Disposition: attachment; filename=liste_declaration.csv");
     
    $stmt = $cbd->prepare('
    SELECT * 
    FROM conteneur 
    INNER JOIN com
        ON conteneur.cdc=com.cdc
    WHERE conteneur.cdet=:cdet 
    ORDER BY datedec DESC'
    );
     
    $stmt->execute(array('cdet' => $_SESSION['cdet']));
    $stmt->setFetchMode(PDO::FETCH_NUM);// retourne un tableau indexé par le numéro de la colonne comme elle est retourné dans votre jeu de résultat, commençant à 0 
     
    $output = fopen('php://output', 'wb');
     
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    $headers = array_keys($row);
     
    fputcsv($output, $headers, ';');  //  Formate une ligne en CSV et l'écrit dans un fichier l'entete 
     
    do {
        fputcsv($output, $row, ';');
    } while ($row = $stmt->fetch(PDO::FETCH_NUM));
    j'ai un affichage avec decalement des dernières colonnes.
    je vais le travaillé !!!!
    merci encore une fois stealth35

  12. #12
    Membre averti
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    316
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 316
    Points : 366
    Points
    366
    Par défaut
    bonsoir,

    un vieux script qui marche super bien:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <?php
     
     
    header("Content-type: application/vnd.ms-excel");
    header("Content-disposition: filename=nom_du_fichier_".date("d-m-Y").".xls");
    // Liste les données de la table
     
    // -------------------------------------------
     
    OuvrirBase();
     
    $sql = mysql_query("SELECT * FROM ta_table ");
     
     
     
    if (mysql_num_rows($sql) != 0) {
     // titre des colonnes
     
     $fields = mysql_num_fields($sql);
     $i = 0;
     while ($i < $fields) {
       echo mysql_field_name($sql, $i)."\t";
       $i++;
     }
     echo "\r\n";
     
     // données de la table
     
     while ($arrSelect = mysql_fetch_array($sql, MYSQL_ASSOC)) {
      foreach($arrSelect as $elem) {
       echo "\"$elem\"\t";
      }
      echo "\r\n";
     }
    }
    FermerBase();
    ?>
    Emmanuel
    --------------------------------------
    aucune réponse en message privé.
    les discussions doivent profiter à tous.

  13. #13
    Expert éminent sénior

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Points : 10 726
    Points
    10 726
    Par défaut
    Citation Envoyé par notar Voir le message
    bonsoir,

    un vieux script qui marche super bien:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <?php
     
     
    header("Content-type: application/vnd.ms-excel");
    header("Content-disposition: filename=nom_du_fichier_".date("d-m-Y").".xls");
    // Liste les données de la table
     
    // -------------------------------------------
     
    OuvrirBase();
     
    $sql = mysql_query("SELECT * FROM ta_table ");
     
     
     
    if (mysql_num_rows($sql) != 0) {
     // titre des colonnes
     
     $fields = mysql_num_fields($sql);
     $i = 0;
     while ($i < $fields) {
       echo mysql_field_name($sql, $i)."\t";
       $i++;
     }
     echo "\r\n";
     
     // données de la table
     
     while ($arrSelect = mysql_fetch_array($sql, MYSQL_ASSOC)) {
      foreach($arrSelect as $elem) {
       echo "\"$elem\"\t";
      }
      echo "\r\n";
     }
    }
    FermerBase();
    ?>
    mais qui ne gère ni l'échappement ni l'enclouure, et le rendu est un CSV et non un Excel le MIME n'est pas correct

  14. #14
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    Salam ; merci notar , j'ai vue ce script mais apparemment il m'aide pas trop suite aux points relevés par stealth35 , i;pose problème devant un volume important.
    pour le déclement des dernieres colonnes , l'intervention été au niveau de la requête comme suite:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    ........
    $stmt = $cbd->prepare('SELECT
                                    c.datedec,
                                     com,
                                     c.m,c.f,
                                     c.m1,c.f1,
                                     c.m2,c.f2,
                                     c.m3,c.f3,
                                     c.m4,c.f4,
                                     c.m5,c.f5,
                                     c.m6,c.f6,
                                     c.m7,c.f7,
                                     c.m8,c.f8 
                                            FROM conteneur c 
    						INNER JOIN com
    							ON c.cdc=com.cdc
    						WHERE c.cdet=:cdet 
    						ORDER BY c.datedec DESC'
    						);
     
    .....................
    sa marche très bien.
    stealth35: excuse mais j'ai pas compris
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $output = fopen('php://output', 'wb');
    'wb' : pour Ouvre en écriture seule ; place le pointeur de fichier au début du fichier et réduit la taille du fichier à 0. Si le fichier n'existe pas, on tente de le créer. selon la doc.
    'php://output' là ce qui est en rouge normalement c'est le c:// non...! merci de l’expliqué ?

  15. #15
    Expert éminent sénior

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Points : 10 726
    Points
    10 726
    Par défaut
    http://www.php.net/manual/fr/wrappers.php.php

    comme fgetcsv ne peux écrire que sur un wrapper c'est le seul moyen d'afficher une ligne CSV
    Mais le mieux est de mettre tout ça dans un fichier et ensuite de le lire, ça de permettra de récupérer la taille pour l’envoyer au navigateur

  16. #16
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    normalement le script gère des quantités importante de données non!!! ou il y a des limites?

  17. #17
    Expert éminent sénior

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Points : 10 726
    Points
    10 726
    Par défaut
    Citation Envoyé par redoran Voir le message
    normalement le script gère des quantités importante de données non!!! ou il y a des limites?
    t'es en stream donc la limite est l’exécution du temps du script

  18. #18
    Membre éprouvé Avatar de redoran
    Homme Profil pro
    Développeur-Amateur
    Inscrit en
    Juin 2010
    Messages
    1 346
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Algérie

    Informations professionnelles :
    Activité : Développeur-Amateur
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 346
    Points : 1 031
    Points
    1 031
    Par défaut
    donc sa reste a testé. merci stealth35 pour ta patience avec moi allez je t'invite chez si tu fait pas de regime

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. C# import export de donnés vers excel
    Par dualife dans le forum Général Dotnet
    Réponses: 6
    Dernier message: 12/06/2013, 16h23
  2. Code VBA pour importer des données vers Excel
    Par thanmirt dans le forum SDK
    Réponses: 1
    Dernier message: 09/04/2011, 17h53
  3. Réponses: 0
    Dernier message: 18/08/2010, 12h43
  4. exportation et importation de données vers excel
    Par Skizo dans le forum Access
    Réponses: 3
    Dernier message: 20/05/2006, 09h50
  5. Export de données vers Excel
    Par psykot63 dans le forum Access
    Réponses: 2
    Dernier message: 03/01/2005, 12h04

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo