manipulate template

Apr 21, 2011 at 11:36 AM
Edited Apr 21, 2011 at 11:39 AM

How can i create a dynamic table and insret it in a template

<?php
require_once '../PHPWord.php';
$competences = array(
        "Developpement"  => array(
              "JAVA","PHP","HTML"),
     
        "Data base"  => array(
              "ORACLE","MYSQL","DATABASE"),
     
        "Framework"  => array(
              "LIEFRY" ,"STRUTS" ,"NOHETO" 
			  )
);
$PHPWord = new PHPWord();

$document = $PHPWord->loadTemplate('CV.docx');

$document->setValue('Nom', 'Boukrim');
$document->setValue('Prenom', 'Ahmed');


/*****   dynamic table  ******/
// New portrait section

	$section = $PHPWord->createSection();
	$table = $section->addTable();
	foreach($competences as $cle1 => $valeur1)
		{
			foreach ($valeur1 as $cle2=>$valeur2)
				 
			{
			 $table->addRow();
			 $table->addCell(1750)->addText("$cle1,$valeur2");
			}
		}


/******   end dynamic table  ****/
$document->setValue('Competences',$table );

$document->save('Novediacv.docx');
?>

Nov 19, 2011 at 5:11 AM
Edited Nov 19, 2011 at 5:21 AM

Let me just start out with, I needed this functionality YESTERDAY, however as such, its not possible using this method.  

What I have found WORKS, will require a bit of know-how about how tables are laid out in XML (Don't worry, I explain enough of it here, if you have a passing knowledge of HTML markup, you'll be able to follow!).

WARNING:  I don't claim that the following code is 100% perfect word XML, but is the bare minimum of fussing that I've found still works fine in word (2010, at least).

Instead of using the built-in table setup (I realize this loses some formatting functionality, but peruse some xml sheets out of a live word document and you can figure out where to add in the formatting), I built my tables as a string of XML markup.

Essentially:

HTML:
<table></table>
is replaced by XML:
<w:tbl></w:tbl>

HTML:
<tr></tr>
is replaced by XML:
<w:tr></w:tr>

HTML:
<td>CONTENT HERE</td>
is replaced by XML:
<w:tc><w:p><w:r><w:t>CONTENT HERE</w:t></w:p></w:tc>

So if you generate your table something like this:

$table = "<w:tbl>";
foreach($competences as $cle1 => $valeur1){
  foreach($valeur1 as $cle2=>$valeur2){
    $table .= "<w:tr>";
    $table .= "<w:tc><w:p><w:r><w:t>";
    $table .= "$cle1,$valeur2";
    $table .= "</w:t></w:p></w:tc>";
    $table .= "</w:tr>";
  }
}
$table .= '</w:tbl>';

$document->setValue('Competences',$table);

I've found this works like a charm, however AS IS offers no formatting options.  If you want formatting options, tear apart a simple word document and look for the XML that does what you want it to do.

Hint:
At least for me, adding:
<w:tblPr>
<w:tblW w:w = "5000" w:type="pct"/>
</w:tblPr>
AFTER the <w:tbl>
gave me a 100% wide table with cells that auto-adjust their width.

Feb 15, 2012 at 4:01 PM

Hi xomby, great post! I'm having to build a word document with table data from a database.

Questions: what do you put in the template to replace as a table? In other words, where is ${Competences} in the xml markup? Is it just a line of text by itself where you want the table?

I'm using PHPWord to build my 'template' and plan to use the template functions to replace all the variable data. I'm assuming the docx file can be saved with a new name. Otherwise, I'll have to copy the template first each time a user needs to create a document. It's a customer quote in word format by the way.

Creating the pdf version using FOP was much simpler!

 

Thanks!

Feb 15, 2012 at 7:36 PM
Edited Feb 15, 2012 at 7:39 PM
frrogoy wrote:

...Questions: what do you put in the template to replace as a table? In other words, where is ${Competences} in the xml markup? Is it just a line of text by itself where you want the table?...

${COMPETENCES} would go in the template file, wherever you want the table to show up.  Essentially what the above "hack" code does is write the XML that gets inserted where ${COMPETENCES} goes...

Here's the important parts of the actual function I use in my application:

//takes in single DB letter data
function evalLetter($data,$filename){
	$PHPWord = new PHPWord();
	$template = $PHPWord->loadTemplate('templates/'.$data['code'].'.tpl.docx');
	$template->setValue('ACCOUNTNUMBER',$data['accountnumber']);
	$select = '
	SELECT * FROM data_debts,data_clients WHERE data_debts.accountnumber="'.$accountnumber.'" AND data_debts.currentamt>"0" AND data_debts.clientname = data_clients.accountnumber
	';
	$results = queryDB($select); //my queryDB function returns an object or false, depending on whether it gets a hit or not - so YMMV depending on how you return your query data
	$table = ''; //empty table
	if($results){ //if there was data returned from queryDB()
		$table .= '<w:tbl>';
		$table .= '<w:tblPr><w:tblW w:w = "5000" w:type="pct"/></w:tblPr>';
		foreach($results as $debt){
			$table .= '<w:tr>'; //new xml table row
			$table .= '<w:tc><w:p><w:r><w:t>'; //start cell
			$table .= $debt->clientname; //cell contents
			$table .= '</w:t></w:r></w:p></w:tc>'; //close cell
			$table .= '<w:tc><w:p><w:r><w:t>';
			$table .= $debt->clientaccount;
			$table .= '</w:t></w:r></w:p></w:tc>';
			$table .= '<w:tc><w:p><w:r><w:t>';
			$table .= $debt->regard;
			$table .= '</w:t></w:r></w:p></w:tc>';		
			$table .= '<w:tc><w:p><w:r><w:t>';
			$table .= int2cash($debt->currentamt);
			$table .= '</w:t></w:r></w:p></w:tc>';
			$table .= '</w:tr>';
		} //done with dynamic data
		$table .= '</w:tbl>'; //close xml table
		$template->setValue('ACCOUNTINFO',$table); //insert xml into template
	
	}else{setAlert('error','No info for this account'); return 0;} //returns 0 if proc failed
		$template->save('data/'.$filename.''); //save filled out template
		return 1; //returns 1 if everything worked
	}
}

 

