io4it 2.0.0.4__tar.gz → 2.0.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. io4it-2.0.0.6/PKG-INFO +7 -0
  2. io4it-2.0.0.6/io4it.egg-info/PKG-INFO +7 -0
  3. {io4it-2.0.0.4 → io4it-2.0.0.6}/io4it.egg-info/SOURCES.txt +0 -2
  4. io4it-2.0.0.6/io4it.egg-info/entry_points.txt +7 -0
  5. {io4it-2.0.0.4 → io4it-2.0.0.6}/io4it.egg-info/requires.txt +2 -4
  6. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/utils/mail.py +12 -28
  7. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/utils/offuscation_basique.py +16 -123
  8. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWChatGpt.py +1 -0
  9. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWDeep_Search.py +1 -0
  10. io4it-2.0.0.6/orangecontrib/IO4IT/widgets/OWExportMarkdown.py +216 -0
  11. io4it-2.0.0.6/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +563 -0
  12. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWS3Uploader.py +1 -0
  13. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWS3downloader.py +1 -0
  14. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWS3list.py +1 -0
  15. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWSpeechToText.py +1 -0
  16. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWmailLoader.py +1 -0
  17. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWmailSender.py +1 -0
  18. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/OWwordpdf2docx.py +1 -0
  19. {io4it-2.0.0.4 → io4it-2.0.0.6}/setup.py +10 -7
  20. io4it-2.0.0.4/PKG-INFO +0 -27
  21. io4it-2.0.0.4/io4it.egg-info/PKG-INFO +0 -27
  22. io4it-2.0.0.4/io4it.egg-info/entry_points.txt +0 -2
  23. io4it-2.0.0.4/orangecontrib/IO4IT/widgets/OWExportMarkdown.py +0 -192
  24. io4it-2.0.0.4/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +0 -251
  25. io4it-2.0.0.4/orangecontrib/IO4IT/widgets/designer/owchatgpt.ui +0 -155
  26. io4it-2.0.0.4/orangecontrib/IO4IT/widgets/designer/owmarkdownizer.ui +0 -101
  27. {io4it-2.0.0.4 → io4it-2.0.0.6}/io4it.egg-info/dependency_links.txt +0 -0
  28. {io4it-2.0.0.4 → io4it-2.0.0.6}/io4it.egg-info/namespace_packages.txt +0 -0
  29. {io4it-2.0.0.4 → io4it-2.0.0.6}/io4it.egg-info/top_level.txt +0 -0
  30. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/__init__.py +0 -0
  31. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/ocr_function/__init__.py +0 -0
  32. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/ocr_function/word_converter.py +0 -0
  33. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/utils/__init__.py +0 -0
  34. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/__init__.py +0 -0
  35. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/__init__.py +0 -0
  36. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/chart.html +0 -0
  37. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/nogui.ui +0 -0
  38. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui +0 -0
  39. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui +0 -0
  40. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui +0 -0
  41. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/owmailloader.ui +0 -0
  42. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/owmailsender.ui +0 -0
  43. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui +0 -0
  44. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/owvisualizationer.ui +0 -0
  45. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui +0 -0
  46. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/__init__.py +0 -0
  47. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/chatgpt.png +0 -0
  48. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/deepsearch.svg +0 -0
  49. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/download.png +0 -0
  50. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/export_md.png +0 -0
  51. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/file_extensor.png +0 -0
  52. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/list_aws.png +0 -0
  53. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/mail_loader.png +0 -0
  54. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/mail_writer.png +0 -0
  55. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/md.png +0 -0
  56. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/speech_to_text.png +0 -0
  57. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/upload.png +0 -0
  58. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/visualizationer.png +0 -0
  59. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons/wordpdf2docx.png +0 -0
  60. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/IO4IT/widgets/icons_dev/__init__.py +0 -0
  61. {io4it-2.0.0.4 → io4it-2.0.0.6}/orangecontrib/__init__.py +0 -0
  62. {io4it-2.0.0.4 → io4it-2.0.0.6}/setup.cfg +0 -0
