PHP Tipps – Arbeiten mit SAP IDoc Dateien

SAP IDoc (Intermediate Document) wird von SAP intern verwendet, um z.B. Bestelldaten abzubilden. In der Regel werden IDoc-Daten mittels Message Mapping in andere Formate hin- und herkonvertiert, z.B. in EDIFACT.
Eine generelle Definition von IDoc findet sich z.B. auf www.php-deluxe.de:

Ein IDoc besteht nur aus Textzeichen und besteht aus einem Kontrollsatz, den Datensätzen und Statussätzen.

Der Kontrollsatz besteht aus Sender, Empfänger, Nachrichtentyp und
IDoc-Typ. Der IDoc-Typ beschreibt die Struktur der Datensätze, der
Nachrichtentyp entspricht einem in dem IDoc-Typ definierten, konkreten
Geschäftsvorfall (z.B. Bestellung, Lieferung).

In den Datensätzen sind die zu übertragenden Informationen enthalten, der Aufbau entspricht grundsätzlich einer Datenbanktabelle mit einer durch den IDoc-Typ definierten Struktur und den dazugehörigen Datensegmenten.

In den Statussätzen sind im IDoc-Format Verwaltungsinformationen wie
Bearbeitungsbeginn, Übermittlungszeit, Fehlermeldungen und ähnliches
abgelegt.

Prinzipiell können IDocs auch im XML-Format verwendet werden, dennoch ist das „klassische“ IDoc Format immer noch weit verbreitet, nicht zuletzt wegen des geringeren Datenvolumens durch die „schlankere“ Struktur.
Dieses „klassische“ IDoc-Format ist gekennzeichnet durch „feste Satzlängen“, d.h. eine Zeile im IDoc ist genau spezifiziert, was die Position der Datensätze (Offsets) und die „Zeilenlänge“ angeht. So kann z.B. eine Zeile im Dokument (Beispiel aus der „ORDERS05“ Struktur) folgendermaßen definiert sein:

Segment E1EDK01:

Segmentdefinition E2EDK01005
freigegeben seit Release 45B , Segmentlänge : 0357

  1. ACTION : Aktionscode, der die gesamte
    EDI-Nachricht betrifft.
    interner Datentyp : CHAR
    interne Länge : 000003 Stellen
    Position in Segment : 001, Offset : 0063. externe Länge : 000003
  2. KZABS : Kennzeichen für
    Auftragsbestätigungspflicht
    interner Datentyp : CHAR
    interne Länge : 000001 Stellen
    Position in Segment : 002, Offset : 0066. externe Länge : 000001
  3. CURCY : Währunginterner Datentyp : CHAR
    interne Länge : 000003 Stellen
    Position in Segment : 003, Offset : 0067. externe Länge : 000003
  4. usw.

Der Parameter „ACTION“ ist also vom Typ „CHAR“, steht in der Zeile, die mit „E1EDK01“ beginnt, an Offset 63 und hat eine Länge von 3 Zeichen, danach kommt der Parameter „KZABS“, dieser steht an Stelle/Offset 66, ist ebenfalls ein „CHAR“ und hat eine Länge von einem Zeichen usw.

Dies würde nun folgendermaßen aussehen:

E1EDK01005                       ABC                           1

wobei sich rechts ggf. noch weitere Werte anschliessen würden.

Zudem hat jede „Zeile“ im Normalfall eine feste Zeichenlänge, unser Beispiel-Segment muss insgesamt 1063 Zeichen haben – stehen am Ende keine Werte, muss die Zeile mit Leerzeichen „aufgefüllt“ werden.

Für einen „IDoc Writer“ in PHP bräuchte man also vor allem eine Möglichkeit, Zeichenketten von vorgegebener (maximaler) Länge an bestimmte Stellen (Offsets) einer Zeile zu schreiben sowie die Zeile mit Leerzeichen bis zum Ende aufzufüllen. Ist eine Zeichenkette / ein Wert länger als an dem vorgegebenen Offset „Platz“ ist, muss die Zeichenkette gegebenenfalls abgeschnitten werden, um nicht versehentlich den Wert am nächsten Offset zu überschreiben. Zeilen werden im Regelfall nur mit „Line Feed“ beendet (in PHP entspricht dies „\n“):

 

