Access-o-Mania

Access-Forum (Deutsch/German) => Access Programmierung => Thema gestartet von: maddhin am Oktober 27, 2020, 10:39:59

Titel: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 27, 2020, 10:39:59
Hallo,

ich lade mit Access einige Dateien herunter. Dazu mache ich "Webscraping" und speichere mit
.fields("Title")= IEElement.title
einen Dateinamen in einer Tabelle. Via
sPath = psPath & "\" & rs!title
...
URLDownloadToFile 0, psURL, sPath, 0, 0

(lade ich herunter und) schreibe ich die Datei. Und mit
If FSO.FileExists(sPath) = True Then
                .Edit
                .Fields("FileDownloaded") = True
                .Update
            End If

möchte ich prüfen, ob die Datei auch auf der Platte gelandet ist und setze ein Häckchen.

So weit, so gut und funktioniert...

...fast einwandfrei:

mein Problem ist, dass bei einigen Dateinamen bzw. Dateien "FSO.FileExists(sPath) = false" bleibt, obwohl die Datei heruntergeladen ist, d.h. FSO.FileExists(sPath) müsste entsprechend "true" sein. D.h. der Vergleich zwischen dem Namen in der Tabelle und dem Namen in Windows schlägt fehl.

Nach endlosem Probieren meine ich das Problem auf einen Bindestrich reduziert zu haben, der eigentlich chr(45) sein sollte, aber nach meinen Experimenten keinem chr() zuzuordnen ist. Kurioserweise lieft ein asc() des Bindestrichs den Wert 45 = ASCII Bindestrich. Allerdings erkennt VBA das Zeichen nicht als chr(45) (z.B. in Replace). Die Webseite wo "Title" herkommt ist in UTF-8.

Ich bin jetzt mit meinem Latein am Ende. Nach stundenlangem Googlen scheint dies ein "encoding" Problem zu sein, ich sehe im Moment aber nicht, wie ich mein Problem konkret lösen kann.

Hat jemand eine Idee?
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 27, 2020, 12:31:42
Neuigkeiten: ich habe inzwischen den (vermeidlichen!) Übeltäter als U+2122 Unicode-Bindestrich identifiziert...

Gibt es eine Funktion, die "Web"-Strings (vermutlich UTF-8) so in Access speichert bzw. konvertiert, dass man problemlos damit arbeiten kann?

In meinem konkreten Fall waren in fast allen der 845 heruntergeladenen Dateien mehrere chr(45)-Bindestriche und nur 9 Dateien mit U+2122 anstatt chr(45) haben Probleme bereitet.

Ich würde jetzt gerne den VBA Code so schreiben, dass sowas nicht mehr passieren kann.

Lieben Dank für jeglichen Input im Voraus!
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 27, 2020, 13:07:44
Vielleicht zeigst du mal, was die Funktion URLDownloadToFile macht.
Falls das eine Windows Funktion ist - > welche Version verwendest du - ANSI/Unicode?
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 27, 2020, 13:19:16
Zitat von: markus888 am Oktober 27, 2020, 13:07:44Vielleicht zeigst du mal, was die Funktion URLDownloadToFile macht.
Falls das eine Windows Funktion ist - > welche Version verwendest du - ANSI/Unicode?

URLDownloadToFile ist die Windows Funktion und ich verwende sie wie dargestellt:
Function dlfile(psURL As String, psPath As String)
    Dim sPath As String
    sPath = psPath
    Debug.Print "Saving to: " & sPath
    Debug.Print "------------------"
    URLDownloadToFile 0, psURL, sPath, 0, 0
End Function
psURL ist die URL und sPath der vollständige Pfad. Was die Nullen machen, weiß ich nicht wirklich :-[

Ich lerne gerade, dass es URLDownloadToFileW (Unicode) and URLDownloadToFileA (ANSI) gibt. Ggf. ist das hilfreich... :)