io4it-2.0.0.6/PKG-INFO ADDED
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.1
2
+ Name: io4it
3
+ Version: 2.0.0.6
4
+ Home-page:
5
+ Author:
6
+ Author-email:
7
+ Keywords: orange3 add-on
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.1
2
+ Name: io4it
3
+ Version: 2.0.0.6
4
+ Home-page:
5
+ Author:
6
+ Author-email:
7
+ Keywords: orange3 add-on
@@ -30,12 +30,10 @@ orangecontrib/IO4IT/widgets/designer/__init__.py
30
30
  orangecontrib/IO4IT/widgets/designer/chart.html
31
31
  orangecontrib/IO4IT/widgets/designer/nogui.ui
32
32
  orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui
33
- orangecontrib/IO4IT/widgets/designer/owchatgpt.ui
34
33
  orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui
35
34
  orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui
36
35
  orangecontrib/IO4IT/widgets/designer/owmailloader.ui
37
36
  orangecontrib/IO4IT/widgets/designer/owmailsender.ui
38
- orangecontrib/IO4IT/widgets/designer/owmarkdownizer.ui
39
37
  orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui
40
38
  orangecontrib/IO4IT/widgets/designer/owvisualizationer.ui
41
39
  orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui
@@ -0,0 +1,7 @@
1
+ [orange.widgets]
2
+ AAIT - ALGORITHM = orangecontrib.ALGORITHM.widgets
3
+ AAIT - API = orangecontrib.API.widgets
4
+ AAIT - LLM INTEGRATION = orangecontrib.LLM_INTEGRATION.widgets
5
+ AAIT - Models = orangecontrib.LLM_MODELS.widgets
6
+ AAIT - Toolbox = orangecontrib.TOOLBOX.widgets
7
+ Advanced Artificial Intelligence Tools = orangecontrib.AAIT.widgets
@@ -14,7 +14,5 @@ pypandoc-binary
14
14
  wave==0.0.2
15
15
  scikit-learn
16
16
  openai
17
- pypandoc
18
- docx2pdf
19
- msal
20
- exchangelib
17
+ CATEGORIT
18
+ pip-system-certs
@@ -4,21 +4,19 @@ import email
4
4
  from email.header import decode_header
5
5
  import time
6
6
  import os
7
+ from bs4 import BeautifulSoup
7
8
  from email import policy
8
9
  from email.message import EmailMessage
9
10
  import mimetypes
10
11
 
11
- from exchangelib import Credentials, HTMLBody, Message, Mailbox
12
+ from exchangelib import Credentials, Account, DELEGATE, Configuration, HTMLBody, Message, Mailbox
12
13
  from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter
13
14
  from exchangelib import OAuth2Credentials, Identity, Configuration, Account, DELEGATE
14
-
15
+ from oauthlib.oauth2 import OAuth2Token
15
16
 
16
17
  from bs4 import BeautifulSoup
17
-
18
-
19
- ##### please uncomment this line to ignore certificate checking (owa)
20
- #BaseProtocol.HTTP_ADAPTER_CLS=NoVerifyHTTPAdapter
21
-
18
+ BaseProtocol.HTTP_ADAPTER_CLS=NoVerifyHTTPAdapter
19
+ import json
22
20
  from msal import ConfidentialClientApplication
23
21
  from oauthlib.oauth2 import OAuth2Token
24
22
 
@@ -664,27 +662,13 @@ def check_send_new_emails(offusc_conf_agent,type_co):
664
662
  else:
665
663
  print("type de co non valide")
666
664
 
667
- def list_conf_files(type_co):
668
- conf_files = []
669
- if type_co is None or type_co == "":
670
- return conf_files
671
- dossier = MetManagement.get_secret_content_dir()
672
- dossier = dossier + type_co
673
- files = os.listdir(dossier)
674
- for file in files:
675
- if file.lower().endswith(".json"):
676
- conf_files.append(file)
677
- return conf_files
678
-
679
-
680
-
681
665
  if __name__ == "__main__":
