diff --git a/modules/shareholder_register_webform/shareholder_register_webform.services.yml b/modules/shareholder_register_webform/shareholder_register_webform.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eb857b6b74b7fe5044aea4c7ecc577552c062ce9
--- /dev/null
+++ b/modules/shareholder_register_webform/shareholder_register_webform.services.yml
@@ -0,0 +1,4 @@
+services:
+ shareholder_register_webform.mapping:
+ class: Drupal\shareholder_register_webform\MappingService
+ arguments: ['@entity_type.manager', '@shareholder_register.default', '@event_dispatcher', '@messenger', '@logger.channel.shareholder_register']
diff --git a/modules/shareholder_register_webform/src/MappingService.php b/modules/shareholder_register_webform/src/MappingService.php
new file mode 100644
index 0000000000000000000000000000000000000000..c47841f1fba348084147321c58295c4a70ae3da3
--- /dev/null
+++ b/modules/shareholder_register_webform/src/MappingService.php
@@ -0,0 +1,245 @@
+<?php
+
+namespace Drupal\shareholder_register_webform;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Messenger\MessengerInterface;
+
+use Drupal\shareholder_register\ShareholderRegisterServiceInterface;
+use Drupal\webform\webformSubmissionInterface;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+
+/**
+ * MappingService service.
+ */
+class MappingService {
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * The shareholder_register.default service.
+ *
+ * @var \Drupal\shareholder_register\ShareholderRegisterServiceInterface
+ */
+ protected $shareholderRegisterDefault;
+
+ /**
+ * The event dispatcher.
+ *
+ * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
+ * The messenger.
+ *
+ * @var \Drupal\Core\Messenger\MessengerInterface
+ */
+ protected $messenger;
+
+ /**
+ * The logger.channel.shareholder_register service.
+ *
+ * @var \Psr\Log\LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * Constructs a MappingService object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\shareholder_register\ShareholderRegisterServiceInterface $shareholder_register_default
+ * The shareholder_register.default service.
+ * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+ * The event dispatcher.
+ * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+ * The messenger.
+ * @param \Psr\Log\LoggerInterface $logger
+ * The logger.channel.shareholder_register service.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, ShareholderRegisterServiceInterface $shareholder_register_default, EventDispatcherInterface $event_dispatcher, MessengerInterface $messenger, LoggerInterface $logger_channel) {
+ $this->entityTypeManager = $entity_type_manager;
+ $this->shareholderRegisterDefault = $shareholder_register_default;
+ $this->eventDispatcher = $event_dispatcher;
+ $this->messenger = $messenger;
+ $this->logger = $logger_channel;
+ }
+
+ /**
+ * Method description.
+ */
+ public function loadMapping(string $mapping) {
+ $mapping_array = [];
+
+ $mapping = str_replace("\r", "\n", str_replace("\r\n", "\n", $mapping));
+ foreach (explode("\n", $mapping) as $mapping_line) {
+ if (!$mapping_line || !trim($mapping_line)) {
+ continue;
+ }
+
+ $map = explode('=', $mapping_line);
+ if (count($map) != 2) {
+ continue;
+ }
+ $property = array_filter(
+ explode('.', trim($map[0]))
+ );
+ if (count($property) < 1 || count($property) > 2) {
+ continue;
+ }
+
+ $data_selector = array_filter(
+ explode('.', trim($map[1]))
+ );
+ if (count($data_selector) < 1) {
+ continue;
+ }
+
+ $mapping_array[] = [
+ 'property' => $property,
+ 'data_selector' => $data_selector,
+ ];
+ }
+
+ return $mapping_array;
+ }
+
+ /**
+ * Extract data_selector from data array.
+ *
+ * throw Exception if first element doesn't exist.
+ *
+ * @returns Element value or FALSE.
+ */
+ public function dataSelectorFromData(array $data, array $data_selector) {
+ $data_element = $data;
+ if (!array_key_exists($data_selector[0], $data_element)) {
+ throw new \Exception("key doesn't exist");
+ }
+
+ foreach ($data_selector as $ds) {
+ if (!$ds || !array_key_exists($ds, $data_element)) {
+ $data_element = FALSE;
+ break;
+ }
+ $data_element = $data_element[$ds];
+ }
+ return $data_element;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function propertyFromEntity(EntityInterface $entity, array $property) {
+ if (!$entity->hasField($property[0])) {
+ return NULL;
+ }
+
+ if (count($property) == 1) {
+ return $entity->get(trim($property[0]))->value;
+ }
+ elseif (count($property) == 2) {
+ try {
+ return $entity->get(trim($property[0]))->{trim($property[1])};
+ }
+ catch (Exception $e) {
+ return NULL;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function mapSubmissionToEntity(string $mapping, WebformSubmissionInterface $submission, $entity) {
+ $data = $submission->getData();
+
+ foreach ($this->loadMapping($mapping) as $mapping_line) {
+
+ try {
+ $data_element = $this->dataSelectorFromData($data, $mapping_line['data_selector']);
+ }
+ catch (\Exception $exception) {
+ // Data element not found, continue with next mapping line.
+ continue;
+ }
+
+ $property = $mapping_line['property'];
+
+ if (!$entity->hasField($property[0])) {
+ $this->logger->notice(
+ 'mapSubmissionToEntity: invalid field @field in mapping.',
+ [
+ '@field' => implode('.', $property),
+ ]
+ );
+ continue;
+ }
+
+ if (count($property) == 1) {
+ $entity->{trim($property[0])} = trim($data_element);
+ }
+ elseif (count($property) == 2) {
+ try {
+ $entity->{trim($property[0])}->{trim($property[1])} = trim($data_element);
+ }
+ catch (Exception $e) {
+ $this->logger->notice(
+ 'mapSubmissionToEntity: invalid field item @field in mapping.',
+ [
+ '@field' => implode('.', $data_element),
+ ]
+ );
+ continue;
+ }
+ }
+
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function mapEntityToSubmission($mapping, WebformSubmissionInterface $submission, $entity) {
+ $data = $submission->getData();
+
+ foreach ($this->loadMapping($mapping) as $mapping_line) {
+ $data_element_selector = array_slice($mapping_line['data_selector'], 0, 1);
+ try {
+ // Test if first element exists.
+ $data_element = $this->dataSelectorFromData($data, $data_element_selector);
+ }
+ catch (\Exception $exception) {
+ $this->logger->notice(
+ 'mapEntityToSubmission: invalid field item @field in mapping',
+ [
+ '@field' => implode('.', $mapping_line['property']),
+ ]
+ );
+ }
+
+ if (count($mapping_line['data_selector'] > 1)) {
+ // FIXME: support multilevel data selector.
+ continue;
+ }
+
+ $property = $this->propertyFromEntity($entity, $mapping_line['property']);
+ if ($property === NULL) {
+ continue;
+ }
+
+ $submission->setElementData(trim($mapping_line['data_selector'][0]), $data_element);
+ }
+ }
+
+}