Forum


Replies: 8   Views: 258
Word to html, display empty line breaks, and work with tables using addtable

Posted by Damian Rousseau  · 17-10-2024 - 14:36

Hello !

I've bought the Advanced license of Phpdocx to convert Word to HTML. However, I am having difficulty converting word style to CSS correctly.

How can I match as must as possible the docx file style?

Many thanks.

Posted by admin  · 17-10-2024 - 14:49

Hello,

If you want to display empty line breaks in a web browser when transforming DOCX to HTML, please enable the includeBlankSpacesInEmptyParagraphs option (https://www.phpdocx.com/api-documentation/format-conversion/transform-docx-html-using-native-php-classes):

$transform->transform($transformHTMLPlugin, ['includeBlankSpacesInEmptyParagraphs' => true]);

Regarding the internal table border question, please check you are setting the borders per cell, not as a global table style. Also please check you are using specific values instead of theme ones.

On https://www.phpdocx.com/documentation/introduction/word-to-html-PHP (Supported OOXML tags and attributes section) are detailed all supported Word tags and styles in the current stable release of DOCX to HTML, and how they are transformed to HTML.

If you send the DOCX document you are transforming to contact[at]phpdocx.com, we'll check it and send some tips to improve the conversion.

Regards.

Posted by Damian Rousseau  · 18-10-2024 - 07:41

Hello ! 

Many thanks for your answer, that solve the half of the problem but it's in a good way ! :-)

The transform action still put all border of a table when i do a line break. But i can always remove it.

Posted by admin  · 18-10-2024 - 08:21

Hello,

Maybe the borders are applied using an unsupported style in the current stable version. Although you can remove them, if you send the DOCX to contact[at]phpdocx.com, we'll check and send it to the dev team.

Regards.

Posted by Damian Rousseau  · 23-10-2024 - 14:53

Many thanks for the help by mail.

Can i use replaceVariableByText() and replaceTableVariable() at the same time on the same template ? I tried to use the same template symbol that i used with replaceVariableByText().

For exemple : the variable ${clientRaison} must be replace by a string and the variable ${clientEcheanceTable}  must be replace by a table.

I encounter a error while trying it : Undefined array key 0

If i'm not specific enough let me know it. 

Posted by admin  · 23-10-2024 - 15:04

Hello,

You can use template methods at the same time with the same template as many times as you need.

Please note that replaceVariableByText replaces a text placeholder with new text content. To use replaceTableVariable the placeholders must be added to a table.

If you want to replace a text placeholder with a table (or any other complex content such as images, charts, text with next styles...), you need to use replaceVariableByWordFragment. For example:

$tableWordFragment = new WordFragment($docx);
$valuesTable = array(
    array(11, 12, 13, 14),
    array(21, 22, 23, 24),
    array(31, 32, 33, 34),
);
$paramsTable = array('border' => 'single', 'tableAlign' => 'center');
$tableWordFragment->addTable($valuesTable, $paramsTable);

$docx->replaceVariableByWordFragment(array('VAR' => $tableWordFragment));

Please note that replaceVariableByWordFragment does "block" type replacements by default.

For more information about WordFragments, we recommend you read the documentation at https://www.phpdocx.com/documentation/practical/wordfragments-and-wordml and check the samples included in the package (Templates/replaceVariableByWordFragment folder).

Regards.

Posted by Damian Rousseau  · 24-10-2024 - 07:40

Thanks for the quick answer.

Okay i see better how it work ! Many thanks once again !

Best regards,

Posted by Damian Rousseau  · 24-10-2024 - 13:45

Hello,

I'm sorry to bother you again, but the style isn't apply at all (i tried to bold the header, center all text in table, set font to "Eurostile" and size at 10) but i didn't do annythings.

Do i do it wrong ?

Here is my code :

$evenement = Event::find($request->evenement);
        $contrat = Contrats::find($request->contrat);
        $dossier = File::find($request->dossier);
        $options = Option::get();
        $listeProduits = '';
        $echeances = array(
            array(
                array('value' => 'Dates d’encaissement'),
                array('value' => 'Montant')
            )
        );
        $s3 = Storage::disk('s3')->get(str_replace("https://booking2a.s3.eu-west-3.amazonaws.com/", "", $contrat->path));
        Storage::disk('contrats')->put('./contrat.docx', $s3);
        
        if($evenement->even_type_id == 1) {
            $lieu = Mdn::where('ville_id', $evenement->villes_id_ville)->first()->adresse;
            $horaires = strip_tags(Mdn::where('ville_id', $evenement->villes_id_ville)->first()->horaires);
            $optionsClient = '';

            if($dossier->mdn_conf['angle']['type'] !== 'angle0'){
                $optionsClient = $optionsClient.'\n- Angle(s) (sous réserve de disponibilité et d’accord de la Société 2A)\n◠'.substr($dossier->mdn_conf['angle']['type'], -1).' angle(s) de '.substr($dossier->mdn_conf['chalet']['type'], -1).'m - '.number_format($dossier->mdn_conf['angle']['prix'], 2, ',', ' ').' € HT soit '.number_format($dossier->mdn_conf['angle']['prix']*1.2, 2, ',', ' ').' € TTC\n';
            }

            if($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'elec')->count() > 0) {
                $optionsClient = $optionsClient.'\n- Supplément(s) électrique (sous réserve de disponibilité et d’accord de la Société 2A)\n';
                foreach($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'elec') as $optElec) {
                    $optionsClient = $optionsClient.'◠'.$dossier->mdn_conf['options'][$optElec->id]['nom'].' x'.$dossier->mdn_conf['options'][$optElec->id]['qte'].' - '.number_format($dossier->mdn_conf['options'][$optElec->id]['prix'], 2, ',', ' ').' € HT soit '.number_format($dossier->mdn_conf['options'][$optElec->id]['prix']*1.2, 2, ',', ' ').' € TTC\n';
                }
            }

            if($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'rest')->count() > 0) {
                $optionsClient = $optionsClient.'\n- Restauration & gaz (sous réserve de disponibilité et d’accord de la Société 2A)\n';
                foreach($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'rest') as $optRest) {
                    $optionsClient = $optionsClient.'◠'.$dossier->mdn_conf['options'][$optRest->id]['nom'].' x'.$dossier->mdn_conf['options'][$optRest->id]['qte'].' - '.number_format($dossier->mdn_conf['options'][$optRest->id]['prix'], 2, ',', ' ').' € HT soit '.number_format($dossier->mdn_conf['options'][$optRest->id]['prix']*1.2, 2, ',', ' ').' € TTC\n';
                }
                $optionsClient = $optionsClient.'\nL’utilisation du gaz dans les chalets nécessite le stockage des bouteilles de gaz dans des coffres grillagés spécifiques et dimensionnés à l’usage. Pour des raisons de sécurité et afin de s’assurer du respect de la jauge de bouteilles de gaz autorisées sur le Marché de noël, la fourniture de bouteilles de gaz sera prise en charge par un prestataire sélectionné par la société 2A. L’Occupant s’engage à ne pas apporter ses propres bouteilles de gaz sur le Marché de noël et à ne pas solliciter un fournisseur tiers pour se faire livrer lesdites bouteilles de gaz.\n';
            }

            if($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'tente')->count() > 0) {
                $optionsClient = $optionsClient.'\n- Tente (sous réserve de disponibilité et d’accord de la Société 2A)\n';
                foreach($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'tente') as $t) {
                    $optionsClient = $optionsClient.'◠'.$dossier->mdn_conf['options'][$t->id]['nom'].' x'.$dossier->mdn_conf['options'][$t->id]['qte'].' - '.number_format($dossier->mdn_conf['options'][$t->id]['prix'], 2, ',', ' ').' € HT soit '.number_format($dossier->mdn_conf['options'][$t->id]['prix']*1.2, 2, ',', ' ').' € TTC\n';
                }
                $optionsClient = $optionsClient.'\nCertains emplacements de chalet peuvent bénéficier à leurs abords d’une tente restauration ainsi qu’une tente arrière-cuisine. La tente de restauration et ces accessoires (ci-après désigné individuellement « Meuble » ou collectivement « Meubles ») seront mis à disposition de l’Occupant pendant toute la durée de la Convention. Les Meubles restent la propriété pleine et entière de la Société 2A. Ils sont insaisissables et incessibles. L’Occupant est considéré comme gardien responsable du/des Meubles dès la livraison et jusqu’à son retour. L’Occupant s’oblige expressément à n’utiliser le/les Meubles que pour l’usage auquel il(s) est/sont normalement destiné(s) de manière raisonnable avec prudence, diligence et précaution. Dès lors il est expressément convenu qu’aucun clouage, pose d’adhésif ou peinture, suspension, perçage ou fixation au sol ou aux structures provisoires, ne pourront être effectuées sur le(s) Meuble(s) et sur l’emplacement, sauf accord préalable et écrit de la Société 2A. L’Occupant devra tenir, à ses frais exclusifs, le/les Meuble(s) en parfait état d’entretien et de fonctionnement.\nLa zone de convivialité sera constituée de Mange debout et/ou de comptoir(s) couvert(s) (ci-après désigné individuellement « Meuble » ou collectivement « Meubles »). Les Meubles seront mis à disposition de l’Occupant pendant toute la durée de la Convention. Les Meubles restent la propriété pleine et entière de la Société 2A. Ils sont insaisissables et incessibles. L’Occupant est considéré comme gardien responsable du/des Meubles dès la livraison et jusqu’à son retour. L’Occupant s’oblige expressément à n’utiliser le/les Meubles que pour l’usage auquel il(s) est/sont normalement destiné(s) de manière raisonnable avec prudence diligence et précaution. Dès lors il est expressément convenu qu’aucun clouage, pose d’adhésif ou peinture, suspension, perçage ou fixation au sol ou aux structures provisoires, ne pourront être effectuées sur le(s) Meuble(s) et sur l’emplacement, sauf accord préalable et écrit de la Société 2A.\nL’Occupant devra tenir, à ses frais exclusifs, le/les Meuble(s) en parfait état d’entretien et de fonctionnement. L’Occupant devra respecter la surface de la Zone de convivialité.\nL’Occupant assurera sous son unique et entière responsabilité et à ses frais exclusifs, la sécurité et le gardiennage du/des Meuble(s).\nL’Occupant ne pourra ni céder, ni sous-louer le/les Meubles ou la Zone de convivialité.\nLe transfert des risques de perte et de détérioration est réalisé dès la prise de possession du/des Meuble(s) lors de la livraison sur le Marché.\nChaque Meuble est réputé en bon état le jour de sa remise par la Société 2A, sans recours contre la Société 2A dans un délai de 2 (deux) jours, pour quelque cause que ce soit et, notamment, pour mauvais état, vices apparents ou cachés.\nEn cas de dommages causés au(x) Meuble(s) de quelque nature qu’ils soient, et notamment en cas de perte ou de destruction totale ou partielle, il est d’ores et déjà convenu que l’Occupant s’engage à indemniser la Société 2A, d’un montant forfaitaire et global de 200 € /mange debout et 300 € / comptoir couvert.\nEn cas de dommages quelconque sur le bien loué nécessitant son démontage d’urgence, l’Occupant ne pourra invoquer aucun droit ou indemnisation pour quelque titre que ce soit. L’intégralité de la facture de mise à disposition sera due, majorée des frais de réparation ou de remplacement. Seule la Société 2A pourra évaluer l’ampleur des dégâts et prévoir les conséquences éventuelles.\nAvant le démontage du Meuble, l’Occupant et la Société 2A feront un état des lieux du matériel constatant les éventuels manquements, dégradations, ou dégâts qui seront écrits sur le bon de reprise. En cas d’absence de l’Occupant ou à défaut, les constatations de la Société 2A seront réputées acceptées par l’Occupant sans que ce dernier ne puisse élever aucune contestation.\n';
            }

            if($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'tenteOpt')->count() > 0) {
                $optionsClient = $optionsClient.'\n- Option(s) de tente (sous réserve de disponibilité et d’accord de la Société 2A)\n';
                foreach($options->whereIn('id', array_keys($dossier->mdn_conf['options']))->where('type', 'tenteOpt') as $optTente) {
                    $optionsClient = $optionsClient.'◠'.$dossier->mdn_conf['options'][$optTente->id]['nom'].' x'.$dossier->mdn_conf['options'][$optTente->id]['qte'].' - '.number_format($dossier->mdn_conf['options'][$optTente->id]['prix'], 2, ',', ' ').' € HT soit '.number_format($dossier->mdn_conf['options'][$optTente->id]['prix']*1.2, 2, ',', ' ').' € TTC\n';
                }
            }
        }else{
            $lieu = '';
            $horaires = '';
            $optionAngle = '';
            $optionElec = '';
            $optionRest = '';
            $tente = '';
            $optionTente = '';
        }

        foreach($dossier->product_details as $produit) {
            $listeProduits = $listeProduits.'- '.$produit->nom.' (Provenance : '.$produit->provenance.' / Fournisseur : '.$produit->localisation_fourn.')\n';
        }

        foreach($dossier->schedule->due as $echeance) {
            array_push($echeances, array(
                    array('value' => $echeance->date !== null ? 'Encaissable le '.$echeance->date->format('d/m/Y') : 'Encaissable à validation'),
                    array('value' => number_format($echeance->montant, 2, ',', ' ').' €')
                ) 
            );
        }

        // load template
        $docx = new \Phpdocx\Create\CreateDocxFromTemplate(public_path('contrats').'/contrat.docx');
        $fragmentTableau = new \Phpdocx\Elements\WordFragment($docx);
        // set custom placeholder symbols as ${}
        $docx->setTemplateSymbol('${', '}');
        // Définir les propriétés de texte pour le tableau
        $cellStyleHeader = array(
            'font' => 'Eurostile',
            'fontSize' => 10,
            'bold' => true
        );
        $cellStyleBody = array(
            'font' => 'Eurostile',
            'fontSize' => 10
        );
        $fragmentTableau->addTable($echeances, 
            array(
                'border' => 'single',
                'tableAlign' => 'center',
                'textProperties' => array(
                    0 => $cellStyleHeader,  // Applique les styles à la première ligne (header)
                    1 => $cellStyleBody,    // Applique les styles au reste du tableau
                )
            )
        );
        // replace variables
        $variables = [
            'ville' => mb_strtoupper($evenement->ville->nom),
            'evenementJourMoisDebutIso' => $evenement->date_début->isoFormat('DD MMMM'),
            'evenementJourMoisFinIso' => $evenement->date_fin->isoFormat('DD MMMM'),
            'evenementAnneeIso' => $evenement->date_début->format('Y'),
            'evenementLieu' => $lieu,
            'evenementFddHT' => number_format($dossier->mdn_conf['fdd'], 2, ',', ' '),
            'evenementFddTTC' => number_format($dossier->mdn_conf['fdd']*1.2, 2, ',', ' '),
            'evenementHoraires' => $horaires,
            'expositionJourMoisDebutIso' => $evenement->installation_exp->isoFormat('DD MMMM'),
            'expositionJourMoisFinIso' => $evenement->depart_exp->isoFormat('DD MMMM'),
            'clientRaison' => $dossier->web_client->raison !== null ? mb_strtoupper($dossier->web_client->raison) : mb_strtoupper($dossier->web_client->nom).' '.$dossier->web_client->prenom,
            'clientNomPrenom' => mb_strtoupper($dossier->web_client->nom).' '.$dossier->web_client->prenom,
            'clientFormeJuridique' => $dossier->web_client->forme_juridique ?? 'n/a',
            'clientNumeroTvaIntra' => $dossier->web_client->tva ?? 'n/a',
            'clientNumeroRcs' => $dossier->web_client->rcs_rm ?? 'n/a',
            'clientAdresse' => $dossier->web_client->adresse ?? 'n/a',
            'clientCp' => $dossier->web_client->cp ?? 'n/a',
            'clientVille' => $dossier->web_client->ville ?? 'n/a',
            'clientPortable' => $dossier->web_client->tel ?? 'n/a',
            'clientEmail' => $dossier->web_client->email ?? 'n/a',
            'clientTypeProduits' => $dossier->category->nom,
            'clientDescriptionProduits' => $dossier->desc_produit,
            'clientListeProduits' => $listeProduits,
            'clientTailleStructure' => $dossier->space !== null ? $dossier->space->type->nom : $dossier->mdn_conf['chalet']['type'],
            'clientPrixChaletHT' => number_format($dossier->mdn_conf['chalet']['prix'], 2, ',', ' '),
            'clientPrixChaletTTC' => number_format($dossier->mdn_conf['chalet']['prix']*1.2, 2, ',', ' '),
            'clientOptions' => $optionsClient,
        ];

        $docx->replaceVariableByText($variables, ['parseLineBreaks' => true]);
        $docx->replaceVariableByWordFragment(array('clientEcheances' => $fragmentTableau), array('type' => 'block'));

        $docx->createDocx(public_path('contrats').'/contrat_formate.docx');

        return response()->download(public_path('contrats').'/contrat_formate.docx');

 

