10/12/2019

Einmal und nie wieder - ein Gespräch mit einem AfD-Unterstützer

Wenn ich gewusst hätte, dass die Dame AfD-Unterstützerin ist, hätte ich das Thema Politik sofort abgewehrt, oder erst gar kein Gespräch begonnen, und meine Zeit angenehmer verbracht. Nun ja, man weiß es vorher nicht, und außerdem soll man ja keine Vorurteile pflegen und vorschnell urteilen - zumindest außerhalb der AfD nicht.

Das Thema kam jedenfalls schnell auf, denn sie hatte vor kurzem AfD gewählt. Aus Protest, nicht aus Überzeugung, wie sie sagte - oder vorgab.

Ich dachte: prima, da kann ich einer Irrläuferin erklären, warum die AfD keine geeignete Partei für den Ausdruck "allgemeiner Unzufriedenheit"  ist. Ja, von wegen!

Es kristallisierte sich schnell heraus, dass Flüchtlinge und Ausländer Schuld an mehr oder weniger allen Verbrechen in Deutschland sind. Dass die Kriminalstatistik das widerlegt und die sogar Horst Seehofer einräumen musste (da haben sich ihm sicher alle Nackenhaare aufgestellt), war kein Argument: erstens ist das gefälscht, und zweitens, selbst wenn das stimmen sollte, dann sind doch die ganzen Verbrecher, die in der Statistik als Deutsche gezählt werden, nur Eingebürgerte, also doch Ausländer, und die Einbürgerung fremder Elemente müsse man sowieso unterbinden.

Belege? Keine. Aber was ins Bild passt, wird nicht hinterfragt. Gegenüber anderen Informationen gibt man sich aber gerne kritisch.

Wir kamen leider auch auf das Thema Religion, und die Dame behauptete steif und fest, dass die Unterdrückung der Frau und der Krieg gegen Andersgläubige im Koran festgeschrieben sei. Ich schlug vor, gemeinsam einen Koran zu kaufen, und einmal die Probe aufs Exempel zu machen. Aber so leicht gewinnt man gegen die Gehirngewaschenen nicht: wenn das da nicht drin stünde, dann wäre das eben eine adaptierte Ausgabe - im Original steht das nämlich drin!

Belege? Keine. Oh, ich wiederhole mich ... Nun, das Original hat sie nie gesehen, aber die AfD hat glaubhaft versichert ... und wer würde das schon hinterfragen?

Ich fand einen Vorwand, das "Gespräch" zu beenden und werde ähnliche Unterhaltungen im Keim ersticken. Gerne höre ich mir andere Meinungen an und stelle meine eigene in Frage, aber bei den AfD-Anhängern scheint sich das Hirn bereits in einer Endlosschleife zu befinden.

Traurig, was diese "Partei" bei manchen Menschen anrichten kann.

9/25/2019

Radio Gewinnspiele

Bei uns gab es vor einiger Zeit ein Radio-Gewinnspiel, das über Wochen lief, und bei dem man iPhones gewinnen konnte.

Ich mache bei so etwas eigentlich nie mit, aber da ich grundsätzlich neugierig bin,  Zeit für ein Experiment hatte und ein Bekannter - im Gegensatz zu mir - voll auf iPhones abfährt, habe ich mitgemacht.

Das Spiel lief so: es wurde ein Ausschnitt aus einem Song gespielt, der nur eine Sekunde lang war. Wenn man ihn erraten konnte, die entsprechende Ansage gespielt wurde, und man dann noch telefonisch durchkam, hatte man eine Chance auf das iPhone.

Die Songs zu identifizieren, war sehr schwer, aber wenn man Zeit hat, nimmt man die eine Sekunde auf und geht dann die Top 40 durch. Das ist machbar, dauert eben eine Zeit. Sound-Suche geht bei dem kurzen Schnipsel natürlich nicht, das wäre ja auch zu einfach.

Jedenfalls ist es mir bei den meisten Songs gelungen, sie zu identifizieren, lange bevor sie erraten wurden. Manchmal Tage vorher!

