bibcite-8.x-1.x-dev/modules/bibcite_endnote/src/Encoder/EndnoteEncoder.php
modules/bibcite_endnote/src/Encoder/EndnoteEncoder.php
<?php
namespace Drupal\bibcite_endnote\Encoder;
use SimpleXMLElement;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Endnote format encoder.
*
* @todo Refactor this class.
*/
class EndnoteEncoder implements EncoderInterface, DecoderInterface {
/**
* The format that this encoder supports.
*
* @var array
*/
protected static $format = ['endnote7', 'endnote8', 'tagged'];
/**
* {@inheritdoc}
*/
public function supportsDecoding($format): bool {
return in_array($format, static::$format);
}
/**
* {@inheritdoc}
*/
public function decode(string $data, string $format, array $context = []): mixed {
if ($format === 'tagged') {
return $this->decodeTagged($data);
}
$result = [];
try {
$sxml = new SimpleXMLElement($data);
}
catch (\Exception $ex) {
$format_definition = \Drupal::service('plugin.manager.bibcite_format')->getDefinition($format);
$format_label = $format_definition['label'];
throw new UnexpectedValueException("Incorrect '{$format_label}' format.");
}
$records = $sxml->records;
$config = \Drupal::config('bibcite_entity.mapping.' . $format);
$indexes = $config->get('indexes');
if ($records instanceof SimpleXMLElement) {
foreach ($records->children() as $record) {
$rec = [];
if ($record instanceof SimpleXMLElement) {
foreach ($record->children() as $child) {
if ($child instanceof SimpleXMLElement) {
switch ($child->getName()) {
case'REFERENCE_TYPE':
case'ref-type':
if (strlen($child->__toString()) > 0) {
$type = array_search($child->__toString(), $indexes);
if ($type) {
$rec['type'] = $type;
}
else {
$rec['type'] = -1;
}
}
break;
case'DATES':
case 'dates':
foreach ($child->children() as $dates) {
if ($dates instanceof SimpleXMLElement) {
switch ($dates->getName()) {
case'YEAR':
case'year':
$rec[$dates->getName()] = $this->getString($dates);
break;
case'DATE':
case'date':
$rec[$dates->{'pub-dates'}->getName()] = $this->getString($dates->{'pub-dates'});
break;
}
}
}
break;
case'CONTRIBUTORS':
case'contributors':
foreach ($child->children() as $authors) {
if ($authors instanceof SimpleXMLElement) {
foreach ($authors->children() as $author) {
// Hotfix: contributorKey property is hardcoded in the
// bibcite_endnote.services.xml. Access authors by this key
// explicitly for the moment, mapping is not working anyway.
// @todo Rework this place when mapping of authors is fixed and flexible.
$rec['authors'][] = $this->getString($author);
}
}
}
break;
case'TITLES':
case'titles':
foreach ($child->children() as $title) {
if ($title instanceof SimpleXMLElement) {
$rec[$title->getName()] = $this->getString($title);
}
}
break;
case'KEYWORDS':
case'keywords':
foreach ($child->children() as $keyword) {
if ($keyword instanceof SimpleXMLElement) {
$rec['keywords'][] = $this->getString($keyword);
}
}
break;
case'source-app':
break;
default:
$rec[$child->getName()] = $this->getString($child);
break;
}
}
}
}
$result[] = $rec;
}
}
return $result;
}
/**
* Get string from element.
*
* @param \SimpleXMLElement $element
* Element value.
*
* @return string
* Result string.
*/
private function getString($element) {
return $element->style->__toString() ?: $element->__toString();
}
/**
* {@inheritdoc}
*/
public function supportsEncoding(string $format): bool {
return in_array($format, static::$format);
}
/**
* {@inheritdoc}
*/
public function encode(mixed $data, string $format, array $context = []): string {
if (isset($data['type'])) {
$data = [$data];
}
if ($format === 'tagged') {
return $this->encodeTagged($data);
}
$sxml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><xml><records></records></xml>');
$records = $sxml->records;
foreach ($data as $id => $ref) {
if ($records instanceof SimpleXMLElement) {
switch ($format) {
case'endnote7':
$record = $records->addChild('RECORD');
$contrib_key = 'CONTRIBUTORS';
$author_key = 'AUTHOR';
$titles_key = 'TITLES';
$keywords_key = 'KEYWORDS';
$dates_key = 'DATES';
$web_key = 'WEB-URLS';
$pub_key = 'PUB-DATES';
$keyword_key = 'KEYWORD';
$authors_key = 'AUTHORS';
break;
case'endnote8':
default:
$record = $records->addChild('record');
$contrib_key = 'contributors';
$author_key = 'author';
$titles_key = 'titles';
$keywords_key = 'keywords';
$dates_key = 'dates';
$web_key = 'web-urls';
$pub_key = 'pub-dates';
$keyword_key = 'keyword';
$authors_key = 'authors';
break;
}
$source = $record->addChild('source-app', 'Drupal-Bibcite');
$source->addAttribute('name', 'Bibcite');
$source->addAttribute('version', '8.x');
$config = \Drupal::config('bibcite_entity.mapping.' . $format);
$indexes = $config->get('fields');
$ref['type_en8id'] = $config->get('indexes')[$ref['type']];
$type_key = array_search('type', $indexes);
if ($type_key) {
$record->addChild($type_key, $ref['type_en8id']);
}
unset($ref[$type_key]);
unset($ref['type']);
unset($ref['type_en8id']);
unset($ref['reference']);
if ($authors_key) {
$authors = $record->addChild($contrib_key)->addChild($authors_key);
// Hotfix: contributorKey property is hardcoded in the
// bibcite_endnote.services.xml. Access authors by this key
// explicitly for the moment, mapping is not working anyway.
// @todo Rework this place when mapping of authors is fixed and flexible.
if (isset($ref['authors'])) {
foreach ($ref['authors'] as $author) {
$author_xml = $authors->addChild($author_key);
$this->setStyledText($author_xml, $author);
}
unset($ref['authors']);
}
}
$titles = $record->addChild($titles_key);
$this->addTitles($titles, $ref, $indexes);
$keywords = $record->addChild($keywords_key);
$this->addKeywords($keywords, $ref, $indexes, $keyword_key);
unset($ref[$keywords_key]);
$dates = $record->addChild($dates_key);
$this->addDates($dates, $ref, $indexes, $pub_key);
$urls_key = array_search('urls', $indexes);
if (isset($ref[$urls_key])) {
$xml = $record->addChild($web_key);
$this->addTag($xml, $urls_key, $ref[$urls_key]);
unset($ref[$urls_key]);
}
// Only in endnote X3.
if (isset($ref['full-title'])) {
$xml = $record->addChild('periodical');
$this->addTag($xml, 'full-title', $ref['full-title']);
unset($ref['full-title']);
}
$this->addFields($record, $ref);
}
}
return $sxml->asXML();
}
/**
* Add titles to xml.
*
* @param \SimpleXMLElement $xml
* Parent XmlElement.
* @param mixed $ref
* Our reference.
* @param array $indexes
* Mapping indexes.
*/
private function addTitles(&$xml, &$ref, $indexes) {
foreach ($ref as $key => $value) {
if (array_key_exists($key, $indexes)) {
$title_key = $indexes[$key];
switch ($title_key) {
case 'title':
case 'secondary-title':
case 'tertiary-title':
case 'alt-title':
case 'short-title':
case 'translated-title':
$this->addTag($xml, $key, $value);
unset($ref[$key]);
break;
}
}
}
}
/**
* Add keywords to xml.
*
* @param \SimpleXMLElement $xml
* Parent XmlElement.
* @param mixed $ref
* Our reference.
* @param array $indexes
* Mapping indexes.
*/
private function addKeywords(&$xml, &$ref, $indexes, $keyword_key) {
foreach ($ref as $key => $value) {
switch ($key) {
case 'keywords':
foreach ($ref[$key] as $keyword) {
$this->addTag($xml, $keyword_key, $keyword);
}
unset($ref[$key]);
break;
}
}
}
/**
* Add dates to xml.
*
* @param \SimpleXMLElement $xml
* Parent XmlElement.
* @param mixed $ref
* Our reference.
* @param array $indexes
* Mapping indexes.
* @param string $pub_key
* Key for pub-date.
*/
private function addDates(&$xml, &$ref, $indexes, $pub_key) {
foreach ($ref as $key => $value) {
switch ($key) {
case 'date':
$date = $xml->addChild($pub_key);
$this->addTag($date, $key, $value);
unset($ref[$key]);
break;
case 'year':
$this->addTag($xml, $key, $value);
unset($ref[$key]);
break;
}
}
}
/**
* Add fields to xml.
*
* @param \SimpleXMLElement $xml
* Parent XmlElement.
* @param mixed $ref
* Our reference.
*/
private function addFields(&$xml, &$ref) {
foreach ($ref as $key => $value) {
$this->addTag($xml, $key, $value);
unset($ref[$key]);
}
}
/**
* Add value to xml tag.
*
* @param \SimpleXMLElement $xml
* Parent XmlElement.
* @param string $tag
* Xml tag to add.
* @param string $value
* Text to set.
*/
private function addTag(&$xml, $tag, $value) {
$xc = $xml->addChild($tag);
$this->setStyledText($xc, $value);
}
/**
* Add xml child style.
*
* @param \SimpleXMLElement $xml
* Parent XmlElement.
* @param string $text
* Text to set.
*/
private function setStyledText(&$xml, $text) {
$styled = $xml->addChild('style', $text);
$styled->addAttribute('face', 'normal');
$styled->addAttribute('font', 'default');
$styled->addAttribute('size', '100%');
}
/**
* Decode tagged format function.
*/
private function decodeTagged($data) {
$result = [];
$config = \Drupal::config('bibcite_entity.mapping.tagged');
$indexes = $config->get('indexes');
$data = explode("\n\n", $data);
foreach ($data as $i => &$record) {
$fields = explode("\n", $record);
foreach ($fields as $field) {
$key = array_search(substr($field, 0, 2), $indexes);
$value = substr($field, 3);
if ($key) {
if (isset($result[$i][$key])) {
if (is_array($result[$i][$key])) {
$result[$i][$key][] = $value;
}
else {
$val = $result[$i][$key];
unset($result[$i][$key]);
$result[$i][$key][] = $val;
$result[$i][$key][] = $value;
}
}
else {
$result[$i][$key] = $value;
}
}
}
}
return $result;
}
/**
* Encode tagged format function.
*/
private function encodeTagged($data) {
$data = array_map(function ($raw) {
return $this->buildEntry($raw);
}, $data);
return implode("\n", $data);
}
/**
* Build tagged entry string.
*
* @param array $data
* Array of tagged values.
*
* @return string
* Formatted tagged string.
*/
protected function buildEntry(array $data) {
$config = \Drupal::config('bibcite_entity.mapping.tagged');
$indexes = $config->get('indexes');
$entry = NULL;
// For not duplicating pages parse.
$pages_parsed = FALSE;
foreach ($data as $key => $value) {
if (is_array($value)) {
if (isset($indexes[$key])) {
$entry .= $this->buildMultiLine($indexes[$key], $value);
}
}
else {
if (isset($indexes[$key])) {
$entry .= $this->buildLine($indexes[$key], $value);
}
}
}
return $entry;
}
/**
* Build multi line entry.
*
* @param string $key
* Line key.
* @param array $value
* Array of multi line values.
*
* @return string
* Multi line entry.
*/
protected function buildMultiLine($key, array $value) {
$lines = '';
foreach ($value as $item) {
$lines .= $this->buildLine($key, $item);
}
return $lines;
}
/**
* Build entry line.
*
* @param string $key
* Line key.
* @param string $value
* Line value.
*
* @return string
* Entry line.
*/
protected function buildLine($key, $value) {
return $key . ' ' . $value . "\n";
}
}