Blöde Frage: welche Version wäre denn in meinem Fall die richtige?
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 27, 2020, 13:27:20
Zitat von: maddhin am Oktober 27, 2020, 13:19:16welche Version wäre denn in meinem Fall die richtige?

Da Access alles in Unicode macht, verwende ich bei win API immer nur die unicode Versionen.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 27, 2020, 13:54:17
Zitat von: markus888 am Oktober 27, 2020, 13:27:20Da Access alles in Unicode macht, verwende ich bei win API immer nur die unicode Versionen.

Nachdem ich mit "URLDownloadToFileW" als Funktion gescheitert bin, habe ich nochmal genau hingesehen und
Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" _
    Alias "URLDownloadToFileA" (ByVal pCaller As Long, ByVal szURL As String, _
    ByVal szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
gefunden... Das hatte ich vergessen, ich hatte ganz am Anfang etwas c&p aus dem Internet gemacht... Sorry.

Ich habe jetzt URLDownloadToFileA in URLDownloadToFileW geändert, dies scheint aber nicht zu funktionieren, da nichts heruntergeladen wird... Wie kann ich das mit "W" machen? :-[
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: daolix am Oktober 27, 2020, 14:20:53
versuch
Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" _
    Alias "URLDownloadToFileW" (ByVal pCaller As Long, ByVal szURL As longptr, _
    ByVal szFileName As longptr, ByVal dwReserved As Long, ByVal lpfnCB As Longptr) As Long


aufruf ist dann
URLDownloadToFile 0, strptr(psURL), strptr(sPath), 0, 0
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 27, 2020, 18:47:51
@daolix,
nur so eine Frage am Rande.
Der erste Parameter ist ja auch ein Pointer.
Weißt du, ob bei einer 64 Bit Anwendung die fehlenden 4 Byte am Stack mit Null überschrieben werden, wenn nur 4 geschrieben werden,
oder bleibt der alte Inhalt stehen?
Falls ja, könnte das Beispiel möglicherweise zu einem Fehler führen.
LG Markus
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: daolix am Oktober 27, 2020, 22:19:28
Stimmt ist ein Pointer. sollte aber kein Prob sein, solange der Wert 0 ist, ich denk mal schon das der Stack vor der Übergabe aufgeräumt ist. 

wenn ich jetzt bei
Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" _
    Alias "URLDownloadToFileW" (ByVal pCaller As Long, ByVal szURL As longptr, _
    ByVal szFileName As longptr, ByVal dwReserved As Long, ByVal lpfnCB As Integer) As Long

den letzte parameter der Deklaration ändere passiert unter 32bit ja jetzt auch nix aufregendes.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 28, 2020, 04:12:38
Zitat von: daolix am Oktober 27, 2020, 14:20:53Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" _
    Alias "URLDownloadToFileW" (ByVal pCaller As Long, ByVal szURL As longptr, _
    ByVal szFileName As longptr, ByVal dwReserved As Long, ByVal lpfnCB As Longptr) As Long

URLDownloadToFile 0, strptr(psURL), strptr(sPath), 0, 0

Das hat jetzt funktioniert! Eine Datei (aus 845) wird immernoch nicht erkannt bzw. der FSO.FileExists-Vergleich schlägt fehl, aber ggf. liegt das an was anderem.

Noch eine Frage:

Bei dbWiki (https://dbwiki.net/wiki/VBA_Tipp:_Download_einer_Datei_aus_dem_Internet_und_Import_in_eine_Access_Datenbank) steht sowas:

Public Function DateiHerunterladen(Url As String, Pfad As String) As Long
   DoCmd.Hourglass True
   DateiHerunterladen = URLDownloadToFile(0, Url, Pfad, 0, 0)
   DoCmd.Hourglass False
End Function

[...]
   If DateiHerunterladen(strquellpfad, strzielpfad) = 0 Then
      MsgBox "Download erfolgreich"
   Else
      MsgBox "Download nicht erfolgreich"
   End If

Heißt dies, dass URLDownloadToFile nach erfolgreichem Herunterladen immer 0 zurückgibt? D.h. dann könnte ich mir die If-FSO.FileExists-Geschichte ja eigentlich sparen. Zumindest wenn es nur darum geht ein isDownloaded?-Häckchen zu setzen.

Lieben Dank schonmal für die exzellent und effiziente Hilfe bisher, das hat mir sehr geholfen!
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 28, 2020, 09:30:22
Zitat von: maddhin am Oktober 28, 2020, 04:12:38Heißt dies, dass URLDownloadToFile nach erfolgreichem Herunterladen immer 0 zurückgibt?

Hasst du dir die Dokumentation zu der Funktion durchgelesen?
Da erfährst du alles zum Rückgabewert.
Oder meinst du jeder kennt jede Funktion auswendig ohne selbst nachsehen zu müssen?
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 28, 2020, 09:35:34
Zitat von: daolix am Oktober 27, 2020, 22:19:28ich denk mal schon das der Stack vor der Übergabe aufgeräumt ist.

Bei dem Punkt bin ich mir eben nicht sicher.
Weiß auch nicht wovon das abhängt - Betriebssystem, calling convention oder was anderes?
Vor dem Push steht ja irgendwas drinnen.
Die Frage ist, ob sicher immer die restlichen Bytes die auf die 8 Byte fehlen auf 0 gestellt werden,
da das ja Zeit kostet die man sich bei korrekter Übergabe sparen kann.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 28, 2020, 10:14:50
Zitat von: markus888 am Oktober 28, 2020, 09:30:22Hasst du dir die Dokumentation zu der Funktion durchgelesen?
Da erfährst du alles zum Rückgabewert.
Ja, ich habe die Doku sogar auf 3-4 unterschiedlichen Seiten gelesen, allerdings fehlt mir leider das nötige Hintergrundwissen, um das wirklich verstehen zu können.

Soweit ich es interpretieren kann, gibt die Funktion 0 zurück, solange nichts Außergewöhnliches/kein Fehler passiert ist. Soweit ich verstanden habe, kann dies auch ein Abbruch durch den Benutzer, etc. bedeuten. D.h. eine Prüfung auf den Rückgabewert um isDownloaded? zu setzen wäre nicht zuverlässig. D.h. die FSO.FileExist-Prüfung erscheint sinnvoll.

Zitat von: markus888 am Oktober 28, 2020, 09:30:22Oder meinst du jeder kennt jede Funktion auswendig ohne selbst nachsehen zu müssen?
Um Deine rhetorische Frage zu beantworten: Nein, ganz sicher nicht! Aber Du/ihr scheint Euch mit dieser Funktion recht gut auszukennen. Daher meine Frage an Euch Experten, wie ihr sowas löst. Ich habe dies sicherlich zu kurz formuliert und verstehe, dass man den Satz auch als der-ist-zu-faul-zum-Googlen interpretieren kann. My bad.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 28, 2020, 11:34:03
Zitat von: maddhin am Oktober 28, 2020, 10:14:50Aber Du/ihr scheint Euch mit dieser Funktion recht gut auszukennen.

Ich hab die Funktion noch nie verwendet.
Mit dem Programmieren ist es recht einfach.
Alles was man nicht versteht, muss man sich erarbeiten.
Da gibt es keine Abkürzung, sonst wird man nie in der Lage sein, eigenständig Code zu schreiben
sondern bleibst ewig ein Kopierer, der bei Problemen nicht weiter kommt.
Du kannst dann auch weder mit der Hilfe noch mit der Dokumentation was anfangen -
fürs Programmieren bleibst du also unbrauchbar, wenn du dich nicht systematisch weiterbildest.

Die Frage ist also wo die Reise hin soll.

Hab jetzt nachgesehen, was unter anderem in der Doku  (https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms775123(v=vs.85))steht:

Zitat von: undefinedURLDownloadToFile returns S_OK even if the file cannot be created and the download is canceled. If the szFileName parameter contains a file path, ensure that the destination directory exists before calling URLDownloadToFile. For best control over the download and its progress, an IBindStatusCallback interface is recommended.

Das ist also ziemlich klar.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: daolix am Oktober 28, 2020, 13:24:34
Zitat von: markus888 am Oktober 28, 2020, 09:35:34Vor dem Push steht ja irgendwas drinnen.
Stimmt.

Zitat von: markus888 am Oktober 28, 2020, 09:35:34
Zitat von: daolix am Oktober 27, 2020, 22:19:28ich denk mal schon das der Stack vor der Übergabe aufgeräumt ist.

Bei dem Punkt bin ich mir eben nicht sicher.
Der Stack wird mit dem Push aufgeräumt, d.h. mit "push eineVariable" wird der Stack bereinigt, der Wert gesetzt, und der Stackptr korrigiert.

Selbstredend kannst du dir deine eigene Aufrufmethode schreiben bei der du meinst das diese schneller ist. Nur ob du damit einen messbaren effekt erreichtst glaub ich jetzt nicht.


Zitat von: markus888 am Oktober 28, 2020, 11:34:03Hab jetzt nachgesehen, was unter anderem in der Doku  (https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms775123(v=vs.85))steht:

Zitat von: undefinedURLDownloadToFile returns S_OK even if the file cannot be created and the download is canceled. If the szFileName parameter contains a file path, ensure that the destination directory exists before calling URLDownloadToFile. For best control over the download and its progress, an IBindStatusCallback interface is recommended.

Das ist also ziemlich klar.

Hmm. wenn bei mir szFileName auf einen nicht existierenden Path zeigt gibt URLDownloadToFile bei mir ein e_abort zurück. ist aber winversionsabhängig, glaub ich.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 28, 2020, 15:09:28
Zitat von: daolix am Oktober 28, 2020, 13:24:34Der Stack wird mit dem Push aufgeräumt, d.h. mit "push eineVariable" wird der Stack bereinigt, der Wert gesetzt, und der Stackptr korrigiert.

Also ich habs einfach getestet.
Hab zwei Byte auf den Stack gepusht und dann per pop 4 Byte ins Register geschrieben.
Und es ist tatsächlich so, dass die vorderen 2 Byte nicht überschrieben werden.
Warum auch, es kostet einfach zu viel Zeit.
Ob das Verhalten beim Schreiben von 4 Byte und lesen von 8 Byte gleich ist, weiß ich allerdings nicht.
Wenn du Lust hast, kannst ja testen. Ich meine einfacher ist es einfach einen korrekten Header zu schreiben.
Es steht dann also irgendetwas im Register was natürlich zu einem Fehler führen könnte.
Bei 32 Bit wurscht, bei 64 Bit nicht.

Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 28, 2020, 17:38:00
Zitat von: markus888 am Oktober 28, 2020, 11:34:03Ich hab die Funktion noch nie verwendet.
Mit dem Programmieren ist es recht einfach.
Alles was man nicht versteht, muss man sich erarbeiten.
Da gibt es keine Abkürzung, sonst wird man nie in der Lage sein, eigenständig Code zu schreiben
sondern bleibst ewig ein Kopierer, der bei Problemen nicht weiter kommt.
Du kannst dann auch weder mit der Hilfe noch mit der Dokumentation was anfangen -
fürs Programmieren bleibst du also unbrauchbar, wenn du dich nicht systematisch weiterbildest.

Die Frage ist also wo die Reise hin soll.

Boah. Ich habe mich schon entschuldigt und erklärt wie ich das gemeint habe. Muss so ein Kommentar sein? Den kannst Du so unter jede Frage hier im Forum posten.

1. Ich bin selbstständig, arbeite wg Corona extrem viel und versuche nebenher noch meine DB aufzubauen, um effizienter zu werden. Trotzdem habe ich 3 Tage versucht das Problem selbst zu lösen bevor ich hier gepostet habe. Mit c&p hat das nichts zu tun, ich kann inzwischen VBA und Access recht gut.

2. Die angesprochene Funktion ist zwar simple zu programmieren, aber für Normalos und Beginner ist der Hintergrund sehr komplex. Ich arbeite mich immer in die Materie ein, aber auch Du solltest eingestehen, dass ohne langjährige Programmiererfahrung oder Informatikstudium das schon ein ziemlicher Brocken ist und über normale Access-Kenntnisse hinausgeht.

Irgendwie ist es doch gerade der Sinn des Forums, dass alte Hasen mit ihrer Übersicht die Anfänger in die richtige Richtung stupsen und nicht sagen "du bist so doof und unfähig, lern erstmal richtig Programmieren und komm dann zurück!"


Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: daolix am Oktober 28, 2020, 22:42:00
Ja wenn du ein Integer/word schiebst wird der Stackpointer nur um 2 Byte decrementiert, was u.u in asm zu Problemen führen kann. Aber ob nun 2 oder 4 Byte geschrieben werden hat keinen Geschwindigkeitsvorteil. Und VBA macht eh sein Ding, und richtet wohl erstmal jede Variable aus.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 29, 2020, 09:16:46
Zitat von: daolix am Oktober 28, 2020, 22:42:00Ja wenn du ein Integer/word schiebst wird der Stackpointer nur um 2 Byte decrementiert

Danke für den Hinweis, das war mir nicht klar.
Ich dachte der verschiebt bei 32 bit einfach immer um 4 Byte, und daher war ich der Meinung das die alten 2 Byte einfach stehen bleiben.

Funktioniert das aber bei der Übergabe an die Funktion nach dem selben Prinzip?
Weil dann müsste es bei einem falschen Parameter ja immer zu einem Fehler kommen.
Habs jetzt mal innerhalb von Delphi getestet.
Da wird bei Standard calling convention die Variable immer zuerst in Register geschrieben und dann immer die ganze Registerbreite in den Stack gepusht.
Daher sollte es tatsächlich egal sein, so lange man den Werte nicht nutzt.

Danke.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: markusxy am Oktober 29, 2020, 09:25:23
Zitat von: maddhin am Oktober 28, 2020, 17:38:00Boah. Ich habe mich schon entschuldigt

Wozu, es ist in keiner Weise relevant ob du nur kopierst oder nicht.
 
ZitatMuss so ein Kommentar sein?

Beim Kommentar geht es darum, dass man bei Win API ohne die Grundlagen nicht weiter kommt.
VBA so lange es nicht API betrifft, ist trivial. Für einen Programmierer eine Sache von zwei Tagen systematischer Lernarbeit bis er mit VBA programmieren kann. Ein Anfänger braucht natürlich länger. Der hat auch nicht Jahre in Grundlagen investiert.
Bei Nutzung von API steigt die Anforderung dann gleich um einige Tausend Prozent.
Das soll einem einfach klar sein, wenn man damit beginnt.
Wenn man die Zeit nicht hat, bezahlt man besser jemanden, der sich das erarbeitet.
Das ist keine Frage der Intelligenz, sondern wofür man seine Zeit einsetzen will.

Ich wäre froh gewesen, wenn man mir das vor Jahren klar gemacht hätte, ich wäre heute viel weiter.
So und das wars jetzt auch von meiner Seite.
Die Frage, ob der Rückgabewert die Kopie garantiert, konntest du ja aus dem Hilfe Zitat entnehmen.
Viel Spaß weiterhin.
Titel: Re: Access .Feld("Dateiname") <> Windows Dateiname
Beitrag von: maddhin am Oktober 29, 2020, 09:41:58
...dann bleibt mir mich für Eure Hilfe zu bedanken! @daolix @markus888