Die Runden gingen oft extrem lang - klar, iPhones sind teuer, und man will die Hörer ja möglichst lange "fesseln". Dazu gab es verschiedene Verzögerungstaktiken:
  • die Ansage zum Anrufen wurde oft verkürzt (als Werbung für die Aktion) gespielt. Wenn man dann anrief, kam man meist durch, dann hieß es aber, dass das nicht die korrekte Ansage war. Gerne würde man aber meine Adresse und Telefonnummer aufnehmen. Aber sicher!
  • Wenn der richtige Zeitpunkz zum Anrufen gekommen war, war immer besetzt. Und zwar mindestens 10 Minuten lang, bis der glückliche Anrufer, der es geschafft hatte, ins Studio durchgestellt wurde. Un dann war die Antwort fast immer falsch. Warum diese Häufung falscher Antworten? Das erklär vielleicht der nächste Punkt.
  • Kam man tatsächlich einmal zum richtigen Zeitpunkt durch, dann wurde man nach seiner Lösung gefragt. Diese durfte man laut Teilnahmebedingungen nach dem Durchstellen ins Studio auch nicht mehr ändern. Es wurden natürlich mehrere Anrufer angenommen, dann konnte man nochmal aussieben, wer tatsächlich im Studio ankommt. Und Überraschung, das war in der Regel nicht der, mit der richtigen Antwort. Zufall?
Ich bin bei meinen hunderten von Arufversuchen mit Wahwiederholung und mehreren Telefonen, tatsächlich mehrmals zur richtigen Zeit und mit der richtigen Antwort durchgekommen. Ins Studio kam ich natürlich nie.

Auf diese Weise hat der Sender mit 10 zu vergebenden iPhones mindestens 8 Wochen lang dieses Spiel gespielt.

Für mich eine interessante Erfahrung, für meinen Bekannten tut es mir leid. Zum Glück konnte ich gut nebenher arbeiten, so dass sich mein Zeitaufwand in Grenzen hielt.

Fazit: wer da anruft, glaubt auch an den Weihnachtsmann!



Und wenn ich schon dabei bin, da gibt es noch eine für den Sender komplett kostenlose Masche:

Drei Songs werden angespielt, und die Hörer dürfen anrufen, welcher davon komplett gespielt wird. Das Ganze wird dann als aufregender Wettlauf moderiert.

Warum sollte man da anrufen? Die Songs werden doch sowieso alle gespielt, die Anrufer haben bestenfalls Einfluss auf die Reihenfolge.

Und wenn ich den Song unbedingt hören will, dann rufe ich nicht an, dann streame ich mir den. Und das auch noch frei von Werbung und wenig originellem Gelaber der Moderatoren.


Dear Boris Johnson

Dear Mr Johnson,

When a court decides against you, and you still claim that you are right an the court is wrong, it only shows that you are stubborn and not fit for PM.

I bet, if there is an annoucement on the radio about a wrong-way driver, and there are hunreds of cars coming your way, you will just claim that it is not one, but hundreds of worng-way drivers.

Well done, Boom-Boom-Boris. Get a grip on yourself and use that brain of yours at least intermittently.

To end with a positive note: I am really glad (and I was not always certain) that the British system is able to protect democracy from clowns.

8/27/2019

Nepp: Amazon Pantry

Ich bin gerade auf das Angebot Amazon Pantry gestoßen, das ganz exklusiv Prime Kunden angeboten wird. Dann muss es ja gut sein! Wirklich?

Bei den Versandkosten aufpassen! Auch für Prime Kunden fallen hier Versandkosten in Höhe von 3,99 an. Das lässt sich vermeiden, wenn man für mindesten 90€ oder mindestens 4 Artikel aus einer Promotion-Liste kauft - will ich aber nicht.

Gut gemacht, Amazon, fast wäre ich Euch auf den Leim gegangen. Schnell aus dem Warenkorb entfernt, ein anderes, gleichwertiges, und nur auf den ersten Blick teureres Produkt genommen, Geld gespart.

7/10/2019

Die traurige Geschichte von der deutschen PKW-Maut


Eine Schnapsidee macht (fast) Karriere



