
    sju;                   
   U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZ e	j                  j                  d e ee      j"                               ddlmZmZmZ  ej,                  dej.                        Z ej,                  dej.                        Z ej,                  dej.                        Z ej,                  d	ej.                        Z ej,                  d
ej.                        Z ej,                  d      Z ej,                  d      Z ej,                  dej.                        Z ej,                  d      Z  ej,                  d      Z! ej,                  d      Z" ej,                  dej.                        Z# ej,                  d      Z$d\dZ%	 	 	 	 	 	 	 	 	 	 d]dZ&d^dZ' ej,                  dej.                        Z(	 	 	 	 d_dZ)d`dZ* ej,                  d      Z+ ej,                  d      Z, ej,                  d      Z- ej,                  d      Z. ej,                  d      Z/ ej,                  d      Z0dd hZ1 ej,                  d!      Z2 ej,                  d"      Z3	 da	 	 	 dbd#Z4 ej,                  d$      Z5d%Z6 ej,                  d&      Z7 ej,                  d'      Z8 ej,                  d(      Z9 e:h d)      Z; ej,                  d*      Z< ej,                  d+      Z= ej,                  d,      Z>d-Z? ej,                  d.      Z@ e:h d/      ZA ej,                  d0ej                        ZC ej,                  d1      ZD ej,                  d2      ZE ej,                  d3ej                        ZFdcd4ZGd5 ej,                  d6      fd7 ej,                  d8      fd9 ej,                  d:      ffZHd;eId<<   ddd=ZJded>ZKdfd?ZLdgd@ZM ej,                  dA      ZN ej,                  dB      ZOdhdCZP	 	 	 	 didDZQ ej,                  dE      ZR ej,                  dF      ZSdjdGZT	 dk	 	 	 	 	 	 	 dldHZU	 	 	 	 dmdIZV ej,                  dJej.                        ZW ej,                  dKej.                        ZX ej,                  dL      ZYdndMZZdodNZ[dpdOZ\dpdPZ]dQZ^dpdRZ_dqdSZ` ej,                  dT      Za ej,                  dU      Zb ej,                  dV      ZcdrdWZddsdXZe	 	 	 	 	 	 dtdYZfdudZZgehd[k(  r ei eg             y)vul	  Anonymisiert eine QAF-Arbeitsmappe auf XML-Ebene, statt sie neu zu schreiben.

Das Original wird ausschliesslich GELESEN. Die Ausgabe ist eine neue Datei.

── Warum eine zweite Fassung neben anonymise_workbook.py ───────────────────────
Die openpyxl-Fassung schreibt die Mappe komplett neu. Zwei Folgen, die sich
nicht wegkonfigurieren lassen:

1. **Formeln verlieren ihr gecachtes Ergebnis.** openpyxl kennt entweder
   Formeln oder Werte, nie beides. Die geschriebene Datei traegt `<f>` ohne
   `<v>`. Der QAF-Parser liest Werte und rechnet nicht — in der Fixture blieben
   deshalb 19 von 20 Kennzahlen leer, der Angebotspreis war `null` statt einer
   Zahl. Eine Fixture, die genau die Groessen nicht traegt, um die es geht,
   prueft nichts.
2. **Der Container schrumpft.** Aus 71 ZIP-Eintraegen wurden 35: `customXml/*`
   und die Erweiterungen zu bedingter Formatierung und Datenpruefung fallen weg.

Diese Fassung arbeitet deshalb am ZIP: jeder Eintrag wird unveraendert
uebernommen, ausser den Blatt-XMLs und `sharedStrings.xml`. Dort wird gezielt
ersetzt. In `<c>`-Elementen liegen `<f>` (Formel) und `<v>` (gecachtes
Ergebnis) nebeneinander — beides bleibt erhalten, nur der Zahlenwert darin
wechselt.

── Die Entscheidung zu den Formel-Caches ───────────────────────────────────────
Ein gecachtes Ergebnis DARF nicht im Original stehen bleiben: es ist ein echter
Kalkulationswert. Es soll aber zu den ersetzten Eingaben passen, sonst schliesst
keine Reconciliation mehr und die Fixture taugt nur noch fuer Strukturtests.

Zwei Betriebsarten, beide ausdruecklich benannt statt still gewaehlt:

  --caches=recompute  Eingaben ersetzen, danach die Mappe neu rechnen lassen und
                      die Ergebnisse als Cache schreiben. Arithmetisch konsistent.
                      Setzt voraus, dass die Formel-Engine die Mappe versteht.
  --caches=replace    Eingaben UND Caches deterministisch ersetzen. Kein
                      Originalwert bleibt stehen, die Arithmetik der Mappe
                      stimmt danach aber nicht mehr. Nur mit
                      `arithmetic_consistency: false` im Manifest zu verwenden.

Die Ersetzungsregel selbst ist dieselbe wie in anonymise_workbook.py und wird
von dort importiert — zwei Regeln waeren zwei Wahrheiten.
    )annotationsN)Path)TextAnonymiseris_characteristicreplace_numberz7<c(?=[ />])(?P<attrs>[^>]*?)(?:/>|>(?P<inhalt>.*?)</c>)z*<v(?P<vattrs>\s[^>]*)?>(?P<wert>[^<]*)</v>z<f[ >]z&(<is>.*?<t[^>]*>)(?P<text>[^<]*)(</t>)z(<t[^>]*>)(?P<text>[^<]*)(</t>)z\br="([A-Z]+\d+)"z\bt="([a-zA-Z]+)"z(<f(?:\s[^>]*)?>)([^<]*)(</f>)z"((?:[^"]|"")*)"z2(?<![\w:.-])(?:v|display|refreshedBy)="([^"]{2,})"zdisplay="([^"]{2,})"z2(<(?:odd|even|first)(?:Header|Footer)>)([^<]+)(</)z&"[^"]*"|&[A-Za-z0-9]|[^&]+c                   | j                  d      j                  dd      }| j                  d      j                  dd      }t        t        j                  d|            }i }t        j                  d|      D ]B  \  }}|j                  |d      }|sd|j                  d	      j                  d      z   }|||<   D |S )
u   Ordnet sheetN.xml den Blattnamen zu.

    Der Blattname geht in den Streuwert der Ersetzung ein — dieselbe Zelle in
    zwei Blaettern muss unterschiedliche Werte bekommen, sonst waere die
    Ersetzung ueber Blattgrenzen hinweg raterbar.
    xl/workbook.xmlutf-8replacezxl/_rels/workbook.xml.relsz!Id="([^"]+)"[^>]*Target="([^"]+)"z,<sheet[^>]*name="([^"]*)"[^>]*r:id="([^"]+)" zxl//)readdecodedictrefindallgetlstripremoveprefix)	zwbrels
id_zu_ziel	zuordnungnameridzielpfads	            J/home/aria/work/kadi-v2/scripts/qaf-v2/anonymise/anonymise_workbook_xml.pyblatt_namenr    r   s     
!	"	)	)'9	=B66./66w	JDbjj!EtLMJ "IZZ OQST #	c~~c2&4;;s+88??D"IdO	#
     c                    dfd}t         j                  ||       } dfd}	dfd}
t        j                  |	|       } t        j                  |
|       S )z3Ersetzt Zahlen und Inline-Texte in einem Blatt-XML.c                  
 | j                  d      }| j                  d      xs d}| j                  d      }||S t        j                  |      }|r|j                  d      ndt        j                  |      }|r|j                  d      nd}t	        t
        j                  |            
