Repo for the search and displace core module including the interface to select files and search and displace operations to run on them. https://searchanddisplace.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
6.2 KiB

  1. <?php
  2. namespace App\SearchDisplace;
  3. use App\SearchDisplace\Documents\DocumentFile;
  4. use Illuminate\Support\Facades\Storage;
  5. use App\SearchDisplace\Convertor\Convertor;
  6. use DOMDocument;
  7. use DOMNode;
  8. use DOMText;
  9. class SearchAndDisplaceXML
  10. {
  11. protected $file;
  12. protected $searchers;
  13. protected $storage;
  14. protected $searchOnly;
  15. protected $markedStyleCreated;
  16. public function __construct($file, $searchers, $searchOnly)
  17. {
  18. $this->fileDirectory = $file;
  19. $this->searchers = $searchers;
  20. $this->storage = Storage::disk('local');
  21. $this->searchOnly = $searchOnly;
  22. $this->markedStyleCreated = false;
  23. }
  24. public function execute()
  25. {
  26. $sdXML = $this->applySD();
  27. $pathinfo = pathinfo($sdXML);
  28. $this->convertSearchDisplacedXMLToHTML($sdXML);
  29. DocumentFile::updateImagesPath($pathinfo['dirname'] . '/document_sdapplied.html', $this->fileDirectory);
  30. return $pathinfo['filename'];
  31. }
  32. /**
  33. * Convert (Search displaced) XML to HTML for browser preview
  34. *
  35. * @return void
  36. */
  37. protected function convertSearchDisplacedXMLToHTML($file)
  38. {
  39. Convertor::convert('html', $file);
  40. }
  41. /**
  42. * Read XML document and send text contents to SD
  43. *
  44. * @return void
  45. */
  46. protected function applySD()
  47. {
  48. $dom = new \DOMDocument();
  49. $filePath = $this->storage->path("contracts/$this->fileDirectory");
  50. $dom->load($filePath . "/document.xml");
  51. foreach($dom->getElementsByTagName('p') as $p) {
  52. if(
  53. !$p instanceof DOMText &&
  54. count($p->childNodes) > 0 &&
  55. isset($p->parentNode->tagName) &&
  56. $p->parentNode->tagName !== "table:table-cell"
  57. ) {
  58. $replacements = [];
  59. foreach($p->childNodes as $child) {
  60. if (in_array($child, $replacements)) {
  61. continue;
  62. }
  63. if (!$child instanceof DOMText) {
  64. continue;
  65. }
  66. $replacements = array_merge($replacements, $this->replace($child, $dom));
  67. }
  68. }
  69. }
  70. $dom->save($filePath . "/document_sdapplied.xml");
  71. return $filePath . "/document_sdapplied.xml";
  72. }
  73. /**
  74. * Apply SD on document's paragraph
  75. *
  76. * @param DOMNode $element DOM element
  77. * @param DOMDocument $dom The document
  78. *
  79. * @return array
  80. */
  81. protected function replace(DOMText &$element, DOMDocument &$dom)
  82. {
  83. /** @var string $content */
  84. $content = $element->textContent ?? $element->nodeValue;
  85. $search = new SearchAndDisplace(
  86. stripslashes($content),
  87. [
  88. 'searchers' => $this->searchers,
  89. ],
  90. $this->searchOnly,
  91. true
  92. );
  93. $changed = $search->execute();
  94. $replacementNodes = [];
  95. if($changed) {
  96. if($this->searchOnly) {
  97. $content = $element->textContent;
  98. $indexes = $changed;
  99. } else {
  100. $content = $changed['content'];
  101. $indexes = $changed['indexes'];
  102. }
  103. foreach($indexes as $searcher => $changes) {
  104. if(empty($changes)) {
  105. continue;
  106. }
  107. foreach($changes as $change) {
  108. $firstContent = substr($content, 0, $change['start']);
  109. $changedContent = substr($content, $change['start'], $change['end'] - $change['start'] + 1);
  110. $lastContent = substr($content, $change['end'] + 1);
  111. // $firstNode = $dom->createElement("text:span", $firstContent);
  112. $element->textContent = $firstContent;
  113. $changedNode = $dom->createElement("text:span", $changedContent);
  114. $changedNode->setAttribute('text:style-name', 'mark');
  115. $lastNode = $dom->createElement("text:span", $lastContent);
  116. // Add the changed and last nodes after the current (element) node
  117. // $element->parentNode->insertBefore($firstNode, $element->nextSibling);
  118. # element->parentNode->insertBefore(... $element->nextSibling) inserts a new node before the node AFTER this one
  119. # So we need to add the `last` node first, and then the `changed` node BEFORE the last.
  120. $element->parentNode->insertBefore($lastNode, $element->nextSibling);
  121. $element->parentNode->insertBefore($changedNode, $element->nextSibling);
  122. $replacementNodes[] = $changedNode;
  123. $replacementNodes[] = $lastNode;
  124. }
  125. }
  126. if(!$this->markedStyleCreated) {
  127. $this->createMarkedStyle($dom);
  128. }
  129. $this->markedStyleCreated = true;
  130. }
  131. return $replacementNodes;
  132. }
  133. /**
  134. * Create marked style for browser preview
  135. *
  136. */
  137. private function createMarkedStyle($dom)
  138. {
  139. $style = $dom->createElement("style:style");
  140. $style->setAttribute("style:name", 'mark');
  141. $style->setAttribute("style:family", 'text');
  142. $child = $dom->createElement('style:text-properties');
  143. $child->setAttribute("officeooo:rsid", '0014890a');
  144. $child->setAttribute("fo:background-color", '#ffff00');
  145. $style->appendChild($child);
  146. $dom->getElementsByTagName('automatic-styles')->item(0)->appendChild($style);
  147. }
  148. /**
  149. * Remove marked style used in browser and convert XML file to original file type
  150. *
  151. * @param $type file type
  152. * @param $file absolute file path
  153. *
  154. * @return string $path
  155. */
  156. public static function prepareForDownload($type, $file)
  157. {
  158. // remove marked style from XML
  159. $dom = new DOMDocument();
  160. $dom->load($file);
  161. $style = $dom->getElementsByTagName('automatic-styles')->item(0);
  162. $style->removeChild($style->lastChild);
  163. $dom->save($file);
  164. return Convertor::convert($type, $file, true);
  165. }
  166. }