682
- type_co="IMAP4_SSL"
666
+ offusc_conf_agent=""
667
+ #type_co="IMAP4_SSL"
668
+ type_co="MICROSOFT_EXCHANGE_OAUTH2"
683
669
  list_agent_email = []
684
- offusc_conf_agents = list_conf_files(type_co)
685
670
  while True:
686
- for offusc_conf_agent in offusc_conf_agents:
687
- check_new_emails(offusc_conf_agent,type_co, list_agent_email)
688
- time.sleep(1)
689
- check_send_new_emails(offusc_conf_agent,type_co)
690
- time.sleep(1)
671
+ check_new_emails(offusc_conf_agent,type_co, list_agent_email)
672
+ time.sleep(1)
673
+ check_send_new_emails(offusc_conf_agent,type_co)
674
+ time.sleep(1)
@@ -2,9 +2,6 @@ import os
2
2
  import json
3
3
  import hashlib
4
4
  import getpass
5
- from cryptography.fernet import Fernet
6
- import base64
7
-
8
5
 
9
6
  if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
10
7
  from Orange.widgets.orangecontrib.AAIT.utils import MetManagement
@@ -50,26 +47,12 @@ def get_keys_dir(type_key: str = "MICROSOFT_EXCHANGE_OAUTH2") -> str:
50
47
  if os.path.basename(base) != "keys":
51
48
  base = os.path.join(base, "keys")
52
49
  dossier = os.path.normpath(os.path.join(base, type_key))
50
+ print("dossier", dossier)
53
51
  os.makedirs(dossier, exist_ok=True)
54
52
  return dossier
55
53
  except Exception as e:
56
54
  raise RuntimeError(f"Erreur création/récupération dossier : {e}")
57
55
 
58
- def get_fernet_key() -> bytes:
59
- """
60
- Dérive une clé Fernet (32 octets base64) à partir du nom d'utilisateur local.
61
- """
62
- username = getpass.getuser().encode("utf-8")
63
- digest = hashlib.sha256(username).digest()
64
- return base64.urlsafe_b64encode(digest[:32]) # 32 bytes en base64
65
-
66
- def encrypt_secure(data: str) -> str:
67
- fernet = Fernet(get_fernet_key())
68
- return fernet.encrypt(data.encode("utf-8")).decode("utf-8")
69
-
70
- def decrypt_secure(data: str) -> str:
71
- fernet = Fernet(get_fernet_key())
72
- return fernet.decrypt(data.encode("utf-8")).decode("utf-8")
73
56
 
74
57
 
75
58
  # GSTION IMAP
@@ -177,83 +160,6 @@ def enregistrer_config_owa(mail,alias,server,username,password,interval):
177
160
  print(f"❌ Erreur lors de l'enregistrement : {e}")
178
161
  return 1
179
162
 
