L'idea di base è che i miei utonti (anche quelli bravi) sono pigri. Quello che vogliono è cliccare nell'email (già faranno la fatica di scegliere l'allegato giusto) e vedere la fattura in modo decente.
Ho quindi preparato una cartella dove mettere tutte le fatture e impostato come programma predefinito per i .p7m il mio; poi ho installato su Firefox l'extension per vedere le fetture e lo ho messo come default per i .xml. Pare funzionare!
L'opzione per spostare serve perché il client di posta (nel mio caso ThunderBird) al doppio click utente salva il file nella temp.
Le modifiche:
Versione 1.2 2019.01.23
+ config file
+ switch per spostare .p7m
+ percorso predefinito dove spostare i file
+ switch per aprire .xml (shell)
+ switch per rinominare file aggiungendo dati fattura (il .p7m resta con il suo nome)
nuovo nome := vecchio + ' ' + RagioneSociale + DataFattura + NumeroFattura
exe, dll, ini e dpr:
{
FatturaElettronicaExtractor 1.0 - Nicola Perotto 2019
Usa le seguenti librerie:
JvString.pas parte di JEDI VCL http://jvcl.sourceforge.net
OpenSSLUtils.pas https://github.com/bambucode/tfacturaelectronica/tree/master/OpenSSL
"With a contribute of Univerity of Genoa (Italy) http://www.unige.it/"
Serve il file libeay32.dll che fa parte di OpenSSL.
Versione 1.1 2019.01.18
+ Ripulisce il nome degli allegati da caratteri strani
+ Gestisce codifiche base 64 con gli a capo
Versione 1.2 2019.01.23
+ config file
+ percorso predefinito dove salvare i file
+ switch per spostare .p7m
+ switch per aprire .xml (shell)
+ switch per rinominare file aggiungendo dati fattura (il .p7m resta con il suo nome)
nuovo nome := vecchio + ' ' + RagioneSociale + DataFattura + NumeroFattura
}
Program FEExtractor;
{$APPTYPE CONSOLE}
Uses
SysUtils, Windows, JvStrings, OpenSSLUtils, XMLIntf, XMLDoc, ComObj, ActiveX, Classes, IniFiles, ShellAPI;
Var
cfgDestFolder : string; //dove spostare i file
cfgSposta : Boolean; //sposto?
cfgApre : Boolean; //apro?
cfgRinomina : Boolean; //rinomino xml e relativa cartella?
NomeVecchio, Destinazione, Estensione, NomeFile : string;
SEInfo: TShellExecuteInfo;
j : integer; //loop
Procedure ReadConfig;
Var
Ini : TIniFile;
begin
try
Ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
cfgDestFolder := Ini.ReadString('Opzioni', 'Destinazione', '');
if (cfgDestFolder <> '') and (cfgDestFolder[Length(cfgDestFolder)] <> '\') then cfgDestFolder := cfgDestFolder + '\';
cfgSposta := Ini.ReadBool('Opzioni', 'Sposta', cfgSposta); //essendo lette dopo il controllo dei parametri
cfgApre := Ini.ReadBool('Opzioni', 'Apre', cfgApre); //queste impostazioni, se ci sono nel file, hanno priorità
cfgRinomina := Ini.ReadBool('Opzioni', 'Rinomina', cfgRinomina);
finally
FreeAndNil(Ini);
end;
end; //ReadConfig;
Function LanciaXML:Boolean;
begin
Result := False;
try
FillChar(SEInfo, SizeOf(SEInfo), 0);
SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
with SEInfo do begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := 0; //Application.Handle;
lpFile := PChar(NomeFile);
nShow := SW_HIDE; //SW_SHOWNORMAL;
end;
if not ShellExecuteEx(@SEInfo) then Exit;
Result := True;
except
end;
end; //LanciaXML
Function ToglieACapo(Const Base:string):string;
Var
i, j : integer;
begin
i := Pos(#13, Base);
j := Pos(#10, Base); //ho beccato un allegato con solo LF
if (i = 0) and (j = 0) then begin
Result := Base;
Exit;
end;
Result := '';
for i := 1 to Length(Base) do begin
if Ord(Base[i]) > 32 then //molto brutale...
Result := Result + Base[i];
end;
end; //ToglieACapo
Function NormalizzaNomeFile(Const NomeStrano:string):string;
Var
i : integer;
begin
Result := NomeStrano;
for i := 1 to Length(NomeStrano) do begin
if Pos(NomeStrano[i], '<>:"\|?*') > 0 then
Result[i] := '_'
else if NomeStrano[i] = '/' then Result[i] := '-'; //mi piace di più!
end;
end; //NormalizzaNomeFile
Procedure EstraiAllegati;
Var
Cartella : string;
XML : IXMLDOCUMENT;
Node1, Node2, Node3, Node4, Node5 : IXMLNODE;
i, n : integer;
Testo, Normale, NomeAllegato : string;
f : TFileStream;
RagioneSociale, NumeroFattura, DataFattura : string;
begin
try
CoInitializeEx(nil, COINIT_APARTMENTTHREADED); //Inizializza il sottosistema COM per poter usare le 'Interface'
XML := NewXMLDocument;
XML.Encoding := 'UTF-8';
XML.NodeIndentStr := ' '; //space *2
XML.Options := [doNodeAutoIndent]; // looks better in Editor ;)
XML.LoadFromFile(NomeFile);
//2 <1.N> <FatturaElettronicaBody>
//2.5 <0.N> <Allegati>
//2.5.1 <1.1> <NomeAttachment> xs:normalizedString 1 … 60
//2.5.2 <0.1> <AlgoritmoCompressione> xs:string 1 … 10
//2.5.3 <0.1> <FormatoAttachment> xs:string 1 … 10
//2.5.4 <0.1> <DescrizioneAttachment> xs:normalizedString 1 … 100
//2.5.5 <1.1> <Attachment> xs:base64Binary valore vincolato alla dimensione max prevista per la fattura elettronica
if CompareText(XML.DocumentElement.LocalName, 'FatturaElettronica') <> 0 then begin
writeln('Non è una Fattura Elettronica.');
Exit;
end;
if cfgRinomina then begin
Node1 := XML.DocumentElement.ChildNodes.FindNode('FatturaElettronicaHeader', ''); //1 <1.1> <FatturaElettronicaHeader>
if not Assigned(Node1) then Exit;
Node2 := Node1.ChildNodes.FindNode('CedentePrestatore', ''); //1.2 <1.1> <CedentePrestatore>
if not Assigned(Node2) then Exit;
Node3 := Node2.ChildNodes.FindNode('DatiAnagrafici', ''); //1.2.1 <1.1> <DatiAnagrafici>
if not Assigned(Node3) then Exit;
Node4 := Node3.ChildNodes.FindNode('Anagrafica', ''); //1.2.1.3 <1.1> <Anagrafica>
if not Assigned(Node4) then Exit;
Node5 := Node4.ChildNodes.FindNode('Denominazione', ''); //1.2.1.3.1 <0.1> <Denominazione> xs:normalizedString 1 … 80
if not Assigned(Node5) then begin
Node5 := Node4.ChildNodes.FindNode('Nome', ''); //1.2.1.3.2 <0.1> <Nome> xs:normalizedString 1 … 60
if not Assigned(Node5) then Exit;
RagioneSociale := Node5.NodeValue;
Node5 := Node4.ChildNodes.FindNode('Cognome', ''); //1.2.1.3.3 <0.1> <Cognome> xs:normalizedString 1 … 60
if not Assigned(Node5) then Exit;
RagioneSociale := Node5.NodeValue + ' ' + RagioneSociale;
end else begin
RagioneSociale := Node5.NodeValue;
end;
end;
Node1 := XML.DocumentElement.ChildNodes.FindNode('FatturaElettronicaBody', ''); //2 <1.N> <FatturaElettronicaBody>
if not Assigned(Node1) then Exit;
if cfgRinomina then begin
Node2 := Node1.ChildNodes.FindNode('DatiGenerali', ''); //2.1 <1.1> <DatiGenerali>
if not Assigned(Node2) then Exit;
Node3 := Node2.ChildNodes.FindNode('DatiGeneraliDocumento', ''); //2.1.1 <1.1> <DatiGeneraliDocumento>
if not Assigned(Node3) then Exit;
Node4 := Node3.ChildNodes.FindNode('Data', ''); //2.1.1.3 <1.1> <Data> xs:date 10
if not Assigned(Node4) then Exit;
DataFattura := Node4.NodeValue;
Node4 := Node3.ChildNodes.FindNode('Numero', ''); //2.1.1.4 <1.1> <Numero> xs:normalizedString 1 … 20
if not Assigned(Node4) then Exit;
NumeroFattura := Node4.NodeValue;
end;
if cfgRinomina then begin
NomeVecchio := NomeFile; //l'XML è aperto, lo rinomino per ultimo
RagioneSociale := NormalizzaNomeFile(RagioneSociale);
NumeroFattura := NormalizzaNomeFile(NumeroFattura); //QUESTO serve!
DataFattura := NormalizzaNomeFile(DataFattura); //questo mooooolto probabilmente no
NomeFile := ChangeFileExt(NomeFile, '') + ' ' + RagioneSociale + ' ' + DataFattura + ' ' + NumeroFattura + '.xml';
end;
Node2 := Node1.ChildNodes.FindNode('Allegati', '');
if Assigned(Node2) then begin
//Ci sono 1 o più allegati, li salvo nella cartella con lo stesso nome del file XML
Cartella := ChangeFileExt(NomeFile, '');
if not DirectoryExists(Cartella) then
CreateDir(Cartella);
Cartella := Cartella + '\';
NomeAllegato := '';
n := 1;
i := 0;
while (i <= Node2.ChildNodes.Count - 1) do begin
Node3 := Node2.ChildNodes.Get(i);
if CompareText(Node3.LocalName, 'NomeAttachment') = 0 then
NomeAllegato := Node3.NodeValue;
if CompareText(Node3.LocalName, 'AlgoritmoCompressione') = 0 then ; //ignora
if CompareText(Node3.LocalName, 'FormatoAttachment') = 0 then ; //ignora
if CompareText(Node3.LocalName, 'DescrizioneAttachment') = 0 then ; //ignora
if CompareText(Node3.LocalName, 'Attachment') = 0 then begin
Testo := Node3.NodeValue;
try
Normale := B64Decode(ToglieACapo(Testo));
except
on E:Exception do begin
writeln('L''allegato ' + IntToStr(n) + ' non è codificato Base64 correttamente.');
Normale := '';
end;
end;
if (NomeAllegato <> '') and (Normale <> '') then begin
try
NomeAllegato := NormalizzaNomeFile(NomeAllegato);
f := TFileStream.Create(Cartella + NomeAllegato, fmCreate);
f.Write(Normale[1], Length(Normale));
finally
FreeAndNil(f);
end;
end;
NomeAllegato := '';
Inc(n);
end; //if
Inc(i);
end; //for i
end; //ci sono allegati
finally
XML.Active := False;
if cfgRinomina and not MoveFileEx(PChar(NomeVecchio), PChar(NomeFile), MOVEFILE_COPY_ALLOWED + MOVEFILE_REPLACE_EXISTING + MOVEFILE_WRITE_THROUGH) then begin
writeln('Errore durante il cambio di nome: ' + IntToStr(GetLastError));
Halt(7);
end;
// CoUninitialize; //se lo chiamo poi si incazza!
end;
end; //EstraiAllegati
Procedure TogliFirma(Nomep7m, NomeXml:string);
Var
Reader : TPKCS7;
begin
Reader := TPKCS7.Create;
Reader.Open(Nomep7m);
Reader.SaveContent(NomeXml);
Reader.Free;
end; //TogliFirma
begin
cfgDestFolder := '';
cfgSposta := False;
cfgApre := False;
cfgRinomina := False;
if ParamCount = 0 then begin
writeln('FEExtractor 1.2');
writeln('Estrae gli allegati da una fattura elettronica, eventualmente rimuovendo la firma.');
writeln('Attenzione: sovrascrive SENZA AVVISARE!');
writeln('Uso: FEExtractor [/a] [/s] [r] nomefile');
writeln('/a : apre il file .xml usando il programma predefinito');
writeln('/s : sposta il file nella cartella impostata nel file .ini');
writeln('/r : rinominca il xile xml e la cartella aggiungendo RagioneSociale, DataFattura e NumeroFattura');
Halt(1);
end;
//controlla i parametri
for j := 1 to ParamCount do begin
if (CompareText('/a', ParamStr(j)) = 0) then
cfgApre := True
else if (CompareText('/s', ParamStr(j)) = 0) then
cfgSposta := True
else if (CompareText('/r', ParamStr(j)) = 0) then
cfgRinomina := True
else if (NomeFile = '') and FileExists(ParamStr(j)) then
NomeFile := ParamStr(j);
end;
if not FileExists(NomeFile) then begin
writeln('Errore: il file <' + NomeFile + '> non esiste.');
Halt(2);
end;
Estensione := ExtractFileExt(NomeFile);
ReadConfig;
if cfgSposta and ((cfgDestFolder = '') or not DirectoryExists(cfgDestFolder)) then begin
writeln('Errore: manca la cartella di destinazione.');
Halt(3);
end;
if cfgSposta and (CompareText(cfgDestFolder, ExtractFilePath(NomeFile)) = 0) then
cfgSposta := False; //è già nella cartella giusta
if cfgSposta then begin
NomeVecchio := NomeFile;
NomeFile := cfgDestFolder + ExtractFileName(NomeVecchio);
if not MoveFileEx(PChar(NomeVecchio), PChar(NomeFile), MOVEFILE_COPY_ALLOWED + MOVEFILE_REPLACE_EXISTING + MOVEFILE_WRITE_THROUGH) then begin
writeln('Errore durante lo spostamento: ' + IntToStr(GetLastError));
Halt(5);
end;
end;
if (CompareText(Estensione, '.xml') = 0) then begin
EstraiAllegati;
end else if (CompareText(Estensione, '.p7m') = 0) then begin
Destinazione := ChangeFileExt(NomeFile, '');
AppStartup;
TogliFirma(NomeFile, Destinazione);
NomeFile := Destinazione;
EstraiAllegati;
end else begin
writeln('Errore: il file deve essere .xml oppure .xml.p7m');
Halt(4);
end;
if cfgApre then begin
if not LanciaXML then begin
writeln('Errore shell: ' + IntToStr(GetLastError));
Halt(6);
end;
end;
Halt(0);
end.
Ovviamente mi piacerebbe sentire le vostre opinioni (e anche gli insulti )
Nicola