Files
aufmass-web/_legacy/_tools/REB23_Aufmass_Tool.au3
T

866 lines
30 KiB
AutoIt
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <Array.au3>
#include <File.au3>
#include <String.au3>
#include <Date.au3>
#include <Debug.au3>
#include <Excel.au3>
#include <GUIConstantsEx.au3>
; =========================================================
; INDUSTRIAL AUTOIT REB 23.003 → GAEB X31 ENGINE
; kompatibel mit DATAflor / California / GAEB DA XML
; =========================================================
Global $INPUT_FILE = @ScriptDir & "\..\Liebenau_BA_5_Mengennachweiß_AZ002_.txt"
Global $OUTPUT_FILE = @ScriptDir & "\..\export2.x31"
; Globaler ID-Zähler für XML-IDs (muss vor Func-Aufrufen deklariert sein)
; Globaler ID-Zaehler (vor allen Local-Deklarationen)
Global $g_iIDCnt = 1000000
; ── Einstiegspunkt ──────────────────────────────────────────
Local $importPfadTxt = @ScriptDir & "\..\Meckenbeueren_AZ10_.txt"
Local $ausgabePfadX31 = @ScriptDir & "\..\Meckenbeueren_AZ10_x31neu.x31"
Local $exportExcelCalifornia = @ScriptDir & "\..\Meckenbeueren_AZ10_.xlsx"
;~ Local $importPfadTxt = @ScriptDir & "\..\FTTx_Achberg_AZ001_.txt"
;~ Local $ausgabePfadX31 = @ScriptDir & "\..\FTTx_Achberg_AZ001_x31neu.x31"
; Beispiel Aufruf
_REB_TXT_to_Excel($importPfadTxt,$exportExcelCalifornia)
;~ _X31_Export($importPfadTxt, $ausgabePfadX31)
Func _replaceKom2Punkt($sString)
Return StringReplace($sString, ",", ".")
EndFunc ;==>_replaceKom2Punkt
Func _replacePunkt2Koma($sString)
Return StringReplace($sString, ".", ",")
EndFunc ;==>_replacePunkt2Koma
Func _REB_TXT_to_Excel($sTXT, $sExcel)
Local $aLines
_FileReadToArray($sTXT, $aLines)
Local $Baustelle = ""
Local $Datum = ""
Local $Abschnitt = ""
Local $inHeader = False
Local $inData = False
; Kopf auslesen
For $i = 1 To $aLines[0]
Local $line = $aLines[$i]
If StringInStr($line, "[Kopfdaten]") Then
$inHeader = True
ContinueLoop
EndIf
If StringInStr($line, "[Aufmaßdaten]") Then
$inHeader = False
$inData = True
ExitLoop
EndIf
If $inHeader Then
If StringLeft($line, 9) = "Baustelle" Then
$Baustelle = StringTrimLeft($line, 10)
EndIf
If StringLeft($line, 5) = "Datum" Then
$Datum = StringTrimLeft($line, 6)
EndIf
If StringLeft($line, 13) = "Bauabschnitt=" Then
$Abschnitt = StringTrimLeft($line, 13)
EndIf
EndIf
Next
; Excel starten
Local $oExcel = _Excel_Open()
Local $oBook = _Excel_BookNew($oExcel)
; Kopf schreiben
_Excel_RangeWrite($oBook, Default, "Bauvorhaben", "A2")
_Excel_RangeWrite($oBook, Default, $Baustelle, "C2")
_Excel_RangeWrite($oBook, Default, "Leistungsverzeichnis", "A3")
_Excel_RangeWrite($oBook, Default, $Abschnitt, "C3")
_Excel_RangeWrite($oBook, Default, "Aufmaß", "A4")
_Excel_RangeWrite($oBook, Default, $Datum, "D4")
; Spaltenüberschrift
;~ _Excel_RangeWrite($oBook, Default, "OZ", "A7")
;~ _Excel_RangeWrite($oBook, Default, "Position", "B7")
;~ _Excel_RangeWrite($oBook, Default, "Abschnitt", "C7")
;~ _Excel_RangeWrite($oBook, Default, "Faktor", "D7")
;~ _Excel_RangeWrite($oBook, Default, "REB Formel", "E7")
;~ _Excel_RangeWrite($oBook, Default, "Ergebnis", "F7")
Local $row = 7
$inData = False
For $i = 1 To $aLines[0]
Local $line = $aLines[$i]
If StringInStr($line, "[Aufmaßdaten]") Then
$inData = True
ContinueLoop
EndIf
If $inData = False Then ContinueLoop
If StringStripWS($line, 8) = "" Then ContinueLoop
Local $f = StringSplit($line, "|")
If $f[0] < 10 Then ContinueLoop
Local $Ort = $f[1]
Local $OZ = $f[2]
Local $Formel = $f[3]
Local $Ergebnis = $f[7]
Local $Text = $f[9]
; Zeile 1 (Bemerkung)
_Excel_RangeWrite($oBook, Default, $OZ, "A" & $row)
_Excel_RangeWrite($oBook, Default, $Text, "B" & $row)
_Excel_RangeWrite($oBook, Default, $Ort, "C" & $row)
$row += 1
; Zeile 2 (Formel)
_Excel_RangeWrite($oBook, Default, $OZ, "A" & $row)
_Excel_RangeWrite($oBook, Default, "", "B" & $row)
_Excel_RangeWrite($oBook, Default, "", "C" & $row)
_Excel_RangeWrite($oBook, Default, $Formel, "D" & $row)
_Excel_RangeWrite($oBook, Default, $Formel, "E" & $row)
_Excel_RangeWrite($oBook, Default, $Ergebnis, "F" & $row)
$row += 1
Next
_Excel_BookSaveAs($oBook, $sExcel)
_Excel_Close($oExcel)
EndFunc
Func _X31_Export($sImportTxt, $sAusgabeX31)
; ── 1. Datei einlesen ────────────────────────────────────
Local $hLese = FileOpen($sImportTxt, 0)
If $hLese = -1 Then
MsgBox(16, "Fehler", "Importdatei nicht gefunden:" & @CRLF & $sImportTxt)
Return SetError(1, 0, False)
EndIf
Local $aRawLines[10001]
$aRawLines[0] = 0
While True
Local $sZ = FileReadLine($hLese)
If @error = -1 Then ExitLoop
$sZ = StringReplace($sZ, Chr(13), "")
If $aRawLines[0] < 10000 Then
$aRawLines[0] += 1
$aRawLines[$aRawLines[0]] = $sZ
EndIf
WEnd
FileClose($hLese)
If $aRawLines[0] = 0 Then
MsgBox(16, "Fehler", "Importdatei ist leer.")
Return SetError(2, 0, False)
EndIf
; ── 2. Kopfdaten lesen ───────────────────────────────────
Local $aKopf[30][2]
Local $iKopfN = 0
Local $bInKopf = False
For $i = 1 To $aRawLines[0]
Local $sL = StringStripWS($aRawLines[$i], 3)
If StringInStr($sL, "[Kopfdaten]") Then
$bInKopf = True
ContinueLoop
EndIf
If StringLeft($sL, 1) = "[" And $bInKopf Then
$bInKopf = False
ContinueLoop
EndIf
If $bInKopf Then
Local $iG = StringInStr($sL, "=")
If $iG > 0 And $iKopfN < 28 Then
$aKopf[$iKopfN][0] = StringStripWS(StringLeft($sL, $iG - 1), 3)
$aKopf[$iKopfN][1] = StringStripWS(StringMid($sL, $iG + 1), 3)
$iKopfN += 1
EndIf
EndIf
Next
; Fallback: erste 15 Zeilen
If $iKopfN = 0 Then
For $i = 1 To ($aRawLines[0] < 15 ? $aRawLines[0] : 15)
Local $sF = StringStripWS($aRawLines[$i], 3)
Local $pF = StringInStr($sF, "=")
If $pF > 0 And $iKopfN < 28 Then
$aKopf[$iKopfN][0] = StringStripWS(StringLeft($sF, $pF - 1), 3)
$aKopf[$iKopfN][1] = StringStripWS(StringMid($sF, $pF + 1), 3)
$iKopfN += 1
EndIf
Next
EndIf
Local $sDatum = _KopfGet($aKopf, "Datum")
Local $sBaustelle = _KopfGet($aKopf, "Baustelle")
Local $sBauabs = _KopfGet($aKopf, "Bauabschnitt")
Local $sVertrag = _KopfGet($aKopf, "Vertrag")
Local $sAspaN = _KopfGet($aKopf, "AspaN")
Local $sAspaTel = _KopfGet($aKopf, "AspaTel")
Local $sAbrufNr = _KopfGet($aKopf, "AbrufNr")
Local $sSMNr = _KopfGet($aKopf, "SMNr")
Local $sStartZ = _KopfGet($aKopf, "StartZ")
Local $sEndZ = _KopfGet($aKopf, "EndZ")
Local $sISODatum = _DatumISO($sDatum)
Local $sStartISO = _DatumISO($sStartZ)
Local $sEndISO = _DatumISO($sEndZ)
Local $sUID = _UUID()
Local $sBoQUID = _UUID()
Local $sLVName = ($sVertrag <> "") ? $sVertrag : ($sBaustelle & " " & $sBauabs)
; Feldlängen auf Dataflor-Limits kürzen
; RefBoQName / RefPrjID: max. 20 Zeichen
; RefPrjName / Name1: max. 50 Zeichen
Local $sLVName20 = StringLeft($sLVName, 20)
Local $sBaust50 = StringLeft($sBaustelle, 50)
; ── 3. Aufmassdaten parsen ───────────────────────────────
; Spalten: 1=Ort|2=OZ|3=Faktor|4=Einzelmenge|5|6|7=GesamtMenge|
; 8=Einheit|9=Beschreibung|10=Bemerkung|11=Menge2|12=EP|13=GP
Local $aPosData[10001][9]
; [n][0]=OZ [n][1]=Qty(float) [n][2]=Ort [n][3]=Beschreibung
; [n][4]=Bemerkung [n][5]=Einheit [n][6]=EP [n][7]=Faktor [n][8]=EinzelMenge
Local $iPosN = 0
Local $bInData = False
For $i = 1 To $aRawLines[0]
Local $sLine = StringStripWS($aRawLines[$i], 3)
If StringInStr($sLine, "[Aufma") Then
$bInData = True
ContinueLoop
EndIf
If StringLeft($sLine, 1) = "[" And $bInData Then
$bInData = False
ContinueLoop
EndIf
If Not $bInData Then ContinueLoop
If $sLine = "" Then ContinueLoop
If StringStripWS(StringReplace($sLine, "|", ""), 3) = "" Then ContinueLoop
If Not StringInStr($sLine, "|") Then ContinueLoop
Local $aS = StringSplit($sLine, "|", $STR_NOCOUNT)
Local $nS = UBound($aS)
If $nS < 2 Then ContinueLoop
Local $sOZ = ($nS > 1) ? StringStripWS($aS[1], 3) : ""
If Not _IsValidOZ($sOZ) Then ContinueLoop
Local $sOrt = ($nS > 0) ? StringStripWS($aS[0], 3) : ""
Local $sFaktor = ($nS > 2) ? StringStripWS($aS[2], 3) : "1"
Local $sEinzel = ($nS > 3) ? StringStripWS($aS[3], 3) : ""
Local $sGesamt = ($nS > 6) ? StringStripWS($aS[6], 3) : ""
Local $sEinh = ($nS > 7) ? StringStripWS($aS[7], 3) : ""
Local $sBeschr = ($nS > 8) ? StringStripWS($aS[8], 3) : ""
Local $sBemerk = ($nS > 9) ? StringStripWS($aS[9], 3) : ""
Local $sEP = ($nS > 11) ? StringStripWS($aS[11], 3) : ""
; Qty berechnen: Faktor * Einzelmenge, oder direkt Gesamtmenge
Local $fFaktor = _ToFloat($sFaktor)
Local $fEinzel = _ToFloat($sEinzel)
Local $fGesamt = _ToFloat($sGesamt)
Local $fQty = 0
If $fFaktor <> 1 And $fEinzel <> 0 Then
$fQty = $fFaktor * $fEinzel
ElseIf $fGesamt <> 0 Then
$fQty = $fGesamt
ElseIf $fEinzel <> 0 Then
$fQty = $fEinzel
Else
$fQty = $fFaktor
EndIf
$aPosData[$iPosN][0] = $sOZ
$aPosData[$iPosN][1] = $fQty
$aPosData[$iPosN][2] = $sOrt
$aPosData[$iPosN][3] = $sBeschr
$aPosData[$iPosN][4] = $sBemerk
$aPosData[$iPosN][5] = $sEinh
$aPosData[$iPosN][6] = $sEP
$aPosData[$iPosN][7] = $sFaktor
$aPosData[$iPosN][8] = $sEinzel
$iPosN += 1
If $iPosN >= 10000 Then ExitLoop
Next
If $iPosN = 0 Then
MsgBox(16, "Fehler", "Keine gueltigen Positionen gefunden.")
Return SetError(3, 0, False)
EndIf
; ── 4. OZ-Schema ermitteln ───────────────────────────────
; Typ bestimmen und OZ-Typ-spezifische Standard-Lengths setzen
; WICHTIG: Length = Feldbreite laut OZ-Maske, NICHT max. Zeichenlänge des Werts!
; Dataflor liest die Length aus BoQBkdn und ordnet OZ-Segmente zu.
; Falsche Lengths = kein Import (Segmente passen nicht ins Feld).
;
; Standard-Lengths (aus D11-OZ-Masken abgeleitet):
; Typ A (XX.XX.XXXX): L1=2, L2=2, L3=4 (Maske: 22PPPPi)
; Typ B (X.X.XX.XXXX): L1=1, L2=2, L3=3, L4=4 (Maske: 123PPPi)
; Typ C (XXXXXXXX): L1=8 (Maske: PPPPPPPPi)
Local $sOZTyp = "A"
Local $iMaxPos = 4
For $i = 0 To $iPosN - 1
Local $aOZ = _OZInfo($aPosData[$i][0])
If $aOZ[0] = "B" Then $sOZTyp = "B"
If $aOZ[0] = "C" And $sOZTyp = "A" Then $sOZTyp = "C"
If $aOZ[9] > $iMaxPos Then $iMaxPos = $aOZ[9]
Next
; Feste Standard-Lengths je Typ (aus D11-OZ-Maske)
Local $iL1 = 2 ; Typ A/C Standard
Local $iL2 = 2
Local $iL3 = 2
Select
Case $sOZTyp = "A"
$iL1 = 2 ; z.B. '01'
$iL2 = 2 ; z.B. '03'
; $iMaxPos bereits 4 für '0008'
Case $sOZTyp = "B"
$iL1 = 1 ; z.B. '1' (1 Stelle)
$iL2 = 2 ; z.B. '3' (Feld 2-stellig laut Maske)
$iL3 = 3 ; z.B. '01' (Feld 3-stellig laut Maske '123PPPi')
; $iMaxPos bereits 4 für '0470'
Case $sOZTyp = "C"
; keine Ebenen, nur Item
EndSelect
; ── 5. OZ-Hierarchie aufbauen ────────────────────────────
Local $aTitel[100]
Local $iTitelN = 0
Local $aUTitel[1000][3] ; [n][0]=E1 [n][1]=E2 [n][2]=E3(nur Typ B)
Local $iUTitelN = 0
For $i = 0 To $iPosN - 1
Local $aOZI = _OZInfo($aPosData[$i][0])
Local $sE1 = $aOZI[1]
Local $sE2 = $aOZI[2]
Local $sE3 = $aOZI[3]
; Titel (Ebene 1)
If $sE1 <> "" Then
Local $bGefT = False
For $j = 0 To $iTitelN - 1
If $aTitel[$j] = $sE1 Then
$bGefT = True
ExitLoop
EndIf
Next
If Not $bGefT Then
$aTitel[$iTitelN] = $sE1
$iTitelN += 1
EndIf
EndIf
; Untertitel (Ebene 2 + ggf. Ebene 3)
If $sE1 <> "" Then
Local $bGefU = False
For $j = 0 To $iUTitelN - 1
If $aUTitel[$j][0] = $sE1 And $aUTitel[$j][1] = $sE2 And $aUTitel[$j][2] = $sE3 Then
$bGefU = True
ExitLoop
EndIf
Next
If Not $bGefU Then
$aUTitel[$iUTitelN][0] = $sE1
$aUTitel[$iUTitelN][1] = $sE2
$aUTitel[$iUTitelN][2] = $sE3
$iUTitelN += 1
EndIf
EndIf
Next
; ── 6. Zeit ──────────────────────────────────────────────
Local $sTime = StringRight("0" & @HOUR, 2) & ":" & StringRight("0" & @MIN, 2) & ":" & StringRight("0" & @SEC, 2)
; ── 7. XML aufbauen ──────────────────────────────────────
Local $sX = ""
$sX &= '<?xml version="1.0" encoding="UTF-8"?>' & @CRLF
$sX &= '<!-- REB 23.003 (2009) - X31 Export AutoIt v5 -->' & @CRLF
$sX &= '<GAEB xmlns="http://www.gaeb.de/GAEB_DA_XML/DA31/3.3" xmlns:BVBS="BVBS">' & @CRLF
; GAEBInfo
$sX &= '<GAEBInfo>'
$sX &= '<Version>3.3</Version>'
$sX &= '<VersDate>2023-01</VersDate>'
$sX &= '<Date>' & $sISODatum & '</Date>'
$sX &= '<Time>' & $sTime & '</Time>'
$sX &= '<ProgSystem>AutoIt REB Engine V1.2</ProgSystem>'
$sX &= '<ProgName>AutoIt REB X31 Export</ProgName>'
$sX &= '</GAEBInfo>' & @CRLF
; QtyDeterm
$sX &= '<QtyDeterm>' & @CRLF
; PrjInfo
$sX &= '<PrjInfo>'
$sX &= '<RefPrjName>' & _XE($sBaust50) & '</RefPrjName>'
$sX &= '<RefPrjID>' & _XE($sLVName20) & '</RefPrjID>'
$sX &= '</PrjInfo>' & @CRLF
; QtyDetermInfo NUR MethodDescription laut GAEB DA XML 3.3 Schema!
; PeriodFrom/PeriodTo sind in QtyDetermInfo NICHT erlaubt → Dataflor-Warnung
$sX &= '<QtyDetermInfo ID="' & $sUID & '">'
$sX &= '<MethodDescription>REB23003-2009</MethodDescription>'
$sX &= '</QtyDetermInfo>' & @CRLF
; DP
$sX &= '<DP>31</DP>' & @CRLF
; OWN (leer, aber Pflichtfeld)
$sX &= '<OWN><Address><Name1>' & _XE($sAspaN) & '</Name1><Name2></Name2>'
$sX &= '<Name3/><Name4/><Street></Street><PCode></PCode><City></City>'
$sX &= '<Contact/><Phone>' & _XE($sAspaTel) & '</Phone><Fax/><Email/>'
$sX &= '</Address></OWN>' & @CRLF
; CTR (leer, aber Pflichtfeld)
$sX &= '<CTR><Address><Name1/><Name2></Name2><Name3/><Name4/>'
$sX &= '<Street></Street><PCode></PCode><City></City>'
$sX &= '<Contact/><Phone/><Fax/><Email/></Address></CTR>' & @CRLF
; BoQ
$sX &= '<BoQ ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<RefBoQName>' & _XE($sLVName20) & '</RefBoQName>'
$sX &= '<RefBoQID>' & $sBoQUID & '</RefBoQID>' & @CRLF
; BoQBkdn exakt wie funktionierende alte Version
; WICHTIG: <Num>No</Num> und <LblBoQBkdn> sind Pflicht für MWM/Dataflor!
Select
Case $sOZTyp = "A"
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Titel</LblBoQBkdn><Length>' & $iL1 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Bauteil</LblBoQBkdn><Length>' & $iL2 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Item</Type><LblBoQBkdn>Position</LblBoQBkdn><Length>' & $iMaxPos & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Index</Type><LblBoQBkdn>Index</LblBoQBkdn><Length>1</Length><Num>No</Num></BoQBkdn>'
Case $sOZTyp = "B"
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Titel</LblBoQBkdn><Length>' & $iL1 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Bauteil</LblBoQBkdn><Length>' & $iL2 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Gewerk</LblBoQBkdn><Length>' & $iL3 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Item</Type><LblBoQBkdn>Position</LblBoQBkdn><Length>' & $iMaxPos & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Index</Type><LblBoQBkdn>Index</LblBoQBkdn><Length>1</Length><Num>No</Num></BoQBkdn>'
Case $sOZTyp = "C"
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Titel</LblBoQBkdn><Length>2</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Item</Type><LblBoQBkdn>Position</LblBoQBkdn><Length>' & $iMaxPos & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Index</Type><LblBoQBkdn>Index</LblBoQBkdn><Length>1</Length><Num>No</Num></BoQBkdn>'
EndSelect
$sX &= @CRLF
; Ctlg (Pflichtfeld in alter Version)
$sX &= '<Ctlg><CtlgID>idDIN276_1993</CtlgID>'
$sX &= '<CtlgType>cost group DIN 276-93</CtlgType>'
$sX &= '<CtlgName>DIN 276-93</CtlgName></Ctlg>' & @CRLF
; BoQBody
$sX &= '<BoQBody>' & @CRLF
; OZ-Zähler für QTakeoff-Blattadressen
Local $iOZCnt = 0
; Typ C: Flachliste unter einer Sammelgruppe
If $sOZTyp = "C" Then
$sX &= '<BoQCtgy RNoPart="' & _XE($sBauabs) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody><Itemlist>' & @CRLF
For $i = 0 To $iPosN - 1
Local $aOZC = _OZInfo($aPosData[$i][0])
$sX &= _BuildItem($aOZC[4], $aPosData[$i][1], $aPosData[$i][2], $aPosData[$i][3], $aPosData[$i][4], $iOZCnt, $aPosData[$i][7], $aPosData[$i][8])
Next
$sX &= '</Itemlist></BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
; Typ A: 2 Ebenen
ElseIf $sOZTyp = "A" Then
For $t = 0 To $iTitelN - 1
Local $sT = $aTitel[$t]
$sX &= '<BoQCtgy RNoPart="' & _XE($sT) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody>' & @CRLF
For $u = 0 To $iUTitelN - 1
If $aUTitel[$u][0] <> $sT Then ContinueLoop
Local $sU = $aUTitel[$u][1]
$sX &= '<BoQCtgy RNoPart="' & _XE($sU) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody><Itemlist>' & @CRLF
For $p = 0 To $iPosN - 1
Local $aOZA = _OZInfo($aPosData[$p][0])
If $aOZA[1] <> $sT Or $aOZA[2] <> $sU Then ContinueLoop
$sX &= _BuildItem($aOZA[4], $aPosData[$p][1], $aPosData[$p][2], $aPosData[$p][3], $aPosData[$p][4], $iOZCnt, $aPosData[$p][7], $aPosData[$p][8])
Next
$sX &= '</Itemlist></BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
$sX &= '</BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
; Typ B: 3 Ebenen (E1.E2.E3.Pos)
ElseIf $sOZTyp = "B" Then
; Eindeutige E1-Werte (bereits in $aTitel)
For $t = 0 To $iTitelN - 1
Local $sTB = $aTitel[$t]
$sX &= '<BoQCtgy RNoPart="' & _XE($sTB) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody>' & @CRLF
; Eindeutige E2-Werte unter diesem E1
Local $aE2List[100]
Local $iE2Cnt = 0
For $u = 0 To $iUTitelN - 1
If $aUTitel[$u][0] <> $sTB Then ContinueLoop
Local $sE2Chk = $aUTitel[$u][1]
Local $bDup = False
For $x = 0 To $iE2Cnt - 1
If $aE2List[$x] = $sE2Chk Then
$bDup = True
ExitLoop
EndIf
Next
If Not $bDup Then
$aE2List[$iE2Cnt] = $sE2Chk
$iE2Cnt += 1
EndIf
Next
; Für jedes E2 unter diesem E1
For $e2i = 0 To $iE2Cnt - 1
Local $sCurE2 = $aE2List[$e2i]
$sX &= '<BoQCtgy RNoPart="' & _XE($sCurE2) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody>' & @CRLF
; Alle E3-Werte unter diesem E1.E2
For $u = 0 To $iUTitelN - 1
If $aUTitel[$u][0] <> $sTB Then ContinueLoop
If $aUTitel[$u][1] <> $sCurE2 Then ContinueLoop
Local $sCurE3 = $aUTitel[$u][2]
$sX &= '<BoQCtgy RNoPart="' & _XE($sCurE3) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody><Itemlist>' & @CRLF
; Positionen unter diesem E1.E2.E3
For $p = 0 To $iPosN - 1
Local $aOZB = _OZInfo($aPosData[$p][0])
If $aOZB[1] <> $sTB Or $aOZB[2] <> $sCurE2 Or $aOZB[3] <> $sCurE3 Then ContinueLoop
$sX &= _BuildItem($aOZB[4], $aPosData[$p][1], $aPosData[$p][2], $aPosData[$p][3], $aPosData[$p][4], $iOZCnt, $aPosData[$p][7], $aPosData[$p][8])
Next
$sX &= '</Itemlist></BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
$sX &= '</BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
$sX &= '</BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
EndIf
$sX &= '</BoQBody>' & @CRLF
$sX &= '</BoQ>' & @CRLF
$sX &= '</QtyDeterm>' & @CRLF
$sX &= '</GAEB>'
; ── 8. Als echtes UTF-8 mit BOM speichern ────────────────
; BOM = EF BB BF, dann UTF-8 Bytes
; StringToBinary($s, 4) = UTF-8 Kodierung
If FileExists($sAusgabeX31) Then FileDelete($sAusgabeX31)
Local $binBOM = Binary("0xEFBBBF")
Local $binBody = StringToBinary($sX, 4)
Local $binFull = $binBOM & $binBody
Local $bOK = FileWrite($sAusgabeX31, $binFull)
If Not $bOK Or Not FileExists($sAusgabeX31) Then
MsgBox(16, "Fehler", "Ausgabedatei konnte nicht geschrieben werden:" & @CRLF & $sAusgabeX31)
Return SetError(4, 0, False)
EndIf
MsgBox(64, "X31 Export", "X31-Datei erfolgreich erstellt!" & @CRLF & @CRLF & _
"Datei: " & $sAusgabeX31 & @CRLF & _
"Positionen: " & $iPosN & @CRLF & _
"OZ-Typ: " & $sOZTyp & @CRLF & _
"Format: GAEB DA XML 3.3 / DA31 / REB 23.003")
Return True
EndFunc
; ==============================================================================
; _BuildItem Einzelnes <Item>-Element erzeugen
; Struktur in Dataflor/MWM:
; K-Zeile (*) = RSA-Abschnitt/Ort
; L-Zeile = Formel 91 mit Faktor und Einzelmenge getrennt
; Parameter werden als Einzelwerte übergeben (kein 2D-Array-Slice in AutoIt!)
; ==============================================================================
Func _BuildItem($sPosNr, $fQty, $sOrt, $sBeschr, $sBemerk, ByRef $iOZCnt, $sFaktor = "1", $sEinzel = "")
; Qty für XML (<Qty> Tag): Punkt als Dezimaltrenner
Local $sQtyXML = StringReplace(_FmtQty($fQty), ",", ".")
; Qty für DA11 L-Zeile: Komma, direkt die berechnete Gesamtmenge
Local $sQty = _FmtQty($fQty)
; L-Zeilen-Menge = berechnetes Qty (Faktor bereits eingerechnet)
; Exakt wie Dataflor: "100091" + Gesamtmenge + "="
Local $sMenge = $sQty
; Faktor bereinigen
Local $sFakClean = StringStripWS($sFaktor, 3)
If $sFakClean = "" Or $sFakClean = "0" Then $sFakClean = "1"
$sFakClean = StringReplace($sFakClean, ".", ",")
; OZ-Codes für K- und L-Zeile
Local $sOZ1 = _REBOZCode($iOZCnt)
$iOZCnt += 1
Local $sOZ2 = _REBOZCode($iOZCnt)
$iOZCnt += 1
; K-Zeilen-Text = RSA-Abschnitt (Ort), Fallback auf Bemerkung
Local $sKText = $sOrt
If $sKText = "" Then $sKText = $sBemerk
If $sKText = "" Then $sKText = $sBeschr
Local $sItem = ""
$sItem &= '<Item ID="DF_' & _NextID() & '" RNoPart="' & _XE($sPosNr) & '">'
$sItem &= '<QtyDeterm>'
$sItem &= '<Qty>' & $sQtyXML & '</Qty>'
; K-Zeile: RSA-Abschnitt als Kommentarzeile
$sItem &= '<QDetermItem>'
$sItem &= '<QTakeoff Row="' & _XE(_KZeile($sKText, $sOZ1)) & '"/>'
$sItem &= '<BVBS:Explanation>' & _XE(StringStripWS($sBeschr, 3)) & '</BVBS:Explanation>'
$sItem &= '</QDetermItem>'
; L-Zeile: Formel 91 mit Faktor und Menge getrennt
$sItem &= '<QDetermItem>'
$sItem &= '<QTakeoff Row="' & _XE(_LZeile($sFakClean, $sMenge, $sOZ2)) & '"/>'
$sItem &= '</QDetermItem>'
$sItem &= '</QtyDeterm>'
$sItem &= '</Item>' & @CRLF
Return $sItem
EndFunc
; ==============================================================================
; Hilfsfunktionen
; ==============================================================================
; K-Zeile: 13sp(*) + Ort(56 Zeichen) + OZCode(6) + 5sp = 80 Zeichen
Func _KZeile($sOrt, $sOZCode)
Local $sOrtPad = StringLeft($sOrt & " ", 56)
Return " *" & $sOrtPad & $sOZCode & " "
EndFunc
; L-Zeile: 25sp + "100091" + Menge + "=" + padding auf 44 + BlatAdr(6) + 5sp = 80 Zeichen
; Exakt wie Dataflor-Export: " 100091Menge= BlatAdr "
; Faktor wird als Multiplikator in die Menge eingerechnet (qty = faktor * einzelmenge)
; Der Faktor ist NICHT separat in der Zeile Dataflor rechnet über die Qty
Func _LZeile($sFaktor, $sMenge, $sOZCode)
; Menge mit Komma (DA11-Standard), Punkt entfernen
Local $sMengeK = StringReplace(StringStripWS($sMenge, 3), ".", ",")
; Formel: direkt "100091" + Menge + "="
Local $sFormel = "100091" & $sMengeK & "="
; Auf 44 Zeichen auffüllen
While StringLen($sFormel) < 44
$sFormel &= " "
WEnd
$sFormel = StringLeft($sFormel, 44)
Return " " & $sFormel & $sOZCode & " "
EndFunc
; REB Blattadresse aus Zähler
; Format: Blattnummer(4) + Zeilenkennung(A-Z) + Index(0)
; idx=0 → "1000A0"
; idx=25 → "1000Z0"
; idx=26 → "1001A0" (nächste Blattnummer)
Func _REBOZCode($iIdx)
Local $iBlatt = 1000 + Int($iIdx / 26)
Local $iZeile = Mod($iIdx, 26)
Local $sZeile = Chr(65 + $iZeile) ; 65 = Asc("A")
Local $sBlatt = String($iBlatt)
Return $sBlatt & $sZeile & "0"
EndFunc
; Qty formatieren: ganzzahlig ohne Dezimalstellen, sonst 3 Stellen
; Komma als Dezimaltrenner (DA11-Standard)
Func _FmtQty($fVal)
Local $fAbs = $fVal
If $fAbs < 0 Then $fAbs = -$fAbs
Local $iGanz = Int($fAbs + 0.0005) ; gerundet
Local $fDez = $fAbs - Int($fAbs)
Local $iD3 = Int($fDez * 1000 + 0.5)
If $iD3 >= 1000 Then
$iGanz += 1
$iD3 = 0
EndIf
; Vorzeichenbehandlung
Local $sVorz = ""
If $fVal < 0 Then $sVorz = "-"
; Ganzzahlig? → kein Dezimalteil
If $iD3 = 0 Then
Return $sVorz & String($iGanz)
EndIf
; Mit Dezimalstellen: 3 Stellen, Komma
Local $sDez = String($iD3)
While StringLen($sDez) < 3
$sDez = "0" & $sDez
WEnd
; Trailing Zeros entfernen (z.B. 2,500 → 2,5 für saubere Darstellung)
While StringRight($sDez, 1) = "0" And StringLen($sDez) > 1
$sDez = StringLeft($sDez, StringLen($sDez) - 1)
WEnd
Return $sVorz & String(Int($fAbs)) & "," & $sDez
EndFunc
; OZ analysieren
Func _OZInfo($sOZ)
Local $aI[10]
$aI[0] = "?"
$aI[1] = ""
$aI[2] = ""
$aI[3] = ""
$aI[4] = $sOZ
$aI[5] = $sOZ
$aI[6] = 0
$aI[7] = 0
$aI[8] = 0
$aI[9] = 0
If $sOZ = "" Then Return $aI
If StringRegExp($sOZ, "^\d{6,10}$") Then
$aI[0] = "C"
$aI[4] = $sOZ
$aI[9] = StringLen($sOZ)
Return $aI
EndIf
Local $aS = StringSplit($sOZ, ".", $STR_NOCOUNT)
Local $n = UBound($aS)
If $n = 3 Then
$aI[0] = "A"
$aI[1] = $aS[0]
$aI[6] = StringLen($aS[0])
$aI[2] = $aS[1]
$aI[7] = StringLen($aS[1])
$aI[4] = $aS[2]
$aI[9] = StringLen($aS[2])
ElseIf $n = 4 Then
$aI[0] = "B"
$aI[1] = $aS[0]
$aI[6] = StringLen($aS[0])
$aI[2] = $aS[1]
$aI[7] = StringLen($aS[1])
$aI[3] = $aS[2]
$aI[8] = StringLen($aS[2])
$aI[4] = $aS[3]
$aI[9] = StringLen($aS[3])
EndIf
$aI[5] = $sOZ
Return $aI
EndFunc
; OZ-Typ prüfen
Func _IsValidOZ($sOZ)
If $sOZ = "" Then Return False
If StringRegExp($sOZ, "^\d{1,4}\.\d{1,4}\.\d{1,6}$") Then Return True
If StringRegExp($sOZ, "^\d{1,4}\.\d{1,4}\.\d{1,4}\.\d{1,6}$") Then Return True
If StringRegExp($sOZ, "^\d{6,10}$") Then Return True
Return False
EndFunc
; Kopfdaten-Wert lesen
Func _KopfGet(ByRef $aKopf, $sKey)
For $i = 0 To UBound($aKopf) - 1
If $aKopf[$i][0] = $sKey Then Return $aKopf[$i][1]
Next
Return ""
EndFunc
; XML-Sonderzeichen kodieren
Func _XE($s)
$s = StringReplace($s, "&", "&amp;")
$s = StringReplace($s, "<", "&lt;")
$s = StringReplace($s, ">", "&gt;")
$s = StringReplace($s, '"', "&quot;")
$s = StringReplace($s, "'", "&apos;")
Return $s
EndFunc
; String zu Float (Komma→Punkt)
Func _ToFloat($s)
If $s = "" Then Return 0
Return Number(StringReplace(StringStripWS($s, 3), ",", "."))
EndFunc
; Datum DD.MM.YYYY → YYYY-MM-DD
Func _DatumISO($s)
$s = StringStripWS($s, 3)
If StringRegExp($s, "^\d{2}\.\d{2}\.\d{4}$") Then
Local $aD = StringSplit($s, ".", $STR_NOCOUNT)
Return $aD[2] & "-" & $aD[1] & "-" & $aD[0]
EndIf
Return $s
EndFunc
; Fortlaufende DF_-ID
Func _NextID()
$g_iIDCnt += 1
Return $g_iIDCnt
EndFunc
; Pseudo-UUID
Func _UUID()
Local $sHex = "0123456789abcdef"
Local $sUID = ""
Local $aLen[5]
$aLen[0] = 8
$aLen[1] = 4
$aLen[2] = 4
$aLen[3] = 4
$aLen[4] = 12
For $i = 0 To 4
If $i > 0 Then $sUID &= "-"
For $j = 1 To $aLen[$i]
$sUID &= StringMid($sHex, Random(1, 16, 1), 1)
Next
Next
Return $sUID
EndFunc