180
- def enregistrer_config_owa_secure(mail, alias, server, username, password, interval):
181
- try:
182
- dossier = get_keys_dir("MICROSOFT_EXCHANGE_OWA_SECURE")
183
- os.makedirs(dossier, exist_ok=True)
184
-
185
- mdp_chiffre = encrypt_secure(password)
186
-
187
- nom_fichier = os.path.join(dossier, f"{alias.replace('@', '_at_')}.json")
188
-
189
- contenu = {
190
- "mail": mail,
191
- "alias": alias,
192
- "server": server,
193
- "username": username,
194
- "password_encrypted": mdp_chiffre,
195
- "interval_second": interval
196
- }
197
-
198
- with open(nom_fichier, "w", encoding="utf-8") as f:
199
- json.dump(contenu, f, indent=4)
200
-
201
- print(f"✅ Fichier OWA sécurisé enregistré : {nom_fichier}")
202
- return 0
203
-
204
- except Exception as e:
205
- print(f"❌ Erreur d'enregistrement sécurisé OWA : {e}")
206
- return 1
207
-
208
- def enregistrer_config_cli_owa_secure():
209
- print("\n🔐 Écriture fichier OWA avec chiffrement sécurisé :")
210
- mail = input("📧 Mail (nom@domain.com) : ").strip()
211
- alias = input("📛 Alias (visible) : ").strip()
212
- server = input("🌐 Server : ").strip()
213
- username = input("👤 Username (domain\\user) : ").strip()
214
- password = input("🔑 Password (non masqué) : ").strip()
215
- interval = int(input("⏱️ Interval (secondes) : ").strip())
216
- if alias == "''" or alias == "\"\"" or alias == "":
217
- alias = mail
218
-
219
- if 0 != enregistrer_config_owa_secure(mail, alias, server, username, password, interval):
220
- print("❌ Erreur lors de enregistrer_config_owa_secure !")
221
-
222
- def lecture_config_cli_owa_secure():
223
- fichier = input("📄 Nom du fichier JSON (sans chemin) : ").strip()
224
- cfg = lire_config_owa_secure(fichier)
225
-
226
- if cfg is None:
227
- print("❌ Erreur")
228
- return
229
-
230
- print(f"\n📧 mail : {cfg[0]}")
231
- print(f"📛 alias : {cfg[1]}")
232
- print(f"🌐 server : {cfg[2]}")
233
- print(f"👤 username : {cfg[3]}")
234
- print(f"🔑 password : {cfg[4]}")
235
- print(f"⏱️ intervalle : {cfg[5]}s")
236
-
237
- def lire_config_owa_secure(chemin_fichier):
238
- try:
239
- chemin_fichier = os.path.join(get_keys_dir("MICROSOFT_EXCHANGE_OWA_SECURE"), chemin_fichier)
240
- with open(chemin_fichier, "r", encoding="utf-8") as f:
241
- contenu = json.load(f)
242
-
243
- mdp_dechiffre = decrypt_secure(contenu["password_encrypted"])
244
-
245
- return [
246
- contenu["mail"],
247
- contenu["alias"],
248
- contenu["server"],
249
- contenu["username"],
250
- mdp_dechiffre,
251
- int(contenu["interval_second"])
252
- ]
253
-
254
- except Exception as e:
255
- print(f"❌ Erreur de lecture sécurisée OWA : {e}")
256
- return None
257
163
 
258
164
 
259
165
  # Fonction pour lire le fichier de configuration et déchiffrer le mot de passe
@@ -451,28 +357,21 @@ def lire_config_api(service_name):
451
357
  def enregistrer_config_cli_api():
452
358
  print("\n📝 Écriture d’une clé API :")
453
359
  service = input("🔖 Nom du service : ").strip()
454
- api_key = input("🔑 Clé API : ").strip()
360
+ api_key = getpass.getpass("🔑 Clé API : ").strip()
455
361
  desc = input("✏️ Description : ").strip()
456
362
  if 0 != enregistrer_config_api(service, api_key, desc):
457
363
  print("erreur!")
458
364
 
459
- def lire_config_cli_api(service=""):
460
- if service == "":
461
- service = input("🔖 Nom du service : ").strip()
462
- try:
463
- cfg = lire_config_api(service)
464
- if cfg is None:
465
- print("erreur")
466
- return
467
- print(f"\n📄 service : {cfg['service']}")
468
- print(f"🔑 clé API : {cfg['api_key']}")
469
- if cfg['description']:
470
- print(f"📝 description : {cfg['description']}")
471
- return cfg['api_key']
472
- except Exception as e:
473
- print(f"❌ Erreur lors de la lecture : {e}")
474
- return None
475
-
365
+ def lire_config_cli_api():
366
+ service = input("🔖 Nom du service : ").strip()
367
+ cfg = lire_config_api(service)
368
+ if cfg is None:
369
+ print("erreur")
370
+ return
371
+ print(f"\n📄 service : {cfg['service']}")
372
+ print(f"🔑 clé API : {cfg['api_key']}")
373
+ if cfg['description']:
374
+ print(f"📝 description : {cfg['description']}")
476
375
 