Wie eine lokale Kleinst-Partei (CSU) die komplette deutsche und fast die europäische Legislative mit erpresserischen Mitteln aushebelt


Vor 2013 – Die Idee

Die Idee (aka Hirngespinst) der „Ausländermaut“ von Horst Seehofer wird belächelt.
Seit 2010 wird Seehofer mit diesem Geistesblitz immer wieder zitiert, er kokettiert gerne mit der Entrüstung der Gegner.
Zunächst versuchte er es mit „seinem“ Bundesverkehrsminister Peter Ramsauer (CSU), der jedoch an den Koalitionspartnern und der Kanzlerin immer wieder scheitert.

2013 – Wahlkampf und Koalitionsvertrag

Seehofer kündigt im Wahlkamps die „Ausländermaut“ an - Konformität mit EU Recht wird schon jetzt angezweifelt

11. August            Seehofer macht Maut zur Bedingung für Koalitionsvertrag: „Ich unterschreibe keinen Koalitionsvertrag, in dem das nicht drinsteht“
1. September        Merkel: „Mit mir wird es keine PKW-Maut geben.“
14. Dezember       die Maut steht im Koalitionsvertrag
17. Dezember       Alexander Dobrindt (CSU) wird Bundesverkehsminister, und damit Seehofers neuer verlängerter Arm zur Durchsetzung der Maut. Ramsauer wird wegen fehlendem Erfolg in die Wüste geschickt.

2014 – Koalitionsstreit und Empfehlung der EU Kommission

26. Juli:               Seehofer stellt die Koalition in Frage: „Käme die Maut nicht, würde sich die Frage der Legitimation der Koalition ebenfalls stellen.“
6. September        Schäuble erklärt, er halte hält die Maut für ein Minusgeschäft. Seehofer wirft ihm „Sabotage“ vor.
November            die EU Kommission empfiehlt Entkopplung von Maut und Steuersenkung. Alexander Dobrindt (CSU) lehnt ab

2015 - Vertragsverletzungsverfahren

18. Juni                die EU Kommission strengt ein Vertragsverletzungsverfahren beim EuGH, nachdem Jean-Claude Juncker "erhebliche Zweifel" angemeldet hatte, dass mit dem Gesetz EU-Ausländer nicht diskriminiert würden.

Dobrindt und Seehofer machen ungerührt weiter.
Sonst ist 2015 (und sogar bis Ende 2016) nicht viel passiert, dennoch hat Dobrindt seine übrigen Aufgaben nicht oder nur sehr nachlässig wahrgenommen, z. B. den Ausbau des Breitbandnetzes.

2016 – Die EU Kommission knickt ein

Dezember            Dobrindt einigt sich mit EU-Verkehrskommissarin Violeta Bulc und ändert den Gesetzentwurf: stärkere Staffelung der Kurzzeittarife, höhere Steuersenkung für Euro-6-Autos

Die EU Kommission setzt Vertragsverletzungsverfahren am EuGH aus

2017 – Eine Serie von Erpressungen

Ende März          Seehofer droht mit Koalitionsbruch, wenn der Koalitionsvertrag nicht eingehalten
                           wird – die Regierung stimmt Maut zu, um die Koalition zu retten, obwohl die
                            Mehrheit dagegen ist.
24. März              Bundestag verabschiedet aus „Koalitionsräson“ das Maut-Gesetz, obwohl es vorher keine Mehrheit gab.

Widerstand formiert sich im Bundesrat – ein Zünglein an der Waage ist Thüringen
Ende März           Seehofer droht (speziell Thüringen) mit dem Ende der Verhandlungen zum Länderfinanzausgleich
31. März              direkt vor Abstimmung im Bundesrat erhält Thüringen von der CSU (in Form von Verkehrsminister Dobrindt) die Zusage zum Ausbau der Bahntrecken in Thüringen.

Thüringens Ministerpräsident Bodo Ramelow (Linke) ändert seine Haltung zur Maut.

Der Bundesrat verabschiedet das Maut-Gesetz, nach dem Umfallen Thüringens sogar ohne Anrufung des Vermittlungsausschusses.
Oktober               Die EU Kommission zieht ihre Klage vor dem EuGH komplett zurück.

Österreich (unterstützt von den Niederlanden) verklagen Deutschland wegen Vertragsverletzung beim EuGH, nachdem die EU Kommission keine Stellungnahme zu ihrem Rückzug abgab
24. Oktober         Dobrindts Amtszeit endet

2018 - Schichtwechsel

14. März              Andreas Scheuer (CSU) wird Bundeserkehrsminister und führt das „Vermächtnis“ der Maut beherzt weiter.

2019 – Die Maut scheitert (zunächst)

18. Juni                      Der EuGH lehnt Maut ab.

Scheuer: „Die Pkw-Maut ist in dieser Form leider vom Tisch“. Scheuer sagt weiter, das EuGH-Urteil sei „überraschend“ gekommen.

Folgen

Seit 2014 wurden vom deutschen Staat 53,6 Mrd. Euro in die Maut investiert.
Dazu kommen Schadensersatzforderungen (geschätzte Höhe im dreistelliger Millionenbereich) der beauftragten Firmen.

Scheuer und Merkel verteidigen die verfrühten Investitionen, man könne ja nicht bei jeder politischen Entscheidung auf die Gerichte warten.

Da schon von Anbeginn (2013) jedem, der es wissen wollte, klar sein konnte, dass die Maut gegen EU Recht verstößt, könnte man dies den beteiligten Personen m. E. als Veruntreuung auslegen.
Der Bundeshaushalt hat ein Loch von 350-500 Mio. Euro, weil man fest mit der Maut rechnete, und sie sich schöngerechnet hat – sogar Herr Schäuble.

Beurteilung

Es ist erschreckend und dem Glauben an die demokratisch gewählten Instanzen sehr abträglich, dass es einer kleinen (2013: 7,4%) lokale Partei gelungen ist, das Gesetz in Deutschland durch alle Instanzen der Legislative zu bringen, gegen deren besseres Wissen und Großteiles auch gegen deren Überzeugung.

Dass auch die EU-Kommission eingeknickt ist, ist nicht wirklich nachvollziehbar.
Hätte der EuGH nicht so geurteilt, wäre dieser unerhörte Vorgang eine Carte Blanche für die großen EU-Staaten, sich nicht um EU-Recht scheren zu müssen!
Offensichtlich ist der EuGH in diese Kette der Ereignisse die einzige unabhängige Instanz.

Ausblick - was kommt als nächstes?

Maut ohne Steuersenkung – wir wollten ja, die EU und evtl. die Österreicher (das hat die CSU ja schon immer gesagt) sind schuld.
Kein Umweltschutz.

6/12/2019

Populate Fritz Adressbuch from owncloud/nextcloud or any other vcard source

I am a heavy user of Fritz!Fax . I know this is an antique technology, but there a still some good reasons (legal) for using it.

Fax numbers can be used from Fritz Adressbuch, but I hate manually typing in fax numbers - you may call me lazy :-)

But I like coding, so I created a small command line application that can import my fax numbers from my owncloud address book.

Running this from time to time (or even on a schedule) ensures that I have the latest fax numbers from all my contacts at hand when I need them.

If you would like to use the tool, help yourself to it here. It requires .NET framework 4.6.1 or later. I do not provide an installer for such a simple tool, just download, extract, run.

When you run it without any parameters, it will show you an explanation how it works. No guarantees, but it works for me. You can use any vcard source (file or web) containing one or multiple vcards.

6/08/2019

Der Titel "Die Zerstörung der CDU" wird falsch interpretirert

Die Kommentare zu Rezos Video gehen merkwürdigerweise alle davon aus, dass der Titel des Videos dazu aufrufen soll, die CDU zu zerstören.

Rein grammatikalisch gibt der Titel diese Interpretation auch her.

Allerdings gibt es eine alternative Interpretation des Genitivs: es muss ja kein Genitivus Objectivus, sondern kann auch ein Genitivus Subjectivus sein, in welchem Fall die Interpretation lauten würde, dass es um die Zerstörung geht, die die CDU (unter anderem an unserer Umwelt) verübt.

Wer das Video gesehen hat, kann eigentlich nur zu letzterer Interpretation gelangen.

Aber natürlich brauchen Presse, Medien und Politiker Aufreger, und da passt die erste Interpretation besser ins Bild.

Ach und wenn ich schon beim Thema bin: Paul Ziemiak, CDU-Generalsekretär, spricht im Zusammenhang mit dem Video von "Pseudofakten". Ist das die deutsche Übersetzung von "fake news"? Ist Trump unter deutschen Politikern jetzt auch schon salonfähig geworden? Um es mit Trump zu sagen: "So sad!"

6/01/2019

Grandstream XML Address Book from OwnCloud

I have all my contacts stored in OwnCloud. Obviously it would be nice to have them available on my Grandstream phones.

I wrote the below script to read the contacts from OwnCloud (VCard format) and create an XML structure that Grandstream phones can process.

If you need something similar help yourself to the code below. I am sure it is far from perfect, but ir works for me. No warranty, of course.

If you want to use it, just replace the URL, username and password and put it on a web server runnung PHP.  Finally point your phone to the URL of the file and have it load your contacts.

This may or may not work with NextCloud too. My guess is that is will.

The nice thing about this approach is that it does not involve exporting and importing, it just grabs the latest information whenever you choose lo load the address book on your phone.

function geturl($url, $username, $password)
{
    $ch = curl_init();
    
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, TRUE);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_VERBOSE, 0);
    curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
    $a = curl_exec($ch);


    if(false === $a)
    {
        var_dump(curl_error($ch), curl_errno($ch));
    }


    /*
    $info = curl_getinfo($ch);
    print_r($info)
    */
    
    return $a;
}


$data = geturl("https://owncloud.torabli.eu:444/remote.php/dav/addressbooks/users/kian/contacts/?export", "kian", "Omposele1");
//$data = fakeData();
//var_dump($data);


if(false === $data)
{
    echo 'No data';
    exit('No data');
}


header('Content-Type: application/xml; charset=utf-8');
echo '' . "\r\n";
echo '' . "\r\n";


$vcards = null;
preg_match_all("/BEGIN:VCARD.*?END:VCARD/s",
        $data, $vcards);
//echo "number of vcards: " . strval(count($vcards[0]));


foreach($vcards[0] as $vcard)
{
    processvcard($vcard);
}


function processvcard($vc)
{
    $vc = str_replace('\r\n', "\r\n", $vc);
    $names = null;
    preg_match_all('/^N:(.*?);(.*?);.*$/m',
        $vc, $names);
    $firstname = $names[2][0];
    $lastname = $names[1][0];


    $phones = null;
    preg_match_all('/^TEL;TYPE=(.*?)((,|;).*)?:(.*)\r?$/m',
        $vc, $phones);
    
    /*
    match first group using
    /^CATEGORIES:(.*?)(,.*)?$/m
    */
    
    foreach($phones[0] as $i => $phone)
    {
        $phonetype = strtolower($phones[1][$i]);
        $type = strtoupper(substr($phonetype, 0, 1));
        $phonenumber = my_replace($phones[4][$i]);
        
        if(in_array($phonetype, array("work", "cell", "home")) &&
            (trim($lastname) !== "" || trim($firstname) !== ""))
        {
            echo "\r\n";
            echo " $lastname ($type)
\r\n";
            echo " $firstname
\r\n";
            echo " \r\n";
            echo " $phonenumber
\r\n";
            echo " 1\r\n";
            echo "
\r\n";
            /*
            echo " \r\n";
            echo " Enter group ID here\r\n";
            echo "
\r\n";
            */
            echo "
\r\n";
        }
    }
}


echo '
' . "\r\n";

function my_replace($s)
{
    $r = str_replace("\n", '', $s);
    $r = str_replace("\r", '', $r);
    
    return $r;
}


?>


function fakeData()
{
return
'BEGIN:VCARD\r\n' .
'VERSION:3.0\r\n' .
'UID:02fe604a-ea63-49fb-8c28-47c37062cd2a\r\n' .
'FN:Heinz Maschke\r\n' .
'N:Maschke;Heinz;;;\r\n' .
'TEL;TYPE=work,pref:+49 8206 961993\r\n' .
'TEL;TYPE=fax,work:+49 8206 - 961994\r\n' .
'EMAIL;TYPE=home:bioexpress-hmaschke@t-online.de\r\n' .
'NOTE:Biokiste\r\n' .
'ADR;TYPE=work:;;Ulrichstr. 10 \n86492 Egling a.d. Paar;;;;\r\n' .
'LABEL;TYPE=work:Ulrichstr. 10 \n86492 Egling a.d. Paar\r\n' .
'PRODID:DAVdroid/1.0-beta (ez-vcard/0.9.3)\r\n' .
'REV:20170105T113243Z\r\n' .
'END:VCARD\r\n' .
'BEGIN:VCARD\r\n' .
'VERSION:4.0\r\n' .
'PRODID:+//IDN bitfire.at//DAVx5/2.2.3.1-ose ez-vcard/0.10.5\r\n' .
'UID:045fa2c0-03ce-47fe-a264-4bdd9301a193\r\n' .
'FN:Martin Schütz\r\n' .
'N:Schütz;Martin;;;\r\n' .
'TEL;TYPE=work:+49 151 12551804\r\n' .
'TEL;TYPE=cell:+49 176 72678207\r\n' .
'EMAIL;TYPE=home:schuetzmm@gmail.com\r\n' .
'URL;TYPE=x-homepage;VALUE=URI:http://www.google.com/profiles/10952715549372\r\n' .
' 9506703\r\n' .
'CATEGORIES:Family\r\n' .
'BDAY:--0301\r\n' .
'REV:20190302T104435Z\r\n' .
'END:VCARD\r\n' .
'BEGIN:VCARD\r\n' .
'VERSION:3.0\r\n' .
'UID:06011ab7-cdd1-4212-8339-eae8f77e1a7b\r\n' .
'FN:Crissi Schöpp\r\n' .
'N:Schöpp;Crissi;;;\r\n' .
'TEL;TYPE=home:+49 821 154562\r\n' .
'TEL;TYPE=cell:+49 1515 1256215\r\n' .
'NOTE:Mutter Magali\r\n' .
'PRODID:DAVdroid/1.0-beta (ez-vcard/0.9.3)\r\n' .
'REV:20170803T054608Z\r\n' .
'END:VCARD\r\n' .
'BEGIN:VCARD\r\n' .
'VERSION:3.0\r\n' .
'PRODID:DAVdroid/1.0-beta (ez-vcard/0.9.3)\r\n' .
'UID:0648bfc9-255d-4f7f-841e-be4c17ce12b3\r\n' .
'CATEGORIES:Egling,Weihnachtskarte\r\n' .
'FN:Familie Sießmeir\r\n' .
'N:Sießmeir;;;Familie;\r\n' .
'REV:2018-12-11T21:51:02Z\r\n' .
'TEL;TYPE=HOME:+49 (8206) 96 15 87\r\n' .
'ADR;TYPE=HOME:;;Hauptstr. 39;Egling 86492;;;\r\n' .
'NOTE:\nTanja\, Manni\r\n' .
'LABEL;TYPE=home:Hauptstr. 39\nEgling 86492\r\n' .
'END:VCARD\r\n'
;
}
?>

PHP Script to Merge Multiple XML Files

For the configuration of my IP phones (Grandstream) I needed a way to create a general configuration that yould apply to all phones, and more specific one pertaining to groups and individual phones.

The configuration is done via XML files, each phone pull a file with its MAC address in the name.

So I needed to merge the general XML with that of the group and that of the phone to create the final configuration for the phone.

Of course this could be done through a deployment management solution, but my approach gives me more flexibility, albeit also more manual work.

I was not able to find a program to merge XML files - I am sure there is one out there, but I couldn't find it, and I like this kind of work, so I created my own.

I usually would use C#, but my PBX does not run the .NET framework, but it does run PHP. I am sure that my preference of C# shows in my PHP code :-)

Anyway, if you are looking for a solution to merge XML files for whatever ends, you may copy the code below. No warranty, of course. The code and usage message are hopefully self-explaining.

It requires PHP 7.0 or later, because I like the type checking.


#!/usr/local/bin/php72


// convenience function
function findXpath(DOMDocument $dom, String $xpath) : DOMNodelist
{
$objXpath = new DOMXPath($dom);
return $objXpath->query($xpath);
}


/*
* Alternative to getNodePath.
* This one uses the ID column to uniquely identify nodes,
* if option is set.
*/
function getXpath(DOMNode $domNode) : String
{
switch(get_class($domNode))
{
case "DOMElement":
$idAttribute = $GLOBALS['idAttribute'];
if(strlen($idAttribute) > 0)
{
$attrValue = $domNode->getAttribute("$idAttribute");
}
$attr = ($attrValue !== "") ? "[@{$idAttribute}='{$attrValue}']" : "";
if(is_null($domNode->parentNode))
{
return "/" . $domNode->tagName . $attr;
}
else
{
return getXpath($domNode->parentNode) . "/" . $domNode->tagName . $attr;
}
break;
case "DOMDocument":
return "";
break;
case "DOMText":
return getXpath($domNode->parentNode) . "/text()";
break;
default:
echo "\nFAIL: getXPath class " . get_class($domNode) . " path " . $domNode->getNodePath() . "\n";
break;
}
}


/*
* Find a match for dom2Node, which belongs to dom2, in dom1
* A match is found, if the XPath of the node has exactly one
* match in both DOMs.
* Returns the matching node in dom1, or NULL if not match was found.
*/
function findMatch(DOMDocument $dom1, DOMDocument $dom2, DOMNode $dom2Node) : ?DOMNode
{
// todo: Jedes Element auf dem Pfad muss auf ID überprüft werden
$attrValue = "";
$idAttribute = $GLOBALS['idAttribute'];
if(strlen($idAttribute) > 0 && $dom2Node instanceof DOMElement)
{
$attrValue = $dom2Node->getAttribute($idAttribute);
}
$attr = ($attrValue !== "") ? "[@{$idAttribute}='{$attrValue}']" : "";
//$xpath = $dom2Element->getNodePath() . $attr;
// own solution using ID attribute if applicable
$xpath = getXpath($dom2Node);
$m1 = findXpath($dom1, $xpath);
$m2 = findXpath($dom2, $xpath);


if(count($m1) === 1 && count($m2) === 1 &&
$m1[0] !== null && $m2[0] !== null)
{
return $m1[0];
}
else
{
return null;
}
}


