Excel VBA – wenn man mich nicht aufhält..

Eigentlich will ich doch nur die Breite eine Spalte ermitteln – wenn da nicht der Parameter wäre…

Da als Spaltenindex sowohl eine Zahl als auch eine Buchstabenkombination angegeben werden können, soll der Parameter für die Spalte vom Typ Variant sein, doch da ist viel Unheil möglich. Selbst wenn es sich dabei um einen Integer, Long oder String handelt, kann auch noch der Wert selbst falsch sein. So ist „ABC“ ein gültiger Spaltenindex, „ABBA“ aber nicht.

  1. Public Function getWidth(oWs As Worksheet, vColumn As Variant) As Double
  2. Const ciTypEmpty As Integer = 0
  3. Const ciTypNull As Integer = 1
  4. Const ciTypObject As Integer = 9
  5. Const csFunction As String = "modExcel.getWidth()"
  6. Const ciMinErrNumber As Integer = 1000
  7. Const ciTypeError As Integer = 13
  8. Const csTypeError As String = _
  9. "Der übergebene Parameter kann nicht als Spaltenindex interpretiert" & _
  10. " werden. Übergeben wurde: "
  11. Const ciErrArgumentNull As Integer = 20
  12. Const csErrArgumentNull As String = _
  13. "Statt Spaltenindex NULL übergeben."
  14. Const ciUnbekanntErr As Integer = 23
  15. Dim sFehler As String, iNummer As Long, sDescription As String
  16. Dim iTyp As Integer
  17. On Error GoTo Fehler
  18.  
  19. iTyp = VarType(vColumn)
  20.  
  21. If iTyp = ciTypEmpty Or iTyp = ciTypNull Or iTyp = ciTypObject Then _
  22. Err.Raise Number:=ciErrArgumentNull + ciMinErrNumber + vbObjectError, _
  23. Source:=csFunction, _
  24. Description:=csErrArgumentNull
  25.  
  26.  
  27. getWidth = oWs.Columns(vColumn).Width
  28.  
  29. Ende:
  30. On Error GoTo 0
  31. Exit Function
  32.  
  33. Fehler:
  34. sDescription = Err.Description
  35. iNummer = Err.Number
  36. On Error GoTo 0
  37. If iNummer = ciTypeError Then
  38. sFehler = csTypeError & CStr(vColumn)
  39. iNummer = ciTypeError + ciMinErrNumber + vbObjectError
  40. Err.Raise Number:=iNummer, Source:=csFunction, _
  41. Description:=sFehler
  42. Else
  43. sFehler = "Ein unbekannter Fehler wurde ausgelöst." & _
  44. "Womöglich war der Spaltenindex ungültig. Meldung: " & _
  45. sDescription
  46. iNummer =iNummer + ciMinErrNumber + vbObjectError
  47.  
  48. Err.Raise Number:=iNummer, Source:=csFunction, _
  49. Description:=sFehler
  50. End If
  51. GoTo Ende
  52. End Function

Der gewünschte Abgabewert wird einzig und allein in Zeile 27 ermittelt, der ganze Rest ist Fehlerbehandlung.

Bonuspunkte für jede(n), der oder die erkennt, dass der Mumpitz nicht mal wiederverwendbar ist.

25000 Primzahlen mit MS Word und VBA

Aus Langeweile habe ich ein Word-Makro geschrieben, welches die ersten 25000 Primzahlen berechnet. Die Primzahlen werden ans Ende eines Worddokuments geschrieben.

