# XML unerlaubte Zeichen aus Dateien entfernen



## Frilakor.Migor (9. Feb 2010)

Hallo,

ich habe mehrere Dateien aus denen ich alle Zeichen entfernen möchte, die in XML nicht erlaubt sind

*Erlaubte Zeichen:*

Char  	   ::=     	#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]  	/* jedes Unicode-Zeichen, außgenommen...


*Unerlaubte Zeichen:*

Ersatzblöcke FFFE und FFFF. */


----------



## Frilakor.Migor (9. Feb 2010)

Erlaubte Zeichen in XML-Dokumenten

Gemäß Spezifikation sind erlaubt:
x9, xA, xD, x20-xD7FF, xE000-xFFFD, x10000-x10FFFF
d.h.

    * keine ASCII-Steuerzeichen unterhalb x20 bis auf die drei Leerraum-Zeichen Tab (x9), LF (xA) und CR (xD)
    * keine einzelnen Codes aus dem Surrogat-Bereich
    * kein xFFFE und xFFFF (keine definierten Unicode-Zeichen)
    * ansonsten der gesamte durch UTF-16 abgedeckte Bereich


Welche Zeichen sind das genau???

Folgende habe ich schon identifiziert:

xFFFE ￾
xFFFF ￿


----------



## SpammerSlammer (24. Feb 2010)

Die Frage ist zwar schon zwei Wochen alt, aber vielleicht hilft es noch:

Ich habe das mal so gelöst:


```
text = text.replaceAll("\u0000|\u0001|\u0002|\u0003|\u0004|\u0005|\u0006|\u0007|\u0008|\u0009|\u000B|\u000C|\u000E|\u000F|\u0010|\u0011|\u0012|\u0013|\u0014|\u0015|\u0016|\u0017|\u0018|\u0019|\u001A|\u001B|\u001C|\u001D|\u001E|\u001F", "");
```

Schau aber zur Sicherheit nach, welche der Zeichen für Dich relevant sind.


----------



## fkh (26. Feb 2010)

So, hier jetzt der aktualisierte Code. Wie ursprünglich schon geschrieben ist der Ansatz von SpammerSlammer in Sachen Performance (200ms zu 700-1000ms mit C2D 2,4Ghz und ner 270KB großen XML-Datei) und LOC besser. Die einzige Schwachstelle, die ich damit sehe ist die Tatsache, dass man damit definiert was nicht erlaubt ist anstatt was erlaubt ist. D. h. man muss ein möglichst vollständiges regex pattern haben, um möglichst viele ungültige Zeichen ausschließen zu können. Mein Ansatz geht den anderen Weg und definiert, welche Zeichen erlaubt sind und alles andere wird verworfen.



```
/**
     * This char can be used in combination with {@link #XMLUtility.replaceIllegalCharsFromXml(char[], char)} in case
     * illegal chars shoud only be removed.
     */
    public static final char DONT_REPLACE = 0x00;

    /**
     * This method can be used to replace all illegal characters from a string with another char.
     * 
     * @param source
     *            The string which should be checked for illegal XML characters.
     * @param replacement
     *            The char with which the original (illegal) char should be replaced with. In case the replacement char
     *            itself is illegal (e. g. in case one is using {@link XMLUtility.DONT_REPLACE}) the original illegal
     *            char will just be removed.
     * @return The original string with only legal XML chars in it.
     */
    public static String replaceIllegalCharsFromXml(String source, char replacement) {
        CharArrayWriter writer = new CharArrayWriter();
        char[] temp = source.toCharArray();
            for (char c : temp) {
                if (isLegalXMLChar(c)) {
                    writer.append(c);
                } else {
                    if (isLegalXMLChar(replacement)) {
                        writer.append(c);
                    }
                }
            }
        return writer.toString();
    }

    /**
     * This is a convenience method for {@link #XMLUtiltiy.replaceIllegalCharsFromXml(char[],char)} and can be used
     * in case illegal chars should be removed from XML (instead of being replaced by another char). See the original
     * method for more information.
     * 
     * @param source
     *            The char array which should be checked for illegal XML characters.
     * @return The original array with only legal XML chars in it.
     */
    public static String removeIllegalCharsFromXml(String source) {
        return replaceIllegalCharsFromXml(source,
                DONT_REPLACE); // we provide an illegal char (0x00 in this case) --> no replacement
    }

    /**
     * This method can be used to determine if a given char is a legal XML char or not.
     * 
     * @param c
     *            The byte which should be checked for invalid XML chars.
     * @return True in case the char is legal inside an XML document, otherwise it will return false.
     */
    public static boolean isLegalXMLChar(char c) {
        return (c == 0x9 || c == 0xA || c == 0xD || (c >= 0x20 && c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFD) || (c >= 0x10000 && c <= 0x10FFFF));
    }
```

Vielleicht ists ja für irgendjemanden interessant


----------



## fkh (28. Feb 2010)

Hallo zusammen,

ich habe die beiden Implementierungen nochmal zuhause auf meinem Heimrechner (Core2Quad 2,83Ghz, Win7, Java 1.6.0_16) und ner 2500KB großen XML-Datei (10 Durchläufe) getestet. Überraschenderweise ist hier, im Gegensatz zu meinem Arbeitsrechner (Core2Duo 2,4Ghz, Win Vista, Java 1.6.0_17), meine Implementierung deutlich schneller (ca. 1100ms bei 10 Durchläufen) als die Variante von SpammerSlammer (ca. 14100ms bei 10 Durchläufen). Kann das vielleicht mal jemand bei sich testen und die Ergebnisse von seinem Rechner posten? Ich versteh derzeit nämlich nicht, warum die Ergebnisse auf zwei Rechnern so gegensätzlich sind.

Hier der Testcode, den ich verwendet habe (nur für den Test auf meinem privaten PC).


```
package xml;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;

public class Main {
    public static long variante1 = 0;
    public static long variante2 = 0;
    public static int rounds = 10;

    public static void main(String[] args) {
        Main m = new Main();

        for (int i = 0; i < rounds; i++) {
            m.start(i + 1);
        }

        System.out.println("Ergebnis:");
        System.out.println("variante 1 (" + rounds + " rounds) = " + variante1 + "ms");
        System.out.println("variante 2 (" + rounds + " rounds) = " + variante2 + "ms");
    }

    private void start(int i) {

        System.out.println("Runde " + i);

        File file = new File("resources/xml", "test.xml");
        String xml = null;
        long start, end;

        try {
            xml = FileUtils.readFileToString(file, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (xml == null)
            System.exit(1);

        // variante 1
        System.out.println("Variante 1");
        start = System.currentTimeMillis();
        XMLUtility.replaceIllegalCharsFromXml(xml, (char) 0x20);
        end = System.currentTimeMillis();
        variante1 = variante1 + (end - start);

        // variante 2
        System.out.println("Variante 2");
        start = System.currentTimeMillis();
        xml
                .replaceAll(
                        "\u0000|\u0001|\u0002|\u0003|\u0004|\u0005|\u0006|\u0007|\u0008|\u0009|\u000B|\u000C|\u000E|\u000F|\u0010|\u0011|\u0012|\u0013|\u0014|\u0015|\u0016|\u0017|\u0018|\u0019|\u001A|\u001B|\u001C|\u001D|\u001E|\u001F",
                        "");
        end = System.currentTimeMillis();
        variante2 = variante2 + (end - start);
    }
}}
```

Zudem gibts hier noch eine weitere Implementierung AW: Escaping, or removing Invalid XML Characters allerdings prüft diese auch nur ein (unvollständiges) Subset der ungültigen chars und hat dabei bei meinen Tests nur eine geringfügig bessere Performance (ca. 1000ms bei 10 Durchläufen).

