Im trying to create a nested table with the library phpdocx. In their documentation they write that it is possible to have a nested table in a table cell. But its not clearly written how to make it work..
I tried the following code:
$valuesTable = array(
array(
array(array(1,2,34),12,13,14),
array(21,22,23,24),
array(31,32,33,34),
);
$params = array(
'border' => 'single',
'tableAlign' => 'center',
'borderWidth' => 10,
'borderColor' => 'B70000',
'textProperties' => array('bold' => true, 'font' => 'Algerian', 'fontSize' => 18),
);
$docx->addTable($valuesTable, $params);
But the cell is just empty. Is there an easy way to get this nested table displayed?
I finally found the solution. It is possible with WordFragments.
$innerData = array(1,2,3,4);
$innerTable = new \WordFragment($docx);
$innerTable->addTable($innerData, array('rawWordML' => true));
$tableParams = array(); // Add here the table params
$outerData = array("A", "B", $innerTable);
$outerTable->addTable($outerData, $tableParams);
Related
I am using PHPOffice/PHPWord in my Laravel Application. It is used to generate a .docx document with results in tables. This works great for a document of 3 tables with 6 rows, but when there are more rows the document is generated but when opening it the following error occurs:
We're sorry, We can't open (documentname) because we found a problem with its contents.
Details: XML parsing error Location: Part:/word/document.xml, Line: 2, Column 14349.
Now, I have started working on another result page where I would also want to generate a .docx document. This will contain 5 tables, but with 3 rows I get the same XML parsing error but in a different location (Location: Part: /word/document.xml, Line:4, Column:2888). Could someone explain to me whether this is a error in my code, or phpword/words?
I have done some troubleshooting by deleting everything, and slowly adding new rows. I have found the error but how could i fix it. The first two tables are generated good..
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection();
$section->addImage('../public/img/2.jpg', array('width' => 230, 'height' => 65, 'alignment' => 'left'));
$section->addText('Project IDs:' . $parameter);
$header =$section->addHeader();
$header->addText('Results Summary');
$section->addLine(
array(
'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(16),
'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(0),
'positioning' => 'absolute',
)
);
$tableName = 'rStyle';
$phpWord->addFontStyle($tableName, array('italic' => true, 'size' => 12));
$thName = 'tStyle';
$phpWord->addFontStyle($thName, array('bold' => true, 'size' => 9));
$section->addText('General Information Table', $tableName);
$fancyTableStyle = array('borderSize' => 6, 'borderColor' => '999999');
$spanTableStyleName = 'Overview tables';
$phpWord->addTableStyle($spanTableStyleName, $fancyTableStyle);
$table = $section->addTable($spanTableStyleName);
$table->addRow(null, array('tblHeader' => true, 'cantSplit' => true));
$table->addCell(1750)->addText('Project ID',$thName);
$table->addCell(1750)->addText('Description',$thName);
$table->addCell(1750)->addText('Notes',$thName);
foreach ($id_array_explode as $char) {
$table->addRow();
$singlenumber = (int)$char;
$cursor = $collection->find(array("id" => $singlenumber));
foreach ($cursor as $document) {
$table->addCell(1750)->addText($document["project_id"]);
$table->addCell(1750)->addText($document["description"]);
$table->addCell(1750)->addText($document["notes"]);
}
}
$section->addText('
');
$section->addLine(
array(
'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(16),
'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(0),
'positioning' => 'absolute',
)
);
$section->addText('Input Table', $tableName);
$table1 = $section->addTable($spanTableStyleName);
$table1->addRow(null, array('tblHeader' => true, 'cantSplit' => true));
$table1->addCell(1750)->addText('Project ID',$thName);
$table1->addCell(1750)->addText('#',$thName);
foreach ($id_array_explode as $char) {
$table1->addRow();
$singlenumber = (int)$char;
$cursor = $collection->find(array("id" => $singlenumber));
foreach ($cursor as $document) {
if (is_array($document['input'])) {
foreach ($document['input'] as $samples) {
$table1->addCell(1750)->addText($document["project_id"]);
$table1->addCell(1750)->addText($samples['nr']);
}
}
}
}
$section->addText('
');
$section->addLine(
array(
'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(16),
'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(0),
'positioning' => 'absolute',
)
);
$section->addText('Output Table', $tableName);
$table2 = $section->addTable($spanTableStyleName);
//// THIS IS WHERE THE ERROR OCCURS!!
$table2->addRow(null, array('tblHeader' => true, 'cantSplit' => true));
$table2->addCell(1750)->addText('ID',$thName);
Thank you!
SOLUTION
Oke, so I have deleted the whole document and added every single sentence separately to see where the error occurred. This led to seeing that the error came from the data which I was getting. It couldn't handle ">" and "&" signs!
So, if you every have this error, check the data which you're printing!
A better solution is to add the following line of code before you do anything with the word document:
PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true);
This will automatically escape any problematic characters.
Indeed, It comes from your data : you have a XML special character in it and when Word, parses your doc, it doesn't understand.
I solved this problem by using htmlspecialchars(). I'm not sure it is the best way but it works.
Not very familiar with PHPWord, but make sure the encoding of your document and the data you are inserting into it are the same. Used to have the same problem with an old library for creating excel files.
i have this array in an example, how can i get the same result from query from database?, i need to replace the values which the values of the database.
$data = array(
array(
'qty' => 1,
'Price' => 1.00,
'total' => 1.00
),
array(
'qty' => 2,
'Price' => 1.00,
'total' => 2.00
),
array(
'qty' => 3,
'Price' => 1.00,
'total' => 3.00
)
);
then in the example use the nusoap lib
foreach($data as $concept) {
$par['Concepts'][] = new soapval('Concept', 'Concept', $concept);
}
so i need to call the query:
$query_data_cot = mysql_query("SELECT * FROM data WHERE id='1'");
while($data_quote=mysql_fetch_array($query_data_cot)){
$conceptosDatos[]["qty"]=$data_quote['qty'];
$conceptosDatos[]["Price"]=$data_quote['price'];
$conceptosDatos[]["total"]=$data_quote['total'];
}
but when i do these i got an error
Error: Array ( [faultcode] => soap:Server [faultstring] => Server was unable to process request. --->
thank you
Everytime you use $conceptosDatos[]... the [] will create a new subarray. So your result will be something like this
array(
array('qty' => ..),
array('Price' => ..),
array('total' => ...),
array('qty' => ..),
array('Price' => ..),
array('total' => ...),
...
)
Instead you need to create a new subarray only for a whole set, so use something like this
$query_data_cot = mysql_query("SELECT qty, price, total FROM data WHERE id='1'");
while($data_quote=mysql_fetch_assoc($query_data_cot)){
$conceptosDatos[] = $data_quote;
}
Of course you could also do it like this
$query_data_cot = mysql_query("SELECT qty, price, total FROM data WHERE id='1'");
while($data_quote=mysql_fetch_assoc($query_data_cot)){
$conceptosDatos[] = array(
'qty' => $data_quote['qty'],
'Price' => $data_quote['price'],
'total' => $data_quote['total'],
);
}
But why write every field if you're going to copy the whole array anyway?
If you need to use different names (as in your example price and Price), you can either change your db schema or use aliases in your query, giving you this code:
$query_data_cot = mysql_query("SELECT qty, price AS Price, total FROM data WHERE id='1'");
while($data_quote=mysql_fetch_assoc($query_data_cot)){
$conceptosDatos[] = $data_quote;
}
This has the advantage that if you wish to modify your code in the future, you'd only need to modify the query (and could probably encapsulate this logic inside a function) - so less work for future you.
By the way, did you see the big red box in the manual on all mysql_* methods's sites? It's deprecated and PDO as well as MySQLi are way better alternatives. This helps decide what to use.
Try this , while($data_quote=mysql_fetch_array($query_data_cot , MYSQL_ASSOC )) , as you are retrieving the $data as an associative array .
Each assignment line in your loop is creating a new element of the $conceptDatos array, not filling in a different element of the same element. So your array looks like:
array(
array('qty' => 1),
array('Price' => 1.0),
array('total' => 1.0),
array('qty' => 2),
array('Price' => 1.0),
array('total' => 1.0),
...
)
Your loop shoud be:
while($data_quote=mysql_fetch_array($query_data_cot)){
$conceptDatos[] = array(
'qty' => $data_quote['qty'],
'Price' => $data_quote['price'],
'total' => $data_quote['total']
);
}
Imagine this situation:
$component = array(
'type' => 'chimney',
'material' => 'stone'
);
What i would like to do is to add a key/value pair to this array, if a certain condition is met.
$hasMetrics = true;
$component = array(
'type' => 'chimney',
'material' => 'stone',
'metrics' => ($hasMetrics ? array('width' => 60, 'height' => 2000) : false)
);
While this could be used, it will always cause a key called 'metrics' in my array.
Of course, if i don't want that, i could use array_merge() to merge a second array with the first (the second being either an empty array or the desired key/value pair, depending on the condition).
But what i am longing to find out is if there is any way to define this array like above, while taking care of $hasMetrics, without the use of any other means (such as array_merge()) but purely in the actual (first and only) definition of this array.
Like this: (non-applicable, demonstrative example)
$component = array(
'type' => 'chimney',
'material' => 'stone',
($hasMetrics ? array('metrics' => array(
'width' => 60,
'height' => 2000
)) : false)
);
(This, as i understand it, would generate two keys (type and material and then create one keyless value that is, itself, an array containing a key (metrics) and another array as value.)
Can anyone show me some proper approach? Perhaps there is some kind of PHP function available, with special properties (such as list() which is capable of cross-assignment).
EDIT
Perhaps some more clarification is needed, as many answers point out ways to go such as:
Using a followup assignment to a certain key
Filtering the generated array after defining it
While these are perfectly valid ways to extend the array, but i am explicitly looking for a way to do this in one go within the one array definition.
Not with the array defenition itself. I would add it to the array if necessary:
if($hasMetrics) {
$component['metrics'] = array('width' => 60, 'height' => 2000);
}
$hasMetrics = true;
$component = array(
'type' => 'chimney',
'material' => 'stone',
);
if($hasMetrics){
$component['metrics'] = array('width' => 60, 'height' => 2000);
}
Try
$component = array(
'type' => 'chimney',
'material' => 'stone',
'metrics' => $hasMetrics ? array('width' => 60, 'height' => 2000) : ''
);
And after that
$component = array_filter( $component ); // remove if it has '' value
OR
$component = array(
'type' => 'chimney',
'material' => 'stone',
);
if($hasMetrics) {
$component['metrics'] = array('width' => 60, 'height' => 2000);
}
I am reading an excell file with php. No problem with that but I am stuck on a little logical part. I want to make an array that containts multiple other arrays with data.
The data is provided in my excell file I know from what column should start reading but not when to stop because this is dynamic.
My question is how can a make a loop that reads my columns and makes on every 5th column a new array.
so what I want is something like this:
(My data for the excell file is proved in $line[] each column has its number.)
array(
'length' => $line[15],
'width' => $line[16]
'price_per' => $line[17],
'price' => $line[18],
'stock' => $line[19]
),
array(
'length' => $line[20],
'width' => $line[21]
'price_per' => $line[22],
'price' => $line[23],
'stock' => $line[24]
),
array(
'length' => $line[25],
'width' => $line[26]
'price_per' => $line[27],
'price' => $line[28],
'stock' => $line[29]
), ....
So how can I make this dynamic (for loop ?) so that I have 1 big indexed Array , with multiple asscociated arrays? Note: my for loop should always star from line[15]!
To begin with, if $line has any elements that you don't want to process (e.g. the first 15 as your example indicates), slice them off with array_slice:
$line = array_slice($line, 15);
Then use array_chunk to split your original array into as many pieces as there are:
$chunks = array_chunk($line, 5);
Then, turn each chunk into its own array by associating each value with the correct key using array_combine:
$results = array();
$keys = array('length', 'width', 'price_per', 'price', 'stock');
foreach ($chunks as $chunk) {
$results[] = array_combine($keys, $chunk);
}
for($i = 15; $i < ????; $i += 5)
{
$your_array[] = array(
'length' => $line[$i],
'width' => $line[$i+1]
'price_per' => $line[$i+2],
'price' => $line[$i+3],
'stock' => $line[$i+4]
);
}
Replace ???? by the number of lines
I have a database table as follows:
This returns all column titles in the pic, but the one's that are most important are slug, and parent (not sure about id_button).
The array gets ordered automatically by id_button ASC, which really irks me. But, anyways, this is not important, as I need to order it completely different, or re-order it after the array is populated.
The array returns this, by order of id_button:
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
),
2 => array(
'id_button' => 3,
'parent' => 'google.com',
'position' => 'after',
'slug' => 'another_test',
),
3 => array(
'id_button' => 4,
'parent' => 'testing'
'position' => 'child_of',
'slug' => 'google.com',
)
);
I need to order it so that if a slug is found within any parent, than the slug that is in the parent needs to be loaded before the one that has it defined within the parent.
Its not important if it is directly before it. For example, you see testing is the first slug that gets returned, and yet the parent for this is the last slug (google.com). So as long as the slug row where the parent is defined gets ordered so that it is BEFORE the row that has the slug value in the parent column, everything is fine.
So in this situation, it can be reordered as any of these 3 ordered arrays below:
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
),
2 => array(
'id_button' => 4,
'parent' => 'testing',
'position' => 'child_of',
'slug' => 'google.com',
),
3 => array(
'id_button' => 3,
'parent' => 'google.com'
'position' => 'after',
'slug' => 'another_test',
)
);
OR this...
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 4,
'parent' => 'testing',
'position' => 'child_of',
'slug' => 'google.com',
),
2 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
),
3 => array(
'id_button' => 3,
'parent' => 'google.com'
'position' => 'after',
'slug' => 'another_test',
)
);
OR even this...
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 4,
'parent' => 'testing',
'position' => 'child_of',
'slug' => 'google.com',
),
2 => array(
'id_button' => 3,
'parent' => 'google.com'
'position' => 'after',
'slug' => 'another_test',
),
3 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
)
);
All 3 of these ordered arrays will work because the array with the slug that matches the parent is before the array with the matching parent, and since the slug value, sub_test_1 doesn't match any of the parent values this array order is unimportant, so that array can be located anywhere within the array.
How can I do this? I'm thinking of just looping through the array somehow and trying to determine if the slug is in any of the parents, and just do a reordering somehow...
In short, the slug needs to be ordered before the parent ONLY if there is a parent that matches a slug within the array. Otherwise, if no match is found, the order isn't important.
As Niko suggested, databases support powerful sorting functionality, so you normally can best solve this by telling the database in which order to return the data. If the data is queried with SQL, that's the ORDER BY clause. This is specified in the documentation of your database, assuming you're using MySQL 5.0: http://dev.mysql.com/doc/refman/5.0/en/sorting-rows.html
If you can not influence the order on the database level, you're in the need to sort the array in PHP. You actually have an array of arrays, in which the outer array is just a list having the id (primary key) of each row and the other fields as a fieldname -> value array as a value (inner array).
Your sort is *user-defined` - you specify the sort order. A common way is to have a sort function that compares two entries which each other. That sort function needs to decide which of those two is of a higher sort-order than the other (or both have the same weight). In you case one item is higher than the other if one is the child of the other.
That's the general principle. You define the sort function that decides (the so called callback function), and PHP takes care to feed it with the array data to sort with the usortDocs function.
A sub-problem you need to solve then is to decide whether or not a child exists in the whole array (an item with a slug having the same value as parent). As this all looks like it can be a bit more complex, it's wise to encapsulate this all into a class of it's own.
Example / Demo:
class menuButtons
{
/**
* #var array
*/
private $buttons;
public function __construct(array $buttons)
{
$this->buttons = $buttons;
}
public function sortChildsFirst()
{
$buttons = $this->buttons;
usort($buttons, array($this, 'sortCallback'));
return $buttons;
}
private function sortCallback($a, $b)
{
// an element is more than any other if it's parent
// value is any other slugs value
if ($this->slugExists($a['parent']))
return 1;
return -1;
}
private function slugExists($slug)
{
foreach($this->buttons as $button)
{
if ($button['slug'] === $slug)
return true;
}
return false;
}
}
$buttons = new menuButtons($new_menu_buttons);
$order = $buttons->sortChildsFirst();
Note: This code is exploiting the fact that your sort order is only roughly specified. You only wrote that you need to have children before parents, so if you take all children first, this will always be the case. It's not that each parent will directly follow the child.
Nevertheless, this skeleton class can work as a base to further improve the search functionality as it's fully encapsulated. You can even change the whole sort method, e.g. to completely write one of your own even w/o usort, like outlined below. The main code does not need to change as it's only making use of the sortChildsFirst method.
You can sort an array once populated using the usort() function.
http://php.net/manual/en/function.usort.php
Since your structure is tree-alike, the first thing that comes to mind is to build a tree out of it. It goes like this:
$tree = array();
foreach($array as $e) {
$p = $e['parent'];
$s = $e['slug'];
if(!isset($tree[$p]))
$tree[$p] = new stdclass;
if(!isset($tree[$s]))
$tree[$s] = new stdclass;
$tree[$s]->data = $e;
$tree[$p]->sub[] = $tree[$s];
}
This creates a set of objects, with the members data and sub = list of child objects.
Now we iterate the tree and for each "root" node, add it and its children to the sorted array:
$out = array();
foreach($tree as $node)
if(!isset($tree[$node->data['parent']]))
add($out, $node);
where add() is
function add(&$out, $node) {
if(isset($node->data))
$out[] = $node->data;
if(isset($node->sub))
foreach($node->sub as $n)
add($out, $n);
}
hope this helps.
Ok, first let me thank you all for your detailed explanations. They are very intuitive. However, I found another way, can you guys let me know if you spot anything wrong with this method here please?
Click here to see a Demo of this working!
$temp_buttons = array();
foreach($new_menu_buttons as $buttons)
$temp_buttons[$buttons['parent']] = $buttons['slug'];
dp_sortArray($new_menu_buttons, $temp_buttons, 'slug');
// The $new_menu_buttons array is now sorted correctly! Let's check it...
var_dump($new_menu_buttons);
function dp_sortArray(&$new_menu_buttons, $sortArray, $sort)
{
$new_array = array();
$temp = array();
foreach ($new_menu_buttons as $key => $menuitem)
{
if (isset($sortArray[$menuitem[$sort]]))
{
$new_array[] = $menuitem;
$temp[$menuitem['parent']] = $menuitem['slug'];
unset($new_menu_buttons[$key]);
}
}
$ordered = array();
if (!empty($new_array))
{
foreach ($new_array as $key => $menuitem)
{
if (isset($temp[$menuitem[$sort]]))
{
$ordered[] = $menuitem;
unset($new_array[$key]);
}
}
}
else
{
$new_menu_buttons = $new_menu_buttons;
return;
}
$new_menu_buttons = array_merge($ordered, $new_array, $new_menu_buttons);
}
Seems to work in all instances that I tested, but ofcourse, their could be a flaw in it somewhere. What do you all think of this?