Womöglich ist es ja für den einen oder die andere von Interesse… ;-)

  1. Option Explicit
  2.  
  3. Sub Primzahlen()
  4. Const ciMax As Integer = 25000 ' 25000 Primzahlen
  5. Dim aPrimzahlen() As Long
  6. Dim iPruefling As Long, iIndex As Integer
  7. Dim iAnzahl As Long ' Long, damit die Funktion "inc" funktioniert.
  8. Dim bTeilbar As Boolean, oParagraf As Paragraph
  9. Dim iWurzel As Long
  10. iAnzahl = 0
  11. iPruefling = 2
  12. ReDim aPrimzahlen(1 To ciMax)
  13. While iAnzahl < ciMax
  14. bTeilbar = False
  15. iWurzel = Round(Sqr(iPruefling), 0) + 1 ' Plus 1 wegen eventuellem Abrunden
  16. For iIndex = 1 To iAnzahl
  17. ' Es reicht, bis zur Quadratwurzel des Prüflings zu testen.
  18. If aPrimzahlen(iIndex) >= iWurzel Then
  19. Exit For
  20. End If
  21. If iPruefling Mod aPrimzahlen(iIndex) = 0 Then
  22. bTeilbar = True
  23. Exit For
  24. End If
  25. Next
  26. If Not bTeilbar Then
  27. inc iAnzahl
  28. aPrimzahlen(iAnzahl) = iPruefling
  29. End If
  30. inc iPruefling
  31. Wend
  32. For iIndex = LBound(aPrimzahlen) To UBound(aPrimzahlen)
  33. Set oParagraf = ActiveDocument.Paragraphs.Add(ActiveDocument.Paragraphs.Last.Range)
  34. oParagraf.Range.Text = CStr(aPrimzahlen(iIndex)) & vbCrLf
  35. Set oParagraf = Nothing ' Nötig; nur: Warum?
  36. Next
  37. End Sub
  38.  
  39. Sub inc(ByRef iZahl As Long)
  40. iZahl = iZahl + 1
  41. End Sub

Ein neues Plugin: Eine abstrakte Klasse für Elemente mit Zeichenkette

Etwas abstrakt bleibt es doch noch. Es gibt im Projekt zwei Arten von Elementen: Die einen enthalten andere Elemente, andere enthalten Text. Daher wird es zwei unterschiedliche Arten geben, wie zum einen getContent() funktioniert, und wie der Inhalt der Instanz hinzugefügt wird.

Zwei weitere abstrakte Klassen bieten sich da an, die die entsprechenden Methoden implementieren.