$idocWriter =  new smxIdocWriter();
$idocWriter->write(array("E1EDKA1","AG", "0815", "Stefan Moises", "Some company", "Street 45a"), array(0,63,66,100,135,240), 1063);
$idocWriter->toFile("/tmp/test_idoc.dat");

 

Ein kleiner Tipp am Rande: werden die IDoc-Dateien nach Erstellung per FTP transportiert, sollten diese im Binär-Modus versendet werden, da FTP im ASCII-Modus die Eigenart hat, am Zeilenende zusätzlich zum „Line Feed“ auch ein „Carriage Return“ (in PHP „\r“) einzufügen – zumindest macht dies der „ftp_put()“ Befehl in PHP, wenn man ihm FTP_ASCII statt FTP_BINARY als Transfer-Typ übergibt).

Viel Spass mit IDoc! 🙂 Hier gibt es eine einfache IDoc-Klasse zum Download: smxIdocWriter.php_.txt.

PHP Tipps: Verborgene Talente – Teil 2

Immer wieder einmal kommt man als Entwickler in die Situation, lange Texte auf mehrere Zeilen aufteilen zu müssen, die eine maximale Länge nicht überschreiten dürfen – ein typisches Beispiel ist z.B. ein mehrzeiliger Kommentar-Text in einem PDF-Dokument, das man mittels einer PHP-Bibliothek wie FPDF erzeugen möchte. Wie teile ich diesen Text auf mehrere Zeilen auf, ohne Wörter mittendrin abzuschneiden?

Nun, in PHP ist das sehr einfach, man muss den Text nicht mühsam mit Regular Expressions oder gar substr() zerlegen, sondern kann dazu die Funktion wordwrap() nutzen:

$mText = wordwrap  ( $orderInfoText, 70, "---", false );
$mTextArray = split("---", $mText);
foreach($mTextArray as $textLine)
{
echo $textLine . "<br />";

}

Dieser Codeschnippsel erzeugt ein Array von Texten mit jeweils maximal 70 Zeichen Länge, die man dann z.B. in einer Schleife ausgeben kann. Der letzte Parameter in der Funktion, hier mit dem Wert „false“, gibt an, ob Wörter „zerschnitten“ werden sollen – was in den meisten Fällen aber nicht gewünscht sein dürfte.
Der vorletzte Parameter fügt am Ende jeder „Zeile“ den String „—“ ein (man kann natürlich jeden anderen beliebigen String verwenden, nur sollte dieser nicht im Text selbst vorkommen), anhand dessen dann der Text in ein Array gesplittet werden kann – hier könnte man natürlich auch gleich „<br />“ oder „\n“ verwenden, um den Text auf mehrere Zeilen zu „verteilen“. Für eine Verarbeitung z.B. mit FPDF ist allerdings ein Array mit einzelnen „Zeilen“ wesentlich praktischer, diese können dann in einer Schleife dem PDF-Dokument hinzugefügt werden.

PHP Tipps: Verborgene Talente – Teil 1

Manche PHP-Funktionen können ja mehr als man denkt oder als ihr Name vermuten lässt – ein Beispiel dafür ist die Funktion „file_get_contents()“: diese erlaubt (per „php.ini“ eingeschaltete fopen wrappers [„allow_url_fopen“] vorausgesetzt) auch das Lesen entfernter Dateien durch Angabe eines URL. So lässt sich etwa blitzschnell ein RSS-Feed einlesen und z.B. mittels SimpleXML auswerten und weiterverarbeiten.

$rssItems = array();
$rssUrl = "http://www.somedomain.de/rss.xml";
try {
$xmlstr = file_get_contents($rssUrl);
if($xmlstr !== FALSE) {
$xml = new SimpleXMLElement($xmlstr);
foreach ($xml->channel->item as $item) {
$rss["title"] = utf8_decode((string)$item->title);
$rss["link"] = (string)$item->link;
$rss["description"] = utf8_decode((string)$item->description);
$rssItems[] = $rss;
}
print_r($rssItems);
}
}
catch(Exception $ex) {
echo $ex->getMessage();
}