As you can see above, I have two spots in my template that I replace (there's more, but for simplification this works) -- ${ACCOUNTNUMBER} and ${ACCOUNTINFO}

The accountinfo spot takes the table data, accountnumber spot just takes a single value.

you can build out your tables like this in any way you want. I'm sure that with a little finesse you could build a function that dynamically converts html table data to xml data, in this case I was in a rush to get the rough draft working, and haven't had to go back and modify it for anything yet.

At any rate, this code worked 100% for me.  Let me know if you have trouble implementing something similar.

note-- if in the directory that you are using to store the actual original templates you see  files that have filenames that look like random numbers, and they are accumulating, then there's SOMETHING WRONG, and the function is NOT closing out properly...  I've found that when this happens, the final "filled out" template file seems to be malformed as well.  Check all of your settings and permissions and just make sure you don't have any errors within your php script.  This was a head-against-wall issue for me a couple of times, and its a real pain to track down sometimes (see my other post about malformed output).

Feb 15, 2012 at 7:49 PM

Thanks! I am writing the code and it looks similar to yours. Let me ask the question a different way.

Did you manually add the ${ACCOUNTINFO} to the document.xml file, or did you use one of the PHPWord addText functions to get it there?

I realize I could just insert the placeholder manually since Word won't be used to open the template, and I don't think loadTemplate cares about it.

Feb 15, 2012 at 7:59 PM

I use Word to set up my templates - so ${ACCOUNTINFO} went into my template.docx file.

IF your query returns NO DATA - you could have ${ACCOUNTINFO} still be replaced, but by blank text...  same result as not putting ${ACCOUNTINFO} into the template in the first place.

If you're trying to dynamically add ${ACCOUNTINFO} directly to the template...  that's a whole can of worms you may not want to get into.

Feb 15, 2012 at 8:16 PM

OK, thanks. I'll try just adding it manually to the xml file just to see if that works. If that doesn't work, I'll add it in Word.

I used PHPWord to build the template. When I started I thought I would be able to use one program to dynamically build the whole file, including the data.

It would have taken less time to build the document in Word, with placeholders, and then just use the PHPWord template functions. I will probably have to modify the template in Word anyway to fix some of the formatting issues I can't get around with PHPWord - the minimum height problem in table rows, and no way to add special characters. It's a great tool though, and I';m sure it will get better.

Note: special characters could be added the way you are adding the table. But, again, it's easier to do it in Word than dig through a compressed xml file to find the sequence.

I'll post again when I get it working.

Feb 15, 2012 at 8:40 PM

Well, if you're up to it, what you COULD do is...

build the template (use phpword to create a docX) and wherever in that template you want the table to be, just put a ${TABLE} placeholder, then REPROCESS that template file like you would any other template.

Assuming you're building the original template file as a docX using phpword, you should be able to open it in word just to confirm that it's laid out exactly where you want it.

1. use PHPWord to build template docx - insert ${PLACEHOLDERS} wherever you will need them for the second pass

2. save that "temp" template.docx somewhere in your filesystem

3. re-open that template.docx as a template with PHPWord

4. complete your replacements

5. save the final docx

6. clean up the template files using PHP file handling functions

Oct 4, 2012 at 3:04 PM

xomby, just want to learn the interaction between MySQL and PHPWord. Here is what I am trying to do:

I am trying to link data from MySQL Database into a Word Document using PHPWord. I've set up the package on my server and examples are working fine. When I use the Simple example Text.php:

<!--p require_once 'PHPWord.php'; // New Word Document $PHPWord = new PHPWord(); // New portrait section $section = $PHPWor-->createSection();

// Add footer
$footer = $section->createFooter();
$footer->addPreserveText('Page {PAGE} of {NUMPAGES}.', array('align'=>'center'));

// Add text elements
$section->addText('Hello World!');
$section->addTextBreak(2);

$section->addText('I am inline styled.', array('name'=>'Verdana', 'color'=>'006699'));
$section->addTextBreak(2);

$PHPWord->addFontStyle('rStyle', array('bold'=>true, 'italic'=>true, 'size'=>16));
$PHPWord->addParagraphStyle('pStyle', array('align'=>'center', 'spaceAfter'=>100));
$section->addText('I am styled by two style definitions.', 'rStyle', 'pStyle');
$section->addText('I have only a paragraph style definition.', null, 'pStyle');


// Save File
$objWriter = PHPWord_IOFactory::createWriter($PHPWord, 'Word2007');
$objWriter->save('Text.docx');
?>



And try to add just before that:



include("config.inc.php3");
include("session.inc.php3");

$query="SELECT Opt1,Current_Exm FROM System WHERE Opt1='1'";
$rbsult=MYSQL_QUERY($query) or redirect("http://www.cspsopcs.com/portal/eng/error.php3?err=4682");
if(!$rbw=MYSQL_FETCH_ARRAY($rbsult))
  {
  redirect("error.php3?err=4682");
  }



The word document is not generated. I'd like to simply replace "HELLO WORLD" by a variable $rbw[1]

Is there a way or something I dont understand?



Thanks!

Oct 4, 2012 at 3:19 PM

Charest, 

This should really have been in a new thread, as it has no bearing on the current one.

Looking at your code, I'm not seeing anything that actually connects to a database before the MYSQ_QUERY.

When you run this code, currently,  is it redirecting you to the error page?

Oct 4, 2012 at 3:59 PM

HI, Thanks it is on post # 397684 but I read other post afterward and realize this might be something similar.

 

So yes please reply to the other post.

 

To answer your question no, it simply stop without creating the docx document. As soon as the first pasrt isnt there it work. If I add the first line:

 

include("config.inc.php");

it wont work.

 

Thanks!

 

 

Dec 19, 2012 at 4:33 PM
xomby wrote:

Let me just start out with, I needed this functionality YESTERDAY, however as such, its not possible using this method.  

What I have found WORKS, will require a bit of know-how about how tables are laid out in XML (Don't worry, I explain enough of it here, if you have a passing knowledge of HTML markup, you'll be able to follow!).

WARNING:  I don't claim that the following code is 100% perfect word XML, but is the bare minimum of fussing that I've found still works fine in word (2010, at least).

Instead of using the built-in table setup (I realize this loses some formatting functionality, but peruse some xml sheets out of a live word document and you can figure out where to add in the formatting), I built my tables as a string of XML markup.

Essentially:

HTML:
<table></table>
is replaced by XML:
<w:tbl></w:tbl>

HTML:
<tr></tr>
is replaced by XML:
<w:tr></w:tr>

HTML:
<td>CONTENT HERE</td>
is replaced by XML:
<w:tc><w:p><w:r><w:t>CONTENT HERE</w:t></w:p></w:tc>

So if you generate your table something like this:

$table = "<w:tbl>";
foreach($competences as $cle1 => $valeur1){
  foreach($valeur1 as $cle2=>$valeur2){
    $table .= "<w:tr>";
    $table .= "<w:tc><w:p><w:r><w:t>";
    $table .= "$cle1,$valeur2";
    $table .= "</w:t></w:p></w:tc>";
    $table .= "</w:tr>";
  }
}
$table .= '</w:tbl>';

$document->setValue('Competences',$table);

I've found this works like a charm, however AS IS offers no formatting options.  If you want formatting options, tear apart a simple word document and look for the XML that does what you want it to do.

 

Hint:
At least for me, adding:
<w:tblPr>
<w:tblW w:w = "5000" w:type="pct"/>
</w:tblPr>
AFTER the <w:tbl>
gave me a 100% wide table with cells that auto-adjust their width.

 

Hello,

It doesn't works for me, the final document seems to be corrupt...

When i open it i've an error about special chars (</> in table insert)

Do you know a solution ? I've tested the setvalue function with a simple value :

$template->setValue('mytab','&');
And the docuement is corrupt too... Why ?

Thx :) (sorry for my bad english)
Dec 24, 2012 at 10:39 AM
Edited Dec 24, 2012 at 10:44 AM

