Loading odt_importer.info.yml +3 −3 Original line number Diff line number Diff line name: ODT Importer type: module description: This module converts .odt documents to HTML code and store them into configured text field. name: 'ODT Importer' description: 'This module converts .odt documents to HTML code and store them into configured text field.' core_version_requirement: ^9 || ^10 package: Fields dependencies: Loading odt_importer.install +8 −5 Original line number Diff line number Diff line <?php /** * @file * Contains odt_importer.install. */ /** * Implements hook_requirements(). * @param $phase * @return array */ function odt_importer_requirements($phase) { $requirements = []; Loading @@ -18,7 +21,7 @@ function odt_importer_requirements($phase) { ]; } if (!in_array("zip", stream_get_wrappers())) { if (!in_array('zip', stream_get_wrappers())) { $requirements['zip'] = [ 'title' => 'Zip Extension', 'value' => $t('Not installed'), Loading odt_importer.module +8 −5 Original line number Diff line number Diff line Loading @@ -4,8 +4,8 @@ * @file * Contains odt_importer.module. */ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\odt_importer\OdtConverter; use Drupal\Core\Routing\RouteMatchInterface; Loading Loading @@ -43,10 +43,10 @@ function odt_importer_entity_presave(EntityInterface $entity) { $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity->getEntityType()->id(), $entity->bundle()); foreach ($fields as $field_name => $field_definition) { if (!empty($field_definition->getTargetBundle()) && $field_definition->getType() == 'odt_file') { // Load destination field from settings and check its existance // Load destination field from settings and check its existance. $destination_field = $field_definition->getSettings()['destination_field']; if ($entity->hasField($destination_field)) { // Save user defined format // Save user defined format. // @todo create separate format and use it by default or instead NULL? $format = (isset($entity->$destination_field->format) && !empty($entity->$destination_field->format)) ? $entity->$destination_field->format : NULL; Loading @@ -71,7 +71,10 @@ function odt_importer_entity_presave(EntityInterface $entity) { 'images_folder' => 'images', ], $odt_file_path); $entity->set($destination_field, (['value' => $odt_converted->markup(), 'format' => $format])); $entity->set($destination_field, ([ 'value' => $odt_converted->markup(), 'format' => $format, ])); } break; Loading src/OdtConverter.php +139 −129 Original line number Diff line number Diff line Loading @@ -2,23 +2,23 @@ namespace Drupal\odt_importer; use \XMLReader; use Drupal\Core\Messenger\MessengerTrait; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Defines an ODT to HTML conversion implementation. * Global ToDos. * * 1. Parameter and return typehinting for class methods: * https://www.drupal.org/docs/develop/standards/coding-standards#s-parameter-and-return-typehinting. * * 2. Create templates for elements for easy overriding markup in themes. */ /** * Global ToDos: * - Parameter and return typehinting for class methods: * https://www.drupal.org/docs/develop/standards/coding-standards#s-parameter-and-return-typehinting * - Create templates for elements for easy overriding markup in themes * Defines an ODT to HTML conversion implementation. */ class OdtConverter { use MessengerTrait; use StringTranslationTrait; Loading @@ -34,7 +34,7 @@ class OdtConverter { * * @var string */ protected $images_folder; protected $imagesFolder; /** * Converted html. Loading @@ -55,7 +55,7 @@ class OdtConverter { */ public function __construct($config, $odt_file) { // Configuration settings. // ToDo: Add this settings to Drupal configuration GUI // @todo Add this settings to Drupal configuration GUI. $this->features['header'] = (bool) $config['features']['header'] ?? TRUE; $this->features['list'] = (bool) $config['features']['list'] ?? TRUE; $this->features['table'] = (bool) $config['features']['table'] ?? TRUE; Loading @@ -67,7 +67,7 @@ class OdtConverter { $this->features['toc'] = (bool) $config['features']['toc'] ?? TRUE; $this->features['bookmark'] = (bool) $config['features']['bookmark'] ?? TRUE; $this->images_folder = $config['images_folder'] ?? 'images'; $this->imagesFolder = $config['images_folder'] ?? 'images'; $this->html = ''; $this->footnotes = ''; Loading @@ -87,7 +87,7 @@ class OdtConverter { /** * {@inheritdoc} */ protected function is_image($file) { protected function isImage($file) { $image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp']; $ext = pathinfo($file, PATHINFO_EXTENSION); Loading @@ -95,15 +95,15 @@ class OdtConverter { return FALSE; } // ToDo: change mime_content_type to symphony MimeTypeGuesserInterface // @todo Change mime_content_type to symphony MimeTypeGuesserInterface? return (strpos(@mime_content_type($file), 'image') === 0); } /** * {@inheritdoc} */ protected function copy_file($from, $to) { //ToDo: change to file.repository service protected function copyFile($from, $to) { // @todo Change to file.repository service. $file = file_get_contents($from); // Load the service statically. Loading @@ -114,7 +114,6 @@ class OdtConverter { /** * Function that preparses the XML and push it to convert() * */ protected function prepare($odt_file, $xml_string = NULL) { if ($xml_string === NULL) { Loading @@ -126,12 +125,11 @@ class OdtConverter { } } // Remove page numbers from TOC // Remove page numbers from TOC. $xml_string = preg_replace('/[ \t]*<text:tab\/>\d{1,}/isu', '', $xml_string); // Replace invalid links to h1,h2,h3 & etc. anchors //href="#\d{1,}\.([\w\t\d ]){2,}\|outline // href="#\d{1,}\.([\w\t\d ]){2,}\|outline. preg_match_all("/<text:bookmark-start text:name=\"([A-Za-z0-9_-]+)\"\/>(.*?)<text:bookmark-end text:name=\"([A-Za-z0-9_-]+)\"\/>/isu", $xml_string, $raw_bookmarks); $bookmarks = []; Loading @@ -145,20 +143,19 @@ class OdtConverter { return $bookmarks[$match[1]]; }, $xml_string); // Revise this: // Convert breaks to br's // @todo Revise converion breaks to br's: // $xml_string = str_replace('<text:line-break/>', '<br />', $xml_string); return $this->convert($odt_file, $xml_string); } /** * Function that parses the XML and converts it to HTML. If $xml is not * provided, extract content.xml from $odt_file * Function that parses the XML and converts it to HTML. * * If $xml is not provided, extract content.xml from $odt_file. */ protected function convert($odt_file, $xml_string = NULL) { // ToDo: move to protected variable? $xml = new XMLReader(); // @todo Move to protected variable? $xml = new \XMLReader(); if ($xml_string === NULL) { if (@$xml->open('zip://' . $odt_file . '#content.xml') === FALSE) { Loading @@ -175,17 +172,16 @@ class OdtConverter { } } // Now, convert the xml from a string to an // Now, convert the xml from a string to an HTML. $elements_tree = []; static $styles = ['Quotations' => ['tags' => ['blockquote']]]; $translation_table = []; // ToDo: Check if we need to return this here? See below: case 'draw:frame' // @todo Check if we need to return this here? See below: case 'draw:frame'. // $translation_table['draw:frame'] = 'div class="odt-frame"'; // ToDo: implement support for ol li lists // @todo Implement support for ol li lists. if ($this->features['list']) { $translation_table['text:list'] = 'ul'; $translation_table['text:list-item'] = 'li'; Loading @@ -211,44 +207,52 @@ class OdtConverter { } while ($xml->read()) { $opened_tags = []; //This array will contain the HTML tags opened in every iteration // This array will contain the HTML tags opened in every iteration. $opened_tags = []; if ($xml->nodeType === XMLReader::END_ELEMENT) { // Handle a closing tag if ($xml->nodeType === \XMLReader::END_ELEMENT) { // Handle a closing tag. if (empty($elements_tree)) { continue; } // Close every opened tag. This should also handle malformed XML files. do { $element = array_pop($elements_tree); if ($element && $element['tags']) { //Close opened tags // Close opened tags. $element['tags'] = array_reverse($element['tags']); foreach ($element['tags'] as $html_tag) { $html_tag = current(explode(' ', $html_tag)); $this->html .= '</' . $html_tag . '>'; } } } while ($xml->name !== $element['name'] && $element); //Close every opened tags. This should also handle malformed XML files } while ($xml->name !== $element['name'] && $element); continue; } elseif (in_array($xml->nodeType, [XMLReader::ELEMENT, XMLReader::TEXT, XMLReader::SIGNIFICANT_WHITESPACE])) { // Handle tags elseif (in_array($xml->nodeType, [ \XMLReader::ELEMENT, \XMLReader::TEXT, \XMLReader::SIGNIFICANT_WHITESPACE, ])) { // Handle tags. switch ($xml->name) { // Text // Text. case '#text': $this->html .= htmlspecialchars($xml->value); break; // Title // Title. case 'text:h': if ($this->features['header']) { $n = $xml->getAttribute('text:outline-level'); if ($n > 6) $n = 6; if ($n > 6) { $n = 6; } $opened_tags[] = 'h' . $n; $bookmark = ''; // Bookmarks when table of contents exists in odt // Bookmarks when table of contents exists in odt. if ($this->features['bookmark']) { $child_xml_string = $xml->readInnerXML(); if (!empty($child_xml_string)) { Loading @@ -266,26 +270,26 @@ class OdtConverter { } break; // Paragraph // Paragraph. case 'text:p': //Just convert odf <text:p> to html <p> // Just convert odf <text:p> to html <p>. if ($xml->isEmptyElement) { // Self closed empty <text:p/> tag // Self closed empty <text:p/> tag. $this->html .= "\n<p> </p>"; } else { $tags = @$styles[$xml->getAttribute('text:style-name')]['tags']; if (!(is_array($tags) && in_array('blockquote', $tags))) { // Do not print a <p> immediatly after or before a <blockquote> // Do not print a <p> immediatly after or before a <blockquote>. $opened_tags[] = 'p'; $this->html .= "\n<p>"; } } break; // Link // ToDo: add options for rel="nofollow" and target="_blank" // for external links // Link. // @todo add options for rel="nofollow" and target="_blank" // for external links. case 'text:a': if ($this->features['link']) { $href = $xml->getAttribute('xlink:href'); Loading @@ -302,7 +306,7 @@ class OdtConverter { $this->html .= ' '; break; // Frame // Frame. case 'draw:frame': if ($this->features['frame']) { $frame_type = $xml->getAttribute('text:anchor-type'); Loading @@ -321,20 +325,20 @@ class OdtConverter { } break; // Image // Image. case 'draw:image': if ($this->features['image']) { $image_file = 'zip://' . $odt_file . '#' . $xml->getAttribute("xlink:href"); if (isset($this->images_folder) && is_dir($this->images_folder)) { if ($this->is_image($image_file)) { $image_to_save = $this->images_folder . '/' . basename($image_file); if (!($src = $this->copy_file($image_file, $image_to_save))) { if (isset($this->imagesFolder) && is_dir($this->imagesFolder)) { if ($this->isImage($image_file)) { $image_to_save = $this->imagesFolder . '/' . basename($image_file); if (!($src = $this->copyFile($image_file, $image_to_save))) { $this->messenger()->addError('Unable to move image file.'); break; } else { // ToDo: make config option for size of inline images // ToDo: check this with xml and webp images // @todo Make config option for size of inline images. // @todo Check this with xml and webp images $image_size = getimagesize($image_to_save); if ((int) $image_size[0] < 128) { $class = 'img-inline'; Loading Loading @@ -362,35 +366,38 @@ class OdtConverter { } break; case "text:line-break"://br $this->html .= "\n";//<br /> // Line break (<br />). case "text:line-break": $this->html .= "\n"; break; case "style:style": $name = $xml->getAttribute('style:name'); $parent = $xml->getAttribute('style:parent-style-name'); if (array_key_exists($parent, $styles)) { $styles[$name] = $styles[$parent]; //Not optimal // Not optimal. $styles[$name] = $styles[$parent]; } if ($xml->isEmptyElement) { break; //We can't handle that at the moment // We can't handle that at the moment. break; } // Read one tag while ($xml->read() && ($xml->name != 'style:style' || $xml->nodeType != XMLReader::END_ELEMENT)) { // Read one tag. while ($xml->read() && ($xml->name != 'style:style' || $xml->nodeType != \XMLReader::END_ELEMENT)) { if ($xml->name == 'style:text-properties') { //Creates the style and add <em> to its tags // Creates the style and add <em> to its tags. if ($xml->getAttribute('fo:font-style') == 'italic') { $styles[$name]['tags'][] = 'em'; } //Creates the style and add <strong> to its tags // Creates the style and add <strong> to its tags. if ($xml->getAttribute('fo:font-weight') == 'bold') { $styles[$name]['tags'][] = 'strong'; } //Creates the style and add <b> to its tags // Creates the style and add <b> to its tags. if ($xml->getAttribute('style:text-underline-style') == 'solid') { $styles[$name]['tags'][] = 'u'; } Loading @@ -398,18 +405,18 @@ class OdtConverter { } break; // Note // Note. case 'text:note': if ($this->features['note']) { $note_id = $xml->getAttribute('text:id'); $note_name = 'Note'; // Read one tag and stop on </style:style> while ($xml->read() && ($xml->name != 'text:note' || $xml->nodeType != XMLReader::END_ELEMENT)) { if ($xml->name == 'text:note-citation' && $xml->nodeType == XMLReader::ELEMENT) { // Read one tag and stop on </style:style>. while ($xml->read() && ($xml->name != 'text:note' || $xml->nodeType != \XMLReader::END_ELEMENT)) { if ($xml->name == 'text:note-citation' && $xml->nodeType == \XMLReader::ELEMENT) { $note_name = $xml->readString(); } elseif ($xml->name == 'text:note-body' && $xml->nodeType == XMLReader::ELEMENT) { elseif ($xml->name == 'text:note-body' && $xml->nodeType == \XMLReader::ELEMENT) { $note_content = $this->convert($odt_file, $xml->readOuterXML()); } } Loading @@ -420,13 +427,14 @@ class OdtConverter { $this->footnotes .= '<a class="footnote-name" href="#anchor-odt-' . $note_id . '">' . $note_name . ' .</a> '; $this->footnotes .= $note_content; $this->footnotes .= '</div>' . "\n"; } else { } else { $xml->next(); break; } break; // Annotation // Annotation. case 'office:annotation': if ($this->features['annotation']) { $annotation_id = (isset($annotation_id)) ? $annotation_id + 1 : 1; Loading @@ -435,19 +443,19 @@ class OdtConverter { $annotation_date = ''; do { $xml->read(); if ($xml->name == 'dc:creator' && $xml->nodeType == XMLReader::ELEMENT) { if ($xml->name == 'dc:creator' && $xml->nodeType == \XMLReader::ELEMENT) { $annotation_creator = $xml->readString(); } elseif($xml->name == 'dc:date' && $xml->nodeType == XMLReader::ELEMENT) { // ToDo: Add date format to module configuration elseif ($xml->name == 'dc:date' && $xml->nodeType == \XMLReader::ELEMENT) { // @todo Add date format to module configuration. $annotation_date = date("jS \of F Y, H\h i\m", strtotime($xml->readString())); } elseif ($xml->nodeType == XMLReader::ELEMENT) { elseif ($xml->nodeType == \XMLReader::ELEMENT) { $annotation_content .= $xml->readString(); $xml->next(); } } while (!($xml->name === 'office:annotation' && $xml->nodeType === XMLReader::END_ELEMENT)); //End of the note // End of the note. } while (!($xml->name === 'office:annotation' && $xml->nodeType === \XMLReader::END_ELEMENT)); $this->html .= '<sup><a href="#odt-annotation-' . $annotation_id . '" name="anchor-odt-annotation-' . $annotation_id . '" title="Annotation (' . $annotation_creator . ')">(' . $annotation_id . ')</a></sup>'; $this->footnotes .= "\n" . '<div class="odt-annotation" id="odt-annotation-' . $annotation_id . '" >'; Loading @@ -469,17 +477,18 @@ class OdtConverter { break; } $tag = explode(' ', $translation_table[$xml->name], 1); //$tag[0] is the tag name, other indexes are attributes // $tag[0] is the tag name, other indexes are attributes. $opened_tags[] = $tag[0]; $this->html .= "\n<" . $translation_table[$xml->name] . '>'; } } } if ($xml->nodeType === XMLReader::ELEMENT && !($xml->isEmptyElement)) { //Opening tag // Opening tag. if ($xml->nodeType === \XMLReader::ELEMENT && !($xml->isEmptyElement)) { $current_element_style = $xml->getAttribute('text:style-name'); if ($current_element_style && isset($styles[$current_element_style])) { //Styling tags management // Styling tags management. foreach ($styles[$current_element_style]['tags'] as $html_tag) { $this->html .= '<' . $html_tag . '>'; $opened_tags[] = $html_tag; Loading @@ -487,9 +496,10 @@ class OdtConverter { } $elements_tree[] = [ 'name' => $xml->name, 'tags' => $opened_tags 'tags' => $opened_tags, ]; } } } } src/Plugin/Field/FieldType/OdtFileItem.php +10 −21 Original line number Diff line number Diff line Loading @@ -3,17 +3,12 @@ namespace Drupal\odt_importer\Plugin\Field\FieldType; use Drupal\Component\Utility\Random; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\TypedData\DataDefinition; use Drupal\file\Entity\File; use Drupal\file\Plugin\Field\FieldType\FileItem; use Symfony\Component\Mime\MimeTypeGuesserInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** Loading @@ -37,8 +32,7 @@ class OdtFileItem extends FileItem { * {@inheritdoc} */ public static function defaultStorageSettings() { return [ ] + parent::defaultStorageSettings(); return [] + parent::defaultStorageSettings(); } /** Loading Loading @@ -102,7 +96,6 @@ class OdtFileItem extends FileItem { // @todo check that this lib attached from parrent or uncomment line: // $element['#attached']['library'][] = 'file/drupal.file'; $this->getFieldDefinition(); $this->getFieldStorageDefinition(); $this->getSettings(); Loading Loading @@ -131,13 +124,17 @@ class OdtFileItem extends FileItem { // Remove the description option. unset($element['description_field']); // Get all text fields for current entity type and bundle // Get all text fields for current entity type and bundle. $destination_field_options = []; $entity = $form_state->getFormObject()->getEntity(); $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity->get('entity_type'), $entity->get('bundle')); foreach ($fields as $field_name => $field_definition) { if (!empty($field_definition->getTargetBundle()) && in_array($field_definition->getType(), ['text', 'text_long', 'text_with_summary'])) { if (!empty($field_definition->getTargetBundle()) && in_array($field_definition->getType(), [ 'text', 'text_long', 'text_with_summary', ])) { $destination_field_options[$field_name] = $field_definition->getLabel(); } } Loading @@ -155,15 +152,6 @@ class OdtFileItem extends FileItem { return $element; } /** * {@inheritdoc} */ public function preSave() { parent::preSave(); // @todo Add validation for destination field? } /** * {@inheritdoc} */ Loading Loading @@ -199,4 +187,5 @@ class OdtFileItem extends FileItem { // OdtFile items do not have per-item visibility settings. return FALSE; } } README.txt +1 −1 File changed.Contains only whitespace changes. Show changes Loading
odt_importer.info.yml +3 −3 Original line number Diff line number Diff line name: ODT Importer type: module description: This module converts .odt documents to HTML code and store them into configured text field. name: 'ODT Importer' description: 'This module converts .odt documents to HTML code and store them into configured text field.' core_version_requirement: ^9 || ^10 package: Fields dependencies: Loading
odt_importer.install +8 −5 Original line number Diff line number Diff line <?php /** * @file * Contains odt_importer.install. */ /** * Implements hook_requirements(). * @param $phase * @return array */ function odt_importer_requirements($phase) { $requirements = []; Loading @@ -18,7 +21,7 @@ function odt_importer_requirements($phase) { ]; } if (!in_array("zip", stream_get_wrappers())) { if (!in_array('zip', stream_get_wrappers())) { $requirements['zip'] = [ 'title' => 'Zip Extension', 'value' => $t('Not installed'), Loading
odt_importer.module +8 −5 Original line number Diff line number Diff line Loading @@ -4,8 +4,8 @@ * @file * Contains odt_importer.module. */ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\odt_importer\OdtConverter; use Drupal\Core\Routing\RouteMatchInterface; Loading Loading @@ -43,10 +43,10 @@ function odt_importer_entity_presave(EntityInterface $entity) { $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity->getEntityType()->id(), $entity->bundle()); foreach ($fields as $field_name => $field_definition) { if (!empty($field_definition->getTargetBundle()) && $field_definition->getType() == 'odt_file') { // Load destination field from settings and check its existance // Load destination field from settings and check its existance. $destination_field = $field_definition->getSettings()['destination_field']; if ($entity->hasField($destination_field)) { // Save user defined format // Save user defined format. // @todo create separate format and use it by default or instead NULL? $format = (isset($entity->$destination_field->format) && !empty($entity->$destination_field->format)) ? $entity->$destination_field->format : NULL; Loading @@ -71,7 +71,10 @@ function odt_importer_entity_presave(EntityInterface $entity) { 'images_folder' => 'images', ], $odt_file_path); $entity->set($destination_field, (['value' => $odt_converted->markup(), 'format' => $format])); $entity->set($destination_field, ([ 'value' => $odt_converted->markup(), 'format' => $format, ])); } break; Loading
src/OdtConverter.php +139 −129 Original line number Diff line number Diff line Loading @@ -2,23 +2,23 @@ namespace Drupal\odt_importer; use \XMLReader; use Drupal\Core\Messenger\MessengerTrait; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Defines an ODT to HTML conversion implementation. * Global ToDos. * * 1. Parameter and return typehinting for class methods: * https://www.drupal.org/docs/develop/standards/coding-standards#s-parameter-and-return-typehinting. * * 2. Create templates for elements for easy overriding markup in themes. */ /** * Global ToDos: * - Parameter and return typehinting for class methods: * https://www.drupal.org/docs/develop/standards/coding-standards#s-parameter-and-return-typehinting * - Create templates for elements for easy overriding markup in themes * Defines an ODT to HTML conversion implementation. */ class OdtConverter { use MessengerTrait; use StringTranslationTrait; Loading @@ -34,7 +34,7 @@ class OdtConverter { * * @var string */ protected $images_folder; protected $imagesFolder; /** * Converted html. Loading @@ -55,7 +55,7 @@ class OdtConverter { */ public function __construct($config, $odt_file) { // Configuration settings. // ToDo: Add this settings to Drupal configuration GUI // @todo Add this settings to Drupal configuration GUI. $this->features['header'] = (bool) $config['features']['header'] ?? TRUE; $this->features['list'] = (bool) $config['features']['list'] ?? TRUE; $this->features['table'] = (bool) $config['features']['table'] ?? TRUE; Loading @@ -67,7 +67,7 @@ class OdtConverter { $this->features['toc'] = (bool) $config['features']['toc'] ?? TRUE; $this->features['bookmark'] = (bool) $config['features']['bookmark'] ?? TRUE; $this->images_folder = $config['images_folder'] ?? 'images'; $this->imagesFolder = $config['images_folder'] ?? 'images'; $this->html = ''; $this->footnotes = ''; Loading @@ -87,7 +87,7 @@ class OdtConverter { /** * {@inheritdoc} */ protected function is_image($file) { protected function isImage($file) { $image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp']; $ext = pathinfo($file, PATHINFO_EXTENSION); Loading @@ -95,15 +95,15 @@ class OdtConverter { return FALSE; } // ToDo: change mime_content_type to symphony MimeTypeGuesserInterface // @todo Change mime_content_type to symphony MimeTypeGuesserInterface? return (strpos(@mime_content_type($file), 'image') === 0); } /** * {@inheritdoc} */ protected function copy_file($from, $to) { //ToDo: change to file.repository service protected function copyFile($from, $to) { // @todo Change to file.repository service. $file = file_get_contents($from); // Load the service statically. Loading @@ -114,7 +114,6 @@ class OdtConverter { /** * Function that preparses the XML and push it to convert() * */ protected function prepare($odt_file, $xml_string = NULL) { if ($xml_string === NULL) { Loading @@ -126,12 +125,11 @@ class OdtConverter { } } // Remove page numbers from TOC // Remove page numbers from TOC. $xml_string = preg_replace('/[ \t]*<text:tab\/>\d{1,}/isu', '', $xml_string); // Replace invalid links to h1,h2,h3 & etc. anchors //href="#\d{1,}\.([\w\t\d ]){2,}\|outline // href="#\d{1,}\.([\w\t\d ]){2,}\|outline. preg_match_all("/<text:bookmark-start text:name=\"([A-Za-z0-9_-]+)\"\/>(.*?)<text:bookmark-end text:name=\"([A-Za-z0-9_-]+)\"\/>/isu", $xml_string, $raw_bookmarks); $bookmarks = []; Loading @@ -145,20 +143,19 @@ class OdtConverter { return $bookmarks[$match[1]]; }, $xml_string); // Revise this: // Convert breaks to br's // @todo Revise converion breaks to br's: // $xml_string = str_replace('<text:line-break/>', '<br />', $xml_string); return $this->convert($odt_file, $xml_string); } /** * Function that parses the XML and converts it to HTML. If $xml is not * provided, extract content.xml from $odt_file * Function that parses the XML and converts it to HTML. * * If $xml is not provided, extract content.xml from $odt_file. */ protected function convert($odt_file, $xml_string = NULL) { // ToDo: move to protected variable? $xml = new XMLReader(); // @todo Move to protected variable? $xml = new \XMLReader(); if ($xml_string === NULL) { if (@$xml->open('zip://' . $odt_file . '#content.xml') === FALSE) { Loading @@ -175,17 +172,16 @@ class OdtConverter { } } // Now, convert the xml from a string to an // Now, convert the xml from a string to an HTML. $elements_tree = []; static $styles = ['Quotations' => ['tags' => ['blockquote']]]; $translation_table = []; // ToDo: Check if we need to return this here? See below: case 'draw:frame' // @todo Check if we need to return this here? See below: case 'draw:frame'. // $translation_table['draw:frame'] = 'div class="odt-frame"'; // ToDo: implement support for ol li lists // @todo Implement support for ol li lists. if ($this->features['list']) { $translation_table['text:list'] = 'ul'; $translation_table['text:list-item'] = 'li'; Loading @@ -211,44 +207,52 @@ class OdtConverter { } while ($xml->read()) { $opened_tags = []; //This array will contain the HTML tags opened in every iteration // This array will contain the HTML tags opened in every iteration. $opened_tags = []; if ($xml->nodeType === XMLReader::END_ELEMENT) { // Handle a closing tag if ($xml->nodeType === \XMLReader::END_ELEMENT) { // Handle a closing tag. if (empty($elements_tree)) { continue; } // Close every opened tag. This should also handle malformed XML files. do { $element = array_pop($elements_tree); if ($element && $element['tags']) { //Close opened tags // Close opened tags. $element['tags'] = array_reverse($element['tags']); foreach ($element['tags'] as $html_tag) { $html_tag = current(explode(' ', $html_tag)); $this->html .= '</' . $html_tag . '>'; } } } while ($xml->name !== $element['name'] && $element); //Close every opened tags. This should also handle malformed XML files } while ($xml->name !== $element['name'] && $element); continue; } elseif (in_array($xml->nodeType, [XMLReader::ELEMENT, XMLReader::TEXT, XMLReader::SIGNIFICANT_WHITESPACE])) { // Handle tags elseif (in_array($xml->nodeType, [ \XMLReader::ELEMENT, \XMLReader::TEXT, \XMLReader::SIGNIFICANT_WHITESPACE, ])) { // Handle tags. switch ($xml->name) { // Text // Text. case '#text': $this->html .= htmlspecialchars($xml->value); break; // Title // Title. case 'text:h': if ($this->features['header']) { $n = $xml->getAttribute('text:outline-level'); if ($n > 6) $n = 6; if ($n > 6) { $n = 6; } $opened_tags[] = 'h' . $n; $bookmark = ''; // Bookmarks when table of contents exists in odt // Bookmarks when table of contents exists in odt. if ($this->features['bookmark']) { $child_xml_string = $xml->readInnerXML(); if (!empty($child_xml_string)) { Loading @@ -266,26 +270,26 @@ class OdtConverter { } break; // Paragraph // Paragraph. case 'text:p': //Just convert odf <text:p> to html <p> // Just convert odf <text:p> to html <p>. if ($xml->isEmptyElement) { // Self closed empty <text:p/> tag // Self closed empty <text:p/> tag. $this->html .= "\n<p> </p>"; } else { $tags = @$styles[$xml->getAttribute('text:style-name')]['tags']; if (!(is_array($tags) && in_array('blockquote', $tags))) { // Do not print a <p> immediatly after or before a <blockquote> // Do not print a <p> immediatly after or before a <blockquote>. $opened_tags[] = 'p'; $this->html .= "\n<p>"; } } break; // Link // ToDo: add options for rel="nofollow" and target="_blank" // for external links // Link. // @todo add options for rel="nofollow" and target="_blank" // for external links. case 'text:a': if ($this->features['link']) { $href = $xml->getAttribute('xlink:href'); Loading @@ -302,7 +306,7 @@ class OdtConverter { $this->html .= ' '; break; // Frame // Frame. case 'draw:frame': if ($this->features['frame']) { $frame_type = $xml->getAttribute('text:anchor-type'); Loading @@ -321,20 +325,20 @@ class OdtConverter { } break; // Image // Image. case 'draw:image': if ($this->features['image']) { $image_file = 'zip://' . $odt_file . '#' . $xml->getAttribute("xlink:href"); if (isset($this->images_folder) && is_dir($this->images_folder)) { if ($this->is_image($image_file)) { $image_to_save = $this->images_folder . '/' . basename($image_file); if (!($src = $this->copy_file($image_file, $image_to_save))) { if (isset($this->imagesFolder) && is_dir($this->imagesFolder)) { if ($this->isImage($image_file)) { $image_to_save = $this->imagesFolder . '/' . basename($image_file); if (!($src = $this->copyFile($image_file, $image_to_save))) { $this->messenger()->addError('Unable to move image file.'); break; } else { // ToDo: make config option for size of inline images // ToDo: check this with xml and webp images // @todo Make config option for size of inline images. // @todo Check this with xml and webp images $image_size = getimagesize($image_to_save); if ((int) $image_size[0] < 128) { $class = 'img-inline'; Loading Loading @@ -362,35 +366,38 @@ class OdtConverter { } break; case "text:line-break"://br $this->html .= "\n";//<br /> // Line break (<br />). case "text:line-break": $this->html .= "\n"; break; case "style:style": $name = $xml->getAttribute('style:name'); $parent = $xml->getAttribute('style:parent-style-name'); if (array_key_exists($parent, $styles)) { $styles[$name] = $styles[$parent]; //Not optimal // Not optimal. $styles[$name] = $styles[$parent]; } if ($xml->isEmptyElement) { break; //We can't handle that at the moment // We can't handle that at the moment. break; } // Read one tag while ($xml->read() && ($xml->name != 'style:style' || $xml->nodeType != XMLReader::END_ELEMENT)) { // Read one tag. while ($xml->read() && ($xml->name != 'style:style' || $xml->nodeType != \XMLReader::END_ELEMENT)) { if ($xml->name == 'style:text-properties') { //Creates the style and add <em> to its tags // Creates the style and add <em> to its tags. if ($xml->getAttribute('fo:font-style') == 'italic') { $styles[$name]['tags'][] = 'em'; } //Creates the style and add <strong> to its tags // Creates the style and add <strong> to its tags. if ($xml->getAttribute('fo:font-weight') == 'bold') { $styles[$name]['tags'][] = 'strong'; } //Creates the style and add <b> to its tags // Creates the style and add <b> to its tags. if ($xml->getAttribute('style:text-underline-style') == 'solid') { $styles[$name]['tags'][] = 'u'; } Loading @@ -398,18 +405,18 @@ class OdtConverter { } break; // Note // Note. case 'text:note': if ($this->features['note']) { $note_id = $xml->getAttribute('text:id'); $note_name = 'Note'; // Read one tag and stop on </style:style> while ($xml->read() && ($xml->name != 'text:note' || $xml->nodeType != XMLReader::END_ELEMENT)) { if ($xml->name == 'text:note-citation' && $xml->nodeType == XMLReader::ELEMENT) { // Read one tag and stop on </style:style>. while ($xml->read() && ($xml->name != 'text:note' || $xml->nodeType != \XMLReader::END_ELEMENT)) { if ($xml->name == 'text:note-citation' && $xml->nodeType == \XMLReader::ELEMENT) { $note_name = $xml->readString(); } elseif ($xml->name == 'text:note-body' && $xml->nodeType == XMLReader::ELEMENT) { elseif ($xml->name == 'text:note-body' && $xml->nodeType == \XMLReader::ELEMENT) { $note_content = $this->convert($odt_file, $xml->readOuterXML()); } } Loading @@ -420,13 +427,14 @@ class OdtConverter { $this->footnotes .= '<a class="footnote-name" href="#anchor-odt-' . $note_id . '">' . $note_name . ' .</a> '; $this->footnotes .= $note_content; $this->footnotes .= '</div>' . "\n"; } else { } else { $xml->next(); break; } break; // Annotation // Annotation. case 'office:annotation': if ($this->features['annotation']) { $annotation_id = (isset($annotation_id)) ? $annotation_id + 1 : 1; Loading @@ -435,19 +443,19 @@ class OdtConverter { $annotation_date = ''; do { $xml->read(); if ($xml->name == 'dc:creator' && $xml->nodeType == XMLReader::ELEMENT) { if ($xml->name == 'dc:creator' && $xml->nodeType == \XMLReader::ELEMENT) { $annotation_creator = $xml->readString(); } elseif($xml->name == 'dc:date' && $xml->nodeType == XMLReader::ELEMENT) { // ToDo: Add date format to module configuration elseif ($xml->name == 'dc:date' && $xml->nodeType == \XMLReader::ELEMENT) { // @todo Add date format to module configuration. $annotation_date = date("jS \of F Y, H\h i\m", strtotime($xml->readString())); } elseif ($xml->nodeType == XMLReader::ELEMENT) { elseif ($xml->nodeType == \XMLReader::ELEMENT) { $annotation_content .= $xml->readString(); $xml->next(); } } while (!($xml->name === 'office:annotation' && $xml->nodeType === XMLReader::END_ELEMENT)); //End of the note // End of the note. } while (!($xml->name === 'office:annotation' && $xml->nodeType === \XMLReader::END_ELEMENT)); $this->html .= '<sup><a href="#odt-annotation-' . $annotation_id . '" name="anchor-odt-annotation-' . $annotation_id . '" title="Annotation (' . $annotation_creator . ')">(' . $annotation_id . ')</a></sup>'; $this->footnotes .= "\n" . '<div class="odt-annotation" id="odt-annotation-' . $annotation_id . '" >'; Loading @@ -469,17 +477,18 @@ class OdtConverter { break; } $tag = explode(' ', $translation_table[$xml->name], 1); //$tag[0] is the tag name, other indexes are attributes // $tag[0] is the tag name, other indexes are attributes. $opened_tags[] = $tag[0]; $this->html .= "\n<" . $translation_table[$xml->name] . '>'; } } } if ($xml->nodeType === XMLReader::ELEMENT && !($xml->isEmptyElement)) { //Opening tag // Opening tag. if ($xml->nodeType === \XMLReader::ELEMENT && !($xml->isEmptyElement)) { $current_element_style = $xml->getAttribute('text:style-name'); if ($current_element_style && isset($styles[$current_element_style])) { //Styling tags management // Styling tags management. foreach ($styles[$current_element_style]['tags'] as $html_tag) { $this->html .= '<' . $html_tag . '>'; $opened_tags[] = $html_tag; Loading @@ -487,9 +496,10 @@ class OdtConverter { } $elements_tree[] = [ 'name' => $xml->name, 'tags' => $opened_tags 'tags' => $opened_tags, ]; } } } }
src/Plugin/Field/FieldType/OdtFileItem.php +10 −21 Original line number Diff line number Diff line Loading @@ -3,17 +3,12 @@ namespace Drupal\odt_importer\Plugin\Field\FieldType; use Drupal\Component\Utility\Random; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\TypedData\DataDefinition; use Drupal\file\Entity\File; use Drupal\file\Plugin\Field\FieldType\FileItem; use Symfony\Component\Mime\MimeTypeGuesserInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** Loading @@ -37,8 +32,7 @@ class OdtFileItem extends FileItem { * {@inheritdoc} */ public static function defaultStorageSettings() { return [ ] + parent::defaultStorageSettings(); return [] + parent::defaultStorageSettings(); } /** Loading Loading @@ -102,7 +96,6 @@ class OdtFileItem extends FileItem { // @todo check that this lib attached from parrent or uncomment line: // $element['#attached']['library'][] = 'file/drupal.file'; $this->getFieldDefinition(); $this->getFieldStorageDefinition(); $this->getSettings(); Loading Loading @@ -131,13 +124,17 @@ class OdtFileItem extends FileItem { // Remove the description option. unset($element['description_field']); // Get all text fields for current entity type and bundle // Get all text fields for current entity type and bundle. $destination_field_options = []; $entity = $form_state->getFormObject()->getEntity(); $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity->get('entity_type'), $entity->get('bundle')); foreach ($fields as $field_name => $field_definition) { if (!empty($field_definition->getTargetBundle()) && in_array($field_definition->getType(), ['text', 'text_long', 'text_with_summary'])) { if (!empty($field_definition->getTargetBundle()) && in_array($field_definition->getType(), [ 'text', 'text_long', 'text_with_summary', ])) { $destination_field_options[$field_name] = $field_definition->getLabel(); } } Loading @@ -155,15 +152,6 @@ class OdtFileItem extends FileItem { return $element; } /** * {@inheritdoc} */ public function preSave() { parent::preSave(); // @todo Add validation for destination field? } /** * {@inheritdoc} */ Loading Loading @@ -199,4 +187,5 @@ class OdtFileItem extends FileItem { // OdtFile items do not have per-item visibility settings. return FALSE; } }