ALeo_Sitemap_Xml_StringContent

  1. <?php
  2. /*
  3. Wird von Elementen erweitert, die nur eine
  4. Zeichenkette als Inhalt haben.
  5. Author: Hermann J. Leopold
  6. Email: hermann.leopold@leopoldnet.de
  7. Date: 2017-02-10
  8. /*
  9. require_once('ALeo_Sitemap_Xml.php');
  10. abstract class ALeo_Sitemap_Xml_StringContent extends ALeo_Sitemap_Xml
  11. {
  12. // Darum geht es: Die Zeichenkette
  13. private $_sContent;
  14. /*
  15. Hier wird der Inhalt übergeben.
  16. /*
  17. public function setStringContent($sString)
  18. {
  19. /* Generell dienen die Ausnahmefehler dazu,
  20. Programmfehler zu vermeiden.
  21. /*
  22. // NULL ist nicht erlaubt.
  23. if (is_null($sString)) {
  24. throw new InvalidArgumentException("NULL statt Zeichenkette");
  25. }
  26. // Der Typ muss String sein,
  27. // oder konvertierbar sein.
  28. if (!is_string($sString)) {
  29. throw new InvalidArgumentException("Keine Zeichenkette (aka String)");
  30. }
  31. // Alles OK
  32. $this->_sContent = $sString;
  33. }
  34. /*
  35. Und hier wird der Inhalt wieder zurück
  36. gegeben.
  37. /*
  38. protected function getContent()
  39. {
  40. if (is_null ($this->_sContent)) {
  41. $this->_sContent = '';
  42. }
  43. // Damit nichts passiert, werden Zeichen, die
  44. // XML verwendet, umgewandelt.
  45. return html_entity_decode($this->_sContent, ENT_XML1);
  46. }
  47. }

ALeo_Sitemap_Xml_StringContent.php

Da ich versuche, den Code vernünftig im Blog darzustellen, dürfen die Codezeilen nicht zu breit werden. Auch soll der Code gut lesbar sein; daher sind einige Details etwas umständlich geraten.

Ein neues Plugin: Eine abstrakte Klasse für XML-Elemente

Weil es Spaß und Sinn macht: Eine abstrakte Klasse für XML-Elemente.

Der Vorteil einer abstrakten Klasse: Methoden, die sich nicht ändern, müssen nicht bei jeder abgeleiteten Klasse wiederholt im Code erscheinen. Es könnten auch Traits verwendet werden, jedoch sind sie nicht für den Fall gedacht, dass die Methoden zur gleichen Klassenhierarchie gehören.

ALeo_Sitemap_Xml

  1. <?php
  2. /*
  3. Hier werden bereits alle Methoden implementiert,
  4. die alle Klasssen vom Typ 'ILeo_Sitemap_Xml'
  5. gemeinsam haben.
  6. Author: Hermann J. Leopold
  7. Email: hermann.leopold@leopoldnet.de
  8. Date: 2017-02-08
  9. /*
  10. require_once('ILeo_Sitemap_Xml.php');
  11. abstract class ALeo_Sitemap_Xml implements ILeo_Sitemap_Xml
  12. {
  13. /* Hier muss bei den konkreten Klassen
  14. der Name aka Symbol zurück
  15. gegeben werden. /*
  16. protected abstract function getSymbol();
  17. /* Hier wird der Inhalt des Elements
  18. zurück gegeben. Da dies entweder
  19. Text oder andere Elemente sein
  20. können, muss auch diese Methode von
  21. jedem Element überschrieben werden.
  22. /*
  23. protected abstract function getContent();
  24. /* Mit den Attributen des Elements könnte
  25. man viel Spaß haben; eigene Klassen,
  26. schön mit addAttribute eine Liste
  27. hinzufügen...
  28. Da aber nur ein Element überhaupt
  29. Attribute hat, die dazu noch stets
  30. gleich sind, habe ich diese
  31. einfache Methode bevorzugt. /*
  32. protected function getAttributes()
  33. {
  34. // Gibt es Attribute, muss
  35. // diese Methode überschrieben
  36. // werden
  37. return '';
  38. }
  39. /* Hier die wichtige konkrete
  40. Klasse -> das Element wird erstellt /*
  41. public function toString()
  42. {
  43. // Die vielen Zeilen haben einen Grund:
  44. // Es sieht so besser im Blog aus.. 😄
  45. $sReturn = ILeo_Sitemap_Xml::csTagAuf . $this->getSymbol();
  46. $sReturn .= $this->getAttributes() . ILeo_Sitemap_Xml::csTagZu;
  47. $sReturn .= $this->getContent();
  48. $sReturn .= ILeo_Sitemap_Xml::csTagAuf;
  49. $sReturn .= '/' . $this->getSymbol();
  50. $sReturn .= ILeo_Sitemap_Xml::csTagZu;
  51. return $sReturn;
  52. }
  53. }

ALeo_Sitemap_Xml.php

Dank der getContent()-"Problematik" kann ich noch zwei abstrakte Klassen schreiben. Hurra – und ich sollte mal ein Klassendiagramm erstellen. Es könnte sich lohnen 😃

Ein neues Plugin: Schnittstellen definieren (Ⅱ)

ILeo_Sitemap_Page

Bei den Daten der einzelnen Seiten habe ich wie besprochen auf die Elemente „changefreq“ und „priority“ verzichtet. Dafür können so viele Bilder wie gewünscht hinzugefügt werden.

  1. <?php
  2. /*
  3. Deklariert Methoden und Konstanten für einen
  4. Seiteneintrag der sitemap.xml
  5. Author: Hermann J. Leopold
  6. Email: hermann.leopold@leopoldnet.de
  7. Date: 2017-02-01
  8. /*
  9. interface ILeo_Sitemap_Page
  10. {
  11. // Symbol des Url-Elements:
  12. const csUrlSymbol = "url";
  13. // Symbol für das Location-Elements
  14. const csLocationSymbol = "loc";
  15. // Symbol für das Zuletzt-geändert-Element
  16. const csLastModSymbol = "lastmod";
  17. // Hinzufügen der Bilder
  18. // Was ein Bild ausmacht, wird in der
  19. // ILeo_Sitemap_Image festgelegt.
  20. function addImage(ILeo_Sitemap_Image $oImage);
  21. // URL der Seite angeben.
  22. function setLocation($sUrl);
  23. // Datum der letzten Änderung angeben.
  24. function setLastMod($sLastMod);
  25. }

ILeo_Sitemap_Image

Die wichtigste Neuerung zum alten Plugin sitzt hier. Für jedes Bild können mehrere Texte hinterlegt werden. Die Möglichkeit, einen Link zur Lizenz zu hinterlegen habe ich behalten; wer weiß, ob es noch für was gut sein kann.

  1. <?php
  2. /*
  3. Deklariert Methoden und Konstanten für einen
  4. Bildeintrag in die sitemap.xml
  5. Author: Hermann J. Leopold
  6. Email: hermann.leopold@leopoldnet.de
  7. Date: 2017-02-02
  8. /*
  9. interface ILeo_Sitemap_Image
  10. {
  11. // Symbol des Image-Elements
  12. const csImageSymbol = "image:image";
  13. // Symbol des Location-Elements
  14. const csLocSymbol = "image:loc";
  15. // Symbol des Untertitel-Elements
  16. const csCaptionSymbol = "image:caption";
  17. // Symbol des Aufnahmeort-Elements
  18. const csGeoLoaction = "image:geo_location";
  19. // Symbol des Titel-Elements
  20. const csTitle = "image:title";
  21. // Symbol des Lizens-Elements
  22. const csLicense = "<image:license>";
  23. // Ich wechsle hier von Loc auf Url,
  24. // um Verwechslungen zu vermeiden.
  25. function setUrl($sUrl);
  26. // Angabe des Untertitels
  27. function setCaption($sCaption);
  28. // Angabe des Aufnahmeorts
  29. function setGeoLocation($sGeoLocation);
  30. // Angabe des Bildtitels
  31. function setTitle($sTitle);
  32. // Angabe der Lizens-Elements
  33. function setLicense($sLicense);
  34. }

Jetzt wird es Zeit für Konkretes.

Ein neues Plugin: Schnittstellen definieren (Ⅰ)

Ich mag Schnittstellen. Schnittstellen (engl. Interfaces) sind ein Instrument objektorientierter Programmierung und sollen – salopp gesagt – sicherstellen, dass verschiedene Programmteile verlässlich zusammen arbeiten können.

Dies spielt besonders dann eine Rolle, wenn Erweiterungen oder ähnliches später und/oder von anderen erstellt werden sollen.

Schnittstellen wären auch eine Möglichkeit für WordPress gewesen, jedoch wurde darauf verzichtet; wohl auch, da PHP keine reine objektorientierte Programmiersprache ist. Auch werden die meisten bei der Entwicklung eines Plugins darauf verzichten, ich jedoch nicht.

Mir gefällt, dass ich auf diese Weise Ideen und Anforderungen in Code festschreiben kann, ohne bereits ins Detail gehen zu müssen; und es entstehen Arbeitsanweisungen für die konkreten Klassen.

Folgende Schnittstellen sind mir in den Sinn gekommen:

ILeo_Sitemap_Xml

  1. <?php
  2. /*
  3. Deklariert Methoden und Konstanten für eine XML-Datei
  4. Author: Hermann J. Leopold
  5. Email: hermann.leopold@leopoldnet.de
  6. Date: 2017-01-30
  7. /*
  8. interface ILeo_Sitemap_Xml
  9. {
  10. // Ich mag die Idee, dass ein ständig gleich benötigtes Zeichen einmal festgelegt wird.
  11. const csTagAuf = "<";
  12. // Hier kann festgelegt werden, ob es nach einem XML-Tag einen Zeichenumbruch geben soll.
  13. // const csTagZu = ">";
  14. const csTagZu = ">\n";
  15. // Das Wichtigste: Das XML-Dokument als String
  16. function toString();
  17. }

Datei: ILeo_Sitemap_Xml.php

Natürlich wird so eine XML-Datei nicht annährend vollständig beschrieben, doch für das Plugin reicht es. Da sich Inhalt und Attribute der Elemente unterscheiden, brauche ich dafür keine Methoden vorschreiben. Das Festlegen der spitzen Klammern als Konstanten ist nicht Humor, sondern es bringt Vorteile:

  • Einmal festeglegt, kann man sie nur noch vergessen, aber nicht verwechseln. Da es auch nur eine Methode geben wird, in der die Klammern verwendet werden, sind die Tags stets richtig (oder stets falsch).
  • Bei der schließenden Klammer kann festgelegt werden, ob es Zeilenumbrüche geben soll. Diese machen zwar das XML-Dokument lesbarer, aber auch größer.

ILeo_Sitemap

  1. <?php
  2. /*
  3. Deklariert Methoden und Konstanten für eine sitemap.xml
  4. Author: Hermann J. Leopold
  5. Email: hermann.leopold@leopoldnet.de
  6. Date: 2017-01-30
  7. Verzeiht mein Denglish..
  8. /*
  9. interface ILeo_Sitemap
  10. {
  11. // Symbolname des Wurzelelements
  12. const csRootSymbol = "urlset";
  13. // Der übliche Dateiname, sollte er nicht ersetzt werden.
  14. const csDefaultName = "sitemap.xml";
  15. // Legt den Namen der Datei fest; bsp.: sitemap.xml
  16. function setSitemapName($sName);
  17. // Gibt die Url der Website an; bsp.: leopoldnet.de
  18. // Wichtig, da die Verweise meist relativ sind, aber absolut sein müssen.
  19. function setSiteUrl($sUrl);
  20. // Der Pfad zum Speichern wird auch benötigt.
  21. function setPath($sPath);
  22. // Das Wichtigste: Seiten der sitemap hinzufügen.
  23. // Was eine Seite ausmacht, wird in der ILeo_Sitemap_Page festgelegt.
  24. function addPage(ILeo_Sitemap_Page $oPage);
  25. }

Datei: ILeo_Sitemap.php

Der Name der Datei ist nicht festgelegt; somit wäre es möglich, für verschiedene Suchmaschinen unterschiedliche Sitemaps zu erstellen. Auch der Speicherpfad ist (im Rahmen des Möglichen) wählbar; es muss nicht das Wurzelverzeichnis sein.

Wichtig ist das hinzufügen der Seiten. Die dafür vorgesehene Methode ‚addPage()‘ erwartet einen Parameter vom (noch) unbekannten Typ ILeo_Sitemap_Page. Alles, was eine Seite (aka Page) ausmacht, wird in der nächsten Schnittstelle deklariert.

Ein neues Sitemap-Plugin muss her!

Eine Möglichkeit, einer Suchmaschine alle Seiten mitzuteilen, ist die Bereitstellung einer Datei namens sitemap.xml. Diese Datei wird von WordPress nicht automatisch erstellt; doch es gibt Plugins, die den Job erledigen. Da mir vor vielen Jahren keines gut gefiel, hatte ich ein eigenes geschrieben.

Der übliche Aufbau einer sitemap.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  3. <url>
  4. <loc>http://www.example.com/</loc>
  5. <lastmod>2005-01-01</lastmod>
  6. <changefreq>monthly</changefreq>
  7. <priority>0.8</priority>
  8. </url>
  9. </urlset>

Beispiel von sitemaps.org

Unterhalb von <url> ist nur das "loc"-Element Pflicht, und im alten Plugin hatte ich auf die optionalen Elemente verzichtet.

"changefreq" ist bei einem Blog sinnlos; auch glaube ich nicht, dass sich irgendeine Suchmaschine davon beeinflussen lässt.

"priority" ist schon berüchtigt für den Schindluder, der damit getrieben wurde. Daher denke ich nicht, dass sich Suchmaschinen dafür interessieren.

"lastmod" hingegen lässt sich ermitteln und könnte nützlich sein. Ich setze es mit auf die Todo-Liste.

Somit sieht die bisherige sitemap.xml reichlich langweilig aus:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  3. <url>
  4. <loc>http://leopoldnet.de/</loc>
  5. </url>
  6. <url>
  7. <loc>http://leopoldnet.de/impressum</loc>
  8. </url>
  9. <url>
  10. <loc>http://leopoldnet.de/start-meiner-website-als-blog</loc>
  11. </url>
  12. <url>
  13. <loc>http://leopoldnet.de/freezing-leafs</loc>
  14. </url>
  15. <!-- und so weiter -->

Die sitemap.xml von leopoldnet.de vom 28.01.2017

Und noch etwas fehlt: Die Übersichtsseiten der Schlagwörter und Kategorien – auf die Todo-Liste damit.

Die Bildererweiterung von Google

Google hat die sitemap.xml um Bildinformationen erweitert; da dies auf sitemaps.org nicht beschrieben wird, könnte es eine Eigeninitiative gewesen sein. Die Bilder müssen einer Seite zugewiesen werden; pro Seite sind 1000 Bilder erlaubt. Im Beispiel sähe es so aus:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
  3. xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
  4. <url>
  5. <loc>http://ihrebeispielurl.de/beispiel.html</loc>
  6. <image:image>
  7. <image:loc>http://ihrebeispielurl.de/bild.jpg</image:loc>
  8. </image:image>
  9. <image:image>
  10. <image:loc>http://ihrebeispielurl.de/foto.jpg</image:loc>
  11. </image:image>
  12. </url>
  13. </urlset>

Beispiel von support.google.com

Sieht auch nicht besser aus, doch hier gibt es spannende, optionale Elemente:

<image:caption>

Dieses Element sieht Google für den Untertitel vor; WordPress wiederum hat ein Feld "Alternativtext". Auch ist das Feld "Beschriftung" denkbar.

<image:geo_location>

Anders als ich dachte, sind hier keine Koordinaten gewünscht, sondern der Ort als Klartext; zum Beispiel "Bremen, Deutschland". Leider stellt WordPress für Medien kein entsprechendes Feld zur Verfügung, die Information könnte ich unter "Beschreibung" einfügen, wobei eine Art Auszeichnung nötig wäre: loc{Bremen, Deutschland} oder so.

<image:title>

Hierfür scheint das Feld "Titel" wie geschaffen.

<image:license>

Sicher interessant, aber derzeit für mich nicht.

Ganz klar: Hier lassen sich deutlich mehr Informationen über die Sitemap übermitteln.

Die Todo-Liste

  • "lastmod" hinzufügen
  • Schlagwörter und Kategorien berücksichtigen
  • Bilder mit Beschreibungen hinzufügen

Dann mache ich mich mal an die Arbeit

Google vergibt

Ergebnis Bildersuche
Für eine größere Ansicht auf das Bild klicken.

Als mir das Instagram-Plugin zunächst den ganzen Text inklusive der Hashtags als Beitragstitel importierte, hat Google meine Website aus der Bildersuche entfernt; denn WordPress nutzt den Beitragstitel als Alternativtext für das Bild, und Google wertet den Alternativtext für die Bildersuche aus. Bei den ganzen Hashtags vermutete der Algorithmus Keyword-Spamming und zog die Konsequenz.

Etwa eine Woche nachdem ich die ganzen Beiträge nachgearbeitet hatte, war Google beruhigt und die Bilder wieder da: Google vergibt den Sündigern 😄