Gruß
fkh


----------



## Noctarius (1. Mrz 2010)

Mikrobenchmarks sind immer gefährlich!


----------



## Gast2 (1. Mrz 2010)

Noctarius hat gesagt.:


> Mikrobenchmarks sind immer gefährlich!



FULL ACK



fkh hat gesagt.:


> [...] zuhause auf meinem Heimrechner (Core2Quad 2,83Ghz, Win7, Java 1.6.0_16) [...] zu meinem Arbeitsrechner (Core2Duo 2,4Ghz, Win Vista, Java 1.6.0_17),



Du gibst hier nur 2 Geschwindigkeiten für die Prozessoren an ... wenn Du mal in die Liste bei Intel schaust - die ist nicht umsonst unendlich lang ... jeder Prozessor macht etwas anderes und ist an anderer Stelle schneller ... da Du kein Threading verwendest sind die Kerne zumindest egal



> und ner 2500KB großen XML-Datei (10 Durchläufe) getestet.


da dürfte schon alleine der Festplattecache Vorteile bringen ... selbst wenn beide keinen Cache haben sind die realen Übertragungsgeschwindigkeiten der Platten interessant ... wobei die Datei bei beiden Platten in den Cache passen dürfte ... dann hat jeder Prozessor bzw. Kern noch einen eigenen Cache L1/L2/L3 selbst hier kommt der größere Cache, zum Bearbeiten der Datei, zum Zuge ... interressant wird es wenn Dein Arbeitsrechner öfter für das Programm den Kern wechselt ... dann bringt der Cache auch nicht viel weil alles von einem zum anderen Kern "umgelagert" werden muss ... das kostet Zeit



hand, mogel


----------



## fkh (1. Mrz 2010)

Hallo ihr zwei,

erstmal danke für die Antwort. Dass das ganze nicht der Weisheit letzter Schluss ist, ist denke ich klar. Mich hat nur das gegensätzliche Ergebnis gewundert (eine Variante auf einem PC fast 5x schneller, dafür auf dem anderen PC fast 13x langsamer), hätte da zumindest von der Tendenz her ein ähnliches Ergebnis erwartet. Habe mich aber auch nicht allzu sehr mit benchmarking bisher beschäftigt, daher danke für den Hinweis.

@Mogel
Auf multithreading habe ich bewusst verzichtet, da ich darin keinen Mehrwert für den kleinen Test gesehen habe und was die Geschwindigkeit der Festplatten angeht, dürfte die auch nicht ins Gewicht fallen, da die Messungen ja erst nach dem Einlesen der Datei gestartet werden. Sollte ich mit diesen Vermutungen falsch liegen, korrigier mich bitte.

Wünsch allen einen guten Start in die Woche

Gruß
fkh


----------



## Noctarius (1. Mrz 2010)

Es könnte einfach sein, dass auf dem zweiten PC ein Hintergrundprozess genau in diesen paar Millisekunden gearbeitet hat und dadurch der gesamte PC-Fluss etwas ins stocken kam.

Wenn Benchmark, dann:
- HotSpot abschalten oder auf puren Interpreter umschalten
- Möglichst alle anderen Prozesse auf dem System deaktivieren (am besten 2 sauber installierte Linuxsysteme nehmen um die selben Voraussetzungen zu haben)
- Die Daten erstmal in den Arbeitsspeicher lesen (und schauen, dass sie nicht im Swap landen)
- Gleiche Java-Version nutzen (und gleiches OS, ganz wichtig!)
- Prozess an einen Kern binden (wenn kein Multithreadding genutzt wird)
- Bei Intel-CPUs Hyperthreading abschalten
- ...

Und dann hast du halt immer noch die Probleme wie Cachegröße im Prozessor.


----------



## fkh (1. Mrz 2010)

Danke für die Infos.

Gruß
fkh


----------

