Skip to content

Commit

Permalink
Breaking Change: ZugferdPdfReader does not return null-values for "re…
Browse files Browse the repository at this point in the history
…adAndGuessFromFile", "readAndGuessFromContent", "getXmlFromFile" and "getXmlFromContent" anymore. Now always exceptions are raised.
  • Loading branch information
HorstOeko committed Nov 3, 2024
1 parent f3b99d7 commit 74bd98c
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 65 deletions.
103 changes: 53 additions & 50 deletions src/ZugferdDocumentPdfReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
namespace horstoeko\zugferd;

use Exception;
use Smalot\PdfParser\Parser as PdfParser;
use horstoeko\zugferd\exception\ZugferdFileNotFoundException;
use horstoeko\zugferd\exception\ZugferdFileNotReadableException;
use horstoeko\zugferd\exception\ZugferdNoPdfAttachmentFoundException;
use horstoeko\zugferd\exception\ZugferdUnknownProfileException;
use horstoeko\zugferd\exception\ZugferdUnknownProfileParameterException;
use horstoeko\zugferd\exception\ZugferdUnknownXmlContentException;
use JMS\Serializer\Exception\RuntimeException;
use Smalot\PdfParser\Parser as PdfParser;

/**
* Class representing the document reader for incoming PDF/A-Documents with
Expand Down Expand Up @@ -40,12 +45,17 @@ class ZugferdDocumentPdfReader
* Load a PDF file (ZUGFeRD/Factur-X)
*
* @param string $pdfFilename Contains a full-qualified filename which must exist and must be readable
* @return ZugferdDocumentReader|null
* @throws Exception
* @return ZugferdDocumentReader
* @throws ZugferdFileNotFoundException
* @throws ZugferdFileNotReadableException
* @throws Exception
* @throws ZugferdNoPdfAttachmentFoundException
* @throws ZugferdUnknownXmlContentException
* @throws ZugferdUnknownProfileException
* @throws ZugferdUnknownProfileParameterException
* @throws RuntimeException
*/
public static function readAndGuessFromFile(string $pdfFilename): ?ZugferdDocumentReader
public static function readAndGuessFromFile(string $pdfFilename): ZugferdDocumentReader
{
if (!file_exists($pdfFilename)) {
throw new ZugferdFileNotFoundException($pdfFilename);
Expand All @@ -62,38 +72,34 @@ public static function readAndGuessFromFile(string $pdfFilename): ?ZugferdDocume

/**
* Tries to load an attachment content from PDF and return a ZugferdDocumentReader
* If any erros occured or no attachments were found null is returned
*
* @param string $pdfContent String Containing the binary pdf data
* @return ZugferdDocumentReader|null
* @param string $pdfContent String containing the binary pdf data
* @return ZugferdDocumentReader
* @throws Exception
* @throws ZugferdNoPdfAttachmentFoundException
* @throws ZugferdUnknownXmlContentException
* @throws ZugferdUnknownProfileException
* @throws ZugferdUnknownProfileParameterException
* @throws RuntimeException
*/
public static function readAndGuessFromContent(string $pdfContent): ?ZugferdDocumentReader
public static function readAndGuessFromContent(string $pdfContent): ZugferdDocumentReader
{
$xmlContent = static::internalExtractXMLFromPdfContent($pdfContent);

if (is_null($xmlContent)) {
return null;
}

try {
return ZugferdDocumentReader::readAndGuessFromContent($xmlContent);
} catch (\Exception $e) {
return null;
}
return ZugferdDocumentReader::readAndGuessFromContent($xmlContent);
}

/**
* Returns a XML content from a PDF file
*
* @param string $pdfFilename
* Contains a full-qualified filename which must exist and must be readable
* @return string|null
* @param string $pdfFilename Contains a full-qualified filename which must exist and must be readable
* @return string
* @throws Exception
* @throws ZugferdFileNotFoundException
* @throws ZugferdFileNotReadableException
* @throws Exception
* @throws ZugferdNoPdfAttachmentFoundException
*/
public static function getXmlFromFile(string $pdfFilename): ?string
public static function getXmlFromFile(string $pdfFilename): string
{
if (!file_exists($pdfFilename)) {
throw new ZugferdFileNotFoundException($pdfFilename);
Expand All @@ -112,10 +118,12 @@ public static function getXmlFromFile(string $pdfFilename): ?string
* Returns a XML content from a PDF binary stream (string)
*
* @param string $pdfContent String Containing the binary pdf data
* @return string|null
* @param string $pdfContent
* @return string
* @throws Exception
* @throws ZugferdNoPdfAttachmentFoundException
*/
public static function getXmlFromContent(string $pdfContent): ?string
public static function getXmlFromContent(string $pdfContent): string
{
return static::internalExtractXMLFromPdfContent($pdfContent);
}
Expand All @@ -125,10 +133,11 @@ public static function getXmlFromContent(string $pdfContent): ?string
* See the allowed filenames which are supported
*
* @param string $pdfContent
* @return null|string
* @return string
* @throws Exception
* @throws ZugferdNoPdfAttachmentFoundException
*/
protected static function internalExtractXMLFromPdfContent(string $pdfContent): ?string
protected static function internalExtractXMLFromPdfContent(string $pdfContent): string
{
$pdfParser = new PdfParser();
$pdfParsed = $pdfParser->parseContent($pdfContent);
Expand All @@ -137,35 +146,29 @@ protected static function internalExtractXMLFromPdfContent(string $pdfContent):
$attachmentFound = false;
$attachmentIndex = 0;
$embeddedFileIndex = 0;
$returnValue = null;

try {
foreach ($filespecs as $filespec) {
$filespecDetails = $filespec->getDetails();
if (in_array($filespecDetails['F'], static::ATTACHMENT_FILENAMES)) {
$attachmentFound = true;
break;
}
$attachmentIndex++;

foreach ($filespecs as $filespec) {
$filespecDetails = $filespec->getDetails();
if (in_array($filespecDetails['F'], static::ATTACHMENT_FILENAMES)) {
$attachmentFound = true;
break;
}
$attachmentIndex++;
}

if (true == $attachmentFound) {
/**
* @var array<\Smalot\PdfParser\PDFObject>
*/
$embeddedFiles = $pdfParsed->getObjectsByType('EmbeddedFile');
foreach ($embeddedFiles as $embeddedFile) {
if ($attachmentIndex == $embeddedFileIndex) {
$returnValue = $embeddedFile->getContent();
break;
}
$embeddedFileIndex++;
if (true == $attachmentFound) {
/**
* @var array<\Smalot\PdfParser\PDFObject>
*/
$embeddedFiles = $pdfParsed->getObjectsByType('EmbeddedFile');
foreach ($embeddedFiles as $embeddedFile) {
if ($attachmentIndex == $embeddedFileIndex) {
return $embeddedFile->getContent();
}
$embeddedFileIndex++;
}
} catch (\Exception $e) {
$returnValue = null;
}

return $returnValue;
throw new ZugferdNoPdfAttachmentFoundException();
}
}
1 change: 1 addition & 0 deletions src/exception/ZugferdExceptionCodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ZugferdExceptionCodes
public const UNKNOWNSYNTAX = -1107;
public const UNKNOWNMIMETYPE = -1108;
public const UNSUPPORTEDMIMETYPE = -1109;
public const NOPDFATTACHMENTFOUND = -1110;
public const FILENOTFOUND = -2000;
public const FILENOTREADABLE = -2001;
}
35 changes: 35 additions & 0 deletions src/exception/ZugferdNoPdfAttachmentFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/**
* This file is a part of horstoeko/zugferd.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace horstoeko\zugferd\exception;

use Throwable;

/**
* Class representing an exception when the ZugferdPdfReader has not found any
* valid attachment
*
* @category Zugferd
* @package Zugferd
* @author D. Erling <horstoeko@erling.com.de>
* @license https://opensource.org/licenses/MIT MIT
* @link https://github.com/horstoeko/zugferd
*/
class ZugferdNoPdfAttachmentFoundException extends ZugferdBaseException
{
/**
* Constructor
*
* @param Throwable|null $previous
*/
public function __construct(?Throwable $previous = null)
{
parent::__construct("No PDF attachment found", ZugferdExceptionCodes::NOPDFATTACHMENTFOUND, $previous);
}
}
95 changes: 80 additions & 15 deletions tests/testcases/PdfReaderGeneralTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,112 @@

namespace horstoeko\zugferd\tests\testcases;

use horstoeko\zugferd\exception\ZugferdExceptionCodes;
use horstoeko\zugferd\exception\ZugferdFileNotFoundException;
use horstoeko\zugferd\exception\ZugferdNoPdfAttachmentFoundException;
use horstoeko\zugferd\tests\TestCase;
use horstoeko\zugferd\ZugferdDocument;
use horstoeko\zugferd\ZugferdDocumentPdfReader;

class PdfReaderGeneralTest extends TestCase
{
public function testCanReadPdf(): void
/* ZugferdPdfReader::readAndGuessFromFile */

public function testReadFromFileWhichDoesNotExist(): void
{
$document = ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/../assets/pdf_invalid.pdf");
$this->assertNull($document);
$this->expectException(ZugferdFileNotFoundException::class);

ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/../assets/unknown.pdf");
}

public function testFileNotFound(): void
public function testReadFromFileWhichHasNoValidAttachment(): void
{
$this->expectException(ZugferdFileNotFoundException::class);
$document = ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/../assets/unknown.pdf");
$this->expectException(ZugferdNoPdfAttachmentFoundException::class);
$this->expectExceptionMessage('No PDF attachment found');
$this->expectExceptionCode(ZugferdExceptionCodes::NOPDFATTACHMENTFOUND);

ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/../assets/pdf_invalid.pdf");
}

public function testCanReadPdf2(): void
public function testReadFromFileWhichExistsAndHasValidAttachment(): void
{
$document = ZugferdDocumentPdfReader::getXmlFromFile(dirname(__FILE__) . "/../assets/pdf_invalid.pdf");
$this->assertNull($document);
$document = ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/../assets/pdf_zf_en16931_1.pdf");

$this->assertNotNull($document);
$this->assertInstanceOf(ZugferdDocument::class, $document);
}

public function testFileNotFound2(): void
/* ZugferdPdfReader::readAndGuessFromContent */

public function testReadFromContentWhichHasNoValidAttachment(): void
{
$this->expectException(ZugferdFileNotFoundException::class);
$document = ZugferdDocumentPdfReader::getXmlFromFile(dirname(__FILE__) . "/../assets/unknown.pdf");
$this->expectException(ZugferdNoPdfAttachmentFoundException::class);
$this->expectExceptionMessage('No PDF attachment found');
$this->expectExceptionCode(ZugferdExceptionCodes::NOPDFATTACHMENTFOUND);

$pdfContent = file_get_contents(dirname(__FILE__) . "/../assets/pdf_invalid.pdf");

ZugferdDocumentPdfReader::readAndGuessFromContent($pdfContent);
}

public function testCanReadPdf3(): void
public function testReadFromContentWhichHasValidAttachment(): void
{
$document = ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/../assets/pdf_zf_en16931_1.pdf");
$pdfContent = file_get_contents(dirname(__FILE__) . "/../assets/pdf_zf_en16931_1.pdf");

$document = ZugferdDocumentPdfReader::readAndGuessFromContent($pdfContent);

$this->assertNotNull($document);
$this->assertInstanceOf(ZugferdDocument::class, $document);
}

public function testCanReadPdf4(): void
/* ZugferdPdfReader::getXmlFromFile */

public function testGetXmlFromFileWhichDoesNotExist(): void
{
$this->expectException(ZugferdFileNotFoundException::class);

ZugferdDocumentPdfReader::getXmlFromFile(dirname(__FILE__) . "/../assets/unknown.pdf");
}

public function testGetXmlFromFileWhichHasNoValidAttachment(): void
{
$this->expectException(ZugferdNoPdfAttachmentFoundException::class);
$this->expectExceptionMessage('No PDF attachment found');
$this->expectExceptionCode(ZugferdExceptionCodes::NOPDFATTACHMENTFOUND);

ZugferdDocumentPdfReader::getXmlFromFile(dirname(__FILE__) . "/../assets/pdf_invalid.pdf");
}

public function testGetXmlFromFileWhichExistsAndHasValidAttachment(): void
{
$xmlString = ZugferdDocumentPdfReader::getXmlFromFile(dirname(__FILE__) . "/../assets/pdf_zf_en16931_1.pdf");

$this->assertNotNull($xmlString);
$this->assertIsString($xmlString);
$this->assertStringContainsString("<?xml version='1.0'", $xmlString);
$this->assertStringContainsString("<rsm:CrossIndustryInvoice", $xmlString);
$this->assertStringContainsString("</rsm:CrossIndustryInvoice>", $xmlString);
}

/* ZugferdPdfReader::getXmlFromContent */

public function testGetXmlFromContentWhichHasNoValidAttachment(): void
{
$this->expectException(ZugferdNoPdfAttachmentFoundException::class);
$this->expectExceptionMessage('No PDF attachment found');
$this->expectExceptionCode(ZugferdExceptionCodes::NOPDFATTACHMENTFOUND);

$pdfContent = file_get_contents(dirname(__FILE__) . "/../assets/pdf_invalid.pdf");

ZugferdDocumentPdfReader::getXmlFromContent($pdfContent);
}

public function testGetXmlFromContentWhichHasValidAttachment(): void
{
$pdfContent = file_get_contents(dirname(__FILE__) . "/../assets/pdf_zf_en16931_1.pdf");

$xmlString = ZugferdDocumentPdfReader::getXmlFromContent($pdfContent);

$this->assertNotNull($xmlString);
$this->assertIsString($xmlString);
$this->assertStringContainsString("<?xml version='1.0'", $xmlString);
Expand Down

0 comments on commit 74bd98c

Please sign in to comment.