477
376
 
478
377
  # Gestion d’éléments de nxp (DOSSIER_NODE_ID, SERVEUR, USERNAME, PASSWORD) (HARD dossier aait_store/keys)
@@ -559,6 +458,8 @@ def lire_config_cli_nxp():
559
458
  print(f"📝 description : {cfg['description']}")
560
459
 
561
460
 
461
+
462
+
562
463
  if __name__ == "__main__":
563
464
  print("1) ecrire fichier IMAP4_SSL")
564
465
  print("2) dechiffer fichier IMAP4_SSL")
@@ -570,10 +471,7 @@ if __name__ == "__main__":
570
471
  print("8) Déchiffrer fichier Microsoft Exchange (OWA)")
571
472
  print("9) Écrire fichier Microsoft Exchange (OAuth2)")
572
473
  print("10) Déchiffrer fichier Microsoft Exchange (OAuth2)")
573
- print("11) Écrire fichier Microsoft Exchange (OWA) [SECURE]")
574
- print("12) Déchiffrer fichier Microsoft Exchange (OWA) [SECURE]")
575
-
576
- choix = input("👉 Que faire ? [1-12] : ").strip()
474
+ choix = input("👉 Que faire ? [1-10] : ").strip()
577
475
 
578
476
  if choix == "1":
579
477
  enregistrer_config_cli_imap4_ssl()
@@ -594,12 +492,7 @@ if __name__ == "__main__":
594
492
  elif choix=="9":
595
493
  enregistrer_config_cli_oauth2()
596
494
  elif choix=="10":
597
- lire_config_cli_oauth2()
598
- elif choix == "11":
599
- enregistrer_config_cli_owa_secure()
600
- elif choix == "12":
601
- lecture_config_cli_owa_secure()
602
-
495
+ lecture_config_cli_oauth2()
603
496
 
604
497
  else:
605
498
  print("❌ Choix invalide. Réessayez.\n")
@@ -30,6 +30,7 @@ class ChatGpt(OWWidget):
30
30
  model = Setting("gpt-4o")
31
31
  gui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "designer/owchatgpt.ui")
32
32
  want_control_area = False
33
+ category = "AAIT - AAIT - LLM INTEGRATION"
33
34
 
34
35
  class Inputs:
35
36
  data = Input("Data", Orange.data.Table)
@@ -27,6 +27,7 @@ else:
27
27
  class OWDeep_Search(widget.OWWidget):
28
28
  name = "Deep Search"
29
29
  description = "Generate a response to a column 'prompt' with a LLM"
30
+ category = "AAIT - AAIT - LLM INTEGRATION"
30
31
  icon = "icons/deepsearch.svg"
31
32
  if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
32
33
  icon = "icons_dev/owqueryllm.svg"