|dk(  r|S |dk(  r*dfd
}d|z   dz   t        j                  ||      z   dz   S |d	k(  r*dfd}d|z   dz   t        j                  ||      z   dz   S |dv r|S 
rt        j                  fd|      }d
fd}	d|z   dz   t        j                  |	|      z   dz   S )Nr   attrsr   inhalt   ?s	inlineStrstrc                    | j                  d      }j                  |       j                  |      }||k(  rdxx   dz  cc<   ndxx   dz  cc<   | j                  d      |z   | j                  d      z   S )Ntexttexte_beschriftungr&   texte_ersetzt   )groupadd	pseudonymmr,   neuanonoriginal_texte	statistiks      r   
pro_inlinez4ersetze_blatt.<locals>.pro_zelle.<locals>.pro_inline   st    wwv""4(nnT*$;23q83o.!3.wwqzC'!''!*44r!   z<c>z</c>c                <   | j                  d      }|j                         dk(  r| j                  d      S j                  |       j                  |      }||k(  rdxx   dz  cc<   ndxx   dz  cc<   d| j                  d      xs d d	t	        |       d
S )Nwertr   r   r-   r&   r.   <vvattrsr:   </v>)r0   stripr1   r2   _xml_escaper3   s      r   pro_strz1ersetze_blatt.<locals>.pro_zelle.<locals>.pro_str   s    wwv::<2%771:%""4(nnT*$;23q83o.!3.AGGH-34Ak#6F5GtLLr!   )ebc                    | j                  d      t        j                  fd| j                  d            z   | j                  d      z   S )Nr&   c                b    dt        j                  | j                  d                  z   dz   S )N"r&   )rA   r2   r0   )mlr6   s    r   <lambda>zDersetze_blatt.<locals>.pro_zelle.<locals>.<lambda>.<locals>.<lambda>   s(    s[1L%MMPSS r!      r/   )r0   FORMEL_LITERALsub)mfr6   s    r   rI   z2ersetze_blatt.<locals>.pro_zelle.<locals>.<lambda>   s?    288A;););SHHQK*! !#%88A;/ r!   c                   | j                  d      j                         }|dk(  r| j                  d      S 	 t        |      }j	                  |       rtdk(  r	dxx   dz  cc<   y|dk(  r	dxx   dz  cc<   | j                  d      S 	dxx   dz  cc<   d	| j                  d
      xs d dt        t        |             dS |dk(  r	dxx   dz  cc<   | j                  d      S 	dxx   dz  cc<   d	| j                  d
      xs d dt        t        |             dS # t        $ r | j                  d      cY S w xY w)Nr<   r   r   	recomputecaches_entferntr&   zahlen_nullcaches_ersetztr=   r>   r:   r?   zahlen_ersetzt)r0   r@   float
ValueErrorr1   _formatierer   )
r4   rohzahlblattcaches
ist_formelkoordoriginal_zahlenseedr8   s
      r   pro_wertz2ersetze_blatt.<locals>.pro_zelle.<locals>.pro_wert   sq   ''&/'')Cbywwqz!"Sz %[(/0A5019m,1,771:%*+q0+QWWX.4"5Q&~dD%'OPQQUW X qy-(A-(wwqz!&'1,'*0b1">$eU#KLMTS T+  "wwqz!"s   D+ +EEr4   re.Matchreturnr*   )
