diff --git a/docroot/modules/custom/foia_raw_data_to_report/README.md b/docroot/modules/custom/foia_raw_data_to_report/README.md index 329aebabe..f8e18e131 100644 --- a/docroot/modules/custom/foia_raw_data_to_report/README.md +++ b/docroot/modules/custom/foia_raw_data_to_report/README.md @@ -34,9 +34,16 @@ Use a lease longer than the maximum processing duration and avoid overlapping runners. Revisit the lease and server PHP resource limits when CSV conversion is implemented; a lease is not a processing timeout. -`RawDataToReportProcessing::generateXmlReport()` is the conversion stub. It -currently writes a **zero-byte placeholder**, not valid XML, and attaches it to -`field_request_data_xml`. It uses the field's configured directory and storage +`RawDataToReportProcessing::generateXmlReport()` uses `XmlReportBuilder` to +create a **metadata-only XML stub** and attaches it to `field_request_data_xml`. +The document uses the example's `iepd:FoiaAnnualReport` root and namespaces, +with `nc:DocumentApplicationName` set to `FOIA Annual Report Workbook` +(application version `1.1`), `nc:DocumentCreationDate/nc:Date` set to the current +generation date (`YYYY-MM-DD`, Drupal runtime timezone), and +`nc:DocumentDescriptionText` set to `FOIA Annual Report`. It is well-formed XML, +but is not yet a complete, schema-valid annual report. CSV-derived sections +will be added to this module's builder; `foia_export_xml` is not modified. +It uses the field's configured directory and storage scheme, with a unique filename, and replaces the current field reference. Each filename includes the generation timestamp in `YYYY-MM-DD-HH-MM-SS` format using the Drupal runtime timezone (normally the site default). Drupal @@ -49,8 +56,8 @@ manage file permanence and usage on node save. Each click creates a separate job. The worker reads the latest node state when processing; it does not snapshot the CSV selection. Deleted nodes are skipped. Processing exceptions leave the item available for retry after its lease expires. -The CSV is validated before the placeholder XML is generated; conversion is not -implemented yet. +The CSV is validated before the XML stub is generated; CSV-derived report +sections are not implemented yet. ## CSV validation and messages @@ -219,7 +226,7 @@ Import the exported configuration and rebuild caches before running the queue. The old node-level CSV field is replaced, with no migration of existing uploads. This follows the pre-launch assumption that existing upload content is disposable. The queue payload remains the parent node ID. Edits during processing are not -locked or snapshotted. CSV conversion remains a stub, producing one empty XML +locked or snapshotted. CSV conversion remains a stub, producing one metadata-only XML document only after every component upload passes validation. Local integration verification (creates and removes temporary fixtures): diff --git a/docroot/modules/custom/foia_raw_data_to_report/src/Plugin/QueueWorker/RawDataToReportProcessing.php b/docroot/modules/custom/foia_raw_data_to_report/src/Plugin/QueueWorker/RawDataToReportProcessing.php index 66ef61be5..eb2f1561c 100644 --- a/docroot/modules/custom/foia_raw_data_to_report/src/Plugin/QueueWorker/RawDataToReportProcessing.php +++ b/docroot/modules/custom/foia_raw_data_to_report/src/Plugin/QueueWorker/RawDataToReportProcessing.php @@ -12,6 +12,7 @@ use Drupal\node\NodeInterface; use Drupal\foia_raw_data_to_report\CsvValidator; use Drupal\foia_raw_data_to_report\UploadAssignments; +use Drupal\foia_raw_data_to_report\XmlReportBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -108,9 +109,10 @@ public function processItem($data) { } /** - * Attaches an empty XML placeholder; future CSV conversion belongs here. + * Builds and attaches report XML after all component CSVs pass validation. */ protected function generateXmlReport(NodeInterface $node): void { + $xml = (new XmlReportBuilder())->build(); $field = $node->get('field_request_data_xml'); $previous_file = $field->entity; $item = $field->first() ?? $field->appendItem(); @@ -124,7 +126,7 @@ protected function generateXmlReport(NodeInterface $node): void { // Include the generation time so users can identify the latest report. $filename = 'raw-data-report-' . $node->id() . '-' . date('Y-m-d-H-i-s') . '.xml'; - $file = $this->fileRepository->writeData('', $directory . '/' . $filename, FileExists::Rename); + $file = $this->fileRepository->writeData($xml, $directory . '/' . $filename, FileExists::Rename); $file->setOwnerId($node->getOwnerId()); $file->save(); $node->set('field_request_data_xml', ['target_id' => $file->id()]); diff --git a/docroot/modules/custom/foia_raw_data_to_report/src/XmlReportBuilder.php b/docroot/modules/custom/foia_raw_data_to_report/src/XmlReportBuilder.php new file mode 100644 index 000000000..566fc9dbb --- /dev/null +++ b/docroot/modules/custom/foia_raw_data_to_report/src/XmlReportBuilder.php @@ -0,0 +1,53 @@ + 'http://leisp.usdoj.gov/niem/FoiaAnnualReport/exchange/1.03', + 'foia' => 'http://leisp.usdoj.gov/niem/FoiaAnnualReport/extension/1.03', + 'i' => 'http://niem.gov/niem/appinfo/2.0', + 'j' => 'http://niem.gov/niem/domains/jxdm/4.1', + 'nc' => 'http://niem.gov/niem/niem-core/2.0', + 's' => 'http://niem.gov/niem/structures/2.0', + 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + ]; + + /** + * Builds the metadata stub; CSV-derived report sections will be added later. + */ + public function build(): string { + $document = new \DOMDocument('1.0', 'UTF-8'); + $document->formatOutput = TRUE; + $root = $document->createElementNS(self::NAMESPACES['iepd'], 'iepd:FoiaAnnualReport'); + $document->appendChild($root); + foreach (self::NAMESPACES as $prefix => $uri) { + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . $prefix, $uri); + } + $root->setAttributeNS(self::NAMESPACES['xsi'], 'xsi:schemaLocation', self::NAMESPACES['iepd'] . ' ../schema/exchange/FoiaAnnualReport.xsd'); + + $application = $document->createElementNS(self::NAMESPACES['nc'], 'nc:DocumentApplicationName', 'FOIA Annual Report Workbook'); + $application->setAttributeNS(self::NAMESPACES['nc'], 'nc:applicationVersionText', '1.1'); + $root->appendChild($application); + + // Use the current generation date in Drupal's runtime timezone. + $creation_date = $document->createElementNS(self::NAMESPACES['nc'], 'nc:DocumentCreationDate'); + $creation_date->appendChild($document->createElementNS(self::NAMESPACES['nc'], 'nc:Date', date('Y-m-d'))); + $root->appendChild($creation_date); + $root->appendChild($document->createElementNS(self::NAMESPACES['nc'], 'nc:DocumentDescriptionText', 'FOIA Annual Report')); + + $xml = $document->saveXML(); + if ($xml === FALSE) { + throw new \RuntimeException('Unable to serialize the raw data report XML.'); + } + return $xml; + } + +}