; ============================================================================== ; _X31_Export – Hauptfunktion ; ============================================================================== 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 $sISODatum = _DatumISO($sDatum) Local $sUID = _UUID() Local $sBoQUID = _UUID() Local $sLVName = ($sVertrag <> "") ? $sVertrag : ($sBaustelle & " " & $sBauabs) ; Feldlängen auf Dataflor-Limits kürzen Local $sLVName20 = StringLeft($sLVName, 20) Local $sBaust50 = StringLeft($sBaustelle, 50) ; ── 3. Aufmassdaten parsen ─────────────────────────────── ; Spalten (0-basiert nach Split mit |): ; 0=Ort, 1=OZ, 2=Faktor, 3=Einzelmenge, 4,5=leer, 6=GesamtMenge, ; 7=Einheit, 8=Beschreibung, 9=Bemerkung, 10=Menge2, 11=EP, 12=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 > 10) ? StringStripWS($aS[10], 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: Spalte 6 (Gesamtmenge) hat immer Vorrang – sie ist bereits mit Faktor berechnet. ; Faktor * Einzelmenge wird NICHT selbst gerechnet, da Spalte 6 das Ergebnis enthält. ; Nur wenn Spalte 6 leer/0: Einzelmenge als Fallback, dann Faktor. Local $fFaktor = _ToFloat($sFaktor) Local $fEinzel = _ToFloat($sEinzel) Local $fGesamt = _ToFloat($sGesamt) Local $fQty = $fGesamt ;~ If $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 ─────────────────────────────── ; KORREKTUR: Dataflor-kompatible Lengths aus dem echten Dataflor-Export ; Für Typ B (X.X.XX.XXXX): ; Titel(E1): Length=1 → '1', '2', '3', ... ; Bauteil(E2): Length=1 → '2', '3', '4', ... (NICHT 2!) ; Abschnitt(E3): Length=2 → '01', '02', '03', ... (NICHT 3!) ; Position: Length=4 → '0470', '0010', ... ; Für Typ A (XX.XX.XXXX): ; Titel(E1): Length=2 ; Bauteil(E2): Length=2 ; Position: Length=4 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 ; ── 5. OZ-Hierarchie aufbauen ──────────────────────────── ; (wie v5, unverändert) ; ── 6. Zeit ────────────────────────────────────────────── Local $sTime = StringRight("0" & @HOUR, 2) & ":" & StringRight("0" & @MIN, 2) & ":" & StringRight("0" & @SEC, 2) ; ── 7. XML aufbauen ────────────────────────────────────── Local $sX = "" $sX &= '' & @CRLF $sX &= '' & @CRLF $sX &= '' ; GAEBInfo (kompakt, kein CRLF zwischen Tags - wie Dataflor-Referenz) $sX &= '' $sX &= '3.3' $sX &= '2023-01' $sX &= '' & $sISODatum & '' $sX &= '' $sX &= 'AutoIt REB Engine V1.2' $sX &= 'AutoIt REB X31 Export' $sX &= '' ; QtyDeterm $sX &= '' ; PrjInfo $sX &= '' $sX &= '' & _XE($sBaust50) & '' $sX &= '' & _XE($sLVName20) & '' $sX &= '' ; QtyDetermInfo $sX &= '' $sX &= 'REB23003-2009' $sX &= '' ; DP $sX &= '31' ; OWN $sX &= '
' $sX &= '' & _XE($sAspaN) & '' $sX &= '' $sX &= '' $sX &= '' & _XE($sAspaTel) & '' $sX &= '
' ; CTR $sX &= '
' $sX &= '' $sX &= '
' ; BoQ $sX &= '' $sX &= '' & _XE($sLVName20) & '' $sX &= '' & $sBoQUID & '' ; ───────────────────────────────────────────────────────────────────── ; BoQBkdn – KORRIGIERT für Dataflor-Kompatibilität ; Dataflor liest die OZ-Segmentlängen exakt aus diesen Length-Feldern. ; Falsche Werte → Import wird lautlos abgebrochen! ; ; Typ B (X.X.XX.XXXX) → Dataflor-Referenz-Schema: ; Titel Length=1 (E1: 1 Stelle) ; Bauteil Length=1 (E2: 1 Stelle, z.B. '3') ; Abschnitt Length=2 (E3: 2 Stellen, z.B. '01') ; Position Length=4 (Pos: 4 Stellen, z.B. '0470') ; Index Length=1 (immer) ; ; Typ A (XX.XX.XXXX): ; Titel Length=2 (E1) ; Bauteil Length=2 (E2) ; Position Length=4 ; Index Length=1 ; ───────────────────────────────────────────────────────────────────── Select Case $sOZTyp = "A" $sX &= 'BoQLevelTitel2No' $sX &= 'BoQLevelBauteil2No' $sX &= 'ItemPosition' & $iMaxPos & 'No' $sX &= 'IndexIndex1No' Case $sOZTyp = "B" ; Dataflor-Referenz (verifiziert): Titel L=1, Bauteil L=1, Abschnitt L=2 ; Labels exakt wie Dataflor-Export: 'Titel', 'Bauteil', 'Abschnitt' $sX &= 'BoQLevelTitel1No' $sX &= 'BoQLevelBauteil1No' $sX &= 'BoQLevelAbschnitt2No' $sX &= 'ItemPosition' & $iMaxPos & 'No' $sX &= 'IndexIndex1No' Case $sOZTyp = "C" $sX &= 'BoQLevelTitel2No' $sX &= 'ItemPosition' & $iMaxPos & 'No' $sX &= 'IndexIndex1No' EndSelect ; Ctlg $sX &= 'idDIN276_1993' $sX &= 'cost group DIN 276-93' $sX &= 'DIN 276-93' ; BoQBody $sX &= '' ; OZ-Zähler für QTakeoff-Blattadressen Local $iOZCnt = 0 ; Eindeutige OZ in TXT-Reihenfolge sammeln Local $aOZOrder[10000][4] Local $iOZOrderN = 0 For $i = 0 To $iPosN - 1 Local $aOZi = _OZInfo($aPosData[$i][0]) Local $bFound = False For $j = 0 To $iOZOrderN - 1 If $aOZOrder[$j][0] = $aOZi[1] And $aOZOrder[$j][1] = $aOZi[2] And _ $aOZOrder[$j][2] = $aOZi[3] And $aOZOrder[$j][3] = $aOZi[4] Then $bFound = True ExitLoop EndIf Next If Not $bFound Then $aOZOrder[$iOZOrderN][0] = $aOZi[1] $aOZOrder[$iOZOrderN][1] = $aOZi[2] $aOZOrder[$iOZOrderN][2] = $aOZi[3] $aOZOrder[$iOZOrderN][3] = $aOZi[4] $iOZOrderN += 1 EndIf Next ; Items ausgeben – hierarchische Struktur Local $sCurE1 = "" Local $sCurE2 = "" Local $sCurE3 = "" Local $bIL = False For $oi = 0 To $iOZOrderN - 1 Local $sE1 = $aOZOrder[$oi][0] Local $sE2 = $aOZOrder[$oi][1] Local $sE3 = $aOZOrder[$oi][2] Local $sPos = $aOZOrder[$oi][3] Local $bSameE1 = ($sE1 = $sCurE1) Local $bSameE2 = ($sE2 = $sCurE2) And $bSameE1 Local $bSameE3 = ($sE3 = $sCurE3) And $bSameE2 ; Schließen bei Wechsel If $sOZTyp = "B" And Not $bSameE3 And $bIL Then $sX &= '' $sX &= '' $bIL = False $sCurE3 = "" EndIf If Not $bSameE2 And $bIL Then $sX &= '' $sX &= '' $bIL = False $sCurE3 = "" EndIf If $sOZTyp = "B" And Not $bSameE2 And $sCurE2 <> "" Then $sX &= '' $sX &= '' $sCurE2 = "" $sCurE3 = "" EndIf If Not $bSameE1 And $sCurE1 <> "" Then $sX &= '' $sX &= '' $sCurE1 = "" EndIf ; Öffnen If $sE1 <> $sCurE1 Then $sX &= '' $sX &= '' $sCurE1 = $sE1 EndIf If $sOZTyp = "B" And $sE2 <> $sCurE2 Then $sX &= '' $sX &= '' $sCurE2 = $sE2 $sCurE3 = "" EndIf If $sOZTyp <> "B" Then $sCurE2 = $sE2 If $sOZTyp = "B" Then $sCurE3 = $sE3 If Not $bIL Then If $sOZTyp = "B" Then $sX &= '' Else $sX &= '' EndIf $sX &= '' $bIL = True EndIf ; Gesamtmenge dieser Position summieren Local $fQtySum = 0 For $i = 0 To $iPosN - 1 Local $aOZck = _OZInfo($aPosData[$i][0]) If $aOZck[1] <> $sE1 Or $aOZck[2] <> $sE2 Then ContinueLoop If $sOZTyp = "B" And $aOZck[3] <> $sE3 Then ContinueLoop If $aOZck[4] <> $sPos Then ContinueLoop $fQtySum += $aPosData[$i][1] Next ; Item schreiben $sX &= '' $sX &= '' ; Qty mit Punkt als Dezimaltrenner (XML-Standard) $sX &= '' & StringReplace(_FmtQty($fQtySum), ",", ".") & '' ; Alle Ansätze dieser Position ausgeben For $i = 0 To $iPosN - 1 Local $aOZan = _OZInfo($aPosData[$i][0]) If $aOZan[1] <> $sE1 Or $aOZan[2] <> $sE2 Then ContinueLoop If $sOZTyp = "B" And $aOZan[3] <> $sE3 Then ContinueLoop If $aOZan[4] <> $sPos Then ContinueLoop ; KORREKTUR: _MakeQDetermPair mit BVBS:Explanation $sX &= _MakeQDetermPair($aPosData[$i][2], $aPosData[$i][1], $iOZCnt) Next $sX &= '' $sX &= '' Next ; Alle offenen Tags schließen If $bIL Then $sX &= '' $sX &= '' EndIf If $sOZTyp = "B" And $sCurE2 <> "" Then $sX &= '' $sX &= '' EndIf If $sCurE1 <> "" Then $sX &= '' $sX &= '' EndIf $sX &= '' $sX &= '' $sX &= '
' $sX &= '
' ; ── 8. Als echtes UTF-8 mit BOM speichern ──────────────── 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 - Dataflor-kompatibel") Return True EndFunc ; ============================================================================== ; _MakeQDetermPair($sOrt, $fQty, $iOZCnt) ; Erzeugt EIN K+L-Zeilenpaar als zwei QDetermItem-Blöcke ; ; REIHENFOLGE: K-Zeile (Ort) zuerst, dann L-Zeile (Menge) ; QDetermItem { QTakeoff Row="*Ort..." ; BVBS:Explanation "Ort" } ← K-Zeile zuerst ; QDetermItem { QTakeoff Row="Menge..." } ← L-Zeile danach ; ============================================================================== Func _MakeQDetermPair($sOrt, $fQty, ByRef $iOZCnt) Local $sQty = _FmtQty($fQty) Local $sOZ1 = _REBOZCode($iOZCnt) ; OZ1 = K-Zeile $iOZCnt += 1 Local $sOZ2 = _REBOZCode($iOZCnt) ; OZ2 = L-Zeile $iOZCnt += 1 Local $sOut = "" ; 1. K-Zeile (Ortsbezeichnung) + BVBS:Explanation – ZUERST $sOut &= '' $sOut &= '' $sOut &= '' & _XE(StringStripWS($sOrt, 3)) & '' $sOut &= '' ; 2. L-Zeile (Mengenzeile Formel 91) – DANACH $sOut &= '' $sOut &= '' $sOut &= '' Return $sOut EndFunc ; ============================================================================== ; Hilfsfunktionen ; ============================================================================== ; K-Zeile: 12 Leerzeichen + '*' + Ort(56 Zeichen padded) + OZCode(6) + 5 Leerzeichen = 80 Zeichen Func _KZeile($sOrt, $sOZCode) Local $sOrtPad = StringLeft($sOrt & " ", 56) Return " *" & $sOrtPad & $sOZCode & " " EndFunc ; L-Zeile: 25 Leerzeichen + '100091' + Menge + '=' + (Padding auf 44) + OZCode(6) + 5 Leerzeichen = 80 Zeichen ; Menge mit Komma (DA11-Standard), kein Faktor-Parameter (Menge ist bereits die Gesamtmenge) Func _LZeile($sMenge, $sOZCode) Local $sMengeK = StringReplace(StringStripWS($sMenge, 3), ".", ",") Local $sFormel = "100091" & $sMengeK & "=" While StringLen($sFormel) < 44 $sFormel &= " " WEnd $sFormel = StringLeft($sFormel, 44) Return " " & $sFormel & $sOZCode & " " EndFunc ; REB Blattadresse aus Zähler (z.B. 0→"1000A0", 25→"1000Z0", 26→"1001A0") Func _REBOZCode($iIdx) Local $iBlatt = 1000 + Int($iIdx / 26) Local $iZeile = Mod($iIdx, 26) Local $sZeile = Chr(65 + $iZeile) Return String($iBlatt) & $sZeile & "0" EndFunc ; Qty formatieren: Komma als Dezimaltrenner, 3 Dezimalstellen (trailing zeros entfernt) Func _FmtQty($fVal) Local $fAbs = $fVal If $fAbs < 0 Then $fAbs = -$fAbs Local $iGanz = Int($fAbs) Local $fDez = $fAbs - $iGanz Local $iD3 = Int($fDez * 1000 + 0.5) If $iD3 >= 1000 Then $iGanz += 1 $iD3 = 0 EndIf Local $sVorz = "" If $fVal < 0 Then $sVorz = "-" If $iD3 = 0 Then Return $sVorz & String($iGanz) EndIf Local $sDez = String($iD3) While StringLen($sDez) < 3 $sDez = "0" & $sDez WEnd While StringRight($sDez, 1) = "0" And StringLen($sDez) > 1 $sDez = StringLeft($sDez, StringLen($sDez) - 1) WEnd Return $sVorz & String($iGanz) & "," & $sDez EndFunc ; OZ analysieren: Gibt Array zurück ; [0]=Typ(A/B/C), [1]=E1, [2]=E2, [3]=E3, [4]=Pos, [5]=OZOriginal 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, "&", "&") $s = StringReplace($s, "<", "<") $s = StringReplace($s, ">", ">") $s = StringReplace($s, '"', """) $s = StringReplace($s, "'", "'") Return $s EndFunc ; String zu Float (Komma→Punkt) Func _ToFloat($s) If $s = "" Then Return 0 $s = StringStripWS($s, 3) While StringRight($s, 1) = "," Or StringRight($s, 1) = "." $s = StringLeft($s, StringLen($s) - 1) WEnd $s = StringReplace($s, ",", ".") Return Number($s) 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