/**
  * Installs filter template entries into the filters table.
  * FIXME: Move this to plug-in installation when moving filters to plug-ins, see #5157.
  */
 function installFilterTemplates()
 {
     // Filters are supported on PHP5+ only.
     if (!checkPhpVersion('5.0.0')) {
         return true;
     }
     $filterDao =& DAORegistry::getDAO('FilterDAO');
     $filtersToBeInstalled = array('lib.pkp.classes.citation.lookup.crossref.CrossrefNlmCitationSchemaFilter', 'lib.pkp.classes.citation.lookup.pubmed.PubmedNlmCitationSchemaFilter', 'lib.pkp.classes.citation.lookup.worldcat.WorldcatNlmCitationSchemaFilter', 'lib.pkp.classes.citation.parser.freecite.FreeciteRawCitationNlmCitationSchemaFilter', 'lib.pkp.classes.citation.parser.paracite.ParaciteRawCitationNlmCitationSchemaFilter', 'lib.pkp.classes.citation.parser.parscit.ParscitRawCitationNlmCitationSchemaFilter', 'lib.pkp.classes.citation.parser.regex.RegexRawCitationNlmCitationSchemaFilter', 'lib.pkp.classes.citation.output.abnt.NlmCitationSchemaAbntFilter', 'lib.pkp.classes.citation.output.apa.NlmCitationSchemaApaFilter', 'lib.pkp.classes.citation.output.mla.NlmCitationSchemaMlaFilter', 'lib.pkp.classes.citation.output.vancouver.NlmCitationSchemaVancouverFilter', 'lib.pkp.classes.importexport.nlm.PKPSubmissionNlmXmlFilter');
     import('lib.pkp.classes.citation.output.PlainTextReferencesListFilter');
     foreach ($filtersToBeInstalled as $filterToBeInstalled) {
         // Instantiate filter.
         $filter =& instantiate($filterToBeInstalled, 'Filter');
         // Install citation output filters as non-configurable site-wide filter instances.
         if (is_a($filter, 'NlmCitationSchemaCitationOutputFormatFilter') || is_a($filter, 'PKPSubmissionNlmXmlFilter')) {
             $filter->setIsTemplate(false);
             // Check whether the filter instance has been
             // installed before.
             $existingFilters =& $filterDao->getObjectsByClass($filterToBeInstalled, 0, false);
             // Install other filter as configurable templates.
         } else {
             $filter->setIsTemplate(true);
             // Check whether the filter template has been
             // installed before.
             $existingFilters =& $filterDao->getObjectsByClass($filterToBeInstalled, 0, true);
         }
         // Guarantee idempotence.
         if ($existingFilters->getCount()) {
             continue;
         }
         // Install the filter or template.
         $filterDao->insertObject($filter, 0);
         // If this is a citation output filter then also install a corresponding references list filter.
         if (is_a($filter, 'NlmCitationSchemaCitationOutputFormatFilter')) {
             // Only Vancouver Style listings require numerical ordering.
             if (is_a($filter, 'NlmCitationSchemaVancouverFilter')) {
                 $ordering = REFERENCES_LIST_ORDERING_NUMERICAL;
             } else {
                 $ordering = REFERENCES_LIST_ORDERING_ALPHABETICAL;
             }
             // Instantiate the filter.
             $referencesListFilter = new PlainTextReferencesListFilter($filter->getDisplayName(), $filter->getClassName(), $ordering);
             $referencesListFilter->setIsTemplate(false);
             // Install the filter.
             $filterDao->insertObject($referencesListFilter, 0);
             unset($referencesListFilter);
         }
         unset($filter);
     }
     // Composite filters are more complex to install because they
     // need to be constructed first:
     // 1) Check and install the ISBNdb filter template.
     $alreadyInstalled = false;
     $existingTemplatesFactory =& $filterDao->getObjectsByClass('lib.pkp.classes.filter.GenericSequencerFilter', 0, true);
     $existingTemplates =& $existingTemplatesFactory->toArray();
     foreach ($existingTemplates as $existingTemplate) {
         $subFilters =& $existingTemplate->getFilters();
         if (count($subFilters) != 2) {
             continue;
         }
         if (!(isset($subFilters[1]) && is_a($subFilters[1], 'IsbndbNlmCitationSchemaIsbnFilter'))) {
             continue;
         }
         if (!(isset($subFilters[2]) && is_a($subFilters[2], 'IsbndbIsbnNlmCitationSchemaFilter'))) {
             continue;
         }
         $alreadyInstalled = true;
         break;
     }
     if (!$alreadyInstalled) {
         // Instantiate the filter as a configurable template.
         $isbndbTransformation = array('metadata::lib.pkp.classes.metadata.nlm.NlmCitationSchema(CITATION)', 'metadata::lib.pkp.classes.metadata.nlm.NlmCitationSchema(CITATION)');
         import('lib.pkp.classes.filter.GenericSequencerFilter');
         $isbndbFilter = new GenericSequencerFilter('ISBNdb', $isbndbTransformation);
         $isbndbFilter->setIsTemplate(true);
         // Instantiate and add the NLM-to-ISBN filter.
         import('lib.pkp.classes.citation.lookup.isbndb.IsbndbNlmCitationSchemaIsbnFilter');
         $nlmToIsbnFilter = new IsbndbNlmCitationSchemaIsbnFilter();
         $isbndbFilter->addFilter($nlmToIsbnFilter);
         // Instantiate and add the ISBN-to-NLM filter.
         import('lib.pkp.classes.citation.lookup.isbndb.IsbndbIsbnNlmCitationSchemaFilter');
         $isbnToNlmFilter = new IsbndbIsbnNlmCitationSchemaFilter();
         $isbndbFilter->addFilter($isbnToNlmFilter);
         // Add the settings mapping.
         $isbndbFilter->setSettingsMapping(array('apiKey' => array('seq' . $nlmToIsbnFilter->getSeq() . '_apiKey', 'seq' . $isbnToNlmFilter->getSeq() . '_apiKey'), 'isOptional' => array('seq' . $nlmToIsbnFilter->getSeq() . '_isOptional', 'seq' . $isbnToNlmFilter->getSeq() . '_isOptional')));
         // Persist the composite filter.
         $filterDao->insertObject($isbndbFilter, 0);
     }
     // 3) Check and install the NLM XML 2.3 output filter.
     $alreadyInstalled = false;
     $existingTemplatesFactory =& $filterDao->getObjectsByClass('lib.pkp.classes.filter.GenericSequencerFilter', 0, false);
     $existingTemplates =& $existingTemplatesFactory->toArray();
     foreach ($existingTemplates as $existingTemplate) {
         $subFilters =& $existingTemplate->getFilters();
         if (count($subFilters) != 2) {
             continue;
         }
         if (!(isset($subFilters[1]) && is_a($subFilters[1], 'PKPSubmissionNlmXmlFilter'))) {
             continue;
         }
         if (!(isset($subFilters[2]) && is_a($subFilters[2], 'XSLTransformationFilter'))) {
             continue;
         }
         $alreadyInstalled = true;
         break;
     }
     if (!$alreadyInstalled) {
         // Instantiate the filter as a non-configurable filter instance.
         $nlm23Transformation = array('class::lib.pkp.classes.submission.Submission', 'xml::*');
         $nlm23Filter = new GenericSequencerFilter('NLM Journal Publishing V2.3 ref-list', $nlm23Transformation);
         $nlm23Filter->setIsTemplate(false);
         // Instantiate and add the NLM 3.0 export filter.
         import('lib.pkp.classes.importexport.nlm.PKPSubmissionNlmXmlFilter');
         $nlm30Filter = new PKPSubmissionNlmXmlFilter();
         $nlm23Filter->addFilter($nlm30Filter);
         // Instantiate, configure and add the NLM 3.0 to 2.3 downgrade XSL transformation.
         import('lib.pkp.classes.xslt.XSLTransformationFilter');
         $downgradeFilter = new XSLTransformationFilter('NLM 3.0 to 2.3 ref-list downgrade', array('xml::*', 'xml::*'));
         $downgradeFilter->setXSLFilename('lib/pkp/classes/importexport/nlm/nlm-ref-list-30-to-23.xsl');
         $nlm23Filter->addFilter($downgradeFilter);
         // Persist the composite filter.
         $filterDao->insertObject($nlm23Filter, 0);
     }
     return true;
 }
 /**
  * Takes the raw xml result of a web service and
  * transforms it via XSL to a (preliminary) XML similar
  * to NLM which is then re-encoded into an array. Finally
  * some typical post-processing is performed.
  * FIXME: Rewrite parser/lookup filter XSL to produce real NLM
  * element-citation XML and factor this code into an NLM XML to
  * NLM description filter.
  * @param $xmlResult string or DOMDocument
  * @param $xslFileName string
  * @return array a metadata array
  */
 function &transformWebServiceResults(&$xmlResult, $xslFileName)
 {
     // Send the result through the XSL to generate a (preliminary) NLM XML.
     $xslFilter = new XSLTransformationFilter(PersistableFilter::tempGroup('xml::*', 'xml::*'), 'Web Service Transformation');
     $xslFilter->setXSLFilename($xslFileName);
     $xslFilter->setResultType(XSL_TRANSFORMER_DOCTYPE_DOM);
     $preliminaryNlm30DOM =& $xslFilter->execute($xmlResult);
     if (is_null($preliminaryNlm30DOM) || is_null($preliminaryNlm30DOM->documentElement)) {
         $translationParams = array('filterName' => $this->getDisplayName());
         $this->addError(__('submission.citations.filter.webserviceResultTransformationError', $translationParams));
         $nullVar = null;
         return $nullVar;
     }
     // Transform the result to an array.
     $xmlHelper = new XMLHelper();
     $preliminaryNlm30Array = $xmlHelper->xmlToArray($preliminaryNlm30DOM->documentElement);
     $preliminaryNlm30Array =& $this->postProcessMetadataArray($preliminaryNlm30Array);
     return $preliminaryNlm30Array;
 }
 /**
  * @covers Nlm30Nlm23CrosswalkFilter
  */
 public function testExecute()
 {
     $this->markTestSkipped('Weird class interaction with ControlledVocabEntryDAO leads to failure');
     // Instantiate test meta-data for a citation. This must use the complete
     // available schema (although in practice this doesn't make sense) so that
     // we can make sure all tags are correctly converted.
     import('lib.pkp.classes.metadata.MetadataDescription');
     $nameSchemaName = 'lib.pkp.plugins.metadata.nlm30.schema.Nlm30NameSchema';
     $nameDescription = new MetadataDescription($nameSchemaName, ASSOC_TYPE_AUTHOR);
     $nameDescription->addStatement('given-names', $value = 'Peter');
     $nameDescription->addStatement('given-names', $value = 'B');
     $nameDescription->addStatement('surname', $value = 'Bork');
     $nameDescription->addStatement('prefix', $value = 'Mr.');
     $nameDescription->addStatement('suffix', $value = 'Jr');
     $citationSchemaName = 'lib.pkp.plugins.metadata.nlm30.schema.Nlm30CitationSchema';
     $citationDescription = new MetadataDescription($citationSchemaName, ASSOC_TYPE_CITATION);
     $citationDescription->addStatement('person-group[@person-group-type="author"]', $nameDescription);
     $citationDescription->addStatement('person-group[@person-group-type="editor"]', $nameDescription);
     $citationDescription->addStatement('article-title', $value = 'PHPUnit in a nutshell', 'en_US');
     $citationDescription->addStatement('source', $value = 'PHPUnit in a nutshell', 'en_US');
     $citationDescription->addStatement('date', $value = '2009-08-17');
     $citationDescription->addStatement('date-in-citation[@content-type="access-date"]', $value = '2009-08');
     $citationDescription->addStatement('issue', $value = 5);
     $citationDescription->addStatement('volume', $value = 6);
     $citationDescription->addStatement('season', $value = 'Summer');
     $citationDescription->addStatement('chapter-title', $value = 'Introduction');
     $citationDescription->addStatement('edition', $value = '2nd edition');
     $citationDescription->addStatement('series', $value = 7);
     $citationDescription->addStatement('supplement', $value = 'Summer Special');
     $citationDescription->addStatement('conf-date', $value = '2009-08-17');
     $citationDescription->addStatement('conf-loc', $value = 'Helsinki');
     $citationDescription->addStatement('conf-name', $value = 'PHPUnit Hackfest');
     $citationDescription->addStatement('conf-sponsor', $value = 'Basti himself');
     $citationDescription->addStatement('institution', $value = 'PKP');
     $citationDescription->addStatement('fpage', $value = 9);
     $citationDescription->addStatement('lpage', $value = 312);
     $citationDescription->addStatement('size', $value = 320);
     $citationDescription->addStatement('publisher-loc', $value = 'Vancouver');
     $citationDescription->addStatement('publisher-name', $value = 'SFU');
     $citationDescription->addStatement('isbn', $value = '123456789');
     $citationDescription->addStatement('issn[@pub-type="ppub"]', $value = '987654321');
     $citationDescription->addStatement('issn[@pub-type="epub"]', $value = '111111111');
     $citationDescription->addStatement('pub-id[@pub-id-type="doi"]', $value = '10420/39406');
     $citationDescription->addStatement('pub-id[@pub-id-type="publisher-id"]', $value = 'xyz');
     $citationDescription->addStatement('pub-id[@pub-id-type="coden"]', $value = 'abc');
     $citationDescription->addStatement('pub-id[@pub-id-type="sici"]', $value = 'def');
     $citationDescription->addStatement('pub-id[@pub-id-type="pmid"]', $value = '999999');
     $citationDescription->addStatement('uri', $value = 'http://phpunit.org/nutshell');
     $citationDescription->addStatement('comment', $value = 'just nonsense');
     $citationDescription->addStatement('annotation', $value = 'more nonsense');
     $citationDescription->addStatement('[@publication-type]', $value = 'conf-proc');
     $citation =& $this->getCitation($citationDescription);
     // Persist one copy of the citation for testing.
     $citationDao =& $this->getCitationDao();
     $citation->setSeq(1);
     $citation->setCitationState(CITATION_APPROVED);
     $citationId = $citationDao->insertObject($citation);
     self::assertTrue(is_numeric($citationId));
     self::assertTrue($citationId > 0);
     // Construct the expected output.
     $expectedOutput = '';
     // Prepare NLM 3.0 input.
     $mockSubmission =& $this->getTestSubmission();
     import('lib.pkp.plugins.metadata.nlm30.filter.PKPSubmissionNlm30XmlFilter');
     $nlm30Filter = new PKPSubmissionNlm30XmlFilter(PersistableFilter::tempGroup('class::lib.pkp.classes.submission.Submission', 'xml::*'));
     $nlm30Xml = $nlm30Filter->execute($mockSubmission);
     // Test the downgrade filter.
     import('lib.pkp.classes.xslt.XSLTransformationFilter');
     // FIXME: Add NLM 2.3 and 3.0 tag set schema validation as soon as we implement the full tag set, see #5648.
     $downgradeFilter = new XSLTransformationFilter(PersistableFilter::tempGroup('xml::*', 'xml::*'), 'NLM 3.0 to 2.3 ref-list downgrade');
     $downgradeFilter->setXSLFilename('lib/pkp/plugins/metadata/nlm30/filter/nlm30-to-23-ref-list.xsl');
     $nlm30Xml = $downgradeFilter->execute($nlm30Xml);
     self::assertXmlStringEqualsXmlFile('./lib/pkp/tests/plugins/metadata/nlm30/filter/sample-nlm23-citation.xml', $nlm30Xml);
 }
 /**
  * Takes the raw xml result of a web service and
  * transforms it via XSL to a (preliminary) XML similar
  * to NLM which is then re-encoded into an array. Finally
  * some typical post-processing is performed.
  * FIXME: Rewrite parser/lookup filter XSL to produce real NLM
  * element-citation XML and factor this code into an NLM XML to
  * NLM description filter.
  * @param $xmlResult string or DOMDocument
  * @param $xslFileName string
  * @return array a metadata array
  */
 function &transformWebServiceResults(&$xmlResult, $xslFileName)
 {
     // Send the result through the XSL to generate a (preliminary) NLM XML.
     $xslFilter = new XSLTransformationFilter();
     $xslFilter->setXSLFilename($xslFileName);
     $xslFilter->setResultType(XSL_TRANSFORMER_DOCTYPE_DOM);
     $preliminaryNlmDOM =& $xslFilter->execute($xmlResult);
     if (is_null($preliminaryNlmDOM)) {
         return $preliminaryNlmDOM;
     }
     // Transform the result to an array.
     $xmlHelper = new XMLHelper();
     $preliminaryNlmArray = $xmlHelper->xmlToArray($preliminaryNlmDOM->documentElement);
     $preliminaryNlmArray =& $this->postProcessMetadataArray($preliminaryNlmArray);
     return $preliminaryNlmArray;
 }
 /**
  * @see OAIMetadataFormat#toXml
  * TODO:
  *  <copyright-holder>
  *  In Isabelle's mapping document:
  *   Article order in the issue's Table of Contents
  */
 function toXml(&$record, $format = null)
 {
     $article =& $record->getData('article');
     $journal =& $record->getData('journal');
     $section =& $record->getData('section');
     $issue =& $record->getData('issue');
     $galleys =& $record->getData('galleys');
     $articleId = $article->getId();
     // Cache issue ordering information.
     static $issueId;
     static $sectionSeq;
     if (!isset($issueId) || $issueId != $issue->getId()) {
         $sectionDao =& DAORegistry::getDAO('SectionDAO');
         $issueId = $issue->getId();
         $sections =& $sectionDao->getSectionsForIssue($issueId);
         $sectionSeq = array();
         $i = 0;
         foreach ($sections as $thisSection) {
             $sectionSeq[$thisSection->getSectionId()] = $i++;
         }
         unset($sections);
     }
     $abbreviation = $journal->getLocalizedSetting('abbreviation');
     $printIssn = $journal->getSetting('printIssn');
     $onlineIssn = $journal->getSetting('onlineIssn');
     $primaryLocale = $journal->getPrimaryLocale();
     $publisherInstitution = $journal->getSetting('publisherInstitution');
     $datePublished = $article->getDatePublished();
     if (!$datePublished) {
         $datePublished = $issue->getDatePublished();
     }
     if ($datePublished) {
         $datePublished = strtotime($datePublished);
     }
     $response = "<article\n" . "\txmlns=\"http://dtd.nlm.nih.gov/publishing/2.3\"\n" . "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" . "\txmlns:mml=\"http://www.w3.org/1998/Math/MathML\"\n" . "\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" . "\txsi:schemaLocation=\"http://dtd.nlm.nih.gov/publishing/2.3\n" . "\thttp://dtd.nlm.nih.gov/publishing/2.3/xsd/journalpublishing.xsd\"\n" . (($s = $section->getSectionIdentifyType()) != '' ? "\tarticle-type=\"" . htmlspecialchars(Core::cleanVar($s)) . "\"" : '') . "\txml:lang=\"" . strtoupper(substr($primaryLocale, 0, 2)) . "\">\n" . "\t<front>\n" . "\t\t<journal-meta>\n" . "\t\t\t<journal-id journal-id-type=\"other\">" . htmlspecialchars(Core::cleanVar(($s = Config::getVar('oai', 'nlm_journal_id')) != '' ? $s : $journal->getPath())) . "</journal-id>\n" . "\t\t\t<journal-title>" . htmlspecialchars(Core::cleanVar($journal->getJournalTitle())) . "</journal-title>\n";
     // Include translated journal titles
     foreach ($journal->getTitle(null) as $locale => $title) {
         if ($locale == $primaryLocale) {
             continue;
         }
         $response .= "\t\t\t<trans-title xml:lang=\"" . strtoupper(substr($locale, 0, 2)) . "\">" . htmlspecialchars(Core::cleanVar($title)) . "</trans-title>\n";
     }
     $response .= (!empty($onlineIssn) ? "\t\t\t<issn pub-type=\"epub\">" . htmlspecialchars(Core::cleanVar($onlineIssn)) . "</issn>" : '') . (!empty($printIssn) ? "\t\t\t<issn pub-type=\"ppub\">" . htmlspecialchars(Core::cleanVar($printIssn)) . "</issn>" : '') . ($publisherInstitution != '' ? "\t\t\t<publisher><publisher-name>" . htmlspecialchars(Core::cleanVar($publisherInstitution)) . "</publisher-name></publisher>\n" : '') . "\t\t</journal-meta>\n" . "\t\t<article-meta>\n" . "\t\t\t<article-id pub-id-type=\"other\">" . htmlspecialchars(Core::cleanVar($article->getBestArticleId())) . "</article-id>\n" . (($s = $article->getDOI()) != '' ? "\t\t\t<article-id pub-id-type=\"doi\">" . htmlspecialchars(Core::cleanVar($s)) . "</article-id>\n" : '') . "\t\t\t<article-categories><subj-group subj-group-type=\"heading\"><subject>" . htmlspecialchars(Core::cleanVar($section->getSectionTitle())) . "</subject></subj-group></article-categories>\n" . "\t\t\t<title-group>\n" . "\t\t\t\t<article-title>" . htmlspecialchars(Core::cleanVar(strip_tags($article->getArticleTitle()))) . "</article-title>\n";
     // Include translated journal titles
     foreach ($article->getTitle(null) as $locale => $title) {
         if ($locale == $primaryLocale) {
             continue;
         }
         $response .= "\t\t\t\t<trans-title xml:lang=\"" . strtoupper(substr($locale, 0, 2)) . "\">" . htmlspecialchars(Core::cleanVar(strip_tags($title))) . "</trans-title>\n";
     }
     $response .= "\t\t\t</title-group>\n" . "\t\t\t<contrib-group>\n";
     // Include authors
     foreach ($article->getAuthors() as $author) {
         $response .= "\t\t\t\t<contrib " . ($author->getPrimaryContact() ? 'corresp="yes" ' : '') . "contrib-type=\"author\">\n" . "\t\t\t\t\t<name name-style=\"western\">\n" . "\t\t\t\t\t\t<surname>" . htmlspecialchars(Core::cleanVar($author->getLastName())) . "</surname>\n" . "\t\t\t\t\t\t<given-names>" . htmlspecialchars(Core::cleanVar($author->getFirstName()) . (($s = $author->getMiddleName()) != '' ? " {$s}" : '')) . "</given-names>\n" . "\t\t\t\t\t</name>\n" . (($s = $author->getLocalizedAffiliation()) != '' ? "\t\t\t\t\t<aff>" . htmlspecialchars(Core::cleanVar($s)) . "</aff>\n" : '') . "\t\t\t\t\t<email>" . htmlspecialchars(Core::cleanVar($author->getEmail())) . "</email>\n" . (($s = $author->getUrl()) != '' ? "\t\t\t\t\t<uri>" . htmlspecialchars(Core::cleanVar($s)) . "</uri>\n" : '') . "\t\t\t\t</contrib>\n";
     }
     // Include editorships (optimized)
     $response .= $this->getEditorialInfo($journal->getId());
     $response .= "\t\t\t</contrib-group>\n";
     if ($datePublished) {
         $response .= "\t\t\t<pub-date pub-type=\"epub\">\n" . "\t\t\t\t<day>" . strftime('%d', $datePublished) . "</day>\n" . "\t\t\t\t<month>" . strftime('%m', $datePublished) . "</month>\n" . "\t\t\t\t<year>" . strftime('%Y', $datePublished) . "</year>\n" . "\t\t\t</pub-date>\n";
     }
     $response .= ($issue->getShowYear() ? "\t\t\t<pub-date pub-type=\"collection\"><year>" . htmlspecialchars(Core::cleanVar($issue->getYear())) . "</year></pub-date>\n" : '') . ($issue->getShowVolume() ? "\t\t\t<volume>" . htmlspecialchars(Core::cleanVar($issue->getVolume())) . "</volume>\n" : '') . ($issue->getShowNumber() ? "\t\t\t<issue seq=\"" . htmlspecialchars(Core::cleanVar($sectionSeq[$section->getId()] * 100 + $article->getSeq())) . "\">" . htmlspecialchars(Core::cleanVar($issue->getNumber())) . "</issue>\n" : '') . "\t\t\t<issue-id pub-id-type=\"other\">" . htmlspecialchars(Core::cleanVar($issue->getBestIssueId())) . "</issue-id>\n" . ($issue->getShowTitle() ? "\t\t\t<issue-title>" . htmlspecialchars(Core::cleanVar($issue->getIssueTitle())) . "</issue-title>\n" : '');
     // Include page info, if available and parseable.
     $matches = null;
     if (String::regexp_match_get('/^[Pp][Pp]?[.]?[ ]?(\\d+)$/', $article->getPages(), $matches)) {
         $matchedPage = htmlspecialchars(Core::cleanVar($matches[1]));
         $response .= "\t\t\t\t<fpage>{$matchedPage}</fpage><lpage>{$matchedPage}</lpage>\n";
         $pageCount = 1;
     } elseif (String::regexp_match_get('/^[Pp][Pp]?[.]?[ ]?(\\d+)[ ]?-[ ]?([Pp][Pp]?[.]?[ ]?)?(\\d+)$/', $article->getPages(), $matches)) {
         $matchedPageFrom = htmlspecialchars(Core::cleanVar($matches[1]));
         $matchedPageTo = htmlspecialchars(Core::cleanVar($matches[3]));
         $response .= "\t\t\t\t<fpage>{$matchedPageFrom}</fpage>\n" . "\t\t\t\t<lpage>{$matchedPageTo}</lpage>\n";
         $pageCount = $matchedPageTo - $matchedPageFrom + 1;
     }
     $response .= "\t\t\t<permissions>\n" . (($s = $journal->getLocalizedSetting('copyrightNotice')) != '' ? "\t\t\t\t<copyright-statement>" . htmlspecialchars(Core::cleanVar($s)) . "</copyright-statement>\n" : '') . ($datePublished ? "\t\t\t\t<copyright-year>" . strftime('%Y', $datePublished) . "</copyright-year>\n" : '') . "\t\t\t</permissions>\n" . "\t\t\t<self-uri xlink:href=\"" . htmlspecialchars(Core::cleanVar(Request::url($journal->getPath(), 'article', 'view', $article->getBestArticleId()))) . "\" />\n";
     // Include galley links
     foreach ($article->getGalleys() as $galley) {
         $response .= "\t\t\t<self-uri content-type=\"" . htmlspecialchars(Core::cleanVar($galley->getFileType())) . "\" xlink:href=\"" . htmlspecialchars(Core::cleanVar(Request::url($journal->getPath(), 'article', 'view', array($article->getBestArticleId(), $galley->getId())))) . "\" />\n";
     }
     // Include abstract(s)
     $abstract = htmlspecialchars(Core::cleanVar(strip_tags($article->getArticleAbstract())));
     if (!empty($abstract)) {
         $abstract = "<p>{$abstract}</p>";
         // $abstract = '<p>' . String::regexp_replace('/\n+/', '</p><p>', $abstract) . '</p>';
         $response .= "\t\t\t<abstract xml:lang=\"" . strtoupper(substr($primaryLocale, 0, 2)) . "\">{$abstract}</abstract>\n";
     }
     if (is_array($article->getAbstract(null))) {
         foreach ($article->getAbstract(null) as $locale => $abstract) {
             if ($locale == $primaryLocale || empty($abstract)) {
                 continue;
             }
             $abstract = htmlspecialchars(Core::cleanVar(strip_tags($abstract)));
             if (empty($abstract)) {
                 continue;
             }
             $abstract = "<p>{$abstract}</p>";
             //$abstract = '<p>' . String::regexp_replace('/\n+/', '</p><p>', $abstract) . '</p>';
             $response .= "\t\t\t<abstract-trans xml:lang=\"" . strtoupper(substr($locale, 0, 2)) . "\">{$abstract}</abstract-trans>\n";
         }
     }
     $subjects = array();
     if (is_array($article->getSubject(null))) {
         foreach ($article->getSubject(null) as $locale => $subject) {
             $s = array_map('trim', explode(';', Core::cleanVar($subject)));
             if (!empty($s)) {
                 $subjects[$locale] = $s;
             }
         }
     }
     if (!empty($subjects)) {
         foreach ($subjects as $locale => $s) {
             $response .= "\t\t\t<kwd-group xml:lang=\"" . strtoupper(substr($locale, 0, 2)) . "\">\n";
             foreach ($s as $subject) {
                 $response .= "\t\t\t\t<kwd>" . htmlspecialchars($subject) . "</kwd>\n";
             }
             $response .= "\t\t\t</kwd-group>\n";
         }
     }
     $response .= (isset($pageCount) ? "\t\t\t<counts><page-count count=\"" . (int) $pageCount . "\" /></counts>\n" : '') . "\t\t</article-meta>\n" . "\t</front>\n";
     // Include body text (for search indexing only)
     import('classes.search.ArticleSearchIndex');
     $text = '';
     $galleys = $article->getGalleys();
     // Give precedence to HTML galleys, as they're quickest to parse
     usort($galleys, create_function('$a, $b', 'return $a->isHtmlGalley()?-1:1;'));
     // Determine any access limitations. If there are, do not
     // provide the full-text.
     import('classes.issue.IssueAction');
     $subscriptionRequired = IssueAction::subscriptionRequired($issue);
     $isSubscribedDomain = IssueAction::subscribedDomain($journal, $issue->getId(), $article->getId());
     if (!$subscriptionRequired || $isSubscribedDomain) {
         foreach ($galleys as $galley) {
             $parser =& SearchFileParser::fromFile($galley);
             if ($parser && $parser->open()) {
                 while (($s = $parser->read()) !== false) {
                     $text .= $s;
                 }
                 $parser->close();
             }
             if ($galley->isHtmlGalley()) {
                 $text = strip_tags($text);
             }
             unset($galley);
             // Use the first parseable galley.
             if (!empty($text)) {
                 break;
             }
         }
     }
     if (!empty($text)) {
         $response .= "\t<body><p>" . htmlspecialchars(Core::cleanVar(Core::cleanVar($text))) . "</p></body>\n";
     }
     // Add NLM citation info
     import('lib.pkp.classes.importexport.nlm.PKPSubmissionNlmXmlFilter');
     $nlmFilter = new PKPSubmissionNlmXmlFilter();
     $nlmXml = $nlmFilter->execute($article);
     // Downgrade to an NLM 2.3 ref-list
     import('lib.pkp.classes.xslt.XSLTransformationFilter');
     $downgradeFilter = new XSLTransformationFilter('NLM 3.0 to 2.3 ref-list downgrade', array('xml::*', 'xml::*'));
     $downgradeFilter->setXSLFilename('lib/pkp/classes/importexport/nlm/nlm-ref-list-30-to-23.xsl');
     // To suppress the XML header, get the DOM and convert it to
     // string explicitly. (Also check for empty node.)
     $downgradeFilter->setResultType(XSL_TRANSFORMER_DOCTYPE_DOM);
     $nlmXmlDom = $downgradeFilter->execute($nlmXml);
     $documentElement =& $nlmXmlDom->documentElement;
     // Work-around for hasChildNodes being stupid about whitespace.
     $hasChildren = false;
     if (isset($documentElement->childNodes)) {
         foreach ($documentElement->childNodes as $c) {
             if ($c->nodeType == XML_ELEMENT_NODE) {
                 $hasChildren = true;
             }
         }
     }
     // If there were any citations, include them.
     if ($hasChildren) {
         $nlmXml = $nlmXmlDom->saveXML($documentElement);
         $response .= "<back>{$nlmXml}</back>\n";
     }
     $response .= "</article>";
     return $response;
 }