Hi okiseb, hi everyone,

Like okieb the first attempt gave me a corrupted document, there a small fault in the examples above : but I don't get any table in my document.

 

Here is the code I use :

 

		$PHPWord = new PHPWord();

		$document = $PHPWord->loadTemplate('myTemplate.docx');
		
		$competences = array(
		        "Developpement"  => array(
		              "JAVA","PHP","HTML"),
		     
		        "Data base"  => array(
		              "ORACLE","MYSQL","DATABASE"),
		     
		        "Framework"  => array(
		              "LIEFRY" ,"STRUTS" ,"NOHETO" 
					  )
		);
		$table = "<w:tbl>";
		foreach($competences as $cle1 => $valeur1){
			foreach($valeur1 as $cle2=>$valeur2){
				$table .= "<w:tr>";
				$table .= "<w:tc><w:p><w:r><w:t>";
				$table .= "$cle1,$valeur2";
				$table .= "</w:t></w:r></w:p></w:tc>";
				$table .= "</w:tr>";
			}
		}
		$table .= '</w:tbl>';
		
		$document->setValue('table', $table	);

For okiseb change the row 

$table .= "</w:t></w:p></w:tc>";

By

$table .= "</w:t></w:r></w:p></w:tc>";

Then the document will not be corrupted anymore.