@@ -0,0 +1,216 @@
1
+ import os
2
+ import sys
3
+ import tempfile
4
+ import traceback
5
+
6
+ import Orange
7
+ from Orange.data import StringVariable
8
+ from Orange.widgets import widget
9
+ from Orange.widgets.widget import Input, Output
10
+ from AnyQt.QtWidgets import QMessageBox, QApplication
11
+
12
+ from docx import Document
13
+ from docx.shared import Pt as pt_docx
14
+ from pptx import Presentation
15
+ from pptx.util import Inches, Pt
16
+ import pypandoc
17
+ from docx2pdf import convert
18
+
19
+ # Chargement UI
20
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
21
+ from Orange.widgets.orangecontrib.AAIT.utils.import_uic import uic
22
+ else:
23
+ from orangecontrib.AAIT.utils.import_uic import uic
24
+
25
+
26
+ class OWExportMarkdown(widget.OWWidget):
27
+ name = "OWExportMarkdown"
28
+ description = "Auto-exporte content→(docx,pptx,pdf) au même path (base + extensions)."
29
+ icon = "icons/export_md.png"
30
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
31
+ icon = "icons_dev/export_md.png"
32
+ want_control_area = True
33
+ priority = 9999
34
+ category = "AAIT - Toolbox"
35
+
36
+ class Inputs:
37
+ data = Input("Data", Orange.data.Table)
38
+
39
+ class Outputs:
40
+ data = Output("Data", Orange.data.Table)
41
+
42
+ def __init__(self):
43
+ super().__init__()
44
+ self.data = None
45
+ ui_path = os.path.join(os.path.dirname(__file__), "designer", "owexportmarkdown.ui")
46
+ uic.loadUi(ui_path, baseinstance=self)
47
+
48
+ # -------- helpers headers/footers --------
49
+ def ajouter_en_tete_pied_docx(self, file_path, header_text, footer_text):
50
+ try:
51
+ doc = Document(file_path)
52
+ section = doc.sections[0]
53
+ header = section.header
54
+ p = header.paragraphs[0] if header.paragraphs else header.add_paragraph()
55
+ p.text = header_text
56
+ if p.runs:
57
+ p.runs[0].font.size = pt_docx(10)
58
+ footer = section.footer
59
+ p = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
60
+ p.text = footer_text
61
+ if p.runs:
62
+ p.runs[0].font.size = pt_docx(10)
63
+ doc.save(file_path)
64
+ except Exception:
65
+ # en-tête/pied non bloquants
66
+ pass
67
+
68
+ def ajouter_entete_pied_pptx(self, file_path, entete_text, pied_text):
69
+ try:
70
+ prs = Presentation(file_path)
71
+ for slide in prs.slides:
72
+ entete = slide.shapes.add_textbox(Inches(0.3), Inches(0.2), Inches(8), Inches(0.5))
73
+ tf_entete = entete.text_frame
74
+ tf_entete.text = entete_text
75
+ tf_entete.paragraphs[0].font.size = Pt(12)
76
+ tf_entete.paragraphs[0].font.bold = True
77
+
78
+ pied = slide.shapes.add_textbox(Inches(0.3), Inches(6.3), Inches(8), Inches(0.5))
79
+ tf_pied = pied.text_frame
80
+ tf_pied.text = pied_text
81
+ tf_pied.paragraphs[0].font.size = Pt(10)
82
+ prs.save(file_path)
83
+ except Exception:
84
+ pass
85
+
86
+ # -------------- input --------------
87
+ @Inputs.data
88
+ def set_data(self, in_data):
89
+ self.error("")
90
+ if in_data is None:
91
+ self.data = None
92
+ self.Outputs.data.send(None)
93
+ return
94
+
95
+ # On exige au moins 'path'
96
+ if "path" not in in_data.domain:
97
+ self.error("La table d'entrée doit contenir au moins la colonne 'path'.")
98
+ self.Outputs.data.send(None)
99
+ return
100
+
101
+ # Optionnellement 'content'
102
+ self.data = in_data
103
+ try:
104
+ table_out = self.export_all_rows()
105
+ self.Outputs.data.send(table_out)
106
+ except Exception as e:
107
+ tb = traceback.format_exc()
108
+ QMessageBox.critical(self, "Erreur d'export", f"{e}\n\n{tb}")
109
+ self.Outputs.data.send(None)
110
+
111
+ # -------------- core --------------
112
+ def export_all_rows(self):
113
+ base_paths = self.data.get_column("path")
114
+ has_content = "content" in self.data.domain
115
+ file_contents = self.data.get_column("content") if has_content else [None] * len(base_paths)
116
+
117
+ pdf_paths, docx_paths, pptx_paths = [], [], []
118
+
119
+ for i, (md_text, base_path) in enumerate(zip(file_contents, base_paths)):
120
+ base_path = str(base_path or "").strip()
121
+
122
+ # Lecture du contenu si 'content' absent et path en .md
123
+ if not has_content:
124
+ if base_path.lower().endswith(".md"):
125
+ try:
126
+ with open(base_path, "r", encoding="utf-8") as f:
127
+ md_text = f.read()
128
+ # on remplace base_path par le même (on garde la base pour sorties)
129
+ except Exception as e:
130
+ self.error(f"Impossible de lire le fichier : {base_path} ({e})")
131
+ pdf_paths.append("")
132
+ docx_paths.append("")
133
+ pptx_paths.append("")
134
+ continue
135
+ else:
136
+ # pas de content et path non .md -> rien à faire pour cette ligne
137
+ pdf_paths.append("")
138
+ docx_paths.append("")
139
+ pptx_paths.append("")
140
+ continue
141
+
142
+ md_text = (str(md_text or "")).strip()
143
+
144
+ if not md_text or not base_path:
145
+ pdf_paths.append("")
146
+ docx_paths.append("")
147
+ pptx_paths.append("")
148
+ continue
149
+
150
+ # Normaliser la base: enlever extension si présente
151
+ base_no_ext, _ = os.path.splitext(base_path)
152
+ # Créer dossier si nécessaire
153
+ out_dir = os.path.dirname(base_no_ext)
154
+ if out_dir and not os.path.isdir(out_dir):
155
+ os.makedirs(out_dir, exist_ok=True)
156
+
157
+ docx_out = base_no_ext + ".docx"
158
+ pptx_out = base_no_ext + ".pptx"
159
+ pdf_out = base_no_ext + ".pdf"
160
+
161
+ # MD temporaire
162
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False, encoding="utf-8") as tmp:
163
+ tmp.write(md_text)
164
+ tmp_md = tmp.name
165
+
166
+ try:
167
+ # DOCX
168
+ pypandoc.convert_file(tmp_md, to="docx", outputfile=docx_out)
169
+ self.ajouter_en_tete_pied_docx(
170
+ docx_out,
171
+ "Rapport - Orange AI",
172
+ "Page générée automatiquement - Ne pas diffuser"
173
+ )
174
+
175
+ # PPTX
176
+ pypandoc.convert_file(tmp_md, to="pptx", outputfile=pptx_out)
177
+ self.ajouter_entete_pied_pptx(
178
+ pptx_out,
179
+ "Orange AI – Présentation",
180
+ "Page générée automatiquement"
181
+ )
182
+
183
+ # PDF (docx -> pdf) avec chemin de sortie exact
184
+ try:
185
+ convert(docx_out, pdf_out)
186
+ except Exception:
187
+ # fallback pandoc->pdf (si LaTeX dispo)
188
+ try:
189
+ pypandoc.convert_file(tmp_md, to="pdf", outputfile=pdf_out)
190
+ except Exception:
191
+ self.error(f"Échec conversion PDF pour la ligne {i+1}.")
192
+ pdf_out = ""
193
+ finally:
194
+ try:
195
+ os.remove(tmp_md)
196
+ except Exception:
197
+ pass
198
+
199
+ pdf_paths.append(pdf_out if os.path.isfile(pdf_out) else "")
200
+ docx_paths.append(docx_out if os.path.isfile(docx_out) else "")
201
+ pptx_paths.append(pptx_out if os.path.isfile(pptx_out) else "")
202
+
203
+ # Ajouter colonnes sortie
204
+ table = self.data
205
+ table = table.add_column(StringVariable("output_pdf_path"), pdf_paths)
206
+ table = table.add_column(StringVariable("output_docx_path"), docx_paths)
207
+ table = table.add_column(StringVariable("output_pptx_path"), pptx_paths)
208
+
209
+ return table
210
+
211
+
212
+ if __name__ == "__main__":
213
+ app = QApplication(sys.argv)
214
+ w = OWExportMarkdown()
215
+ w.show()
216
+ sys.exit(app.exec() if hasattr(app, "exec") else app.exec_())