Posted by admin  · 24-10-2024 - 14:47

Hello,

We recommend you check and run the samples included in the package, that illustrate many use cases.
The problem with your code is that the following structure:

'textProperties' => array(
  0 => $cellStyleHeader,  // Applique les styles à la première ligne (header)
  1 => $cellStyleBody,    // Applique les styles au reste du tableau
)

isn't valid. On the API documentation page of addTable (https://www.phpdocx.com/api-documentation/word-content/add-table-Word-document-with-PHP), you can read the following information:

textProperties    array   It may include any of the paragraph properties of the addText method so you can completely customize the properies of the text content of the table.

This textProperties option applies to all string contents added to the table (that doesn't use a WordFragment), but it doesn't apply styles only to the first row or the second row. This only applies to the rowProperties option available in addTable, which supports setting an index number for each row.
From Contents/addTable/sample_1.php included in the package:

$valuesTable = array(
    array(11, 12, 13, 14),
    array(21, 22, 23, 24),
    array(31, 32, 33, 34),
);

$paramsTable = array(
    'border' => 'single',
    'tableAlign' => 'center',
    'borderWidth' => 10,
    'borderColor' => 'B70000',
    'textProperties' => array('bold' => true, 'font' => 'Algerian', 'fontSize' => 18),
);

$docx->addTable($valuesTable, $paramsTable);

If you want to apply styles to the cells, you need to use an array as a cell value.
From Contents/addTable/sample_3.php included in the package:

$col_1_1 = array(
    'rowspan' => 4,
    'value' => '1_1',
    'backgroundColor' => 'cccccc',
    'borderColor' => 'b70000',
    'border' => 'single',
    'borderTopColor' => '0000FF',
    'borderWidth' => 16,
    'cellMargin' => 200,
);

$col_2_2 = array(
    'rowspan' => 2,
    'colspan' => 2,
    'width' => 200,
    'value' => $textFragment,
    'backgroundColor' => 'ffff66',
    'borderColor' => 'b70000',
    'border' => 'single',
    'cellMargin' => 200,
    'fitText' => 'on',
    'vAlign' => 'bottom',
);

$col_2_4 = array(
    'rowspan' => 3,
    'value' => 'Some rotated text',
    'backgroundColor' => 'eeeeee',
    'borderColor' => 'b70000',
    'border' => 'single',
    'borderWidth' => 16,
    'textDirection' => 'tbRl',
);

// set the global table properties
$options = array(
    'columnWidths' => array(400,1400,400,400,400),
    'border' => 'single',
    'borderWidth' => 4,
    'borderColor' => 'cccccc',
    'borderSettings' => 'inside',
    'float' => array(
        'align' => 'right',
        'textMargin_top' => 300,
        'textMargin_right' => 400,
        'textMargin_bottom' => 300,
        'textMargin_left' => 400,
    ),
);
$values = array(
    array($col_1_1, '1_2', '1_3', '1_4', '1_5'),
    array($col_2_2, $col_2_4, '2_5'),
    array('3_5'),
    array('4_2', '4_3', '4_5'),
);

$docx->addTable($values, $options, $trProperties);

If you want to apply styles to the cell contents or add other contents such as images or other tables, you need to use WordFragments. 
From Contents/addTable/sample_4.php included in the package:

$textFragment = new WordFragment($docx, 'document');

$text = array();
$text[] = array('text' => 'Other text ');
$text[] = $footnote;

$paragraphOptions = array(
  'textAlign' => 'center',
  'bold' => true,
);
$textFragment->addText($text, $paragraphOptions);

$htmlFragment = new WordFragment($docx, 'document');

$htmlFragmentString = new WordFragment($docx, 'document');
$htmlFragmentString->embedHtml('<p style="font-family: verdana; font-size: 11px">HTML tags <b>bold</b></p>');

$textHtml = array();
$textHtml[] = $htmlFragmentString;

$htmlFragment->addText($textHtml);

$valuesTable = array(
  array(
    $textFragment,
  ),
  array(
    '2',
  ),
  array(
    $htmlFragment,
  ),
);

$docx->addTable($valuesTable);

The addTable method can apply table styles (alignment, sizes, ident...), row styles (height, header, split...), and cell styles (background color, borders...). The contents added into the cells can include their own styles using WordFragments (for example text alignment or text color). WordFragments can also be added when applying cell styles by setting the WordFragment as cell value:

$textFragment = new WordFragment($docx);
$text = array();
$text[] = array('text' => 'Fit text and ');
$text[] = array('text' => 'Word fragment', 'bold' => true);
$textFragment->addText($text);

$cell = array(
    'width' => 200,
    'value' => $textFragment,
    'backgroundColor' => 'ffff66',
    'borderColor' => 'b70000',
    'border' => 'single',
    'cellMargin' => 200,
    'fitText' => 'on',
    'vAlign' => 'bottom',
);

Also please note that you can create custom table styles using createTableStyle and apply them using addTable.

You can also create tables using embedHtml: https://www.phpdocx.com/htmlapi-documentation/html-standard/insert-table-Word-document-with-HTML , and use replaceVariableByHTML to replace a text placeholder with HTML in a DOCX template.

Regards.