Nevertheless this code write no table in my template.

Any help will be appreciated.

Best regards,

Roger

Feb 14, 2013 at 4:33 AM
hi xomby,

Your code was very succesfully, but I want to know how Can I set styles like borderColor or bgColor to the rows of the table and the text inside.

Do you know a solution ?

P.D. sorry for the bad English.
Mar 13, 2013 at 6:41 PM
Edited Mar 13, 2013 at 6:42 PM
I had the same issue. To resolve it I wrote a small function to clone a template row.

The concept is as follows:
  1. Create a template with one or more tables each containing one row that will be used as a template row
  2. Clone this template row as many times a required
  3. Use the existing setValue to set the values of all tags. The original tags have been appended with #rowNumber to identify the individuality tags on each row.
This is the function you'll have to add to the Template.php file found inside the PHPWord directory.
public function cloneRow($search, $numberOfClones) {
        if(substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') {
            $search = '${'.$search.'}';
        }
                
        $tagPos      = strpos($this->_documentXML, $search);
        $rowStartPos = strrpos($this->_documentXML, "<w:tr", ((strlen($this->_documentXML) - $tagPos) * -1));
        $rowEndPos   = strpos($this->_documentXML, "</w:tr>", $tagPos) + 7;

        $result = substr($this->_documentXML, 0, $rowStartPos);
        $xmlRow = substr($this->_documentXML, $rowStartPos, ($rowEndPos - $rowStartPos));
        for ($i = 1; $i <= $numberOfClones; $i++) {
            $result .= preg_replace('/\$\{(.*?)\}/','\${\\1#'.$i.'}', $xmlRow);
        }
        $result .= substr($this->_documentXML, $rowEndPos);

        $this->_documentXML = $result;
    }
Some more documentation and the latest version of this code can be found at http://jeroen.is/phpword-templates-with-repeating-rows/
Jan 31 at 10:38 AM
a suggestion to replace a token in template by a table

In /PHPWord/Shared/XMLWriter.php

Add this
public function setIndent($indent) {
    $this->_xmlWriter->setIndent((bool)$indent);
}
public function setIndentString($indentString) {
    $this->_xmlWriter->setIndentString((string)$indentString);
}
public function getWriter() {
    return $this->_xmlWriter;
}
In /PHPWord/Writer/Word2007/Base.php

replace on line 372 :
protected function _writeTable(PHPWord_Shared_XMLWriter $objWriter = null, PHPWord_Section_Table $table)
by
public function _writeTable(PHPWord_Shared_XMLWriter $objWriter = null, PHPWord_Section_Table $table)
And a example of use :
$PHPWord = new PHPWord();
$document = $PHPWord->loadTemplate('Template.docx');

// the construction of the table 
$section = $PHPWord->createSection();
$table = $section->addTable();
$competences = array(
    "Developpement"  => array("JAVA","PHP","HTML", "ttttt"),     
    "Data base"  => array("ORACLE","MYSQL","DATABASE", "ttttt"),     
    "Framework"  => array("LIEFRY" ,"STRUTS" ,"NOHETO", "ttttt"),
);
foreach($competences as $cle1 => $valeur1)
{
    foreach ($valeur1 as $cle2=>$valeur2)
    {
     $table->addRow();
     $table->addCell(1750)->addText("$cle1,$valeur2");
    }
}

$objWriter = new PHPWord_Shared_XMLWriter();
$objWriter->setIndent(FALSE);
$objWriter->setIndentString('');
$word2007Writer = new PHPWord_Writer_Word2007_Base();
$word2007Writer->_writeTable($objWriter, $table);
$xmlWriter = $objWriter->getWriter();
$output = $xmlWriter->outputMemory();

$document->setValue('test', $output );

$document->save('Solarsystem.docx');