Skip to content
Snippets Groups Projects
Commit 1b043a74 authored by Laurent Lecluse's avatar Laurent Lecluse
Browse files

Mise en place du système de génération de documents avec LibreOffice et Unoconv

parent bfc492c8
No related branches found
No related tags found
No related merge requests found
<?php
namespace Unicaen\OpenDocument;
use Exception;
use DOMDocument;
use ZipArchive;
/**
* Class Data
* @todo à terminer : non fonctionnel
* @package Unicaen\OpenDocument
*/
class Data
{
const PERIMETRE_TAB_LIGNE = 'table:table-row';
const PERIMETRE_FRAME = 'draw:frame';
const PERIMETRE_LIST_ITEM = 'text:list-item';
/**
* @var string[]
*/
public $variables = [];
/**
* @var
*/
public $subDataVariable;
/**
* @var
*/
public $subDataPerimetre;
/**
* @var Data[]
*/
public $subData = [];
public static function create(array $variables = [])
{
$data = new self;
$data->variables = $variables;
return $data;
}
public function addSubData(string $variable, string $perimetre, array $variables = [])
{
$subData = new self;
$subData->subDataVariable = $variable;
$subData->subDataPerimetre = $perimetre;
$subData->variables = $variables;
return $subData;
}
}
\ No newline at end of file
This diff is collapsed.
<?php
namespace Unicaen\OpenDocument;
use Exception;
use DOMDocument;
use DOMElement;
use ZipArchive;
class Publisher
{
const PAGE_BREAK_NAME = 'UNICAEN_PAGE_BREAK';
/**
* Lecteur de fichier OpenDocument
*
* @var Document
*/
private $document;
/**
* Contenu XML du corps de texte
*
* @var DOMDocument
*/
private $content;
/**
* @var DOMElement
*/
private $body;
/**
* Ajoute un saut de page automatiquement entre deux instances de document lors du publipostage
*
* @var boolean
*/
private $autoBreak = true;
/**
* @var array
*/
private $values = [];
/**
* Variable contenant le résultat final du content.xml
*
* @var string
*/
private $outContent;
/**
* @return Document
*/
public function getDocument(): Document
{
return $this->document;
}
/**
* @param Document $document
*
* @return Publisher
*/
public function setDocument(Document $document): Publisher
{
$this->document = $document;
return $this;
}
/**
* Peuple l'instance courante du document et l'ajoute à la suite du fichier
*
* @param array $values
*/
public function add(array $values): Publisher
{
$this->values[] = $values;
return $this;
}
/**
* @return array
*/
public function getValues(): array
{
return $this->values;
}
/**
* @param array $values
*/
public function setValues(array $values): Publisher
{
$this->values = $values;
return $this;
}
/**
* @return bool
*/
public function isAutoBreak(): bool
{
return $this->autoBreak;
}
/**
* @param bool $autoBreak
*
* @return Publisher
*/
public function setAutoBreak(bool $autoBreak): Publisher
{
$this->autoBreak = $autoBreak;
return $this;
}
/**
* Crée un sous-document à parcourir ensuite
*
* @param string $node_name
* @param string $variable_name
*
* @return Query
*/
public function subDoc($nodeName, $variableName)
{
return $this->query->subDoc($nodeName, $variableName);
}
/**
* Cache une section
*
* @param string $sectionName
*/
public function hideSection($sectionName)
{
$this->query->hideSection($sectionName);
}
/**
* @param DOMElement $element
*
* @return Publisher
* @throws Exception
*/
public function getVariables(DOMElement $element): array
{
$textNs = $this->getDocument()->getNamespaceUrl('text');
$variables = [];
$vElements = $element->getElementsByTagNameNS($textNs, 'variable-set');
foreach ($vElements as $vElement) {
$name = $vElement->getAttributeNS($textNs, 'name');
if (!isset($variables[$name])) $variables[$name] = [];
$variables[$name][] = $vElement;
}
return $variables;
}
/**
* @param DOMElement $element
* @param string $value
*
* @return Publisher
*/
public function setVariable(DOMElement $element, string $value): Publisher
{
$textNs = $this->getDocument()->getNamespaceUrl('text');
$document = $element->ownerDocument;
$value = explode("\n", $value);
for ($i = 0; $i < count($value); $i++) {
if ($i > 0) {
$returnVNode = $document->createElementNS($textNs, 'text:line-break');
$element->parentNode->insertBefore($returnVNode, $element);
}
$vText = $document->createTextNode($value[$i]);
$element->parentNode->insertBefore($vText, $element);
}
$element->parentNode->removeChild($element);
return $this;
}
private function addPageBreakStyle(): Publisher
{
/* get office:automatic-styles node */
$styles = $this->content->getElementsByTagNameNS(
$this->getDocument()->getNamespaceUrl('office'),
'automatic-styles'
)->item(0);
$styleNs = $this->getDocument()->getNamespaceUrl('style');
$stylePageBreak = $this->content->createElementNS($styleNs, 'style:style');
$stylePageBreak->setAttributeNS($styleNs, 'style:name', self::PAGE_BREAK_NAME);
$stylePageBreak->setAttributeNS($styleNs, 'style:family', 'paragraph');
$stylePageBreak->setAttributeNS($styleNs, 'style:parent-style-name', 'Standard');
$stylePageBreakProperties = $this->content->createElementNS($styleNs, 'style:paragraph-properties');
$stylePageBreakProperties->setAttribute('fo:break-after', 'page');
$stylePageBreak->appendChild($stylePageBreakProperties);
$styles->appendChild($stylePageBreak);
return $this;
}
private function addPageBreak(DOMElement $element): Publisher
{
$textNs = $this->getDocument()->getNamespaceUrl('text');
$pageBreak = $this->content->createElementNS($textNs, 'text:p');
$pageBreak->setAttributeNS($textNs, 'text:style-name', self::PAGE_BREAK_NAME);
$element->insertBefore($pageBreak, $element->firstChild);
/*$pageBreak = "<text:p text:style-name=\"".self::PAGE_BREAK_NAME."\"></text:p>";
$xml = str_replace('<office:text>','<office:text>'.$pageBreak, $xml);*/
return $this;
}
/**
* @return Publisher
* @throws Exception
*/
private function publishBegin(): Publisher
{
$contentText = $this->content->saveXML();
$officeDocumentContentPos = strpos($contentText, '<office:document-content');
$length = strpos($contentText, '>', $officeDocumentContentPos) + 1;
$this->out(substr($contentText, 0, $length));
/* wtite all nodes, except body */
foreach ($this->content->documentElement->childNodes as $node) {
if ($node->nodeName != 'office:body') {
$this->out($this->content->saveXML($node));
}
}
$this->out("<office:body>");
/* declaration tags */
$declTags = [
'variable-decls', 'sequence-decls', 'user-field-decls', 'dde-connexion-decls',
];
foreach ($declTags as $tagName) {
$node = $this->content->getElementsByTagNameNS($this->getDocument()->getNamespaceUrl('text'), $tagName);
if ($node->length > 0) {
$this->out($this->content->saveXML($node->item(0)));
$node->item(0)->parentNode->removeChild($node->item(0));
}
}
return $this;
}
/**
* @return Publisher
*/
private function publishEnd(): Publisher
{
$this->out("</office:body></office:document-content>");
return $this;
}
/**
* @param DOMElement $element
* @param array $values
*
* @return Publisher
* @throws Exception
*/
private function publishValues(DOMElement $element, array $values): Publisher
{
$variables = $this->getVariables($element);
foreach ($values as $name => $val) {
if (is_array($val)) {
/* On traite les données filles... */
list($vname, $vparent) = explode("@", $name);
if (isset($variables[$vname])) {
foreach ($variables[$vname] as $elVar) {
$this->publishSubData($elVar, $vparent, $val);
}
}
} elseif (isset($variables[$name])) {
foreach ($variables[$name] as $vElement) {
$this->setVariable($vElement, $val);
}
}
}
return $this;
}
/**
* @param DOMElement $element
* @param string $parent
* @param array $values
*
* @return Publisher
*/
private function publishSubData(DOMElement $element, string $parent, array $values): Publisher
{
$i = 10;
$found = false;
for ($i = 0; $i < 10; $i++) {
$parentNode = isset($parentNode) ? $parentNode->parentNode : $element->parentNode;
if ($parentNode->nodeName == $parent) {
$found = true;
break;
}
}
if (!$found) {
throw new \Exception('Le noeud parent de type ' . $parent . ' n\'a pas été trouvé');
}
foreach( $values as $vals ){
$clone = $parentNode->cloneNode(true);
$this->publishValues($clone, $vals);
$parentNode->parentNode->insertBefore($clone, $parentNode);
}
$parentNode->parentNode->removeChild($parentNode);
return $this;
}
/**
* Construit le fichier final à partir des données
*/
public function publish()
{
/* On récupère le content du document */
$this->content = new DOMDocument();
$this->content->loadXML($this->getDocument()->getContent()->saveXML());
if ($this->isAutoBreak()) {
$this->addPageBreakStyle();
}
$this->publishBegin();
$this->body = $this->content->getElementsByTagNameNS($this->getDocument()->getNamespaceUrl('office'), 'text')[0];
$first = true;
foreach ($this->values as $values) {
$bodyNode = $this->body->cloneNode(true);
if (!$first && $this->isAutoBreak()) {
$this->addPageBreak($bodyNode);
}
$this->publishValues($bodyNode, $values);
$this->out($this->content->saveXML($bodyNode));
$first = false;
}
$this->publishEnd();
/* On renvoie le content dans le document */
$this->getDocument()->getArchive()->addFromString('content.xml', $this->outContent);
}
/**
* Ajoute du contenu au fichier content.xml
* Méthode à ne pas exploiter
*
* @param string $xml
*/
public function out($xml)
{
$this->outContent .= $xml;
}
/**
* @return string
*/
public function getOutContent(): string
{
return $this->outContent;
}
}
\ No newline at end of file
<?php
namespace Unicaen\OpenDocument;
use Exception;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMNodeList;
use UnicaenAuth\Service\PrivilegeServiceFactory;
use ZipArchive;
class Stylist
{
/**
* Lecteur de fichier OpenDocument
*
* @var Document
*/
private $document;
/**
* @return Document
*/
public function getDocument(): Document
{
return $this->document;
}
/**
* @param Document $document
*
* @return Stylist
*/
public function setDocument(Document $document): Stylist
{
$this->document = $document;
return $this;
}
public function getAutomaticStyles()
{
$res = $this->find('office:automatic-styles');
return $res->item(0);
}
/**
* @param $name
* @param $family
* @param array $properties
*
* @return DOMElement
*/
public function addAutomaticStyle($name, $family, array $properties = []): DOMElement
{
$ns = $this->newElement('style:style', [
'style:name' => $name,
'style:family' => $family,
]);
foreach ($properties as $proName => $proAttrs) {
$p = $this->newElement($proName, $proAttrs);
$ns->appendChild($p);
}
$this->getAutomaticStyles()->appendChild($ns);
return $ns;
}
public function addFiligrane($text, array $options = []): Stylist
{
$dw = strlen($text) * 2;
if ($dw > 20) $dw = 20;
$dh = $dw / strlen($text) * 2;
$defaultOptions = [
'color' => '#c0c0c0',
'opacity' => 0.5,
'font-name' => 'Liberation Sans',
'width' => $dw,
'height' => $dh,
];
$options = array_merge($defaultOptions, $options);
$this->addAutomaticStyle('UNICAEN_FIL_MP', 'paragraph', [
'style:text-properties' => [
'style:font-name' => $options['font-name'],
'fo:font-size' => '1pt',
],
'loext:graphic-properties' => [
'draw:fill' => 'solid',
'draw:fill-color' => $options['color'],
'draw:opacity' => round($options['opacity'] * 100) . '%',
],
]);
$this->addAutomaticStyle('UNICAEN_FIL_MGR', 'graphic', [
'style:graphic-properties' => [
'draw:stroke' => 'none',
'draw:fill' => 'solid',
'draw:fill-color' => $options['color'],
'draw:opacity' => round($options['opacity'] * 100) . '%',
'draw:auto-grow-height' => 'false',
'draw:auto-grow-width' => 'false',
'fo:min-height' => $options['width'] . 'cm',
'fo:min-width' => $options['height'] . 'cm',
'style:run-through' => 'background',
'style:wrap' => 'run-through',
'style:number-wrapped-paragraphs' => 'no-limit',
'style:vertical-pos' => 'middle',
'style:vertical-rel' => 'page-content',
'style:horizontal-pos' => 'center',
'style:horizontal-rel' => 'page-content',
'draw:wrap-influence-on-position' => 'once-concurrent',
'style:flow-with-text' => 'false',
],
]);
$masterPages = $this->find('style:master-page');
foreach ($masterPages as $masterPage) {
$fh = $this->findFrom($masterPage, 'style:header');
if ($fh->length == 1) {
$header = $fh->item(0);
} else {
$header = $this->newElement('style:header');
$masterPage->appendChild($header);
}
$p1 = $this->newElement('text:p', [
'text:style-name' => 'Header']
);
$draw1 = $this->newElement('draw:custom-shape', [
'text:anchor-type' => "char",
'draw:z-index' => "0",
'draw:name' => "PowerPlusWaterMarkObject",
'draw:style-name' => "UNICAEN_FIL_MGR",
'draw:text-style-name' => "UNICAEN_FIL_MP",
'svg:width' => $options['width'] . 'cm',
'svg:height' => $options['height'] . 'cm',
'draw:transform' => "rotate (0.785398163397448) translate (-0.134055555555556cm 15.9845416666667cm)",
]);
$p2 = $this->newElement('text:p');
$p2->nodeValue = $text;
$draw2 = $this->newElement('draw:enhanced-geometry', [
'svg:viewBox' => "0 0 21600 21600",
'draw:text-areas' => "0 0 21600 21600",
'draw:text-path' => "true",
'draw:type' => "fontwork-plain-text",
'draw:modifiers' => "10800",
'draw:enhanced-path' => "M ?f3 0 L ?f5 0 N M ?f6 21600 L ?f7 21600 N",
]);
$equations = [
'f0' => '$0 -10800',
'f1' => '?f0 *2',
'f2' => 'abs(?f1 )',
'f3' => 'if(?f1 ,0,?f2 )',
'f4' => '21600-?f2',
'f5' => 'if(?f1 ,?f4 ,21600)',
'f6' => 'if(?f1 ,?f2 ,0)',
'f7' => 'if(?f1 ,21600,?f4 )',
];
foreach ($equations as $eqn => $eqf) {
$eqNode = $this->newElement('draw:equation', [
'draw:name' => $eqn,
'draw:formula' => $eqf,
]);
$draw2->appendChild($eqNode);
}
$handle = $this->newElement('draw:handle', [
'draw:handle-position' => '$0 21600',
'draw:handle-range-x-minimum' => '6629',
'draw:handle-range-x-maximum' => '14971',
]);
$draw2->appendChild($handle);
$header
->appendChild($p1)
->appendChild($draw1)
->appendChild($p2);
$draw1->appendChild($draw2);
}
$this->getDocument()->setStylesChanged(true);
return $this;
}
/**
* @param string $name
*
* @return DOMNode[]
* @throws Exception
*/
private function find(string $name): DOMNodeList
{
$document = $this->getDocument();
return $document->find($document->getStyles(), $name);
}
/**
* @param DOMNode $node
* @param $name
*
* @return DOMNodeList
* @throws Exception
*/
private function findFrom(DOMNode $node, $name): DOMNodeList
{
return $this->getDocument()->find($node, $name);
}
/**
* @param string $name
* @param array $attrs
*
* @return DOMElement
*/
private function newElement(string $name, array $attrs = []): DOMElement
{
$document = $this->getDocument();
return $document->newElement($document->getStyles(), $name, $attrs);
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment