From d9fd6f7a0a793c818daf92ca7ce878e46c2aad4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laurent=20L=C3=A9cluse?= <laurent.lecluse@unicaen.fr>
Date: Tue, 29 Jan 2019 16:15:53 +0100
Subject: [PATCH] =?UTF-8?q?Le=20publish=20ne=20remplacait=20plus=20les=20v?=
 =?UTF-8?q?ariables=20en=20ent=C3=AAte=20de=20PDP?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/Document.php | 880 +++++++++++++++++------------------------------
 1 file changed, 322 insertions(+), 558 deletions(-)

diff --git a/src/Document.php b/src/Document.php
index 782ae86..286e6ad 100644
--- a/src/Document.php
+++ b/src/Document.php
@@ -2,196 +2,83 @@
 
 namespace Unicaen\OpenDocument;
 
+use Exception;
 use DOMDocument;
 use DOMElement;
 use DOMNode;
 use DOMNodeList;
-use Exception;
 use ZipArchive;
 
-
-class Document
+class Publisher
 {
+    const PAGE_BREAK_NAME = 'UNICAEN_PAGE_BREAK';
 
-    /**
-     * @var ZipArchive
-     */
-    private $archive;
-
-    /**
-     * @var bool
-     */
-    private $pdfOutput = false;
+    const PARAGRAPH = 'text:p';
+    const TABLE_ROW = 'table:table-row';
 
     /**
-     * @var Publisher
-     */
-    private $publisher;
-
-    /**
-     * @var Stylist
-     */
-    private $stylist;
-
-    /**
-     * @var DomDocument;
-     */
-    private $meta;
-
-    /**
-     * @var DOMDocument
+     * Lecteur de fichier OpenDocument
+     *
+     * @var Document
      */
-    private $styles;
+    private $document;
 
     /**
+     * Contenu XML du corps de texte
+     *
      * @var DOMDocument
      */
     private $content;
 
     /**
-     * @var array
-     */
-    private $namespaces = [];
-
-    /**
-     * @var bool
-     */
-    private $metaChanged = false;
-
-    /**
-     * @var bool
-     */
-    private $stylesChanged = false;
-
-    /**
-     * @var bool
-     */
-    private $contentChanged = false;
-
-    /**
-     * @var string
-     */
-    private $convCommand = 'unoconv -f pdf -o :outputFile :inputFile';
-
-    /**
-     * @var string
+     * @var DOMElement
      */
-    private $tmpDir;
+    private $body;
 
     /**
-     * @var array 
+     * Ajoute un saut de page automatiquement entre deux instances de document lors du publipostage
+     *
+     * @var boolean
      */
-    private $formatters = [];
-    
+    private $autoBreak = true;
 
     /**
      * @var array
      */
-    private $tmpFiles          = [];
-
-    private $defaultNamespaces = [
-        'office'    => "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
-        'style'     => "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
-        'text'      => "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
-        'table'     => "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
-        'draw'      => "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
-        'fo'        => "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
-        'xlink'     => "http://www.w3.org/1999/xlink",
-        'dc'        => "http://purl.org/dc/elements/1.1/",
-        'meta'      => "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
-        'number'    => "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
-        'svg'       => "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
-        'chart'     => "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
-        'dr3d'      => "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
-        'math'      => "http://www.w3.org/1998/Math/MathML",
-        'form'      => "urn:oasis:names:tc:opendocument:xmlns:form:1.0",
-        'script'    => "urn:oasis:names:tc:opendocument:xmlns:script:1.0",
-        'ooo'       => "http://openoffice.org/2004/office",
-        'ooow'      => "http://openoffice.org/2004/writer",
-        'oooc'      => "http://openoffice.org/2004/calc",
-        'dom'       => "http://www.w3.org/2001/xml-events",
-        'rpt'       => "http://openoffice.org/2005/report",
-        'of'        => "urn:oasis:names:tc:opendocument:xmlns:of:1.2",
-        'xhtml'     => "http://www.w3.org/1999/xhtml",
-        'grddl'     => "http://www.w3.org/2003/g/data-view#",
-        'officeooo' => "http://openoffice.org/2009/office",
-        'tableooo'  => "http://openoffice.org/2009/table",
-        'drawooo'   => "http://openoffice.org/2010/draw",
-        'calcext'   => "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
-        'css3t'     => "http://www.w3.org/TR/css3-text/",
-        'xforms'    => 'http://www.w3.org/2002/xforms',
-        'xsd'       => 'http://www.w3.org/2001/XMLSchema',
-        'xsi'       => 'http://www.w3.org/2001/XMLSchema-instance',
-        'loext'     => 'urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0',
-        'field'     => 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0',
-        'formx'     => 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0',
-    ];
-
-
-
-    /**
-     * @return ZipArchive
-     * @throws Exception
-     */
-    public function getArchive(): ZipArchive
-    {
-        if (!$this->archive) {
-            throw new \Exception("Aucun document n\'est chargé");
-        }
-
-        return $this->archive;
-    }
-
-
+    private $values = [];
 
     /**
-     * @return DOMDocument
-     */
-    public function getContent(): DOMDocument
-    {
-        return $this->content;
-    }
-
-
-
-    /**
-     * @return DOMDocument
+     * Variable contenant le résultat final du content.xml
+     *
+     * @var string
      */
-    public function getStyles(): DOMDocument
-    {
-        return $this->styles;
-    }
-
-
+    private $outContent;
 
     /**
-     * @return DOMDocument
+     * @var bool
      */
-    public function getMeta(): DOMDocument
-    {
-        return $this->meta;
-    }
+    private $published = false;
 
 
 
     /**
-     * @return bool
+     * @return Document
      */
-    public function isPdfOutput(): bool
+    public function getDocument(): Document
     {
-        return $this->pdfOutput;
+        return $this->document;
     }
 
 
 
     /**
-     * @param bool $pdfOutput
+     * @param Document $document
      *
-     * @return Document
+     * @return Publisher
      */
-    public function setPdfOutput(bool $pdfOutput): Document
+    public function setDocument(Document $document): Publisher
     {
-        $this->pdfOutput = $pdfOutput;
+        $this->document = $document;
 
         return $this;
     }
@@ -199,23 +86,23 @@ class Document
 
 
     /**
-     * @return bool
+     * @return DOMElement
      */
-    public function isMetaChanged(): bool
+    public function getBody(): DOMElement
     {
-        return $this->metaChanged;
+        return $this->body;
     }
 
 
 
     /**
-     * @param bool $metaChanged
+     * Peuple l'instance courante du document et l'ajoute à la suite du fichier
      *
-     * @return Document
+     * @param array $values
      */
-    public function setMetaChanged(bool $metaChanged): Document
+    public function add(array $values): Publisher
     {
-        $this->metaChanged = $metaChanged;
+        $this->values[] = $values;
 
         return $this;
     }
@@ -223,23 +110,21 @@ class Document
 
 
     /**
-     * @return bool
+     * @return array
      */
-    public function isStylesChanged(): bool
+    public function getValues(): array
     {
-        return $this->stylesChanged;
+        return $this->values;
     }
 
 
 
     /**
-     * @param bool $stylesChanged
-     *
-     * @return Document
+     * @param array $values
      */
-    public function setStylesChanged(bool $stylesChanged): Document
+    public function setValues(array $values): Publisher
     {
-        $this->stylesChanged = $stylesChanged;
+        $this->values = $values;
 
         return $this;
     }
@@ -249,21 +134,21 @@ class Document
     /**
      * @return bool
      */
-    public function isContentChanged(): bool
+    public function isAutoBreak(): bool
     {
-        return $this->contentChanged;
+        return $this->autoBreak;
     }
 
 
 
     /**
-     * @param bool $contentChanged
+     * @param bool $autoBreak
      *
-     * @return Document
+     * @return Publisher
      */
-    public function setContentChanged(bool $contentChanged): Document
+    public function setAutoBreak(bool $autoBreak): Publisher
     {
-        $this->contentChanged = $contentChanged;
+        $this->autoBreak = $autoBreak;
 
         return $this;
     }
@@ -271,167 +156,50 @@ class Document
 
 
     /**
-     * @return string
-     */
-    public function getConvCommand(): string
-    {
-        return $this->convCommand;
-    }
-
-
-
-    /**
-     * @param string $convCommand
+     * @param DOMElement $element
      *
-     * @return Document
+     * @return Publisher
+     * @throws Exception
      */
-    public function setConvCommand(string $convCommand): Document
+    public function getVariables(DOMElement $element): array
     {
-        $this->convCommand = $convCommand;
-
-        return $this;
-    }
+        $textNs = $this->getDocument()->getNamespaceUrl('text');
 
+        $variables = [];
+        $vElements = $element->getElementsByTagNameNS($textNs, 'variable-set');
+        foreach ($vElements as $vElement) {
+            $name = $vElement->getAttributeNS($textNs, 'name');
 
-
-    /**
-     * Retourne les méta-données du fichier OpenDocument
-     *
-     * @return array
-     */
-    public function getMetaArray(): array
-    {
-        $m = [];
-
-        $nodes = $this->getMeta()->documentElement->childNodes->item(0)->childNodes;
-        foreach ($nodes as $node) {
-            if (isset($node->tagName)) {
-                switch ($node->tagName) {
-                    case 'meta:generator':
-                    case 'meta:initial-creator':
-                    case 'meta:editing-cycles':
-                    case 'meta:editing-duration':
-                    case 'meta:printed-by':
-                    case 'dc:title':
-                    case 'dc:description':
-                    case 'dc:subject':
-                    case 'dc:creator':
-                    case 'dc:language':
-                        list($ns, $tag) = explode(':', $node->tagName);
-                        $m[$tag] = $node->textContent;
-                    break;
-                    case 'meta:creation-date':
-                        $m['creation-date'] = substr($node->textContent, 0, 10);
-                    break;
-                    case 'meta:print_date':
-                        $m['print_date'] = substr($node->textContent, 0, 10);
-                    break;
-                    case 'dc:date':
-                        $m['date'] = substr($node->textContent, 0, 10);
-                    break;
-                    case 'meta:document-statistic':
-                        $m['document-statistic'] = [];
-                        foreach ($node->attributes as $attribute) {
-                            $m['document-statistic'][$attribute->name] = $attribute->value;
-                        }
-                    break;
-                    case 'meta:user-defined':
-                        if (!isset($m['user-defined'])) $m['user-defined'] = [];
-                        foreach ($node->attributes as $attribute) {
-                            $m['user-defined'][$attribute->name] = $attribute->value;
-                        }
-                    break;
-                    case 'meta:keywords':
-                        $m['keywords'] = [];
-                        foreach ($node->childNodes as $knode) {
-                            $m['keywords'][] = $knode->textContent;
-                        }
-                    break;
-                }
-            }
+            if (!isset($variables[$name])) $variables[$name] = [];
+            $variables[$name][] = $vElement;
         }
 
-        return $m;
+        return $variables;
     }
 
 
 
-    /**
-     * @param string $variable
-     * @param mixed $value
-     *
-     * @return string
-     */
-    public function formatValue(string $variable, $value): string
+    public function getSections(DOMElement $element): array
     {
-        if (isset($this->formatters[$variable]) && $format = $this->formatters[$variable]){
-            if (is_callable($format)){
-                $value = $format($value);
-            }
-        }
-
-        if (is_float($value)){
-            return number_format($value, 2, ',', ' ');
-        }
-
-        return (string)$value;
-    }
-
+        $textNs = $this->getDocument()->getNamespaceUrl('text');
 
+        $sections  = [];
+        $vElements = $this->findFrom($element, 'text:section');
+        foreach ($vElements as $vElement) {
+            $name = $vElement->getAttributeNS($textNs, 'name');
 
-    /**
-     * Retourne les espaces de nom associés à leurs URI respectives sous forme de tableau associatif
-     *
-     * @return array
-     */
-    public function getNamespaces(): array
-    {
-        if (empty($this->namespaces)) {
-            $content = $this->getArchive()->getFromName('content.xml');
-
-            $begin            = strpos($content, '<', 1) + 2 + strlen('office:document-content');
-            $end              = strpos($content, '>', 50) - $begin;
-            $content          = explode(' ', substr($content, $begin, $end));
-            $this->namespaces = $this->defaultNamespaces;
-            foreach ($content as $str) {
-                if (0 === strpos($str, 'xmlns:')) {
-                    $namespace                    = substr($str, 6, strpos($str, '"') - 7);
-                    $url                          = substr($str, strpos($str, '"') + 1, -1);
-                    $this->namespaces[$namespace] = $url;
-                }
-            }
+            if (!isset($sections[$name])) $sections[$name] = [];
+            $sections[$name][] = $vElement;
         }
 
-        return $this->namespaces;
-    }
-
-
-
-    /**
-     * @param string $name
-     *
-     * @return bool
-     */
-    public function hasNamespace(string $name): bool
-    {
-        $this->getNamespaces();
-
-        return isset($this->namespaces[$name]);
+        return $sections;
     }
 
 
 
-    /**
-     * @param string $name
-     * @param string $url
-     *
-     * @return Document
-     */
-    public function addNamespace(string $name, string $url): Document
+    public function hideSection(DOMElement $section): Publisher
     {
-        if (!$this->hasNamespace($name)) {
-            $this->namespaces[$name] = $url;
-        }
+        $section->parentNode->removeChild($section);
 
         return $this;
     }
@@ -439,73 +207,70 @@ class Document
 
 
     /**
-     * Retourne l'url associé à un espace de nom
-     *
-     * @param string $namespace
+     * @param DOMElement $element
+     * @param mixed      $value
      *
-     * @return string
+     * @return Publisher
      */
-    public function getNamespaceUrl(string $namespace): string
+    protected function setVariable(DOMElement $element, $value): Publisher
     {
-        $ns = $this->getNamespaces();
+        $textNs = $this->getDocument()->getNamespaceUrl('text');
+        $document = $element->ownerDocument;
 
-        if (!isset($ns[$namespace])) {
-            throw new Exception('L\'espace de nom ' . $namespace . ' n\'a pas été trouvé.');
+        if (is_string($value) && false !== strpos($value, "\n")){
+            $value = explode("\n", $value);
+        }else{
+            $value = (array)$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($this->getDocument()->formatValue($element->nodeValue,$value[$i]));
+            $element->parentNode->insertBefore($vText, $element);
+        }
+        $element->parentNode->removeChild($element);
 
-        return $ns[$namespace];
+        return $this;
     }
 
 
 
-    /**
-     * Retourne un champ d'information
-     *
-     * @param integer $index
-     *
-     * @return string
-     */
-    public function getInfo($index)
+    private function addPageBreakStyle(): Publisher
     {
-        $infonodes = $this->getMeta()->getElementsByTagNameNS($this->getNamespaceUrl('meta'), 'user-defined');
-
-        return $infonodes->item($index)->nodeValue;
-    }
+        /* get office:automatic-styles node */
+        $styles = $this->content->getElementsByTagNameNS(
+            $this->getDocument()->getNamespaceUrl('office'),
+            'automatic-styles'
+        )->item(0);
 
+        $stylePageBreak           = $this->newElement('style:style', [
+            'style:name'              => self::PAGE_BREAK_NAME,
+            'style:family'            => 'paragraph',
+            'style:parent-style-name' => 'Standard',
+            'style:master-page-name'  => 'Standard',
+        ]);
+        $stylePageBreakProperties = $this->newElement('style:paragraph-properties', [
+            'style:writing-mode' => 'page',
+            'style:page-number'  => '1',
+        ]);
 
-
-    /**
-     * Modifie un champ d'information
-     *
-     * @todo à finir d'implémenter, car ça ne marche pas!!
-     *
-     * @param integer $index
-     * @param string  $value
-     */
-    public function setInfo($index, $value): Document
-    {
-        throw new \Exception('Implémentation à corriger : ne fonctionne pas');
-
-        $infonodes = $this->getMeta()->getElementsByTagNameNS($this->getNamespaceUrl('meta'), 'user-defined');
-        if ($infonodes->length > 0) {
-            $infonodes->item($index)->nodeValue = $value;
-            $this->setMetaChanged(true);
-        }
+        $stylePageBreak->appendChild($stylePageBreakProperties);
+        $styles->appendChild($stylePageBreak);
 
         return $this;
     }
 
 
 
-    /**
-     * @param array $values
-     *
-     * @return Document
-     */
-    public function publish(array $values): Document
+    private function addPageBreak(DOMElement $element): Publisher
     {
-        $this->getPublisher()->setValues($values);
-        $this->getPublisher()->publish();
+        $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);
 
         return $this;
     }
@@ -514,153 +279,161 @@ class Document
 
     /**
      * @return Publisher
+     * @throws Exception
      */
-    public function getPublisher(): Publisher
+    public function publishBegin(): Publisher
     {
-        if (!$this->publisher) {
-            $this->publisher = new Publisher();
-            $this->publisher->setDocument($this);
-        }
+        /* On récupère le content du document */
+        $this->content = new DOMDocument();
+        $this->content->loadXML($this->getDocument()->getContent()->saveXML());
 
-        return $this->publisher;
-    }
+        if ($this->isAutoBreak()) {
+            $this->addPageBreakStyle();
+        }
 
+        $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));
+            }
+        }
 
-    /**
-     * @return Stylist
-     */
-    public function getStylist(): Stylist
-    {
-        if (!$this->stylist) {
-            $this->stylist = new Stylist();
-            $this->stylist->setDocument($this);
+        $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->stylist;
+        $this->body = $this->content->getElementsByTagNameNS($this->getDocument()->getNamespaceUrl('office'), 'text')[0];
+
+        return $this;
     }
 
 
 
     /**
-     * @param $data
-     *
-     * @return Document
-     * @throws Exception
+     * @return Publisher
      */
-    public function loadFromData($data): Document
+    public function publishEnd(): Publisher
     {
-        if (!class_exists('ZipArchive')) {
-            throw new Exception('Zip extension not loaded');
-        }
+        $this->out("</office:body></office:document-content>");
 
-        $odtFile = $this->tempFileName('odtfile_', 'odt');
-        file_put_contents($odtFile, $data);
+        /* On renvoie le content dans le document */
+        $this->getDocument()->getArchive()->addFromString('content.xml', $this->outContent);
+        $this->published = true;
 
-        return $this->loadFromFile($odtFile, false);
+        return $this;
     }
 
 
 
     /**
-     * @param string $fileName
-     * @param bool   $duplicate
+     * @param DOMElement $element
+     * @param array      $values
      *
-     * @return Document
+     * @return Publisher
      * @throws Exception
      */
-    public function loadFromFile(string $fileName, bool $duplicate = true): Document
+    public function publishValues(DOMElement $element, array $values): Publisher
     {
-        if (!class_exists('ZipArchive')) {
-            throw new Exception('Zip extension not loaded');
+        if ($element === $this->body) {
+            $this->getDocument()->getStylist()->setVariables($values);
         }
 
-        if (!file_exists($fileName)) {
-            throw new Exception('OpenDocument file "' . $fileName . '" doesn\'t exists.');
-        }
-
-        if ($duplicate) {
-            $odtFile = $this->tempFileName('odtFile_', 'odt');
-            copy($fileName, $odtFile);
-        } else {
-            $odtFile = $fileName;
+        $variables = $this->getVariables($element);
+        $sections  = $this->getSections($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);
+                    }
+                }
+                if (isset($sections[$vname])) {
+                    foreach ($sections[$vname] as $elSec) {
+                        $this->publishSubData($elSec, $vparent, $val);
+                    }
+                }
+            } else {
+                if (isset($variables[$name])) {
+                    foreach ($variables[$name] as $vElement) {
+                        $this->setVariable($vElement, $val ? $val : '');
+                    }
+                }
+                if (false !== strpos($name, '@')) {
+                    list($sName, $sType) = explode("@", $name);
+                    if ($sType == 'text:section') {
+                        $name = $sName;
+                    }
+                }
+                if (isset($sections[$name])) {
+                    foreach ($sections[$name] as $sElement) {
+                        if ($val === null
+                            || $val === 0
+                            || $val === '0'
+                            || $val === false
+                            || $val === ''
+                            || strtolower($val) === 'false'
+                        ) {
+                            $this->hideSection($sElement);
+                        }
+                    }
+                }
+            }
         }
 
