Open Monograph Press  1.1
 All Classes Namespaces Functions Variables Groups Pages
Mods34DescriptionXmlFilter.inc.php
1 <?php
20 import('lib.pkp.classes.filter.PersistableFilter');
21 import('lib.pkp.classes.xml.XMLCustomWriter');
22 
28  function Mods34DescriptionXmlFilter($filterGroup) {
29  $this->setDisplayName('MODS 3.4');
30  parent::PersistableFilter($filterGroup);
31  }
32 
33 
34  //
35  // Implement template methods from PersistableFilter
36  //
40  function getClassName() {
41  return 'lib.pkp.plugins.metadata.mods34.filter.Mods34DescriptionXmlFilter';
42  }
43 
44 
45  //
46  // Implement template methods from Filter
47  //
52  function &process(&$input) {
53  // Start the XML document.
55 
56  // Create the root element.
57  $root =& XMLCustomWriter::createElement($doc, 'mods');
58 
59  // Add the XML namespace and schema.
60  XMLCustomWriter::setAttribute($root, 'version', '3.4');
61  XMLCustomWriter::setAttribute($root, 'xmlns', 'http://www.loc.gov/mods/v3');
62  XMLCustomWriter::setAttribute($root, 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
63  XMLCustomWriter::setAttribute($root, 'xsi:schemaLocation', 'http://www.loc.gov/mods/v3 http://www.loc.gov/standards/mods/v3/mods-3-4.xsd');
64 
65  // Prepare the MODS document hierarchy from the MODS MetadataDescription instance.
66  $documentHierarchy =& $this->_buildDocumentHierarchy($doc, $root, $input);
67 
68  // Recursively join the document hierarchy into a single document.
69  $root =& $this->_joinNodes($documentHierarchy);
70  XMLCustomWriter::appendChild($doc, $root);
71 
72  // Retrieve the XML from the DOM.
73  $output = XMLCustomWriter::getXml($doc);
74  return $output;
75  }
76 
77  //
78  // Private helper methods
79  //
87  function &_processCompositeProperty(&$doc, $elementName, &$metadataDescription) {
88  // Create the root element.
89  $root =& XMLCustomWriter::createElement($doc, $elementName);
90 
91  // Prepare the MODS hierarchy from the MODS name MetadataDescription instance.
92  $documentHierarchy =& $this->_buildDocumentHierarchy($doc, $root, $metadataDescription);
93 
94  // Recursively join the name document hierarchy into a single element.
95  $root =& $this->_joinNodes($documentHierarchy);
96  return $root;
97  }
98 
110  function &_buildDocumentHierarchy(&$doc, &$root, &$mods34Description) {
111  // Get the MODS schema.
112  $mods34Schema =& $mods34Description->getMetadataSchema();
113  if (is_a($mods34Schema, 'Mods34Schema')) {
114  // Identify the cataloging language.
115  assert($mods34Description->hasStatement('recordInfo/languageOfCataloging/languageTerm[@authority="iso639-2b"]'));
116  $catalogingLanguage = $mods34Description->getStatement('recordInfo/languageOfCataloging/languageTerm[@authority="iso639-2b"]');
117  } else {
118  // This must be a MODS name schema.
119  assert(is_a($mods34Schema, 'Mods34NameSchema'));
120  $catalogingLanguage = 'undefined';
121  }
122 
123  // Initialize the document hierarchy with the root node.
124  $documentHierarchy = array(
125  '@branch' => &$root
126  );
127 
128  // Find the translations required for top-level elements.
129  // We need this array later because we'll have to repeat non-translated
130  // values for every translated top-level element.
131  $properties = $mods34Description->getProperties();
132  $translations = array();
133  foreach ($properties as $propertyName => $property) { /* @var $property MetadataProperty */
134  if ($mods34Description->hasStatement($propertyName)) {
135  $nodes = explode('/', $propertyName);
136  $topLevelNode = array_shift($nodes);
137  if (!isset($translations[$topLevelNode])) $translations[$topLevelNode] = array();
138  if ($property->getTranslated()) {
139  foreach ($mods34Description->getStatementTranslations($propertyName) as $locale => $value) {
140  $isoLanguage = AppLocale::get3LetterIsoFromLocale($locale);
141  if (!in_array($isoLanguage, $translations[$topLevelNode])) {
142  $translations[$topLevelNode][] = $isoLanguage;
143  }
144  }
145  } else {
146  if (!in_array($catalogingLanguage, $translations[$topLevelNode])) {
147  $translations[$topLevelNode][] = $catalogingLanguage;
148  }
149  }
150  }
151  }
152 
153  // Build the document hierarchy.
154  foreach ($properties as $propertyName => $property) { /* @var $property MetadataProperty */
155  if ($mods34Description->hasStatement($propertyName)) {
156  // Get relevant property attributes.
157  $translated = $property->getTranslated();
158  $cardinality = $property->getCardinality();
159 
160  // Get the XML element hierarchy.
161  $nodes = explode('/', $propertyName);
162  $hierarchyDepth = count($nodes) - 1;
163 
164  // Normalize property values to an array of translated strings.
165  if ($translated) {
166  // Only the main MODS schema can contain translated values.
167  assert(is_a($mods34Schema, 'Mods34Schema'));
168 
169  // Retrieve the translated values of the statement.
170  $localizedValues =& $mods34Description->getStatementTranslations($propertyName);
171 
172  // Translate the PKP locale into ISO639-2b 3-letter codes.
173  $translatedValues = array();
174  foreach($localizedValues as $locale => $translatedValue) {
175  $isoLanguage = AppLocale::get3LetterIsoFromLocale($locale);
176  assert(!is_null($isoLanguage));
177  $translatedValues[$isoLanguage] = $translatedValue;
178  }
179  } else {
180  // Untranslated statements will be repeated for all languages
181  // present in the top-level element.
182  $untranslatedValue =& $mods34Description->getStatement($propertyName);
183  $translatedValues = array();
184  assert(isset($translations[$nodes[0]]));
185  foreach($translations[$nodes[0]] as $isoLanguage) {
186  $translatedValues[$isoLanguage] = $untranslatedValue;
187  }
188  }
189 
190  // Normalize all values to arrays so that we can
191  // handle them uniformly.
192  $translatedValueArrays = array();
193  foreach($translatedValues as $isoLanguage => $translatedValue) {
194  if ($cardinality == METADATA_PROPERTY_CARDINALITY_ONE) {
195  assert(is_scalar($translatedValue));
196  $translatedValueArrays[$isoLanguage] = array(&$translatedValue);
197  } else {
198  assert(is_array($translatedValue));
199  $translatedValueArrays[$isoLanguage] =& $translatedValue;
200  }
201  unset($translatedValue);
202  }
203 
204  // Add the translated values one by one to the element hierarchy.
205  foreach($translatedValueArrays as $isoLanguage => $translatedValueArray) {
206  foreach($translatedValueArray as $translatedValue) {
207  // Add a language attribute to the top-level element if
208  // it differs from the cataloging language.
209  $translatedNodes = $nodes;
210  if ($isoLanguage != $catalogingLanguage) {
211  assert(strpos($translatedNodes[0], '[') === false);
212  $translatedNodes[0] .= '[@lang="'.$isoLanguage.'"]';
213  }
214 
215  // Create the node hierarchy for the statement.
216  $currentNodeList =& $documentHierarchy;
217  foreach($translatedNodes as $nodeDepth => $nodeName) {
218  // Are we at a leaf node?
219  if($nodeDepth == $hierarchyDepth) {
220  // Is this a top-level attribute?
221  if (substr($nodeName, 0, 1) == '[') {
222  assert($nodeDepth == 0);
223  assert($translated == false);
224  assert($cardinality == METADATA_PROPERTY_CARDINALITY_ONE);
225  assert(!is_object($translatedValue));
226  $attributeName = trim($nodeName, '[@"]');
227  XMLCustomWriter::setAttribute($root, $attributeName, (string)$translatedValue);
228  continue;
229  }
230 
231  // This is a sub-element.
232  if (isset($currentNodeList[$nodeName])) {
233  // Only properties with cardinality "many" can
234  // have more than one leaf node.
235  assert($cardinality == METADATA_PROPERTY_CARDINALITY_MANY);
236 
237  // Check that the leaf list is actually there.
238  assert(isset($currentNodeList[$nodeName]['@leaves']));
239 
240  // We should never find any branch in a leaves node.
241  assert(!isset($currentNodeList[$nodeName]['@branch']));
242  } else {
243  // Create the leaf list in the hierarchy.
244  $currentNodeList[$nodeName]['@leaves'] = array();
245  }
246 
247  if (is_a($translatedValue, 'MetadataDescription')) {
248  // Recursively process composite properties.
249  $leafNode =& $this->_processCompositeProperty($doc, $propertyName, $translatedValue);
250  } else {
251  // Cast scalar values to string types for XML binding.
252  $translatedValue = (string)$translatedValue;
253 
254  // Create the leaf element.
255  $leafNode =& $this->_createNode($doc, $nodeName, $translatedValue);
256  }
257 
258  // Add the leaf element to the leaves list.
259  $currentNodeList[$nodeName]['@leaves'][] =& $leafNode;
260  unset($leafNode);
261  } else {
262  // This is a branch node.
263 
264  // Has the branch already been created? If not: create it.
265  if (isset($currentNodeList[$nodeName])) {
266  // Check that the branch node is actually there.
267  assert(isset($currentNodeList[$nodeName]['@branch']));
268 
269  // We should never find any leaves in a branch node.
270  assert(!isset($currentNodeList[$nodeName]['@leaves']));
271  } else {
272  // Create the branch node.
273  $branchNode =& $this->_createNode($doc, $nodeName);
274 
275  // Add the branch node list and add the new node as it's root element.
276  $currentNodeList[$nodeName] = array(
277  '@branch' => &$branchNode
278  );
279  unset($branchNode);
280  }
281  }
282 
283  // Set the node list pointer to the sub-element
284  $currentNodeList =& $currentNodeList[$nodeName];
285  }
286  }
287  }
288  }
289  }
290 
291  return $documentHierarchy;
292  }
293 
302  function &_createNode($doc, $nodePath, $value = null) {
303  // Separate the element name from the attributes.
304  $elementPlusAttributes = explode('[', $nodePath);
305  $element = $elementPlusAttributes[0];
306  assert(!empty($element));
307 
308  // Create the element.
309  $newNode =& XMLCustomWriter::createElement($doc, $element);
310 
311  // Add attributes.
312  if (count($elementPlusAttributes) == 2) {
313  // Separate the attribute key/value pairs.
314  $unparsedAttributes = explode('@', rtrim(ltrim($elementPlusAttributes[1], '@'), ']'));
315  foreach($unparsedAttributes as $unparsedAttribute) {
316  // Split attribute expressions into key and value.
317  list($attributeName, $attributeValue) = explode('=', rtrim($unparsedAttribute, ' '));
318  $attributeValue = trim($attributeValue, '"');
319  XMLCustomWriter::setAttribute($newNode, $attributeName, $attributeValue);
320  }
321  }
322 
323  // Insert a text node if we got a value for it.
324  if (!is_null($value)) {
325  $textNode =& XMLCustomWriter::createTextNode($doc, $value);
326  XMLCustomWriter::appendChild($newNode, $textNode);
327  }
328 
329  return $newNode;
330  }
331 
337  function &_joinNodes(&$documentHierarchy) {
338  // Get the root node of the hierarchy.
339  $root = $documentHierarchy['@branch'];
340  unset($documentHierarchy['@branch']);
341 
342  // Add the sub-hierarchies to the root element.
343  foreach($documentHierarchy as $subHierarchy) {
344  // Is this a leaf node?
345  if (isset($subHierarchy['@leaves'])) {
346  // Make sure that there's no rubbish in this node.
347  assert(count($subHierarchy) == 1);
348 
349  foreach($subHierarchy['@leaves'] as $leafNode) {
350  XMLCustomWriter::appendChild($root, $leafNode);
351  }
352  } else {
353  // This is a branch node.
354  $subNode =& $this->_joinNodes($subHierarchy);
355  XMLCustomWriter::appendChild($root, $subNode);
356  }
357  }
358 
359  // Return the node list.
360  return $root;
361  }
362 }
363 ?>
static & createDocument($type=null, $dtd=null, $url=null)
static get3LetterIsoFromLocale($locale)
& _createNode($doc, $nodePath, $value=null)
& _buildDocumentHierarchy(&$doc, &$root, &$mods34Description)
setDisplayName($displayName)
Definition: Filter.inc.php:140
A filter that can be persisted to the database.
Class that converts a meta-data description to a MODS 3.4 XML document.
& _processCompositeProperty(&$doc, $elementName, &$metadataDescription)