# Wie würdet ihr Strings gegen Farbe austauschen?



## Noahscript (23. Feb 2021)

Hallo zusammen,

folgende Situation: ihr wollt in ein EditText farbige Texte hinzufügen. Ihr verfügt über ein String in dem sich bestimmte Zeichen befinden die auf die Farbe hinweisen, die ihr setzen müsst. Diese Zeichen müssen am Ende entfernt werden um ein "sauberes" String auszugeben.

Wie würdet ihr also Zeichen gegen Farbe ersetzen?

Ich persönlich bekomme es mit Zeichen ohne Probleme hin.

Aber ohne Zeichen bekomme ich es nicht sauber hin. Ich verstehe nicht warum. Ich habe alles was mir eingefallen und für mich realistisch ist gemacht, um es alleine zu schaffen. Aber mein logisches Denken lässt nach...

Hier meine ganze Klasse:

```
public class MainActivity extends Activity
{
    EditText e;
   
    String[] cases = {
        new String(new byte[]{3,48,50}), //navy
        new String(new byte[]{6}),          //bold
        new String(new byte[]{5,49,51}), //fushia background
        new String(new byte[]{3,49,49}), //aqua
        new String(new byte[]{3,48,57}), //lime
        new String(new byte[]{5,48,48}), //white blackground
        new String(new byte[]{5,48,52}), //red background
        new String(new byte[]{3,49,50}), ////blue
        new String(new byte[]{5,48,57}), //lime background
        new String(new byte[]{3,48,52}), //red
        new String(new byte[]{3,48,54}), //purple
        new String(new byte[]{3,49,51})}; //fushia
   
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        e = (EditText) findViewById(R.id.chatEditText1);
       
        byte[] b = new byte[]{5, 48, 48, 3, 48, 50, 32, 3, 48, 50, 84, 101, 109, 112, 108, 97, 116, 101, 32, 101, 114, 115, 116, 101, 108, 108, 116, 32, 118, 111, 110, 32, 32, 32, 6, 5, 49, 51, 3, 49, 49, 91,
        3, 48, 57,
        -30, -103, -93, 3, 49, 49,
        93,
        5, 48, 48,
        32,
        5, 48, 52,
        3, 49, 50, 91, 3, 49, 49, -30, -103, -93, 3, 49, 50, 93,
        5, 48, 48, 32,
        5, 48, 57,
        3, 48, 52, 91,
        3, 48, 54, -30, -103, -93, 3, 48, 52, 93, 5, 48, 48, 32,
        3, 49, 51,
        116, -32, -72, -83, 109, 109, 121, 32, 91, -30, -103, -93, 93, 32, 5, 48, 57, 3, 48, 52, 91, 3, 48, 54, -30, -103, -93, 3, 48, 52, 93,
        5, 48, 48, 32, 5, 48, 52, 3, 49, 50, 91, 3, 49, 49, -30, -103, -93, 3, 49, 50, 93, 5, 48, 48, 32, 5, 49, 51, 3, 49, 49, 91, 3, 48, 57, -30, -103, -93, 3, 49, 49, 93};
       
       
        String chatText = new String(b);
        Spannable modifiedText = new SpannableString(chatText);
       
        for(int f=0;f<chatText.length();f++){
            String ls = chatText.substring(f,chatText.length());
            int i;
            for(i = 0; i < cases.length; i++)
                if(ls.startsWith(cases[i])) break;
            switch(i) {
                case 0:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#000080")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 1:
                    modifiedText.setSpan(new StyleSpan(Typeface.BOLD),f,chatText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 2:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#ff00ff")),f,chatText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 3:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#00FFFF")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 4:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#00FF00")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 5:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#FFFFFF")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 6:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#FF0000")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 7:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.BLUE), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 8:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#00FF00")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 9:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#FF0000")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 10:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#990099")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 11:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#ff00ff")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
            }
        }
        e.append(modifiedText); //Mit Zeichen
       
        String chatText2 = new String(b);
        String cleanText = clean(chatText2);
        Spannable modifiedText2 = new SpannableString(cleanText);
       
        int[] i = new int[]{};
        Object[] o = new Object[]{};
        for(int f=0; f<chatText2.length(); f++){
            String lc = chatText2.substring(f);
            if(lc.startsWith(new String(new byte[]{3,48,50}))){
                i = Arrays.copyOf(i,i.length + 1);
                i[i.length-1] = f;
                o = Arrays.copyOf(o,o.length + 1);
                o[o.length-1] = new ForegroundColorSpan(Color.parseColor("#000080"));
                chatText2 = chatText2.replaceFirst(new String(new byte[]{3,48,50}),"");
               
            }
            else if(lc.startsWith(new String(new byte[]{6}))){
                i = Arrays.copyOf(i,i.length + 1);
                i[i.length-1] = f;
                o = Arrays.copyOf(o,o.length + 1);
                o[o.length-1] = new StyleSpan(Typeface.BOLD);
                chatText2 = chatText2.replaceFirst(new String(new byte[]{6}),"");
               
            }
        }
        for(int f=0; f<i.length; f++){
            modifiedText2.setSpan(o[f],i[f],cleanText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        e.append("\n");
        e.append(modifiedText2); //Ohne Zeichen
       
    }
   
    public String clean(String s){
        for(int i = 0; i < cases.length; i++){
            s= s.replaceAll(cases[i],"");
        }
        return s;
    }
}
```

Wie könnte sich das Problem lösen lassen?


LG und vielen Dank!

PD.: Ohne Zeichen habe ich zunächst nur mit der Farbe Navy und fett gedruckt ausprobieren wollen...


----------



## MoxxiManagarm (23. Feb 2021)

Sorry ich verstehe es nicht. Hast du vielleicht ein Beispiel?


----------



## Robert Zenz (23. Feb 2021)

Zunaechst muss ich sagen, dass ein String in Java kein arbitraerer Byte-Container ist wie in C. Ein String ist eine Abfolge an UTF-16 enkodierten Codepoints. Ich glaube, dass wenn du beliebige Bytes und Byte-Folgen in den String schiebst, bekommst du undefiniertes Verhalten, ich wuerde nicht darauf wetten dass du die selben Bytes weider so herausbekommst. Wenn du beliebige Byte-Daten halten willst, welche irgendwann man zu einem String werden, solltest du diese als "byte[]" halten, und dann in einen String mit den passenden Codepoints ueberfuehren wenn du den String willst. Was vermutlich die beste Variante ist fuer das was du machen willst, ist Escape-Zeichen verwenden welche als normaler String darestellbar sind. Und dann stellt sich natuerlich die Frage mit welcher Enkodierung der Text verschickt wird, und ob eventuell die Formatierungszeichen mit der Enkodierung kollidieren (denke aber nicht, wenn ich mir die Werte so ansehe).

Aber ich sehe gerade dass dein eigentlicher Text in Bytes vorliegt, damit stellt sich mir nur die Frage wieso du deine Formattierungszeichen als String halten willst, hier bieten sich byte-Arrays direkt an. Also was viel besser waere, ist wenn du durch dein byte-Array laeufst, und pruefst ob du eine Formatierung findest, wenn nicht, fuegst die Sache einfach dem String hinzu ohen Formatierung. Also Pseudo-Code:


```
public interface Formatting {
    public byte[] getRawBytes();
    public Span getSpan();
}

for (int index =0; index < textAsBytes.length; index++) {
    Formatting formatting = findMatchingFormatForPosition(textAsBytes, index);
    
    if (formatting != null) {
        formattedText.setSpan(formatting.getSpan());
    } else {
        formattedText.addCharacter(textAsBytes[index]);
    }
}
```

Sowas in der Art. Das gleiche gilt natuerlich auch fuer den Umgekehrten Weg, du baust nicht einen String zusammen, sondern nur noch ein byte-Array aus dem formattierten Text.


----------



## Noahscript (24. Feb 2021)

Hallo Robert Zenz!

vielen Dank für dein Beitrag!




Robert Zenz hat gesagt.:


> Zunaechst muss ich sagen, dass ein String in Java kein arbitraerer Byte-Container ist wie in C.


Was ist ein arbiträrer Byte-Container? Ein Byte-Container, der dem Zufall überlassen ist?



Robert Zenz hat gesagt.:


> Wenn du beliebige Byte-Daten halten willst, welche irgendwann man zu einem String werden, solltest du diese als "byte[]" halten, und dann in einen String mit den passenden Codepoints ueberfuehren wenn du den String willst.


Ich habe die Byte-Daten als byte[], oder nicht?

```
String[] cases = {
        new String(new byte[]{3,48,50}), //navy
        new String(new byte[]{6}),          //bold
        new String(new byte[]{5,49,51}), //fushia background
        new String(new byte[]{3,49,49}), //aqua
        new String(new byte[]{3,48,57}), //lime
        new String(new byte[]{5,48,48}), //white blackground
        new String(new byte[]{5,48,52}), //red background
        new String(new byte[]{3,49,50}), ////blue
        new String(new byte[]{5,48,57}), //lime background
        new String(new byte[]{3,48,52}), //red
        new String(new byte[]{3,48,54}), //purple
        new String(new byte[]{3,49,51})}; //fushia
```
Ich habe vermutlich von diesem Satz von dir etwas falsch verstanden, oder?




Robert Zenz hat gesagt.:


> Was vermutlich die beste Variante ist fuer das was du machen willst, ist Escape-Zeichen verwenden welche als normaler String darestellbar sind.


In diesem Fall meinst du ich sollte besser Unicode (auch Steuerungszeichen) mit \u lesen, statt versuchen Unicode von bytes aus zu lesen?



Robert Zenz hat gesagt.:


> damit stellt sich mir nur die Frage wieso du deine Formattierungszeichen als String halten willst, hier bieten sich byte-Arrays direkt an. Also was viel besser waere, ist wenn du durch dein byte-Array laeufst, und pruefst ob du eine Formatierung findest, wenn nicht, fuegst die Sache einfach dem String hinzu ohen Formatierung.


Das Problem ist, dass die Methode SpannableString.setSpan() das Objekt, das ich ihr übergebe zu lesen können muss. Aber bis jetzt finde ich kein Weg, wie man das machen kann.




MoxxiManagarm hat gesagt.:


> Sorry ich verstehe es nicht. Hast du vielleicht ein Beispiel?


Ja, vielen Dank:

Ich habe ein byte Array:

```
String[] cases = {
        new String(new byte[]{3,48,50}), //navy
        new String(new byte[]{6}),          //bold
        new String(new byte[]{5,49,51}), //fushia background
        new String(new byte[]{3,49,49}), //aqua
        new String(new byte[]{3,48,57}), //lime
        new String(new byte[]{5,48,48}), //white blackground
        new String(new byte[]{5,48,52}), //red background
        new String(new byte[]{3,49,50}), ////blue
        new String(new byte[]{5,48,57}), //lime background
        new String(new byte[]{3,48,52}), //red
        new String(new byte[]{3,48,54}), //purple
        new String(new byte[]{3,49,51})};
```

In diesem sind jeweils ein Steuerzeichen und ein in Zahlen dargestellter Code gespeichert. Diese Kombination wird von einem Server als eine bestimmte Farbe interpretiert.


Dann habe noch ein anderer byte Array:

```
byte[] b = new byte[]{5, 48, 48, 3, 48, 50, 32, 3, 48, 50, 84, 101, 109, 112, 108, 97, 116, 101, 32, 101, 114, 115, 116, 101, 108, 108, 116, 32, 118, 111, 110, 32, 32, 32, 6, 5, 49, 51, 3, 49, 49, 91,
        3, 48, 57,
        -30, -103, -93, 3, 49, 49,
        93,
        5, 48, 48,
        32,
        5, 48, 52,
        3, 49, 50, 91, 3, 49, 49, -30, -103, -93, 3, 49, 50, 93,
        5, 48, 48, 32,
        5, 48, 57,
        3, 48, 52, 91,
        3, 48, 54, -30, -103, -93, 3, 48, 52, 93, 5, 48, 48, 32,
        3, 49, 51,
        116, -32, -72, -83, 109, 109, 121, 32, 91, -30, -103, -93, 93, 32, 5, 48, 57, 3, 48, 52, 91, 3, 48, 54, -30, -103, -93, 3, 48, 52, 93,
        5, 48, 48, 32, 5, 48, 52, 3, 49, 50, 91, 3, 49, 49, -30, -103, -93, 3, 49, 50, 93, 5, 48, 48, 32, 5, 49, 51, 3, 49, 49, 91, 3, 48, 57, -30, -103, -93, 3, 49, 49, 93};
```

Hier wird ein Text mit manchen Kombinationen (aus Steuerzeichen und Code) gespeichert.

Jetzt muss ich die Kombinationen aus Steuerzeichen und Code gegen die entsprechende Farbe ersetzen.

Mit diesem Code:

```
String chatText = new String(b);
        Spannable modifiedText = new SpannableString(chatText);
        
        for(int f=0;f<chatText.length();f++){
            String ls = chatText.substring(f,chatText.length());
            int i;
            for(i = 0; i < cases.length; i++)
                if(ls.startsWith(cases[i])) break;
            switch(i) {
                case 0:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#000080")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 1:
                    modifiedText.setSpan(new StyleSpan(Typeface.BOLD),f,chatText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 2:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#ff00ff")),f,chatText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 3:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#00FFFF")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 4:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#00FF00")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 5:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#FFFFFF")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 6:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#FF0000")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 7:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.BLUE), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 8:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#00FF00")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 9:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#FF0000")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 10:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#990099")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 11:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#ff00ff")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
            }
        }
        e.append("\n");
        e.append(modifiedText); //Mit Zeichen
```

kann man schön die unterschiedlichen Farben sehen aber leider auch die Codes (also 00, 02, etc...):



Und mit diesem Code:

```
String chatText2 = new String(b);
        String cleanText = clean(chatText2);
        Spannable modifiedText2 = new SpannableString(cleanText);
        
        int[] i = new int[]{};
        Object[] o = new Object[]{};
        for(int f=0; f<chatText2.length(); f++){
            String lc = chatText2.substring(f);
            if(lc.startsWith(new String(new byte[]{3,48,50}))){
                i = Arrays.copyOf(i,i.length + 1);
                i[i.length-1] = f;
                o = Arrays.copyOf(o,o.length + 1);
                o[o.length-1] = new ForegroundColorSpan(Color.parseColor("#000080"));
                chatText2 = chatText2.replaceFirst(new String(new byte[]{3,48,50}),"");
                
            }
            else if(lc.startsWith(new String(new byte[]{6}))){
                i = Arrays.copyOf(i,i.length + 1);
                i[i.length-1] = f;
                o = Arrays.copyOf(o,o.length + 1);
                o[o.length-1] = new StyleSpan(Typeface.BOLD);
                chatText2 = chatText2.replaceFirst(new String(new byte[]{6}),"");
                
            }
        }
        for(int f=0; f<i.length; f++){
            modifiedText2.setSpan(o[f],i[f],cleanText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        e.append("\n");
        e.append("\n");
        e.append(modifiedText2); //Ohne Zeichen
```

Sieht man die Codes zwar nicht, aber dafür funktioniert es mit dem Farben auch nicht richtig. Zunächst habe ich es mit der Farbe Navy und mit der Formatierung fett gedruckt ausprobieren wollen. Man kann die Farbe Navy sehen und auch das Fettgedruckte. Das Fettgedruckte sollte allerdings vor der ersten linke eckige Klammer beginnen. Es beginnt aber vor der zweite linke eckige Klammer:


Es gibt also ein Problem mit den Positionen. Und für dieses Problem finde ich keine Möglichkeit für die Lösung...


----------



## kneitzel (24. Feb 2021)

Noahscript hat gesagt.:


> Ich habe ein byte Array:


Das Du aber direkt zu einem String machst:

```
String chatText = new String(b);
```

Aus meiner Sicht macht es Sinn, sich die Daten erst einmal zurecht zu bauen. Wie dir das am Besten gefällt, ist Dir überlassen. Was man machen kann, ist eine Aufteilung in Blöcke (Wäre eine Idee). Jeder Block enthält dann Text + Formatierungen. Also vom Ablauf her wäre es sowas in der Art:
- leere Formatierungen.
- neuer Block erstellt.
- für jedes Byte:
-- Ist es eine Formatierung?
--   --> Setze Formatierung
--   --> Block sichern und neuen Block erstellen mit aktuellen Formatierungen.
-- sonst
--   --> füge Zeichen zu Block hinzu.

Dann hast Du hinterher einfach nur eine Liste von Blocks die Du der Reihe nach hinzu fügen kannst incl. der gewünschten Formatierungen.

Aber wenn man sich dann Spannable anschaut - da sieht man dann auch ein 


			https://developer.android.com/reference/android/text/SpannableStringBuilder
		

und dann sieht man, dass man da eigentlich nichts mehr selbst implementieren muss, da dort ja schon alles gegeben ist...

Ach ja: Arbiträr: Willkürlich - ein String ist kein willkürliches byte Array. So sind nicht alle byte Werte / Wertekombinationen erlaubt. Und bei der Umwandlung kann es auch dazu kommen, dass es anders interpretiert wird, als es Dir lieb ist. Das mag aber hier gehen, da wohl vorhandene Zeichen Verwendung gefunden haben . Und bei dem byte Array ist generell die Frage: Was für ein Encoding wurde verwendet um die Bytes zu bekommen. Siehe dazu z.B. auch https://www.baeldung.com/java-string-to-byte-array - Und Methoden die auf "platform's default charset" zugreifen, sind immer kritisch zu sehen in Bezug auf Datenaustausch.


----------



## Robert Zenz (24. Feb 2021)

Noahscript hat gesagt.:


> Was ist ein arbiträrer Byte-Container? Ein Byte-Container, der dem Zufall überlassen ist?


Was ich meinte ist, dass ein String in Java nicht dafuer gedacht ist beliebige Byte-Folgen zu halten. In C kannst du dort alles hinein schieben solange es kein 0 ist (ich glaube sogar das geht, eigentlich). Strings in Java sind aber nicht beliebige Byte-Folgen, es ist ein UTF-16 String mit Codepoints, die erlaubten Byte-Werte sind mit UTF-16 definiert und eingeschraenkt.


Noahscript hat gesagt.:


> Ich habe die Byte-Daten als byte[], oder nicht?
> Ich habe vermutlich von diesem Satz von dir etwas falsch verstanden, oder?


Ja, hast du, aber du wandelst diese sofort in einen String um.


Noahscript hat gesagt.:


> In diesem Fall meinst du ich sollte besser Unicode (auch Steuerungszeichen) mit \u lesen, statt versuchen Unicode von bytes aus zu lesen?


Aber das willst du ja nicht, wenn ich dich richtig verstanden habe. Du hast ja keinen Unicode String, du hast ein Byte-Array wovon *Teile* davon ein Unicode String sind, und andere deine eigenen Formatierungszeichen.


----------



## Noahscript (27. Feb 2021)

Hallo kneitzel!!

wie geht's so?

Ich bin ein Dummy. Das heißt: oft kapiere ich beim Lernen nicht sofort was andere Personen mir beibringen bzw. mitteilen.



kneitzel hat gesagt.:


> Also vom Ablauf her wäre es sowas in der Art:





kneitzel hat gesagt.:


> - leere Formatierungen.
> - neuer Block erstellt.


Was meinst du damit? Ich verstehe es nicht 



kneitzel hat gesagt.:


> - für jedes Byte:
> -- Ist es eine Formatierung?
> -- --> Setze Formatierung


Ja, aber wie kann ich die Formatierung setzen?



kneitzel hat gesagt.:


> -- --> Block sichern und neuen Block erstellen mit aktuellen Formatierungen.


Meinst du den byte-Block? Wie kann ich den sichern? Warum muss man ihn sichern und danach einen neuen erstellen? In welchem Zusammenhang befindet sich diese Idee? Oh Gott, ich verliere den Überblick 🙈🙈 Ich glaube ich sollte jetzt so ein Programmablaufplan versuchen zu skizzieren...

Ich glaube ich habe nur dieses Ergebnis am Besten verstanden:


kneitzel hat gesagt.:


> Dann hast Du hinterher einfach nur eine Liste von Blocks die Du der Reihe nach hinzu fügen kannst incl. der gewünschten Formatierungen.






kneitzel hat gesagt.:


> Aber wenn man sich dann Spannable anschaut - da sieht man dann auch ein
> https://developer.android.com/reference/android/text/SpannableStringBuilder  und dann sieht man, dass man da eigentlich nichts mehr selbst implementieren muss, da dort ja schon alles gegeben ist...


Ich habe es auch schon mit SpannableStringBuilder versucht. Ich habe probiert ob ich ein Algorithmus erstellt bekomme, der richtig Inhalte mit Formatierungen hinzufügt
oder
der am Ende die nicht gewünschten Codes löscht.
Das ist mir nicht gelungen, zum Teil, weil ich einfach den Überblick verloren habe.




Robert Zenz hat gesagt.:


> Ja, hast du, aber du wandelst diese sofort in einen String um.


Wenn du mir schreibst, das sei falsch, das glaube ich dir sofort. Nur ich kann nicht nachvollziehen warum. Irgendwann muss ich sie ja in einen String umwandeln, warum nicht gleich am Anfang?


Ich weiß: Wenn man doof ist, dann ist es ganz blöd. 
Aber vielleicht gibt es ein wenig Hoffnung?


LG und vielen Dank!!


----------



## Robert Zenz (28. Feb 2021)

Noahscript hat gesagt.:


> Wenn du mir schreibst, das sei falsch, das glaube ich dir sofort. Nur ich kann nicht nachvollziehen warum. Irgendwann muss ich sie ja in einen String umwandeln, warum nicht gleich am Anfang?


Weil ein String definierte Werte hat, nicht beliebige. UTF-16 ist ein Format welches definiert wie man von Byte-Folgen auf Glyphen kommt, daneben gibt es noch UTF-8 was in der Zwischenzeit sehr weit verbreitet ist und von eigentlich jedem verwendet wird (auszer Microsoft Windows, weil Microsoft...). Aber fangen wir kurz hinten an, ASCII hast du sicher schon gehoert. ASCII definiert welcher Byte-Wert welches Zeichen/Glyphe ist, also zum Beispiel wenn da ein Byte ist mit dem Wert "75" dann muss man ein "K" anzeigen, beziehungsweise dann ist das ein "K". So vom Prinzip her. Jetzt ist es aber so dass in UTF-8 und UTF-16 nicht mehr ein Byte einem Zeichen entspricht, es koennen 1-4 Byte einem Zeichen entsprechen. Deswegen spricht man auch von CodePoints anstelle von Bytes oder Characters, weil ein CodePoint 1-4 Byte entsprechen kann. Um zu erkennen wieviele Bytes zu verwenden sind, sind Surrogate-Werte definiert anhand welchen erkannt werden kann wieviele Byte-Werte folgen. UTF-8 verwendet die Byte-Werte 0-127 ident zu ASCII, die Werte 128-255 sind hingegen nicht als Zeichen ausgefuehrt, sondern sind entweder verboten (duerfen im String also nicht vorkommen) oder werden als Surrogate-Werte verwendet. In UTF-16 ist sehr aehnlich, aber es verwendet von Haus aus schon 2-Byte CodePoints. Also das Byte Array "75, 75" ist kein gueltiger UTF-16 String. Du kannst also nicht beliebige Byte-Werte in einen String schieben, das laesst das Encoding nicht zu. Java wird es vermutlich schlucken und verarbeiten, aber das Verhalten wird undefiniert. Das es so im Moment funktioniert ist nur dem Default-Encoding zu verdanken, welches wahrscheinlich auf UTF-8 zurueckfaellt. Es verhaelt sich also nur wegen den Defaults halbwegs richtig.

Und du musst es nicht in einen String umwandeln, es waere viel besser wenn du deinen formatierten String als rohes Byte-Array verarbeitest, und einen String nur aus den String-Teilen aufbaust.


----------



## kneitzel (28. Feb 2021)

Generell ist es so wie bei jedem Programm: Du musst Dir überlegen, was für Formatierungen du hast und was Du da alles merken können musst.

Formatierung könnte also enthalten:
- Farbe Schrift
- Farbe Hintergrund
- bold
- ....
Du musst halt schauen, was Du da alles hast - ich weiss ja nicht, ob die Auflistung oben komplett war oder nicht.

Wie Du die Farbe speicherst, kannst Du Dir noch überlegen. Das muss nicht zwingend eine vorhandene klasse Color sein, sondern das können auch enum sein, die Du definierst. Derzeit sieht es ja so aus, dass Du eine Art Steuercode hast (3 Schriftfarbe, 5 Hintergrundfarbe und dann die Farbe mit den nächsten zwei Byte angegeben wird. Daher scheint es hier sowas wie eine definierte Farbe zu geben...

Was Du nun haben kannst ist eine Methode, die prüft, ob ein Byte ein Steuerzeichen ist. (Also 3, 5, 6, ... Das waren die Steuerzeichen, die ich gesehen habe...) 

Wenn Du eine neue Instanz von Formatierung erstellst, dann sind die Werte auf Standard-Werte gesetzt. 

Also die Schleife ist dann sowas wie:

```
byte[] nachricht = .....; // Deine zu dekodierende Nachricht.
Formatierung formatierung = new Formatierung();
int blockStart = 0;
int blockEnd = 0;

while (blockEnd < nachricht.length) {
    if (istSteuerzeichen(nachricht[blockEnd])) {
        zeichenHinzufügen(formatierung, nachricht, blockStart, blockEnd-1);
        blockEnd = evaluateSteuerzeichen(formatierung, blockEnd);
        blockEnd++;
        blockStart=blockEnd;
    } else {
        blockEnd++;
    }
}
// Der letzte Block muss noch hinzugefügt werden!
zeichenHinzufügen(formatierung, nachricht, blockStart, blockEnd);
```

zeichenHinzufügen würde einfach den block der Nachricht anzeigen. Das aktuelle Zeichen in blockEnd ist ja schon das Steuerzeichen, daher -1. Die Methode muss prüfen, ob blockEnd >= blockStart. Wenn das nicht der Fall ist, dann gibt es kein Zeichen zum hinzufügen.
Das Hinzufügen selbst hast Du ja schon vom Prinzip her. Nur eben dass Du jetzt nicht das ganze byte-Array zu einem String umwandelst sondern nur einen Ausschnitt 
==> https://docs.oracle.com/en/java/jav.../java/lang/String.html#<init>(byte[],int,int)
Und die Formatierung musst Du vorab setzen, also z.B. das bold setzen, die Farben setzen u.s.w.

evaluateSteuerzeichen liest das aktuelle Steuerzeichen incl. ggf. weiterer Zeichen (Also die Codes der Farbe) und geben dann das neue BlockEnd zurück. Also bei einem einzelnen Steuerzeichen wird blockEnd zurück gegeben, bei einer Farbe blockEnd+2. 

Danach wird blockEnd erhöht und ein neuer Block fängt an.

Wenn das aktuelle Zeichen kein Steuerzeichen ist, wird nur die Blockgrenze verschoben.

Nach der Schleife muss der letzte Block noch geschrieben werden.

==> Eine erste einfache Lösung. Vielleicht noch nicht optimal, aber das wäre ein mögliches Vorgehen.


----------



## Noahscript (3. Mrz 2021)

Hallo kneitzel,




kneitzel hat gesagt.:


> Wenn Du eine neue Instanz von Formatierung erstellst, dann sind die Werte auf Standard-Werte gesetzt.


Welche Werte sind auf Standard gesetzt?

Ich könnte mir zum Beispiel vorstellen, dass du dir vorstellst, dass die Klasse Formatierung so aussieht:


```
import java.util.*;
import android.text.style.*;
import android.graphics.*;

public class Formatierung
{
    public Object Format;
    
    public void getSpan(byte[] b){
        if(Arrays.equals(b,new byte[]{3,48,50})){
            Format = new ForegroundColorSpan(Color.parseColor("#000080"));
        }
        if(Arrays.equals(b,new byte[]{6})){
            Format = new StyleSpan(Typeface.BOLD);
        }
        //...usw. mit den anderen Formatierungen
    }
}
```

Habe ich mir das so richtig vorgestellt?



kneitzel hat gesagt.:


> zeichenHinzufügen würde einfach den block der Nachricht anzeigen.


Was meinst du damit? Dass die Nachricht zusammen mit der Formatierung hinzugefügt wird? Wenn ich das so richtig verstanden habe, wohin wird das hinzugefügt? Zu einen Objekt? Zu den EditText?
Oder wie werden die Blöcke hinzugefügt? Mit SpannableStringBuilder, zum Beispiel?


LG und vielen Dank, kneitzel!!


Auch dir, vielen Dank für deine Erklärung, die du schön zusammenfasst hast, @Robert Zenz !! Besser als Wikipedia.



Robert Zenz hat gesagt.:


> Und du musst es nicht in einen String umwandeln, es waere viel besser wenn du deinen formatierten String als rohes Byte-Array verarbeitest, und einen String nur aus den String-Teilen aufbaust.


Danke auch für diesen Tipp. Ich möchte gerade versuchen, ihn umzusetzen mit der Lösung, die mir @kneitzel angeboten hat.


LG


----------



## kneitzel (3. Mrz 2021)

Bei der Idee ist es um eine komplette Abstrahierung gegangen. Die Klasse Formatierung ist also etwas, das komplett von Dir geschrieben werden müsste und die Formatierung für einen Block enthält.

Das ist also ein Standard-Vorgehen:
Du schaust Dir an, was Du an Daten brauchst. Also wenn Du so willst dein Model. Dazu musst Du Dir ansehen, was Du alles genau brauchst.

Da ich aber die Problem-Domäne (also das, um was es da genau geht) nicht wirklich überblicke, kann ich nicht sagen, was wie benötigt wird.

Aber das ist aus meiner Sicht in der Regel immer der erste Schritt.
Dann kann man das umsetzen. Du kannst also z.B. die Entities bauen, die Du brauchst. Da sehe ich vor allem die Formatierung. Die Blöcke selbst sind einfache Strings.

Wenn Du das hast, dann kannst Du sozusagen "Adapter" schreiben:
- Also das Wandeln von / in so Byte-Arrays
- Das Wandeln in irgend welche anderen Dinge. Das kann dann von mir aus ein Spannable sein. Aber das kann alles Mögliche sein, z.B. auch HTML oder so.

Das ist eine universelle Vorgehensweise, der ich gerne folge. So hat man relativ gut strukturiert seinen Code und kann sehr gut Teile weiter verwenden.

Aber evtl. ist das auch gar nicht nötig. Evtl. kannst Du auch einfach die ganzen Bytes byte für byte lesen und du kannst Formatierungen direkt einem SpannableStringBuilder hinzu fügen. Aber ich muss gestehen, dass ich mit der Klasse noch nichts gemacht habe und ich daher nicht weiß, ob dies so funktioniert oder nicht. Ich vermute aber nicht, denn wenn man sich z.B. https://developer.android.com/reference/android/text/style/ForegroundColorSpan anschaut, dann wird zumindest in dem Beispiel eine Eigenschaft für einen Bereich angewendet:

```
SpannableString string = new SpannableString("Text with a foreground color span");
string.setSpan(new ForegroundColorSpan(color), 12, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
```

Die universelle Lösung kann aber mit allen Eventualitäten aber gut umgehen denke ich mal ....


----------



## Noahscript (5. Mrz 2021)

Hallo kneitzel!!!

Alles OK?




kneitzel hat gesagt.:


> Da ich aber die Problem-Domäne (also das, um was es da genau geht) nicht wirklich überblicke, kann ich nicht sagen, was wie benötigt wird.


Im Grunde genommen geht es darum, Strings gegen Farbe auszutauschen. Es ging am Anfang mit `HTML.From(String)` sehr gut. Da konnte man mit `String.replaceAll(String,String)` Strings gehen HTML-Etiketten austauschen. Aber wenn da Etiketten-Verschachtelungen kommen, funktioniert diese Methode nicht mehr richtig wie ein normaler Browser es anzeigen würde. Deswegen musste ich es mit Spans versuchen.




kneitzel hat gesagt.:


> Du kannst also z.B. die Entities bauen, die Du brauchst.


Und wie kann ich das bauen? Ich verstehe dich nicht 




kneitzel hat gesagt.:


> Da sehe ich vor allem die Formatierung. Die Blöcke selbst sind einfache Strings.
> 
> Wenn Du das hast, dann kannst Du sozusagen "Adapter" schreiben:
> - Also das Wandeln von / in so Byte-Arrays
> - Das Wandeln in irgend welche anderen Dinge. Das kann dann von mir aus ein Spannable sein. Aber das kann alles


Ich verstehe dich nicht 




kneitzel hat gesagt.:


> Ich vermute aber nicht, denn wenn man sich z.B. https://developer.android.com/reference/android/text/style/ForegroundColorSpan anschaut, dann wird zumindest in dem Beispiel eine Eigenschaft für einen Bereich angewendet:


Genau!!!! Oh mein Gott. Ich konnte mich nicht so ausdrücken wie du.

Das ist zum Teil das Problem und der Grund warum ich es nicht geschafft habe bis jetzt eine Lösung zu finden.

Ich habe aus all dem was ich versucht habe von dir zu verstehen, etwas gebastelt.

Formatierungen.java:

```
import android.text.style.*;
import android.graphics.*;

public class Formatierung
{
    public Object Format;
    public byte[] code;
    
    public void getSpan(byte[] b){
        if(Arrays.equals(b,new byte[]{3,48,50})){
            Format = new ForegroundColorSpan(Color.parseColor("#000080"));
            code = b;
        }
        if(Arrays.equals(b,new byte[]{6})){
            Format = new StyleSpan(Typeface.BOLD);
            code = b;
        }
    }
}
```

MainActivity.java:

```
public class MainActivity extends Activity
{
    EditText e;
    
    Spannable modifiedText2;
    String cleanText;
    
    String[] cases = {
        new String(new byte[]{3,48,50}), //navy
        new String(new byte[]{6}),          //bold
        new String(new byte[]{5,49,51}), //fushia background
        new String(new byte[]{3,49,49}), //aqua
        new String(new byte[]{3,48,57}), //lime
        new String(new byte[]{5,48,48}), //white blackground
        new String(new byte[]{5,48,52}), //red background
        new String(new byte[]{3,49,50}), ////blue
        new String(new byte[]{5,48,57}), //lime background
        new String(new byte[]{3,48,52}), //red
        new String(new byte[]{3,48,54}), //purple
        new String(new byte[]{3,49,51})}; //fushia
    
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        e = (EditText) findViewById(R.id.chatEditText1);
        
        byte[] b = new byte[]{5, 48, 48, 3, 48, 50, 32, 3, 48, 50, 84, 101, 109, 112, 108, 97, 116, 101, 32, 101, 114, 115, 116, 101, 108, 108, 116, 32, 118, 111, 110, 32, 32, 32, 6, 5, 49, 51, 3, 49, 49, 91,
        3, 48, 57,
        -30, -103, -93, 3, 49, 49,
        93,
        5, 48, 48,
        32,
        5, 48, 52,
        3, 49, 50, 91, 3, 49, 49, -30, -103, -93, 3, 49, 50, 93,
        5, 48, 48, 32,
        5, 48, 57,
        3, 48, 52, 91,
        3, 48, 54, -30, -103, -93, 3, 48, 52, 93, 5, 48, 48, 32,
        3, 49, 51,
        116, -32, -72, -83, 109, 109, 121, 32, 91, -30, -103, -93, 93, 32, 5, 48, 57, 3, 48, 52, 91, 3, 48, 54, -30, -103, -93, 3, 48, 52, 93,
        5, 48, 48, 32, 5, 48, 52, 3, 49, 50, 91, 3, 49, 49, -30, -103, -93, 3, 49, 50, 93, 5, 48, 48, 32, 5, 49, 51, 3, 49, 49, 91, 3, 48, 57, -30, -103, -93, 3, 49, 49, 93};
        
        
        String chatText = new String(b);
        Spannable modifiedText = new SpannableString(chatText);
        
        for(int f=0;f<chatText.length();f++){
            String ls = chatText.substring(f,chatText.length());
            int i;
            for(i = 0; i < cases.length; i++)
                if(ls.startsWith(cases[i])) break;
            switch(i) {
                case 0:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#000080")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 1:
                    modifiedText.setSpan(new StyleSpan(Typeface.BOLD),f,chatText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 2:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#ff00ff")),f,chatText.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 3:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#00FFFF")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 4:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#00FF00")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 5:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#FFFFFF")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 6:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#FF0000")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 7:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.BLUE), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 8:
                    modifiedText.setSpan(new BackgroundColorSpan(Color.parseColor("#00FF00")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 9:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#FF0000")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 10:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#990099")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 11:
                    modifiedText.setSpan(new ForegroundColorSpan(Color.parseColor("#ff00ff")), f, chatText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
            }
        }
        e.append("\n");
        e.append(modifiedText); //Mit Zeichen funktioniert es ausgezeichnet
        
        cleanText = clean(chatText);
        modifiedText2 = new SpannableString(cleanText);
        Formatierung formatierung = new Formatierung();
        int blockStart = 0;
        int blockEnd = 0;
        
        while (blockEnd < b.length) {
            if (istSteuerzeichen(b[blockEnd])) {
                formatierung.getSpan(Arrays.copyOfRange(b,blockStart,blockStart+3));
                zeichenHinzufügen(formatierung, blockStart);
                blockEnd = evaluateSteuerzeichen(formatierung, blockEnd);
                blockEnd++;
                blockStart=blockEnd;
            } else {
                blockEnd++;
            }
        }
        
    }
    
    public String clean(String s){
        for(int i = 0; i < cases.length; i++){
            s= s.replaceAll(cases[i],"");
        }
        return s;
    }
    
    public Boolean istSteuerzeichen(byte b){
        if(b == (byte)3||b == (byte)5||b == (byte)6){
            return true;
        }
        else{
            return false;
        }
    }
    
    public void zeichenHinzufügen(Formatierung f, int p){
        modifiedText2.setSpan(f.Format, p, cleanText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    
    public int evaluateSteuerzeichen(Formatierung f, int p){
        if(f.code.length > 1){
            return p;
        }
        else{
            return p + 2;
        }
    }
}
```

Das erste Problem zeigt seine Grausamkeit:

Beim ausführen wird die App gecrasht und im LogCat steht geschrieben:

```
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mycompany.myapp/com.mycompany.myapp.MainActivity}: java.lang.NullPointerException: Attempt to get length of null array
```

Der byte-Array aus der Formatierung-Klasse ist null. Wie kann das denn sein, wenn bei jeder while-Schleife dieses Array mit Werten gefüllt wird? Hä?

Bitte gedenkt mein Leiden und lasst mich eine Minute lang bitterlich weinen 😭😭...


LG und vielen Dank!


----------

