Récupérer le texte d'un document depuis une appli externe



Pour faire ouvrir un fichier par son application par défaut utiliser "openFile:" de la classe NSWorkspace


NSWorkspace permet également de faire ouvrir un fichier par une application désignée.


 


 


 


  >:D   Grillé par notre extraterrestre !




 


Merci quand même ! Le problème que j'avais, c'était plus sur le path du fichier à  ouvrir qui n'était pas bon !


 


Par contre peut-être que tu as une idée pour sélectionner le contenu du fichier ouvert et le copier vers le pasteboard. Je viens de retourner stackoverflow sans succès... J'y replonge d'ailleurs !

Réponses

  • Tout dépend de ce que tu veux faire. Si c'est pour avoir au final le contenu du fichier dans ton appli, passer par le pasteboard n'est pas utile. Si tu veux récupérer le contenu du fichier par glisser-déposer, il existe des méthodes pour cela.


    Pour te répondre il faudrait connaitre la finalité. Tu sélectionnes un document d'un type X. Qu'est-ce que tu veux en faire?


    Pour le voir ou l'imprimer, l'envoyer à  l'application par défaut qui le traite est suffisant. Pour modifier toi-même son contenu il faut le charger en mémoire puis écrire ou utiliser un éditeur adéquat.


    Donc il me manque quelques informations pour répondre à  ta question.

  • En fait, jaimerais récupérer sous forme de NSString le contenu d'un fichier que l'on obtient quand on fait édition/sélectionner tout et édition/copier-coller

    J'ai des traitements à  faire dessus.

    Merci en tout cas
  • AliGatorAliGator Membre, Modérateur
    Ca a besoin d'être exact (mise en page précise et tout) ou tu veux juste copier texte et image ?

    J'y pense comme ça en passant (c'est p'tet pas du tout la bonne solution remarque), peut-être qu'en utilisant l'API de Spotlight pour récupérer un aperçu rapide du fichier, plutôt que de l'ouvrir et copier son contenu dans le Pasteboard ?

    Enfin ça c'est surtout pour éviter d'avoir à  lancer l'application juste pour copier le contenu du fichier... au risque d'avoir un moteur de rendu du contenu du fichier un peu moins strict. A moins que ça ne te dérange pas d'ouvrir l'application concernée (Pages ou Aperçu ou Adobe Reader) pour ce que tu as à  faire ?

    Et du coup attention à  ce que l'utilisateur ait bien l'application concernée d'installée. Par exemple moi j'ai pas Adobe Reader (Aperçu fait très bien son travail, pas besoin d'AdobeReader pour les les PDFs), mais si ton appli pilote Reader (genre AppleScript ou autre) ou exécute des commandes spécifiques à  Reader pour faire son copier/coller qui ne fonctionneront pas si moi c'est Aperçu qui est configuré pour ouvrir les PDFs...

    Soit tu laisses l'appli choisie par défaut par l'utilisateur pour ouvrir le fichier (par exemple Aperçu chez moi pour les PDFs, AdobeReader chez d'autres, un PDFViewer alternatif chez d'autres...) et du coup faut espérer que ton code de copier/coller marche sur toutes (ce qui est jouable, ça reste une opération courante avec des API communes, genre les mêmes commandes AS pour ce genre de choses et sans doute les mêmes NSMenu aussi), soit tu imposes l'application car ta procédure pour faire le copier/coller fonctionne que pour une appli donnée, mais du coup faut que l'utilisateur ait cette appli... ce qui n'est pas forcément le cas pour AdobeReader.
  • Bah en fait, j'ai besoin de récupérer uniquement le texte, mais il doit rester dans un ordre particulier ; c'est d'ailleurs pour cela que j'utilise Adobe et pas Aperçu qui ne garde pas le bon ordre du texte. Et donc effectivement il faut que l'utilisateur aie Adobe, c'est un prérequis et je le vérifie avant. De toute façon, c'est pas un appli pour le store...


     


    Je suis en train de refaire un programme que j'avais développé il y a quelques années et qui utilisait des procédures AppleScript pour faire certaines tâches :


    - ouvrir le fichier sous adobe et copier le contenu dans un NSString


    - créer des évènements dans iCal à  partir de l'analyse du texte copié


     


    Ces scripts ne fonctionnent plus sous Mavericks, et du coup j'essaye de trouver une solution ; pour la création d'évènements iCal, je devrais m'en sortir, par contre la récupération du texte me pose souci... 


     


    Pour résumer, ce que je voudrais pouvoir faire, après l'ouverture du fichier pdf, c'est simuler une action de l'utilisateur (Edition/sélectionner tout puis édition/copier) et ensuite 


    NSString *mySuperText=contenu du pasteBoard


     


    Est-ce réalisable ?


    Merci pour votre aide


    PS : vu que le sujet est un peu différent, ne serait il pas préférable de spliter ce sujet en deux ?


  • tabliertablier Membre
    novembre 2013 modifié #6

    Ces scripts ne fonctionnent plus sous Mavericks, et du coup j'essaye de trouver une solution



    Es-tu bien sur de ça? Je viens de voir qu'AppleScript fonctionne toujours sous Mavericks.


    Tes scripts sont des sources ou des compilés?


  • Des sources

    Je posterai les sources ce soir (je part bosser)
  • Bonsoir,


    Merci pour le split du sujet...


    Alors, je viens de faire quelques tests avec Applescript sous Mavericks et effectivement, j'arrive à  le faire fonctionner, et du coup, je n'arrive pas à  m'expliquer que mon application complète ne fonctionne plus... Mais bon, c'était un veux truc développé sous RealBasic (beurk) et du coup, quand je me replonge dedans, je prend peur ! B)


     


    Voici un exemple de ce script tel qu'il était appelé depuis realbasic (je n'arrive pas à  joindre le fichier) :



    on run (fileName)
    set namePDFFile to fileName as string
    tell application "Finder"
    set theDesktop to a reference to desktop
    set pathFile to folder "Test" of theDesktop
    set pathPDFFile to ((pathFile as string) & namePDFFile) as alias
    open document file pathPDFFile
    delay 3
    tell application "System Events" to tell process "Adobe Reader"
    set frontmost to true
    click menu item "Sélectionner tout" of menu "Edition" of menu bar item "Edition" of menu bar 1
    click menu item "Copier" of menu "Edition" of menu bar item "Edition" of menu bar 1
    click menu item "Fermer" of menu "Fichier" of menu bar item "Fichier" of menu bar 1
    end tell
    end tell
    end run

    Est-il possible de l'inclure dans un projet Xcode ? Si oui, puis-je toujours garder l'argument ? Ou alors, il serait peut-être plus simple d'ouvrir le fichier depuis Xcode comme vu dans mon autre post, et ensuite de lancer le script en ne gardant que les 3 lignes de "sélectionner-copier-fermer", ce qui éviterait le "delay 3" qui est horrible !


    J'ai cherché un peu avec la classe NSApplescript, mais je ne vois pas trop comment l'utiliser.


     


    Une petite piste ?


     


  • Alf1996Alf1996 Membre
    novembre 2013 modifié #9

    Bon, ce n'est peut-être pas la solution la plus simple, mais j'ai fait ceci, çà  fonctionne. Il me reste à  récupérer le contenu du pasteboard... Je verrai çà  demain !



    NSDictionary* errorDict;
    NSAppleEventDescriptor* returnDescriptor = NULL;
        
    NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:
                                       @\
                                       tell application \"System Events\" to tell process \"Adobe Reader\"\n\
                                       set frontmost to true\n\
                                       click menu item \"Sélectionner tout\" of menu \"Edition\" of menu bar item \"Edition\" of menu bar 1\n\
                                       click menu item \"Copier\" of menu \"Edition\" of menu bar item \"Edition\" of menu bar 1\n\
                                       click menu item \"Fermer\" of menu \"Fichier\" of menu bar item \"Fichier\" of menu bar 1\n\
                                       end tell"];
        
    returnDescriptor = [scriptObject executeAndReturnError: &errorDict];


    Edit, je viens de voir dans la doc Apple que je peux inclure mon script dans le projet Xcode, ce sera plus propre. Je verrai çà  demain aussi !


  • J'aurais inclus le source sous forme de NSString (conditionnel) en passant le nom du fichier en paramètre.


    Là , je monte en montagne. je verrai ça quand j'y serai.


  • Merci à  vous deux pour votre aide. J'ai une solution qui fonctionne pour les fichiers pdf...  :D


     


    @AliGator : merci pour le lien sur le pasteboard, j'avais déjà  regardé, mais il me semble que je n'avais pas cette même page... où alors j'avais vraiment besoin de dormir !!


     


    @Tablier : finalement, je ne vais pas mettre d'argument. C'est plus simple et plus rapide d'ouvrir le pdf depuis Xcode. Cela me permet aussi de surveiller la fin d'ouverture avant de lancer le script après. Voici le code final, si çà  peut servir à  d'autres :


     


    NSURL * fileToOpen=[self chooseFileToImport];
    [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                                                           selector:@selector(appDidLaunch:)
                                                               name:NSWorkspaceDidLaunchApplicationNotification
                                                             object:nil];
    if ([[fileToOpen pathExtension] isEqualToString:@pdf]) {
        [[NSWorkspace sharedWorkspace] openFile:[fileToOpen path] withApplication:@Adobe Reader andDeactivate:NO];
    }

     


    avec la méthode suivante exécutée lorsque le fichier est fini d'ouvrir :


    - (void)appDidLaunch:(NSNotification*)notification {
        NSDictionary* errorDict;
        NSAppleEventDescriptor* returnDescriptor = NULL;
        NSString *myScript=@\
        tell application \"System Events\" to tell process \"Adobe Reader\"\n\
        set frontmost to true\n\
        click menu item \"Sélectionner tout\" of menu \"Edition\" of menu bar item \"Edition\" of menu bar 1\n\
        click menu item \"Copier\" of menu \"Edition\" of menu bar item \"Edition\" of menu bar 1\n\
        click menu item \"Fermer\" of menu \"Fichier\" of menu bar item \"Fichier\" of menu bar 1\n\
        end tell";
        
        NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:myScript];
        
        returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
        
        if (returnDescriptor != NULL) {
            // successful execution
            NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
            NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];
            NSDictionary *options = [NSDictionary dictionary];
            NSArray *copiedItems = [pasteboard readObjectsForClasses:classes options:options];
            if (copiedItems != nil) {
                NSString *contentOfMyFile=[copiedItems objectAtIndex:0];
                NSLog(@Contenu du pasteboard %@",contentOfMyFile);
            }
        } else {
            // No data in pasteboard - error message...
        }
        
    }

     


    Si vous avez des remarques, n'hésitez pas. Et merci encore pour votre aide précieuse !  


  • Tu ouvres AReader sous objective-C.  


    Juste en  observation (je ne sais pas si c'est toujours comme ça!): 


    Si on fait un tell application "AReader" et que AReader soit absent, on reste souvent bloqué sur l'ouverture par le script du dictionnaire de AReader.  Pour éviter ça, en AppleScript on fait souvent la double ouverture.


    tell application "Finder" to open application "AReader"


    on error


     ..... pas trouvé AReader


    end tell


    Comme le Finder existe forcément, il renvoie une erreur mais le programme ne se bloque plus sur l'ouverture d'un dictionnaire.


    L'autre solution est d'utiliser  try ....... end try


  • Du coup ça vaudrait peut-être le coup de programmer ça en ApplescriptObjC... On y perd en vitesse, mais c'est plus cohérent que de mixer avec des appels à  NSScript en Objective-C. Et une fois la syntaxe maà®trisée c'est presque du 1:1 au niveau rédaction du code.




  • Tu ouvres AReader sous objective-C.  


    Juste en  observation (je ne sais pas si c'est toujours comme ça!): 


    Si on fait un tell application "AReader" et que AReader soit absent, on reste souvent bloqué sur l'ouverture par le script du dictionnaire de AReader.  Pour éviter ça, en AppleScript on fait souvent la double ouverture.


    tell application "Finder" to open application "AReader"


    on error


     ..... pas trouvé AReader


    end tell


    Comme le Finder existe forcément, il renvoie une erreur mais le programme ne se bloque plus sur l'ouverture d'un dictionnaire.


    L'autre solution est d'utiliser  try ....... end try




     


    OK, j'avais de toute façon prévu d'ajouter un "try", c'est effectivement plus prudent. Merci


     




    Du coup ça vaudrait peut-être le coup de programmer ça en ApplescriptObjC... On y perd en vitesse, mais c'est plus cohérent que de mixer avec des appels à  NSScript en Objective-C. Et une fois la syntaxe maà®trisée c'est presque du 1:1 au niveau rédaction du code.




     


    Tu dois avoir raison, mais une fois le contenu du pasteboard récupéré, j'ai un traitement du texte qui est assez long, et complexe... Et je ne maitrise pas assez (même pas du tout !) AppleScript pour faire ce traitement. De plus, le traitement en question est déjà  programmé sous Xcode. Et comme c'est une application qui n'a pas vocation à  être distribuée je n'ai pas trop envie d'y passer trop de temps !


    Merci tout de même

  • FKDEVFKDEV Membre
    novembre 2013 modifié #16

    Sinon il y avait la classe PDFSelection et notamment la méthode "string" de PDFKit. Je  ne l'ai jamais utilisée mais cela semble coller avec le besoin.


    Je suppose que l'ordre du texte doit être le même que dans Aperçu, quoiqu'il y a un moyen de récupérer le document ligne par ligne.


  • Merci FKDEV, je vais tester... Mais si c'est le même ordre qu' aperçu ça n'ira pas, car j'ai besoin du même ordre qu'Adobe Reader...
  • As tu essayé les classes du PDFKit ?


    Aperçu utilise une PDFView mais le PDFKit c'est bien plus. Et ça permet vraiment tout un tas d'inspections et de manipulations du contenu d'un pdf avec PDFDocument, PDFPage, PDFAnnotation etc, sans l'ouvrir dans une fenêtre.


     


     


    Sinon en fait, et en apparte, je me demandais pourquoi



    NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];

    plutôt que



    NSArray *classes = [NSArray arrayWithObject:[NSString class]];

    Qui m'aurait semblé plus logique (et rapide à  écrire dans le feu de l'action ;) )?


  • AliGatorAliGator Membre, Modérateur


    Sinon en fait, et en apparte, je me demandais pourquoi


    NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];

    plutôt que

    NSArray *classes = [NSArray arrayWithObject:[NSString class]];

    Qui m'aurait semblé plus logique (et rapide à  écrire dans le feu de l'action ;) )?
    a ce tarif là  autant écrire :
    NSArray *classes = @[;NSString.class];
    c'est encore + court ;)


  • As tu essayé les classes du PDFKit ?


    Aperçu utilise une PDFView mais le PDFKit c'est bien plus. Et ça permet vraiment tout un tas d'inspections et de manipulations du contenu d'un pdf avec PDFDocument, PDFPage, PDFAnnotation etc, sans l'ouvrir dans une fenêtre.


     


     


    Sinon en fait, et en apparte, je me demandais pourquoi



    NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];

    plutôt que



    NSArray *classes = [NSArray arrayWithObject:[NSString class]];

    Qui m'aurait semblé plus logique (et rapide à  écrire dans le feu de l'action ;) )?




     


    FKDEV m'a déjà  suggéré PDFKit, mais malheureusement je n'ai pas encore eu le temps de tester. Par contre, il semblerait que l'ordre du texte récupéré soit celui d'Aperçu, et pour mon projet j'ai besoin de l'ordre d'Adobe Reader.


    Je vais essayer çà  dès que j'ai un moment, et je vous tiens au courant.


     


     




    a ce tarif là  autant écrire :



    NSArray *classes = @[;NSString.class];

    c'est encore + court ;)

     




     


    Et oui !!!  ::)


    Merci



  •  Par contre, il semblerait que l'ordre du texte récupéré soit celui d'Aperçu, et pour mon projet j'ai besoin de l'ordre d'Adobe Reader.


     




     


    C'est juste une supposition.

  • laudemalaudema Membre
    décembre 2013 modifié #22

    Sans le pdf en question c'est difficile à  savoir.


    PDFDocument a une méthode -string qui retourne la chaà®ne de tout le document ce qui est peut être trop "brut" pour toi, PDFPage offre une méthode -attributedString qui serait peut être plus adaptée à  ton cas.


    J'ai eu à  m'en servir pour remplir les champs d'un formulaire, et au début je me demandais si j'y arriverais alors qu'en fait c'était, relativement, simple une fois que j'ai compris les PDFAnnotations.


  • AliGatorAliGator Membre, Modérateur
    J'ai pas testé, mais je pense qu'il y a de forte chances que Preview.app (Aperçu) utilise les frameworks Cocoa sous le capot, et donc PDFKit, et donc que s'il retourne le texte dans un certain ordre c'est parce que c'est l'ordre dans lequel PDFKit le lui retourne lui-même... donc y'a des risques que ce soit le même ordre dans tous les cas au final... Mais bon, ça coûte pas grand chose de vérifier j'imagine.
  • J'ai testé (la curiosité....)


    C'est très facile à  mettre en oeuvre. C'est bien le même ordre que pour aperçu.


    Mais l'ordre semble correct tant que le pdf ne contient pas des colonnes.


     


    On peut même faire un convertisseur PDF -> HTML en quelques lignes (sans les images bien-sûr) :



    @import Quartz;

    PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:pdfFilePath]];
    if (pdfDocument)
    {
    PDFSelection *pdfSelection = [pdfDocument selectionForEntireDocument];
    if ([attributed boolValue] == YES)
    {
    NSAttributedString *attributedString = [pdfSelection attributedString];
    NSDictionary *documentAttributes = [NSDictionary dictionaryWithObjectsAndKeys:NSHTMLTextDocumentType, NSDocumentTypeDocumentAttribute, nil];
    NSData *htmlData = [attributedString dataFromRange:NSMakeRange(0, attributedString.length) documentAttributes:documentAttributes error:NULL];
    NSString *htmlString = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding] ;
    [responseStream writeString:htmlString];
    }
    else
    {
    [responseStream writeString:[pdfSelection string]];
    }
    }

    Un petit WS: http://backtoweb.net/pdftext.b2w




  • J'ai testé (la curiosité....)


    C'est très facile à  mettre en oeuvre. C'est bien le même ordre que pour aperçu.


    Mais l'ordre semble correct tant que le pdf ne contient pas des colonnes.


     


    On peut même faire un convertisseur PDF -> HTML en quelques lignes (sans les images bien-sûr) :



    @import Quartz;

    PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:pdfFilePath]];
    if (pdfDocument)
    {
    PDFSelection *pdfSelection = [pdfDocument selectionForEntireDocument];
    if ([attributed boolValue] == YES)
    {
    NSAttributedString *attributedString = [pdfSelection attributedString];
    NSDictionary *documentAttributes = [NSDictionary dictionaryWithObjectsAndKeys:NSHTMLTextDocumentType, NSDocumentTypeDocumentAttribute, nil];
    NSData *htmlData = [attributedString dataFromRange:NSMakeRange(0, attributedString.length) documentAttributes:documentAttributes error:NULL];
    NSString *htmlString = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding] ;
    [responseStream writeString:htmlString];
    }
    else
    {
    [responseStream writeString:[pdfSelection string]];
    }
    }

    Un petit WS: http://backtoweb.net/pdftext.b2w




     


    Merci pour le test. Malheureusement je n'ai pratiquement rien fait sous Xcode ces derniers jours... çà  confirme donc ce que tu pensais, et donc je ne pourrais pas l'utiliser car mes fichiers comportent effectivement des colonnes, et du coup la lecture ne se fait pas du tout dans l'ordre qui m'intéresse. Cependant, ces quelques lignes de code pourront peut-être intéresser quelqu'un d'autre, ou peut-être en aurais je besoin pour un autre projet...  8--)

  • Je pense qu'il doit etre possible d'obtenir le texte dans le bon ordre mais cela demande un peu plus de travail.


    A partir de la selection totale, on peut recuperer un objet selection pour chaque ligne.

    Ensuite on peut avoir le NSRect de chaque ligne (boundsForPage:), on peut donc trier les lignes par positions...
Connectez-vous ou Inscrivez-vous pour répondre.