-        $this->archive = new ZipArchive();
-        if (!true === $this->archive->open($odtFile, ZIPARCHIVE::CREATE)) {
-            throw new Exception('OpenDocument file "' . $fileName . '" don\'t readable.');
+        if ($element === $this->body) {
+            $this->out($this->content->saveXML($element));
         }
 
-        $this->meta = new DOMDocument;
-        $this->meta->loadXML($this->archive->getFromName('meta.xml'));
-
-        $this->styles = new DOMDocument;
-        $this->styles->loadXML($this->archive->getFromName('styles.xml'));
-
-        $this->content = new DOMDocument;
-        $this->content->loadXML($this->archive->getFromName('content.xml'));
-
-        $this->namespaces = [];
-
         return $this;
     }
 
 
 
     /**
-     * @return string
+     * @param DOMElement $element
+     * @param string     $parent
+     * @param string     $variable
+     *
+     * @return DOMElement
      * @throws Exception
      */
-    private function prepareSaving($filename = null): string
+    public function getSubDoc(DOMElement $element, string $parent, string $variable): DOMElement
     {
-        if ($this->isMetaChanged()) {
-            $this->getArchive()->addFromString('meta.xml', $this->getMeta()->saveXML());
-        }
-
-        if ($this->isStylesChanged()) {
-            $this->getArchive()->addFromString('styles.xml', $this->getStyles()->saveXML());
-        }
-
-        if ($this->isContentChanged()) {
-            $this->getArchive()->addFromString('content.xml', $this->getContent()->saveXML());
+        $variables = $this->getVariables($element);
+        if (!isset($variables[$variable][0])) {
+            throw new \Exception('La variable "' . $variable . '"" n\'a pas été trouvée dans le document');
         }
 
-        $actualFile = $this->getArchive()->filename;
-        $this->getArchive()->close();
-        $this->archive = null;
-
-        if ($this->isPdfOutput()) {
-            if (!$filename) {
-                $filename = $this->tempFileName('odt2pdf_', 'pdf');
-            }
-
-            $this->odtToPdf($actualFile, $filename);
-            $actualFile = $filename;
-        }
-
-        return $actualFile;
+        return $this->getSubDocWithVariable($variables[$variable][0], $parent);
     }
 
 
 
     /**
-     * @param $origine
-     * @param $destination
+     * @param DOMElement $element
      *
-     * @return Document
-     * @throws Exception
+     * @return Publisher
      */
-    public function odtToPdf($origine, $destination): Document
+    public function remove(DOMElement $element): Publisher
     {
-        $command = $this->getConvCommand();
-
-        $command = str_replace(':inputFile', $origine, $this->getConvCommand());
-        $command = str_replace(':outputFile', $destination, $command);
-        exec($command, $output, $return);
-
-        if (0 != $return) {
-            throw new \Exception('La conversion de document en PDF a échoué');
-        }
+        $element->parentNode->removeChild($element);
 
         return $this;
     }
@@ -668,14 +441,17 @@ class Document
 
 
     /**
-     * @param string $fileName
+     * @param DOMElement $subDoc
+     * @param array      $variables
+     * @param DOMElement $refNode
      *
-     * @return Document
-     * @throws Exception
+     * @return Publisher
      */
-    public function saveToFile(string $fileName): Document
+    public function publishBefore(DOMElement $subDoc, array $variables, DOMElement $refNode): Publisher
     {
-        $actualFile = $this->prepareSaving($fileName);
+        $node = $subDoc->cloneNode(true);
+        $this->publishValues($node, $variables);
+        $refNode->parentNode->insertBefore($node, $refNode);
 
         return $this;
     }
@@ -683,186 +459,174 @@ class Document
 
 
     /**
-     * @return string
+     * @param DOMElement $element
+     * @param string     $parent
+     * @param DOMElement $variable
+     *
+     * @return DOMElement
      * @throws Exception
      */
-    public function saveToData(): string
+    private function getSubDocWithVariable(DOMElement $element, string $parent): DOMElement
     {
-        $actualFile = $this->prepareSaving();
+        if ($element->tagName == 'text:section') {
+            return $element; // C'est la section qui est dupliquée
+        } else {
+            $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é');
+            }
 
-        return file_get_contents($actualFile);
+            return $parentNode;
+        }
     }
 
 
 
     /**
-     * @return string
+     * @param DOMElement $element
+     * @param string     $parent
+     * @param array      $values
+     *
+     * @return Publisher
      */