r0   ATTR_RsearchATTR_TboolFORMELINLINE_TEXTrL   WERTFORMEL_INHALT)trefferganzer$   r%   koord_treffertyp_treffertypr9   rB   r_   r[   r\   r6   rY   rZ   r7   r]   r^   r8   s             @@r   	pro_zellez ersetze_blatt.<locals>.pro_zelle   sc   a g&,"x(>Le,*7##A&SmmE*&1k"r&--/0
#:L+5 %<#%
F(KKfTT%<
M %<#%&(AAFJJ*L #&&/ 	F	T 	T< e|c!DHHXv$>>GGr!   c                    | j                  d      }j                  |       j                  |      }||k7  rdxx   dz  cc<   dt        |       dS )Nr&   r.   z	display="rG   )r0   r1   r2   rA   )r4   r<   r5   r6   r7   r8   s      r   pro_beschriftungz'ersetze_blatt.<locals>.pro_beschriftung   sX    wwqz4 nnT"$;o&!+&;s+,A..r!   c                    dfd}t         j                  |t        j                  | j	                  d                  }| j	                  d      t        |      z   | j	                  d      z   S )Nc                    | j                  d      }|j                  d      st        j                  d|      s|S j	                  |       j                  |      }||k7  rdxx   dz  cc<   |S )Nr   &u   [A-Za-zÄÖÜäöüß]{3,}r.   r&   )r0   
startswithr   rd   r1   r2   )r(   stueckr5   r6   r7   r8   s      r   
pro_stueckz8ersetze_blatt.<locals>.pro_kopfzeile.<locals>.pro_stueck   sj    WWQZF  %RYY7TV\-]v&..(Cf}/*a/*Jr!   rJ   r&   r/   )r(   ra   rb   r*   )KOPFZEILEN_STUECKrL   htmlunescaper0   rA   )r4   rx   
inhalt_neur6   r7   r8   s      r   pro_kopfzeilez$ersetze_blatt.<locals>.pro_kopfzeile   sR    	 '**:t}}QWWQZ7PQ
wwqzK
33aggaj@@r!   )rk   ra   rb   r*   r`   )ZELLErL   HYPERLINK_TEXT	KOPFZEILE)xmlrY   r^   r6   rZ   r8   r]   r7   rp   rr   r}   s    ```````   r   ersetze_blattr      sU    
^H ^H@ ))Is
#C/A. 

-s
3C==,,r!   c                    t        |       j                         rt        t        |             S t	        t        t        |       d            j                  d      j                  d      S )zLSchreibt eine Zahl so, wie Excel sie im XML fuehrt (kein Exponent, kein .0).
   0.)rT   
is_integerr*   intreprroundrstrip)r<   s    r   rV   rV     sL    T{3t9~eDk2&'..s3::3??r!   z<si>(.*?)</si>c                D    dfd}t         j                  ||       S )u  Ersetzt je `<si>`-Eintrag, nicht je `<t>`-Element.

    Excel zerlegt formatierten Text in Laeufe: `Bezeichnung` und ` des Bauteils`
    stehen als zwei `<r><t>` im selben `<si>`. Wer Lauf fuer Lauf entscheidet,
    fragt fuer Bruchstuecke nach einer Beschriftung, die nur als Ganzes eine ist
    — auf der HICE-Mappe blieben so drei Fragmente uebrig, waehrend dieselbe
    Beschriftung an anderer Stelle korrekt stehen blieb.

    Ist der zusammengesetzte Text eine Beschriftung, bleiben alle Laeufe. Sonst
    traegt der erste Lauf das Pseudonym und die uebrigen werden geleert; die
    Zeichenkette als Ganzes stimmt damit, die Lauf-Formatierung geht verloren.
    Das ist fuer eine Fixture der richtige Tausch.
    c                   | j                  d      }t        j                  |      }|s| j                  d      S dj                  d t        j	                  |      D              }|j                         dk(  r| j                  d      S j                  |       j                  |      }||k(  rdxx   dz  cc<   | j                  d      S dxx   dz  cc<   dt        |       dS )	Nr&   r   r   c              3  >   K   | ]  }|j                  d         ywr,   Nr0   .0r4   s     r   	<genexpr>z9ersetze_shared_strings.<locals>.pro_si.<locals>.<genexpr>3  s     !Ta!''&/!T   r-   r.   z<si><t xml:space="preserve">z	</t></si>)
r0   SI_TEXTr   joinfinditer__iter__r@   r1   r2   rA   )blockr%   stueckeganzer_textr2   r6   r7   r8   s        r   pro_siz&ersetze_shared_strings.<locals>.pro_si-  s    Q//&);;q>!''!T7;K;KF;S!TT 	"$;;q>!;'NN;/	#*+q0+;;q>!/"a'" 0I0F/GyQQr!   )r   ra   rb   r*   )SI_BLOCKrL   )r   r6   r8   r7   r   s    ``` r   ersetze_shared_stringsr     s     R4 <<$$r!   c                f    | j                  dd      j                  dd      j                  dd      S )Nru   z&amp;<z&lt;r:   z&gt;)r   )r,   s    r   rA   rA   J  s-    LLg&..sF;CCCPQr!   z^(?:docProps/(?:core|app|custom)\.xml|customXml/item\d+\.xml|xl/pivotCache/pivotCache\w+\d*\.xml|xl/externalLinks/externalLink\d+\.xml|docMetadata/LabelInfo\.xml)$z>([^<>&]{2,})<zz(<(?:dc:title|dc:creator|dc:subject|dc:description|cp:lastModifiedBy|cp:keywords|cp:category|Company|Manager)>)([^<]+)(</)z\b(xr:uid|xr2:uid|xr9:uid|xr10:uidLastSave|documentId|ds:itemID|ma:fieldId|ma:sspId|ma:termSetId|ma:anchorId|ma:list|ma:web)="([^"]+)"z\b([\w:]+)="((?:0x[0-9A-Fa-f]{8,})|(?:\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?)|(?:[0-9A-Fa-f]{32,}))"z\b(?!uri|xmlns|namespace|targetNamespace)(\w+(?::\w+)?)="(\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?)"z$D5CDD505-2E9C-101B-9397-08002B2CF9AEz$00000000-0000-0000-0000-000000000000zK[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}z:\b(?:uri|xmlns(?::\w+)?|namespace|targetNamespace)="[^"]*"c                Z   dfd}t         j                  ||       } t        j                  ||       } |sdfd}t        j                  ||       } g dfd}|rt        j                  ||       } dfd}t
        j                  ||       } t        j                  dfd|       S )	u  Ersetzt Dokument-GUIDs deterministisch, unter Erhalt der Eindeutigkeit.

    Gleiche Kennung bleibt gleich, verschiedene bleiben verschieden — sonst
    braechen Verweise zwischen den Teilen der Mappe.

    `schema_schuetzen=False` fuer die `customXml/`-Teile: dort ist der
    Namensraum KEINE Formatkonstante, sondern die GUID der SharePoint-Liste
    beziehungsweise des Inhaltstyps der erzeugenden Organisation. Fuer
    `uri="{78C0D931-...}"` in den Zeichnungen gilt das Gegenteil — das ist eine
    Office-Erweiterungskennung und in jeder Datei dieselbe. Weil beide Seiten
    denselben Seed benutzen, bleiben `xmlns` in `item2.xml` und `ds:uri` in
    `itemProps2.xml` auch nach der Ersetzung aufeinander abgebildet.
    c                   | j                  d      }|j                  d      j                         t        v r| j                  d      S t	        j
                  dz   |z   j                               j                         }d|dd  d|dd  d|dd	  d|d	d
  d|d
d  dj                         }dxx   dz  cc<   | j                  d       d| dS )NrJ   {}r   	|kennung|{   -             }kennungen_neutralisiertr&   ="rG   )r0   r@   upperBEKANNTE_GUIDShashlibsha256encode	hexdigestr4   rW   hr5   r^   r8   s       r   pro_kennungz,neutralisiere_kennungen.<locals>.pro_kennung  s    ggaj99T?  "n4771:NND;.4<<>?IIK1Qq6(!AaG9Aa2hZq2b
!AbH:RHNNP+,1,''!*RuA&&r!   c                   | j                  d      }|j                  d      j                         t        v r| j                  d      S t	        j
                  dz   |z   j                               j                         }dxx   dz  cc<   |j                         j                  d      r4| j                  d       d|d t        |      dz
   j                          d	S d
|v rl|dd  d
|dd  d
|dd  d
|dd  d
|dd  	j                         }|j                  d      r| j                  d       d| dS | j                  d       d| d	S | j                  d       d|d t        |       j                          d	S )NrJ   r   r   r   r   r&   0xz="0xrG   r   r   r   r   r   r   r   z="{z}"r   )r0   r@   r   r   r   r   r   r   lowerrv   lenr   s       r   pro_formz)neutralisiere_kennungen.<locals>.pro_form  s   ''!*Cyy$$&.8wwqz!{ 2S 8@@BCMMOA/0A50yy{%%d+ ''!*T!MSX\*:*@*@*B)C1EEcz1Q!Ab'!AbH:QqBxj!Br(LRRT69nnS6I!''!*T#c2fRSRYRYZ[R\Q]]_`c_ddeOffggaj\Ais3xL$6$6$8#9;;r!   c                h    j                  | j                  d             dt              dz
   dS )Nr    r&   )appendr0   r   r4   schutzs    r   merkenz'neutralisiere_kennungen.<locals>.merken  s/    aggaj!c&kAo&d++r!   c                8   | j                  d      }|j                         t        v r|S t        j                  dz   |z   j                               j                         }dxx   dz  cc<   |dd  d|dd  d|dd  d|dd	  d|d	d
  	j                         S )Nr   r   r   r&   r   r   r   r   r   r   )r0   r   r   r   r   r   r   )r4   rW   r   r^   r8   s      r   pro_eingebettetz0neutralisiere_kennungen.<locals>.pro_eingebettet  s    ggaj99;.(JNND;.4<<>?IIK+,1,Aa&1Qr7)1Qr"XJa"Rz1R8*EKKMMr!   z\x00(\d+)\x00c                >    t        | j                  d               S )Nr&   )r   r0   r   s    r   rI   z)neutralisiere_kennungen.<locals>.<lambda>  s    fS_.E r!   r`   )r4   z're.Match[str]'rb   r*   )DOKUMENT_KENNUNGrL   GUID_ATTRIBUTKENNUNGSFORMSCHEMA_ATTRIBUTGUID_EINGEBETTETr   )	r   r^   r8   schema_schuetzenr   r   r   r   r   s	    ``     @r   neutralisiere_kennungenr     s    ' 

{C
0C


K
-C	< x- F, !!&#.N 


4C66"$EsKKr!   z\b(id|siteId)="([^"]{8,})"z&{00000000-0000-0000-0000-000000000000}z0(<vt:(?:lpwstr|i4|filetime|bool)>)([^<]+)(</vt:)zd\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?|0x[0-9A-Fa-f]{10,}z\d{4}-\d{2}-\d{2}T[\d:.]+Z?>   ManualStandard	Automatic
Privilegedzh[\d.,:/+-]+|\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f-]{4,}\}?|\d{4}-\d{2}-\d{2}T[\d:.Z+-]+|true|false|0x[0-9A-Fa-f]+z,^xl/printerSettings/printerSettings\d+\.bin$z.(Target=")([^"]+)("[^>]*TargetMode="External")zhttps://beispiel.invalid/externz\d+>   purl.org
www.w3.orgwww.iso.orgdublincore.orgbeispiel.invalidgo.microsoft.comwww.dublincore.orgdocs.oasis-open.orgschemas.microsoft.comschemas.openxmlformats.orgschemas.opendocumentformat.orgz@(?:https?://|ftps?://|www\.)[^\s\"'<>]+|\\\\[\w.-]+\\[^\s\"'<>]+zg\b[A-Za-z0-9][\w-]*(?:\.[A-Za-z0-9][\w-]*)+\.(?:corp|local|intranet|lan|net|com|org|de|eu|io|gov|edu)\bz4(?:\+\d{1,3}[\s/()-]{1,3}|\(0\)?\s?)\d[\d\s/-]{5,}\dze\b(?:tel|telefon|fon|rufnummer|phone|fax|mobil|mobile|handy|durchwahl)\b[^\d]{0,12}\d[\d\s/()-]{5,}\dc           
         t         t        fD ]J  }|j                  |       }|st        t	        j
                  dd|j                  d                  dk\  sJ y y)Nz\Dr   r      TF)TEL_INTERNATIONALTEL_MIT_KONTEXTrd   r   r   rL   r0   )r,   musterrk   s      r   _ist_rufnummerr   b  sQ    $o6 --%s266%W]]1-=>?1D r!   Postfachz[\w.+-]+@[\w-]+\.[A-Za-z]{2,}	Anmeldungz9(?<![\\\w])[A-Za-z][\w-]{2,}\\[A-Za-z][\w.-]{2,}(?![\w-])AnredeuF   \b(?:Herr|Frau|Hr|Fr|Mr|Mrs|Ms|Dear)\.?\s+[A-ZÄÖÜ][\wäöüß-]{2,}z)tuple[tuple[str, 're.Pattern[str]'], ...]VERBOTENE_INHALTEc                    t        j                  dd| t         j                        j                  d      }t        j                  d|      d   j                         j                         S )Nz
^[a-z]+://r   )flags\z[/\\]r   )r   rL   Ir   splitr   r@   )adresseohnes     r   _hostr   q  sM    66-WBDD9@@FD88Hd#A&,,.4466r!   c                   g }d}t         j                  |       D ]V  }t        |j                  d            t        vr y|j                  | ||j                                 |j                         }X |j                  | |d        dj                  |      }t        D ]  \  }}|j                  |      s|c S  t        j                  |      D ])  }|j                  d      j                         t        vs) y t        |      ryy)um  Nennt die Inhaltsklasse, wenn der Text nicht in eine Fixture gehoert.

    Unbedenkliche Adressen werden vor den uebrigen Klassen herausgeschnitten,
    nicht nur uebersprungen: `\\beispiel.invalid\ablage\...` — der eigene
    Ersatzpfad — sieht sonst wie eine Anmeldung `invalid\ablage` aus, und die
    Pruefung meldet 89 Treffer auf ihre eigene Ausgabe.
    r   AdresseN Rechnername	Rufnummer)ADRESSEr   r   r0   UNBEDENKLICHE_HOSTSr   startendr   r   rd   RECHNERNAMEr   r   )r,   teileletzterk   restklasser   s          r   verbotener_inhaltr   v  s     EF##D) q!"*==T&12	
 
LLfg88E?D+ ==M ''- !==!!#+>> ! dr!   c                F    	 | j                  d       y# t        $ r Y yw xY w)a  Entscheidet ueber den Inhalt, nicht ueber die Dateiendung.

    Dieselbe Lehre wie beim Zaehlen von Dateiformaten: der Container
    entscheidet, nicht der Name. `.vml` ist Text und wurde als Binaerdatei
    behandelt, `.bin` ist Binaerdatei und wurde als "kein Text" abgetan.
    r
   FT)r   UnicodeDecodeError)rohdatens    r   ist_textr     s,        s    	  c                $   t        j                  d|       D cg c]!  }|j                         j                  d      # }}|t        j                  d|       D cg c]"  }|j                         j                  dd      $ c}z  }|S c c}w c c}w )u  Lesbare Zeichenketten aus einem nicht-XML-Eintrag — ASCII und UTF-16LE.

    `pruefe_ausgabe` hat solche Eintraege bis hierher mit der Begruendung
    uebersprungen, sie traegen "kein Text, keine Werte". Fuer Bilder und
    Schriften stimmt das; fuer `xl/printerSettings/*.bin` nicht. Dort steht
    die DEVMODE-Struktur des Druckers, und darin standen interne
    Server-Rechnernamen und ein Anmeldename — byte-identisch mit der Quelle,
    in allen drei Fixtures, durch jede Stufe der Kette hindurch.
    s   [\x20-\x7e]{4,}asciis   (?:[\x20-\x7e]\x00){4,}z	utf-16-ler   )r   r   r0   r   )r   r4   kettens      r   zeichenketten_aus_binaerdatenr    s     {{#6AC ggiw' CF C
$?JL wwyY7 L LFM	CLs   &B'Bz+'([^']*(?:\\\\[\w.-]+\\|[A-Za-z]:\\)[^']*)'z((?:\\\\[\w.-]+\\|[A-Za-z]:\\)[^\s\"'<>]*c                    dfddfd}dfd}dfd}t         j                  ||       } t        j                  ||       } t        j                  ||       S )u  Ersetzt Pfade und Adressen deterministisch, formerhaltend.

    Formerhaltend heisst: aus einem Quellverweis wird wieder ein
    Quellverweis. Wer ihn durch ein Wortpseudonym ersetzt, macht aus
    `VLOOKUP($D23, '\\server\[Mappe.xlsx]Blatt'!$A:$B, 2, 0)` eine
    ungueltige Formel — und die Fixture soll gerade zeigen, dass Formeln und
    ihre gecachten Ergebnisse erhalten bleiben.
    c                x    t        j                  dz   | z   j                               j                         d d S )Nz|pfad|r   r   r   r   r   )rW   r^   s    r   markez%neutralisiere_adressen.<locals>.marke  s4    ~~th4<<>?IIKBQOOr!   c                T    dxx   dz  cc<   d | j                  d             dS )Nadressen_neutralisiertr&   z"'\\beispiel.invalid\ablage\[mappe-z.xlsx]Blatt'r   r4   r  r8   s    r   pro_verweisz+neutralisiere_adressen.<locals>.pro_verweis  s1    *+q0+7aggaj8I7J,WWr!   c                R    dxx   dz  cc<   d | j                  d             S )Nr
  r&   z\\beispiel.invalid\ablage\r   r   r  s    r   pro_pfadz(neutralisiere_adressen.<locals>.pro_pfad  s/    *+q0+/aggaj0A/BCCr!   c                |    | j                  d      }t        |      t        v r|S dxx   dz  cc<   d |       S )Nr   r
  r&   zhttps://beispiel.invalid/)r0   r   r   )r4   rW   r  r8   s     r   pro_adressez+neutralisiere_adressen.<locals>.pro_adresse  sD    ggaj:,,J*+q0+*5:,77r!   )rW   r*   rb   r*   r`   )QUELLVERWEISrL   NACKTER_PFADr   )r   r^   r8   r  r  r  r  s    ``   @r   neutralisiere_adressenr    sQ    PXD8 

;
,C


8S
)C;;{C((r!   c                    dfddfddfd}dfd}t         j                  ||       } t        j                  ||       S )uy  Letzter Durchgang ueber jeden Eintrag: was eine verbotene Inhaltsklasse
    trifft oder eine Personenwortmarke traegt, wird ersetzt — egal wo es steht.

    Der Grund steht in den Blaettern selbst. Bis hierher ersetzte der
    Anonymisierer auf einem Blatt `<t>`-Text und `<v>`-Werte; ein Name in einem
    ATTRIBUT desselben Blattes (Gueltigkeitsmeldung, Hyperlink-Hinweis,
    Kopfzeile) kam unveraendert durch. Dieselbe Bauart wie alle Vorfaelle
    davor: eine Stelle, an die keine Regel gedacht hat.

    Diese Sicherung listet keine Stellen mehr auf. Sie prueft jeden Wert und
    ersetzt den, der nicht in eine Fixture gehoert. Damit kann `pruefe_ausgabe`
    nichts mehr finden, was hier nicht schon behandelt waere — die Pruefung ist
    danach eine Regressionssperre, keine Entdeckung. Entdeckt wird durch ein
    unabhaengiges Review; das ist Verfahren, nicht Werkzeug.
    c                    t        j                  dz   | z   j                               j                         d d }dxx   dz  cc<   d| S )Nz|sicherung|r   sicherung_ersetztr&   zanonymisiert-r  )r<   r   r^   r8   s     r   ersatzz letzte_sicherung.<locals>.ersatz  sS    NND=047??ABLLNsPRS%&!+&qc""r!   c                N    t        |       yd uxr j                  |       d uS )NTr   rd   )r<   gesperrts    r   	betroffenz#letzte_sicherung.<locals>.betroffen  s/    T".t#I(=T(IIr!   c                     | j                  d            rd | j                  d             dS | j                  d      S )Nr&   r   rG   r   r   r4   r  r  s    r   pro_attributz&letzte_sicherung.<locals>.pro_attribut  s=    -6qwwqz-BF1771:&'q)RPQ
Rr!   c                     | j                  d            rd | j                  d             dS | j                  d      S )Nr&   r:   r   r   r   r  s    r   pro_textz"letzte_sicherung.<locals>.pro_text  s<    ,5aggaj,A6!''!*%&a(QqwwqzQr!   r<   r*   rb   r*   )r<   r*   rb   rf   r`   )ATTRIBUT_BELIEBIGrL   
TEXTKNOTEN)r   r^   r  r8   r  r   r  r  s    ```  @@r   letzte_sicherungr$    s?    "#
J
SR 

c
2C>>(C((r!   z^<(?:cp:lastModifiedBy|dc:creator|dc:lastModifiedBy|DisplayName|UserName|Author)[^>]*>([^<]+)</u   [A-Za-zÄÖÜäöüßÀ-ÿ]{4,}c           	     |   t               }t        j                  |       5 }|j                         D ]  }|j	                  d      s|j                  d      s'|j                  |      j                  dd      }t        j                  |      D ]E  }|t        j                  |j                  d            D ch c]  }|j                          c}z  }G  	 ddd       |syt        j                  ddj!                  d	 t#        |      D              z   d
z   t        j$                        S c c}w # 1 sw Y   ZxY w)u  Wortmarken aus den Personenfeldern der Quelle — gesperrt fuer die
    GESAMTE Ausgabe, auch auf Blaettern.

    Der Nachname des letzten Bearbeiters wurde in `cp:lastModifiedBy` korrekt
    zu einem Pseudonym und blieb gleichzeitig in einer Redaktionsnotiz auf
    einem Blatt stehen: derselbe Wert, zwei Fundorte, zwei Entscheidungen. Wer
    einen Wert einmal als personenbezogen erkennt, darf ihn nirgends stehen
    lassen — auch nicht unter dem Titel "Formularvokabular".

    Faellt ein Token mit einem echten Vorlagenwort zusammen, wird das Wort
    ersetzt und der Parser erkennt die Mappe nicht mehr: die Abnahmetests
    schlagen fehl. Das ist die richtige Richtung — ein lautes Fehlschlagen
    statt eines stillen Lecks.
    )	docProps/
customXml/.xmlr
   r   r&   Nu   (?<![A-Za-zÄÖÜäöüß])(?:|c              3  F   K   | ]  }t        j                  |        y w)N)r   escape)r   ts     r   r   z!personen_token.<locals>.<genexpr>#  s     7A299Q<7s   !u   )(?![A-Za-zÄÖÜäöüß]))setzipfileZipFilenamelistrv   endswithr   r   PERSONENFELDr   NAMENSTOKENr   r0   r   r   compiler   sortedr   )srctokenr   r   r   r4   r,  s          r   personen_tokenr8    s    eE		 NJJL 	ND??#>?==(&&,%%gy9C!**3/ N[-@-@-LM!'')MMN	NN ::)
((77
7	8
(	)*,$$0 0 NN Ns   BD28D-	D2-D22D;c                p   d
fd| dk(  rdfd}t         j                  ||      S | j                  d      r?dfd}t        j                  ||      }| dk(  rdfd}t        j                  ||      }|S dfd}	dfd	}
t
        j                  |	|      }t        j                  |
|      S )u  Pseudonymisiert Werte in Eintraegen, die keine Blaetter sind.

    Der PivotCache fuehrt seine Werte als ATTRIBUT (`<s v="Rohstoff"/>`), die
    Dokument-Eigenschaften als Elementtext, und `customXml/*` traegt
    SharePoint-Felder mit Namen von Bearbeitern. Alle drei kamen in der ersten
    Fassung unveraendert durch — 24 Eintraege Byte fuer Byte, darunter 17
    Rohstoffbezeichnungen im Klartext.

    **Nicht pauschal ueber alle Textknoten.** Die Dokument-Eigenschaften
    enthalten typisierte Felder: Zeitstempel (`dcterms:created`), Wahrheitswerte
    (`ScaleCrop`, `SharedDoc`), Zaehler und die erzeugende Anwendung. Werden die
    zu Pseudonymen, ist die Mappe nicht mehr OOXML-konform — `openpyxl` bricht
    dann beim Oeffnen mit `ValueError` ab, waehrend der ExcelJS-basierte
    QAF-Parser sie noch liest. Eine Fixture, die nur die Haelfte der Werkzeuge
    oeffnen kann, ist keine. Deshalb hier eine Positivliste der Freitextfelder.
    c                |   j                  |        | j                         }t        j                  |      rt	        j
                  dz   |z   j                               j                         }	dxx   dz  cc<   |j                         j                  d      r"d|dt        |      dz
   j                         z   S |j                  d      }|dd	  d
|d	d  d
|dd  d
|dd  d
|dd  	}|rd| dj                         S |S t        j                  |      rht	        j
                  dz   |z   j                               j                         }	dxx   dz  cc<   dj                  d |D              dt        |       S t        j                  |      r| S j                  |       }|| k7  r	dxx   dz  cc<   |S )u|  In den Metadaten wird IMMER ersetzt, ohne Ruecksicht auf die
        Beschriftungsliste.

        Ein Formularwort kann auf einem Blatt Vokabular sein und im selben
        Dokument als `<Company>` eine Herkunftsangabe — dieselbe Zeichenkette,
        zwei Bedeutungen. Wer hier die Beschriftungsliste fragt, schuetzt die
        Herkunft mit dem Argument des Vokabulars: der reale Konzernname blieb
        so in `docProps/app.xml` stehen, obwohl er auf den Blaettern zu Recht
        bleiben durfte.

        Metadaten tragen kein Vokabular, das ein Parser braucht. Also weg
        damit, ausser es ist erkennbar Struktur.
        z|meta|r   r&   r   NrJ   r   r   r   r   r   r   r   r   r   z|zahl|r   c              3  L   K   | ]  }t        t        |d       dz          yw)r   r   N)r*   r   )r   cs     r   r   z<ersetze_weiteren_eintrag.<locals>.ersetze.<locals>.<genexpr>]  s     ;A3s1bzB/;s   "$r.   )r1   r@   KENNUNGSWERT	fullmatchr   r   r   r   r   rv   r   r   REINE_ZIFFERNr   STRUKTURWERTpseudonym_erzwungen)
r<   rW   r   
geklammertneu_guidneu_wertr6   r7   seed_fuer_kennungenr8   s
         r   ersetzez)ersetze_weiteren_eintrag.<locals>.ersetze:  s    	4 jjl !!#& 3h > DLLNOYY[A/0A50yy{%%d+aSA.44666,JAa&1Qr7)1Qr"XJa"Rz1R8*MH0:Rz$**,HH""3'  3h > DLLNOYY[A/0A5077;;;ISXFF!!#&K++D1to&!+&r!   zdocMetadata/LabelInfo.xmlc                h     | j                  d             | j                  d       dt         dS )NrJ   r&   r   rG   )r0   NEUTRALE_GUID)r4   rF  s    r   pro_guidz*ersetze_weiteren_eintrag.<locals>.pro_guidl  s.    AGGAJggaj\M?!44r!   r&  c                    | j                  d      }|j                         dk(  r| j                  d      S | j                  d      t         |            z   | j                  d      z   S NrJ   r   r   r&   r/   r0   r@   rA   r4   r<   rF  s     r   pro_freitextz.ersetze_weiteren_eintrag.<locals>.pro_freitextr  sU    771:Dzz|r!wwqz!771:GDM ::QWWQZGGr!   zdocProps/custom.xmlc                    | j                  d      }|j                         dk(  r| j                  d      S | j                  d      t         |            z   | j                  d      z   S rK  rL  rM  s     r   pro_eigenschaftz1ersetze_weiteren_eintrag.<locals>.pro_eigenschaft~  sU    wwqz::<2%771:%wwqzK$>>KKr!   c           	         | j                  d      }| j                  d      j                  d| ddt         |             d      S )Nr&   r   rG   )r0   r   rA   rM  s     r   r  z.ersetze_weiteren_eintrag.<locals>.pro_attribut  sF    wwqzwwqz!!AdV1+;wt}3M2Na/PQQr!   c                    | j                  d      }|j                         }|dk(  s*t        j                  |      r&t        j                  |      s| j                  d      S dt         |            z   dz   S )Nr&   r   r   r:   r   )r0   r@   r@  r>  r?  rA   )r4   r<   rW   rF  s      r   pro_elementz-ersetze_weiteren_eintrag.<locals>.pro_element  sf    wwqz jjl"9//4]=T=TUX=Y771:[//#55r!   r!  r`   )
LABEL_GUIDrL   rv   DOCPROPS_FREITEXTVT_WERT	ATTR_WERTELEMENT_TEXT)r   r   r6   r8   r7   rE  rI  rN  rP  r  rS  rF  s     ````     @r   ersetze_weiteren_eintragrY  '  s    &) )V **	5 ~~h,,{#	H
  ##L#6 ((L
 ++os3C
R
6 --c
*CK--r!   c                	   t        ||      }ddddddddddddd}t               }t               }t        |       }	|j                  j	                  dd       t        j                  |       5 }
t        |
      }t        j                  |dt
        j                        5 }|
j                         D ]3  }|
j                  |j                        }t        j                  d|j                        r\|j                  |j                  |j                        }|j                  d      }t!        ||||||||      }|j#                  d      }n|j                  dk(  r2|j                  d      }t%        ||||      }|j#                  d      }nh|j                  j'                  d	      rM|j                  d      }t(        j+                  d
 |      }||k7  r|dxx   dz  cc<   |j#                  d      }n t,        j                  |j                        r=|j                  d      }t/        |j                  |||||      }|j#                  d      }nt0        j                  |j                        rdt3        |      z  }|dxx   dz  cc<   ni|dxx   dz  cc<   t5        |      sQt7        |      D ]C  }t9        |      }||	|	j;                  |      rdnd }|*t=        |j                   d| d       t5        |      rdt?        |j                  d      |||j                  jA                  d             }tC        |||      }tE        |||	|      }|j#                  d      }t        jF                  |j                  |jH                        }|jJ                  |_%        |jL                  |_&        |jN                  |_'        |jP                  |_(        |jS                  ||       6 	 d d d        d d d        tU        |       }|tW        |       z  }tY        | ||||      \  }}|t[        ||      z  }|t]        ||	      z  }|t_        | |      z  }||d<   ||t3        t        j                  |       ja                               t3        t        j                  |      ja                               tc        jd                  |jg                               ji                         dS # 1 sw Y   xY w# 1 sw Y   xY w)Nr   )rS   rQ   rR   rP   r.   r-   externe_ziele_neutralisiertr   r
  r  binaerdaten_genullteintraege_unveraendertT)parentsexist_okwxl/worksheets/sheet\d+\.xml$r
   xl/sharedStrings.xmlz.relsc                V    | j                  d      t        z   | j                  d      z   S )Nr&   r/   )r0   NEUTRALES_ZIEL)r4   s    r   rI   zanonymise_xml.<locals>.<lambda>  s     !''!*~"=
"J r!   r[  r&       r\  r]  Personenwortmarke: u    in einem Binaereintrag, den das Werkzeug nicht behandeln kann. Erst entscheiden, wie dieser Eintragstyp neutralisiert wird — nicht blind nullen, nicht stehen lassen.r'  )r   )	date_timekollisionen_geprueft)r8   leckszip_eintraege_quellezip_eintraege_zielr   )5r   r-  r8  parentmkdirr.  r/  r    ZIP_DEFLATEDinfolistr   filenamer   matchr   r   r   r   r   r1  EXTERNES_ZIELrL   WEITERE_WERTTRAEGERrY  INERTE_BINAERDATENr   r   r  r   rd   
SystemExitr   rv   r  r$  ZipInforh  compress_typeexternal_attrinternal_attrcreate_systemwritestrmetadaten_der_quellealle_texte_der_quellepruefe_leckpruefe_metadatenpruefe_ausgabepruefe_unveraenderte_kennungenr0  r   r   
read_bytesr   )r6  dstlabelsr^   rZ   r6   r8   r]   r7   gesperrte_tokenquellenamenr   eintragr   rY   r   neu_xmlketter   text_neuinfometadaten_texterj  kollisionens                            r   anonymise_xmlr    s   &$'D#$Q!$%QR01a+,1()+,.I #&%O"uN$S)OJJTD1		 X.F#__S#w';';< V	.!??, U.!;;w'7'7888;W=M=MN!IIg&6&68H8HIE"//'2C'UD$	(7IC"zz'2H%%)??"//'2C0dI~VC"zz'2H%%..w7"//'2C+//JCQG#~!"?@AE@&~~g6H(..w/?/?@"//'2C273C3CS$3<ndTC"zz'2H'--g.>.>?  'X6H3494671<7
 $H-%B8%L 
QE%6u%=F%~/2M@O@V@VW\@])<cg%1&0'.'7'7&86( CO %P'Q !Q
Q< H%6 0$	-4-=-=-H-H-V)V XH  6hiPH/$09 ;H'w7Hw'7'77CTCTU%,%:%:"%,%:%:"%,%:%:"%,%:%:"dH-kU.V	.X.@ +3/O+C00N$S#PTUE; 
c?33E 
^C11E	+C55E(3I$% #GOOC$8$A$A$C D!'//#"6"?"?"AB..!12<<> UV	. V	.X. X.s,   -1S+H8SDS'S+S(	$S++S4z&<(?:v|f)(?:\s[^>]*)?>([^<]*)</(?:v|f)>z<t(?:\s[^>]*)?>([^<]*)</t>z%(?<![\w.$!:])(\d+(?:\.\d+)?)(?![\w.])c           
        i }t        j                  |       5 }t        |      }|j                         D ]  }t	        j
                  d|      s|j                  ||      }|j                  |      j                  dd      }t        j                  |      D ]  }|j                  d      }||j                  d      xs d}	t        j                  |	      }
|
r|
j                  d      d	v rWt        j                  |	      }|sot        j                  |      }|s	 t!        |j                  d
            || d|j                  d       <      	 ddd       |S # t"        $ r Y w xY w# 1 sw Y   |S xY w)u7  Alle Zahlwerte je Blatt und Koordinate, direkt aus der XML.

    Formelzellen liefern ihren gecachten Wert mit — anders als bei einer
    Bibliotheks-Sicht, die entweder Formel ODER Wert zeigt. Genau diese Zellen
    sind der Grund fuer diese Fassung, also muessen sie auch in der Pruefung
    vorkommen.
    ra  r
   r   r%   Nr$   r   r&   )r(   r*   rC   rD   r)   r<   !)r.  r/  r    r0  r   rr  r   r   r   r~   r   r0   re   rd   rc   ri   rT   rU   )r   werter   r  r   rY   r   rk   r%   r$   rn   rm   wert_treffers                r   
_zellwerter  "  sj    !E		 !AJJL 	D88;TBIIdD)E&&,%%gy9C >>#.  x0>g.4"$mmE2;#4#4Q#7;^#^ &e 4$#{{62#AF|GYGYZ`GaAbEUG1]%8%8%;$<=>	2 L " /2 Ls0   DE/1E E/ 	E,	)E/+E,	,E//E9c           	     8   g }g }t        j                  |       5 }|j                         D ]  }|j                  |      }t	        |      s!|j                  d      }|j                  t        j                  |             |j                  t        j                  |             |j                  d      rW|j                  d t        j                  |      D               |j                  d t        j                  |      D               ny|j                  d      r,|j                  d t        j                  |      D               n<|j                  d      r+|j                  d t        j                  |      D               |j                  d	 t        j                  |      D               t!        j                  d
|t         j"                        D ]$  }|j%                  ||j'                  d      f       &  	 ddd       ||fS # 1 sw Y   ||fS xY w)u  Liest aus der Ausgabe nur die Stellen, die Fachwerte tragen koennen.

    Bewusst NICHT die ganze Datei: `xl/theme/theme1.xml` fuehrt Farbanteile wie
    `val="105000"`, und `xr:uid`-Attribute enthalten lange Ziffernfolgen. Eine
    Suche darueber meldet Treffer, die keine sind — und eine Pruefung, die
    Fehlalarme produziert, wird abgeschaltet. Genau daran ist die erste Fassung
    dieser Funktion aufgefallen.
    r
   r&  c              3  >   K   | ]  }|j                  d         ywrJ   Nr   r   s     r   r   z _wertbereiche.<locals>.<genexpr>i       QAQWWQZQr   c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z _wertbereiche.<locals>.<genexpr>j  s     GAQWWQZGr   docMetadata/c              3  >   K   | ]  }|j                  d         ywr&   Nr   r   s     r   r   z _wertbereiche.<locals>.<genexpr>l       LAQWWQZLr   r'  c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z _wertbereiche.<locals>.<genexpr>n  r  r   c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z _wertbereiche.<locals>.<genexpr>q       EEr   z<f(?:\s[^>]*)?>([^<]*)</f>r&   N)r.  r/  r0  r   r   r   extendWERT_ODER_FORMELr   TEXT_INHALTrv   rU  r   rV  rX  rW  r   Sr   r0   )r  r  formelnr   r   r   r   r4   s           r   _wertbereicher  G  s    E%'G		 !3JJL  	3D vvd|HH%//'*CLL)11#67LL,,S12 {+Q1B1K1KC1PQQG1A1A#1FGG0L1F1Fs1KLL.L1F1Fs1KLL LLEY-?-?-DEE[[!>RTTJ 3aggaj123? 	3!3L '>M!3L '>s   G&HHc                   t        |       dfd}t               }t        j                  |       5 }|j	                         D ]  }t        j                  d|      s|dk(  s|j                  |      j                  dd      }|dk(  rdt        j                  |      D ]K  }|j                  dj                  d t        j                  |j                  d            D                     M |j                  d	 t         j                  |      D               |j                  d
 t"        j                  |      D                	 ddd       |D ch c]  }|j%                         s ||      s|  c}S # 1 sw Y   3xY wc c}w )u  Nur was auf den Blaettern steht — NIE die Dokument-Metadaten.

    Der Blanko-Modus schuetzt Vokabular vor dem Ersetzen. In der ersten Fassung
    nahm er dafuer `alle_texte_der_quelle()`, und das liest auch `docProps`,
    `customXml` und `docMetadata`. Damit galten Autorname, Bearbeiter,
    Firmenname und ein vollstaendiger SharePoint-Personendatensatz (Login,
    E-Mail, Abteilung) als "Formularvokabular" und blieben unveraendert in den
    erzeugten Fixtures stehen. Aus einer Verbesserung wurde ein groesseres Leck
    als das, was sie beheben sollte.

    Ein leeres Formular traegt sein Vokabular auf den BLAETTERN. Alles andere
    ist Herkunft und gehoert ersetzt.

    Aber auch das reichte nicht: "steht auf einem Blatt" hiess bis hierher
    "ist Formularvokabular", und damit standen eine vollstaendige interne
    SharePoint-Adresse und eine Redaktionsnotiz mit dem Nachnamen des
    Bearbeiters unter Vokabularschutz — beide Werte hatte derselbe Lauf an
    anderer Stelle korrekt ersetzt. Deshalb gilt der Schutz jetzt nur noch
    fuer Texte, die keiner verbotenen Inhaltsklasse angehoeren und keine
    Personenwortmarke enthalten.
    c                N    t        |       yd u xs j                  |       d u S )NFr  )r,   r  s    r   unbedenklichz,vokabular_der_blaetter.<locals>.unbedenklich  s/    T".4@8??4#8D#@@r!   ra  rb  r
   r   r   c              3  >   K   | ]  }|j                  d         ywr   r   r   s     r   r   z)vokabular_der_blaetter.<locals>.<genexpr>  "      &Q*+ '(ggfo &Qr   r&   c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z)vokabular_der_blaetter.<locals>.<genexpr>       KAQWWQZKr   c              3     K   | ]D  }|j                  d       j                  dd      j                         s|j                  d        F yw)r<   r   r   N)r0   r   isdigitr   s     r   r   z)vokabular_der_blaetter.<locals>.<genexpr>  sB      P$%GGFO$;$;C$D$L$L$N WWV_ Ps   A
ANr,   r*   rb   rf   )r8  r-  r.  r/  r0  r   rr  r   r   r   r   r1   r   r   r0   updater  ri   r@   )	r6  r  texter   r   r   r   xr  s	           @r   vokabular_der_blaetterr  {  sY   , c"HA
 eE		 PJJL 	PDHH<dC55&&,%%gy9C--%..s3 REIIbgg &Q/6/?/?A/O&Q Q RR K1E1Ec1JKK PdmmC6H P P	PP >!	l1oA>>P P ?s   DE8F(F1F8Fc                   t               }t        j                  |       5 }|j                         D ]  }|j	                  |      }t        |      s!|j                  d      }|dk(  rdt        j                  |      D ]K  }|j                  dj                  d t        j                  |j                  d            D                     M n+|j                  d t        j                  |      D               |j                  d t        j                  |      D               |j!                  d      rX|j                  d	 t"        j                  |      D               |j                  d
 t$        j                  |      D               Z|j!                  d      r-|j                  d t&        j                  |      D               |j!                  d      s|j                  d t&        j                  |      D                	 ddd       |D ch c]  }|j)                         s| c}S # 1 sw Y   *xY wc c}w )u-  Jeder Text der Quelle, unabhaengig davon, ob ihn jemand ersetzt hat.

    Liest ALLE XML- und .rels-Eintraege: Elementinhalte, `<t>`-Knoten und die
    Werte in Attributen. Das ist die Menge, gegen die geprueft wird — sie darf
    nicht davon abhaengen, was die Ersetzung zufaellig erfasst hat.
    r
   rb  r   c              3  >   K   | ]  }|j                  d         ywr   r   r   s     r   r   z(alle_texte_der_quelle.<locals>.<genexpr>  r  r   r&   c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z(alle_texte_der_quelle.<locals>.<genexpr>  r  r   c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z(alle_texte_der_quelle.<locals>.<genexpr>  r  r   r&  c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z(alle_texte_der_quelle.<locals>.<genexpr>  r  r   c              3     K   | ]G  }t         j                  |j                  d       j                               s|j                  d        I ywr  r@  r>  r0   r@   r   s     r   r   z(alle_texte_der_quelle.<locals>.<genexpr>  @      PA$0$:$:1771:;K;K;M$N WWQZ P   AAr  c              3     K   | ]G  }t         j                  |j                  d       j                               s|j                  d        I ywr  r  r   s     r   r   z(alle_texte_der_quelle.<locals>.<genexpr>  r  r  r'  c              3     K   | ]G  }t         j                  |j                  d       j                               s|j                  d        I ywr  r  r   s     r   r   z(alle_texte_der_quelle.<locals>.<genexpr>  r  r  N)r-  r.  r/  r0  r   r   r   r   r   r1   r   r   r0   r  r  rW  rv   rU  rV  rX  r@   )r6  r  r   r   r   r   r   r  s           r   r~  r~    s    eE		 $PJJL #	PDvvd|HH%//'*C-- &..s3 REIIbgg &Q/6/?/?A/O&Q Q RR K1E1Ec1JKKLLEY-?-?-DEE {+Q1B1K1KC1PQQ  P1A1A#1F P P0 P1F1Fs1K P P. P1F1Fs1K P PE#	P$PJ *!	A**K$P $PJ +s   F:H4.H4I -I 4H=)r&  r'  r  c           	        t               }t        j                  |       5 }|j                         D ]  }|j	                  t
              r|j                  d      s*|j                  |      j                  dd      }|dk(  r,|j                  d t        j                  |      D               |t        t        fD ])  }|j                  d |j                  |      D               + |j                  d t        j                  |      D                	 ddd       t        t        t        j                  |             j                               }t        j                  |       5 }|j                  d	      j                  dd      }|t        t!        j"                  d
|            z  }ddd       |D ch c]  }|j%                         rt'        |j%                               dk\  r~|j%                         |vrl|j%                         t(        vrVt*        j-                  |j%                               s3t.        j-                  |j%                               s|j%                          c}S # 1 sw Y   XxY w# 1 sw Y   xY wc c}w )ux   Freitext aus den Metadaten-Teilen — Autor, Bearbeiter, Firma,
    SharePoint-Felder. Das ist Herkunft, kein Vokabular.r(  r
   r   zdocProps/app.xmlc              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z'metadaten_der_quelle.<locals>.<genexpr>  r  r   c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z'metadaten_der_quelle.<locals>.<genexpr>  s     FAQWWQZFr   c              3  >   K   | ]  }|j                  d         ywr  r   r   s     r   r   z'metadaten_der_quelle.<locals>.<genexpr>  s     HHr   Nr	   <definedName[^>]*name="([^"]+)"   )r-  r.  r/  r0  rv   	METADATENr1  r   r   r  rU  r   rV  rX  r    valuesr   r   r@   r   AUFZAEHLUNGr@  r>  ZEITSTEMPEL)	r6  r  r   r   r   r   strukturr   r  s	            r   r}  r}    s    eE		 IJJL 	ID??9-T]]65J&&,%%gy9C))
 Q1B1K1KC1PQQ,g6 GF1EFFGLLH\-B-B3-GHH	II& ;ws34;;=>H		 LVV%&--gyAC

#ErJKKL  % 6!wwyS^q0	)	, **17795))!'')4 GGI 6 6/I I(L L6s    C2I)%AI65B1J)I36I?c                    t        j                  |       5 dj                  fdj                         D              }ddd       |D cg c]  }t	        |      v rd|dd   c}S # 1 sw Y   ,xY wc c}w )zKein Metadaten-Wert der Quelle darf in den Metadaten der Ausgabe stehen.

    Bewusst NUR gegen die Metadaten geprueft: derselbe Text darf auf einem
    Blatt weiterhin vorkommen, wenn er dort Formularvokabular ist.
    
c              3     K   | ]K  }|j                  t              r4|j                  d       r#j                  |      j	                  dd       M yw)r(  r
   r   N)rv   r  r1  r   r   )r   nr   s     r   r   z#pruefe_metadaten.<locals>.<genexpr>  sE      ?||I&1::f+= FF1IWi0?s   AANz%Metadatum steht noch in der Ausgabe: (   )r.  r/  r   r0  rA   )r  
kandidatenr%   kr   s       @r   r  r     s     
	 ? ?ZZ\? ??  =;q>V#; 4AcrF8< = =? ?
=s   )A+A7+A4z=\s*"([^"]*)"z
>([^<>]+)<z(?:0x[0-9A-Fa-f]{8,})|(?:\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?)|(?:\b[0-9A-Fa-f]{32,}\b)c                v    dd} ||        ||      z  }t        |      D cg c]  }d|dd  d c}S c c}w )u  Jede Kennung, die in Quelle UND Ausgabe steht, ist ein Befund.

    Das ist die Klassen-Luecke, die die sechste Leck-Klasse durchgelassen hat.
    `pruefe_ausgabe()` prueft namensfrei ueber jeden Eintrag und jedes Attribut,
    kennt aber nur Inhaltsklassen wie Adresse, Postfach oder Anrede — und ein
    unveraenderter Kennungswert ist keine davon. Die andere Pruefung,
    `pruefe_leck()`, sammelt ihre Kandidaten ueber `ATTR_WERT`, also ueber drei
    feste Attributnamen; `ma:contentTypeID` steht dort nicht.

    Beide Pruefungen zusammen hatten damit ein Loch in der Groesse dieser
    Funktion.

    **Warum nicht einfach `ATTR_WERT` namensfrei machen.** Gemessen: eine
    namensfreie Sammlung ALLER Attributwerte ergibt auf der HICE-Mappe 6194
    zusaetzliche Kandidaten — Zahlenformate (`#,##0.000000`), Stil-Token,
    Zellbezuege. Die Pruefung waere wieder so verrauscht, dass sie den einen
    echten Fund nicht mehr zeigt; genau daran ist eine fruehere Fassung
    gescheitert. Diese Funktion ist deshalb namensfrei UND auf die Form einer
    Kennung beschraenkt: lange Hexfolgen mit und ohne Bindestriche, `0x`-Praefix.
    Solche Werte sind selten und spezifisch.
    c                &   t               }t        j                  |       5 }|j                         D ]  }|j	                  |      }t        |      s |j                  d      }|j                  d      st        j                  d|      }t        j                  |      D ]X  }|j                  d      }|j                  d      j                         t        v r:|j!                  |j                                Z  	 d d d        |S # 1 sw Y   |S xY w)Nr
   r'  r   r   r   )r-  r.  r/  r0  r   r   r   rv   r   rL   KENNUNG_IRGENDWOr   r0   r@   r   r   r1   )pgefundenr   r   r   r,   rk   r<   s           r   	kennungenz1pruefe_unveraenderte_kennungen.<locals>.kennungen/  s     U__Q &	/1

 %/66$<)w/6 |4*..r48D/88> /G"==+Dzz$'--/>A LL.	/C%/&	/N O&	/N s   CDDz%Kennung unveraendert aus der Quelle: Nr   u   …)r  r   rb   set[str])r5  )r6  r  r  	gemeinsamr  s        r   r  r    sF    ,)V #3/IIOPYIZ[A3AcrF83?[[[s   6c                d   g }t        j                  |       5 }|j                         D ]  }|j                  |      }	 |j	                  d      }t        j                  |      D 	cg c]  }	|	j                  d       }
}	|
t        j                  |      D 	cg c]  }	|	j                  d       c}	z  }
|
D ][  }t        |      }||j                  | d| d|dd         -|0|j                  |      sB|j                  d| d|dd         ]  	 ddd       |S # t
        $ rm t        |      D ][  }t        |      }||j                  | d| d|dd         -|0|j                  |      sB|j                  d| d|dd         ] Y nw xY wc c}	w c c}	w # 1 sw Y   |S xY w)	u  Prueft die fertige Ausgabe nach Inhaltsform — jeder Eintrag, jedes
    Attribut, jeder Textknoten. Ohne Positivliste, ohne Attributnamen, ohne
    Ruecksicht darauf, was der Ersetzer angefasst hat.

    Zwei Gegenproben haben diese Fassung erzwungen: ein erfundener
    Personendatensatz als Wert eines BELIEBIGEN Attributs (nicht `v`,
    `display` oder `refreshedBy`) in einem Eintrag AUS der Positivliste, und
    derselbe Datensatz als Elementtext in einem Eintrag AUSSERHALB davon.
    Beide kamen unveraendert durch, und die Pruefung meldete `"lecks": []`.
    Beide Blindstellen entstanden dadurch, dass Ersetzung und Pruefung
    dieselbe Kandidatenauswahl benutzt haben.
    r
   Nz in z (binaer): r  zPersonenwortmarke in r&   rg  )r.  r/  r0  r   r   r   r  r   r   rd   r"  r   r0   r#  )r  r  befunder   r   r   r   r  r   r4   r  r<   s               r   r  r  ^  s    G		 PJJL 	PDvvd|Hoog. *;)C)CC)HIAQWWQZIEI**=*=c*BCQaggajCCE P*40%NNfXT$r$s)#EF)hood.CNN%:4&49+#NOP%	PP2 N) & 
 ;8D ^E.u5F)&dV;uSbzl'ST!-(//%2H)>tfKPUVYWYPZ|'\]^  JC%P2 Ns_   %F%D"F%&F>F%F 
06F%'F%9F%"AF&F8FF%FF%%F/c                *   g }|D ch c]  }t        |      s| }}|D ch c]  }|j                  |      |k7  rt        j                  |j	                               sx|j	                         t
        vrb|j	                         j                         dvrBt        |j	                               dk\  r&t        j                  d|j	                               s| }	}t        |      \  }
}d}t        |       }t        |      }|j                         D ]<  \  }}|dk(  r|j                  |      }| ||k(  s&|j                  d| d|        > i }|j                         D ].  \  }}|j                  |t                     j!                  |       0 |D ];  }|j                  |t                     D ]  }|j                  |      |k(  r|dz  } = |
D ch c]  }|j	                          }}|	D ],  }|j	                         |v s|j                  d	|dd
         . |D ]N  \  }}t"        j%                  |      D ]1  }	 t'        |      }||v s|j                  d| d| d|dd  d       3 P ||fS c c}w c c}w c c}w # t(        $ r Y Tw xY w)u  Kein charakteristischer Originalwert darf in der Ausgabe stehen — auch
    nicht als Konstante INNERHALB einer Formel.

    Der zweite Teil ist der Grund fuer diese Fassung. Die openpyxl-Pruefung
    liest Zellwerte ueber die Bibliothek und ueberspringt Formelzellen
    ausdruecklich (`if v.startswith("=") : continue`). Eine hartcodierte
    Konstante in `=SUM(D10:D20)*105000` ist damit unsichtbar gewesen, obwohl sie
    ein echter Kalkulationswert sein kann. Hier wird der Formeltext mitgeprueft.
    )truefalser  z[\d\s.,:/-]+r   Nzunveraendert rg  r&   z Text steht noch in der Ausgabe: r  zKonstante in Formel (z): z in '0   ')r   r2   r  r>  r@   r  r   r   r   r  r  itemsr   r   
setdefaultr-  r1   ZAHL_IN_FORMELr   rT   rU   )r6  r  r]   r7   r6   rj  r   r  r,  r  r%   r  r  fuer_zelle_originalfuer_zelle_ausgabe
schluesselaltr5   ausgabe_nach_wertr<   rX   r`  vorhandene_werter,   r   formelrW   s                              r   r  r    s    E,E0A!0D!EJE
 ' 	?1q!Q& **17795+- "*;;QWWY1$\\/1779=  	?E 	? $C(OFGK
 %S/#C.446 >
C!8 $$Z0?sczLL=Bse<=> 02.446 B
D$$T35155jAB +//ce< 	J"&&z2d:1K	 ,22a	22 I::<++LL;D"I;GHI   Xf!))&1 	XCSz z!4TF#cU%sPR}TUVW	XX +G F
	?\ 3  s(   I7I7B6I<JJ	JJc            	     ~   t        j                  t        t         j                        } | j	                  ddt
               | j	                  ddt
               | j	                  dd       | j	                  dt
        d	
       | j	                  dddgdd       | j	                  ddd       | j	                  dt
        d
       | j                         }t               }|j                  r|j                  j                         r|j                  j                         }|j                  j                  dk(  r6t        j                  |      }t        t        |t              r|d   n|      }n<|j!                         D ch c]#  }|j#                         s|j#                         % }}|j$                  r|t'        |j(                        z  }t+        j,                  |j(                        5 }|t        t/        |      j1                               z  }|j3                  d      j5                  dd      }|t        t7        j8                  d|            z  }|j;                         D ]|  }|j=                  d      s|j=                  d      s&|j3                  |      j5                  dd      }|t7        j>                  d|      D 	ch c]  }	|	jA                  d       c}	z  }~ 	 d d d        |jB                  dk(  rtE        dtF        jH                          y!tK        |j(                  |jL                  ||jN                  |jB                        }
t        jP                  |
d!d"#      }|jR                  r|jR                  jU                  |d$z          tE        |       |
d%   rdS d&S c c}w c c}	w # 1 sw Y   xY w)'N)descriptionformatter_classz--srcT)requiredtypez--dstz--seed)r  z--labelsz8Datei mit einer Beschriftung je Zeile, die stehen bleibt)r  helpz--cachesrO   r   z2Wie mit gecachten Formelergebnissen verfahren wird)choicesdefaultr  z--blanko-vorlage
store_truezxQuelle ist ein leeres Formular: das gesamte Vokabular gilt als Beschriftung und bleibt stehen, ersetzt werden nur Zahlen)actionr  z--reportzBericht als JSONz.jsonr  r	   r
   r  r'  r&  z<([A-Za-z_][\w.-]*)[ />]r&   z--caches=recompute ist vorbereitet, aber nicht verdrahtet: die Caches wuerden entfernt und niemand rechnet sie neu. Das Ergebnis waere eine Fixture mit 1 von 20 Kennzahlen. Bis der Rechenschritt steht, nur --caches=replace verwenden.)filerJ   F)indentensure_asciir  rj  r   )+argparseArgumentParser__doc__RawDescriptionHelpFormatteradd_argumentr   
parse_argsr-  r  exists	read_textsuffixjsonloads
isinstancer   
splitlinesr@   blanko_vorlager  r6  r.  r/  r    r  r   r   r   r   r0  rv   r   r0   rZ   printsysstderrr  r  r^   dumpsreport
write_text)r  argsr  rW   datenr   r   r  r   r4   berichtr,   s               r   mainr    s   G080T0T	VANN7TN5NN7TN5NN8dN+NN:DR  TNN:Y'?L  NNN%l&  ( NN:D/ANB<<>DuF{{t{{))+kk##%;;(JJsOEJud,CxOF),)9GAQWWYaggiGFG 	(22 
	" ^a#k!n++-..VV%&--gyA#bjj!CRHIIzz| 	^G!!,/73E3Ek3RffWo,,Wi@r{{;VX[/\]!1771:]]	^	^ {{k!
 	 7 >AZZ	I DHHdhh		4;;OG::gae<D{{td{+	$K 1'a'[ H6 ^^ ^s1   N)N)0BN3:N3	N.
!N3.N33N<__main__)r   zzipfile.ZipFilerb   zdict[str, str])r   r*   rY   r*   r^   r*   r6   r   rZ   r*   r8   r   r]   
set[float]r7   r  rb   r*   )r<   rT   rb   r*   )
r   r*   r6   r   r8   r   r7   r  rb   r*   )r,   r*   rb   r*   )T)
r   r*   r^   r*   r8   r   r   rf   rb   r*   r  )r   r*   rb   r*   )r,   r*   rb   z
str | None)r   bytesrb   rf   )r   r  rb   	list[str])r   r*   r^   r*   r8   r   rb   r*   )
r   r*   r^   r*   r  're.Pattern[str] | None'r8   r   rb   r*   )r6  r   rb   r  )r   )r   r*   r   r*   r6   r   r8   r   r7   r  rE  r*   rb   r*   )r6  r   r  r   r  r  r^   r*   rZ   r*   rb   r   )r   r   rb   zdict[str, float])r  r   rb   z'tuple[list[str], list[tuple[str, str]]])r6  r   rb   r  )r  r   r  r  rb   r  )r6  r   r  r   rb   r  )r  r   r  r  rb   r  )r6  r   r  r   r]   r  r7   r  r6   r   rb   ztuple[list[str], int])rb   r   )jr  
__future__r   r  r   rz   r  r   shutilr  r.  pathlibr   pathinsertr*   __file__rm  anonymise_workbookr   r   r   r4  r  r~   ri   rg   rh   r   rc   re   rj   rK   rW  r   r   ry   r    r   rV   r   r   rA   rt  rX  rU  r   r   r   r   r   r   r   rT  rH  rV  r=  r  	frozensetr  r@  ru  rs  rd  r?  r   r   r   r   r   r   r   r   __annotations__r   r   r   r  r  r  r  r$  r2  r3  r8  rY  r  r  r  r  r  r  r  r~  r  r}  r  r"  r#  r  r  r  r  r  __name__rv   r!   r   <module>r!     s  (T #     	  
   3tH~,,- . P P, 	

MrttT rzz?F	Irtt	$bjjBBDDI
"**7
>	(	)	(	) 

<bddC/0 BJJLM	34BJJLbddS	BJJ=> &K-K-*.K-AKK-"*K-/2K-\@ 2::'.*%+3*%8;*%ZR !bjj%&  rzz+, BJJ>?  2::OP 4 rzz  

]^ + + 2::RT "**Z[ 6:AL.2AL>AALH RZZ56
8
"**H
I rzz
 bjj78IJrzz"#  RZZ OP 

LM2 

6""   !   "**GO8 bjjDE0 BJJVW "**%&(dd, <=>*"**YZ[zrzzcde@ < 7
8. rzzHIrzzEF)@") $")),")J rzz  bjj;<0F 9;p.(,p.>Fp.25p.?Bp.f@@"&@F 2::GN bjj6=DE"J1h,?^-+` 8	 6F= BJJ/0 RZZ&
 2::!" B\J'TO (O0>OCXOdF(R z
TV
 r!   