/*
* This is where the real work is done.
* Merges node addElement of DOM add into DOM dom.
* Manipulates dom.
*/
function merge(DOMDocument $dom, DOMDocument $add, DOMNode $addElement = null) : void
{
// starting point is the document itself
if(null === $addElement)
{
$addElement = $add->documentElement;
}


$classname = get_class($addElement);
//echo "Class {$classname}\n";
switch ($classname)
{
case ("DOMDocument"):
merge($dom, $add, $addElement->documentElement);
break;
case ("DOMElement"):
// do we have a delete attribute from options?
$delete = false;
$deleteAttribute = $GLOBALS['deleteAttribute'];
if(strlen($deleteAttribute) > 0 &&
$addElement->getAttribute($deleteAttribute) !== "")
{
$delete = true;
}
$xp = findMatch($dom, $add, $addElement);
if(null === $xp && !$delete)
{
// no match found, add whole element to dom
$node = findXpath($dom, getXpath($addElement->parentNode))[0];
$addElementClone = $dom->importNode($addElement, true);
$node->appendChild($addElementClone);
}
else // match found
{
if($delete)
{
$xp->parentNode->removeChild($xp);
}
else
{
// merge all children
for($i=0; $i < $addElement->childNodes->length; $i++)
{
$cn = $addElement->childNodes->item($i);
merge($dom, $add, $cn);
}
// merge all attributes
if ($addElement->hasAttributes())
{
foreach ($addElement->attributes as $attr)
{
merge($dom, $add, $attr);
}
}
}
}
break;
case ("DOMAttr"):
/*
* copy attribute to matching node in dom.
* there must be a match, since the element has already been matched.
* setting the attribute will create a new one or overwrite
* an existing one.
*/
$xp = findXpath($dom, getXpath($addElement->ownerElement))[0];
$xp->setAttribute($addElement->name, $addElement->value);
break;
case ("DOMNodeList"):
// calculated field, hence we store the value for performane
$n = $addElement->length;
//echo "dom node list has {$n} items\n";
for ($i = 0; $i < $n; $i++)
{
//echo "dom node list item #{$i}\n";
merge($dom, $add, $addElement->item($i));
}
break;
case ("DOMText"):
/*
* empty text will not be merged.
* if no match is found, the whole node will be copied over,
* otherwise we create a new text node and replace the old one,
* because you cannot set the text in an existing text node.
*/
$xp = findMatch($dom, $add, $addElement);
if(null === $xp)
{
if(trim($addElement->wholeText) !== "")
{
// add whole element to dom
$node = findXpath($dom, getXpath($addElement->parentNode))[0];
$addElementClone = $dom->importNode($addElement, true);
$node->appendChild($addElementClone);
}
}
else
{
$txt = trim($addElement->wholeText);
$newTextNode = $dom->createTextNode($txt);
$xp->parentNode->replaceChild($newTextNode, $xp);
}
break;
case ("DOMComment"):
// do not process comments
break;
default:
// should never happen
if($classname)
{
exit("Abort: instance of unknown class: $classname\n");
}
else
{
exit("Abort: no XML found\n");
}
}
}


/*
* Main Program
*/


if(count($argv) < 3)
{
echo "Usage: {$argv[0]} [-o] [inputXML3..N] \n
-o: overwrite existing output file
-id: specifies the id attribute to identify unique elements
-d: dpecified the attribute that indicated that an element should be deleted";
exit(-1);
}


// evaluate options
// options will be stored in global variables
$overwrite = false;
$idAttribute = "";
$deleteAttribute = "";
while(substr($argv[1], 0, 1) === "-")
{
if($argv[1] === "-o")
{
$overwrite = true;
array_shift($argv);
}


else if($argv[1] === "-id")
{
$idAttribute = $argv[2];
array_shift($argv);
array_shift($argv);
}


else if($argv[1] === "-d")
{
$deleteAttribute = $argv[2];
array_shift($argv);
array_shift($argv);
}


else
{
echo "Unknow option: {$argv[1]}\n";
exit(-10);
}
}


// test input files
for ($i = 1; $i < count($argv) - 1; $i++)
{
if(!file_exists($argv[$i]))
{
echo "Input file '{$argv[$i]}' does not exist.\n";
exit(-2);
}
}


// test outut file
if(!$overwrite && file_exists($argv[count($argv) - 1]))
{
echo "Output file '{$argv[count($argv) - 1]}' already exists.\n";
exit(-3);
}


//echo "Delete attribute: {$deleteAttribute}\n";
//echo "ID attribute: {$idAttribute}\n";


// load 1st XML
echo "Loading file {$argv[1]} ... ";
$mainDom = new DomDocument();
// pretty formatting later
$mainDom->preserveWhiteSpace = false;
$mainDom->formatOutput = true;
$mainDom->load($argv[1]);
echo "Done\n";


// load and merge all other files
for ($i = 2; $i < count($argv) - 1; $i++)
{
echo "Loading file {$argv[$i]} ... ";
$dom = new DomDocument();
    $dom->load($argv[$i]);
// merge dom into mainDom
echo "Merging ... ";
merge($mainDom, $dom);
echo "Done\n";
}


echo "Processed all files\n";


// write to file
echo "Writing output file {$argv[count($argv) - 1]} ... ";
/* does not work here
$mainDom->preserveWhiteSpace = false;
$mainDom->formatOutput = true;
*/
$mainDom->save($argv[count($argv) - 1]);
echo "Done\n";


exit(0);
?>
adaxas Web Directory