-    public function getTmpDir(): string
+    private function publishSubData(DOMElement $element, string $parent, array $values): Publisher
     {
-        if (!$this->tmpDir) {
-            return sys_get_temp_dir();
+        $parentNode = $this->getSubDocWithVariable($element, $parent);
+
+        foreach ($values as $vals) {
+            $clone = $parentNode->cloneNode(true);
+            $this->publishValues($clone, $vals);
+            $parentNode->parentNode->insertBefore($clone, $parentNode);
         }
 
-        return $this->tmpDir;
+        return $this->remove($parentNode);
     }
 
 
 
     /**
-     * @param string $tmpDir
-     *
-     * @return Document
+     * Construit le fichier final à partir des données
      */
-    public function setTmpDir(string $tmpDir): Document
+    public function publish()
     {
-        $this->tmpDir = $tmpDir;
-        if (!file_exists($tmpDir)) {
-            mkdir($tmpDir);
+        $this->publishBegin();
+
+        $first = true;
+        foreach ($this->values as $values) {
+            $bodyNode = $this->body->cloneNode(true);
+            if ($first) {
+                $first = false;
+            } elseif ($this->isAutoBreak()) {
+                $this->addPageBreak($bodyNode);
+            }
+
+            $this->publishValues($bodyNode, $values);
+            $this->getDocument()->getStylist()->setVariables($values);
+            $this->out($this->content->saveXML($bodyNode));
+            $first = false;
         }
 
-        return $this;
+        $this->publishEnd();
     }
 
 
 
     /**
-     * @param null $prefix
+     * @param string $name
      *
-     * @return string
+     * @return DOMNode[]
+     * @throws Exception
      */
-    private function tempFileName($prefix = null, $ext = 'odt'): string
+    private function find(string $name): DOMNodeList
     {
-        $tmpDir = $this->getTmpDir();
-        if ('/' != substr($tmpDir, -1)) {
-            $tmpDir .= '/';
-        }
+        $document = $this->getDocument();
 
-        $tempFileName     = uniqid($tmpDir . $prefix) . '.' . $ext;
-        $this->tmpFiles[] = $tempFileName;
-
-        return $tempFileName;
+        return $document->find($this->content, $name);
     }
 
 
 
     /**
-     * @return array
+     * @param DOMNode $node
+     * @param         $name
+     *
+     * @return DOMNodeList
+     * @throws Exception
      */
-    public function getFormatters(): array
+    private function findFrom(DOMNode $node, $name): DOMNodeList
     {
-        return $this->formatters;
+        return $this->getDocument()->find($node, $name);
     }
 
 
 
     /**
-     * @param string $variable
-     * @param mixed $format
+     * @param string $name
+     * @param array  $attrs
      *
-     * @return Document
+     * @return DOMElement
      */
-    public function addFormatter(string $variable, $format): Document
+    private function newElement(string $name, array $attrs = []): DOMElement
     {
-        $this->formatters[$variable] = $format;
-        
-        return $this;
+        $document = $this->getDocument();
+
+        return $document->newElement($this->content, $name, $attrs);
     }
 
 
 
     /**
-     * PHP 5 introduces a destructor concept similar to that of other object-oriented languages, such as C++.
-     * The destructor method will be called as soon as all references to a particular object are removed or
-     * when the object is explicitly destroyed or in any order in shutdown sequence.
-     *
-     * Like constructors, parent destructors will not be called implicitly by the engine.
-     * In order to run a parent destructor, one would have to explicitly call parent::__destruct() in the destructor body.
+     * Ajoute du contenu au fichier content.xml
+     * Méthode à ne pas exploiter
      *
-     * Note: Destructors called during the script shutdown have HTTP headers already sent.
-     * The working directory in the script shutdown phase can be different with some SAPIs (e.g. Apache).
-     *
-     * Note: Attempting to throw an exception from a destructor (called in the time of script termination) causes a fatal
-     * error.
-     *
-     * @return void
-     * @link https://php.net/manual/en/language.oop5.decon.php
+     * @param string $xml
      */
-    public function __destruct()
+    public function out($xml)
     {
-        /* On supprime les éventuels fichiers temporaires pour libérer l'espace */
-        foreach ($this->tmpFiles as $tmpFile) {
-            if (file_exists($tmpFile)) {
-                unlink($tmpFile);
-            }
-        }
+        $this->outContent .= $xml;
     }
 
 
 
     /**
-     * @param $fileName
-     *
-     * @return Document
-     * @throws Exception
+     * @return string
      */
-    public function download($fileName): Document
+    public function getOutContent(): string
     {
-        $actualFile = $this->prepareSaving();
-
-        if (headers_sent()) {
-            throw new Exception('Headers Allready Sent');
-        }
-
-        $contentType = $this->isPdfOutput() ? 'application/pdf' : 'application/vnd.oasis.opendocument.text';
-
-        header('Content-type: ' . $contentType);
-        header('Content-Disposition: attachment; filename="' . $fileName . '"');
-        header('Content-Transfer-Encoding: binary');
-        header('Pragma: no-cache');
-        header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
-        header('Expires: 0');
-        readfile($actualFile);
-
-        return $this;
+        return $this->outContent;
     }
 
 
 
     /**
-     * @param DOMNode $node
-     * @param string  $name
-     *
-     * @return DOMNode[]
-     * @throws Exception
+     * @return bool
      */
-    public function find(DOMNode $node, string $name): DOMNodeList
+    public function isPublished(): bool
     {
-        list($namespace, $name) = explode(':', $name);
-
-        return $node->getElementsByTagNameNS($this->getNamespaceUrl($namespace), $name);
+        return $this->published;
     }
 
 
 
     /**
-     * @param DOMDocument $document
-     * @param string      $name
-     * @param array       $attrs
+     * @param bool $published
      *
-     * @return DOMElement
-     * @throws Exception
+     * @return Publisher
      */
-    public function newElement(DOMDocument $document, string $name, array $attrs = []): DOMElement
+    public function setPublished(bool $published): Publisher
     {
-        list($namespace) = explode(':', $name);
+        $this->published = $published;
 
-        $newNode = $document->createElementNS($this->getNamespaceUrl($namespace), $name);
-        foreach ($attrs as $attrName => $attrValue) {
-            list($attrNS) = explode(':', $attrName);
-            $newNode->setAttributeNS($this->getNamespaceUrl($attrNS), $attrName, $attrValue);
-        }
-
-        return $newNode;
+        return $this;
     }
-}
+
+}
\ No newline at end of file
-- 
GitLab