実現したいこと
・EditTextに入力した文字を任意で装飾しRealmに保存
・その後、保存したデータを読み込み、EditTextに正しく表示を行いたいと考えています
前提
入力した文字を範囲選択し、ボタンを押下することで文字の装飾(文字色、ボールド 等)を行うことができるメモ帳のようなアプリを作成しています。
発生している問題・エラーメッセージ
前提に記載したアプリを実現するため、入力された文字(装飾含む)をHTML化してRealmに保存。
次回起動時にRealmからデータを読み込み再表示を行ったのですが、一部の改行が削除されてしまいました。
具体的には、
1 2 3 4 5 6
が
1 2 3 4 5 6
となってしまいます。
該当のソースコード
kotlin
1val button = findViewById<Button>(R.id.button) 2button.setOnClickListener { 3 val editTest1 = findViewById<EditText>(R.id.editText1) 4 val editTest2 = findViewById<EditText>(R.id.editText2) 5 6 val htmlString = Html.toHtml(editTest1.text, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) 7 8 val htmlSpanned = Html.fromHtml(htmlString, Html.FROM_HTML_MODE_COMPACT) 9 editTest2.setText(htmlSpanned) 10}
※6行目:editTest1.textの値 1\n2\n\n3\n\n\n4\n\n\n\n5\n\n\n\n\n6 ※6行目:htmlStringの値 <p dir="ltr">1<br> 2</p> <p dir="ltr">3<br></p> <p dir="ltr">4<br><br></p> <p dir="ltr">5<br><br><br></p> <p dir="ltr">6</p> ※8行目:htmlSpannedの値 1\n2\n3\n4\n\n5\n\n\n6\n
試したこと
「改行が2個以上なら改行コードを2個追加すれば良い」という記載をしているサイトがあったので正規表現で置換しようとしましたが上手くいきませんでした。
補足情報
そもそもの書き方が悪いのか、正規表現で2個追加するという力業が正しいのか、何も分からない状況で恐縮なのですが、解決方法がわかる方がいればご教示お願いいたします。
正しい書き方、正規表現での対応方法、どちらでも構いません。
どうぞよろしくお願いいたします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2023/03/01 08:17
2023/03/01 10:12
2023/03/01 20:52 編集

回答1件
0
ベストアンサー
まず、 toHtml と fromHtml はお互いを実行することで完全に元に戻ることが保証されたモノでは無いことは理解しておく必要があるでしょう。
その上で、公式のドキュメント https://developer.android.com/reference/android/text/Html#FROM_HTML_MODE_COMPACT
の記述が参考になります。
This inverts the Spanned to HTML string conversion done with the option TO_HTML_PARAGRAPH_LINES_INDIVIDUAL.
これは、 Spannedオプションで行われた HTML 文字列への変換を 逆にしますTO_HTML_PARAGRAPH_LINES_INDIVIDUAL。(Google 翻訳)
つまり、追加頂いたデータで改行が ほぼ 再現されるフラグの組み合わせは、
Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL
Html.FROM_HTML_MODE_COMPACT
と思われます。
ただ、 realm で保存・再生する為に HTMLを介するのが正しいのかという点は再考する余地はあるように感じます。
操作できず表示の確認だけですが、 json を用いるようにしてみました。
Span 毎に処理を行う必要があるため、 サンプルとして AbsoluteSizeSpan と ForegroundColorSpan だけに対応です。
java
1import androidx.appcompat.app.AppCompatActivity; 2 3import android.graphics.Color; 4import android.os.*; 5import android.text.*; 6import android.text.style.*; 7import android.util.Log; 8import android.widget.*; 9 10import com.google.gson.Gson; 11import com.google.gson.annotations.SerializedName; 12 13import java.lang.reflect.*; 14import java.util.regex.*; 15 16class EditedText { 17 private static final class SpanObject { 18 private static class What { 19 private static final class Param { 20 final String type, value; 21 Param(int value) { 22 type = int.class.getCanonicalName(); 23 this.value = "" + value; 24 } 25 Param(boolean value) { 26 type = boolean.class.getCanonicalName(); 27 this.value = "" + value; 28 } 29 Class<?> getType() { 30 switch(type) { 31 case "int": return int.class; 32 case "boolean": return boolean.class; 33 default: return String.class; 34 } 35 } 36 Object getValue() { 37 switch(type) { 38 case "int": return Integer.parseInt(value); 39 case "boolean": return Boolean.parseBoolean(value); 40 default: return value; 41 } 42 } 43 @Override 44 public String toString() { return "[type="+type+",value="+value+"]"; } 45 } 46 @SerializedName("class") 47 String className; 48 Param[] params; 49 What(Class<?> clazz, int size) { 50 className = clazz.getCanonicalName(); 51 params = new Param[size]; 52 } 53 What(AbsoluteSizeSpan span) { 54 this(span.getClass(), 2); 55 params[0] = new Param(span.getSize()); 56 params[1] = new Param(span.getDip()); 57 } 58 What(ForegroundColorSpan span) { 59 this(span.getClass(), 1); 60 params[0] = new Param(span.getForegroundColor()); 61 } 62 Object generate() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { 63 Class<?>[] paramTypes = new Class[params.length]; 64 Object[] initargs = new Object[params.length]; 65 for(int i=0; i<params.length; i++) { 66 paramTypes[i] = params[i].getType(); 67 initargs[i] = params[i].getValue(); 68 } 69 return Class.forName(className).getConstructor(paramTypes).newInstance(initargs); 70 } 71 @Override 72 public String toString() { 73 StringBuilder sb = new StringBuilder("["); 74 sb.append("class=").append(className); 75 sb.append(",params=["); 76 for(int i=0; i<params.length; i++) sb.append(i > 0 ? "," : "").append(params[i]); 77 sb.append("]"); 78 return sb.append("]").toString(); 79 } 80 } 81 @SerializedName("object") 82 What what; 83 int start, end, flags; 84 85 @Override 86 public String toString() { 87 StringBuilder sb = new StringBuilder("["); 88 sb.append("what=").append(what); 89 sb.append(",start=").append(start); 90 sb.append(",end=").append(end); 91 sb.append(",flags=").append(flags); 92 return sb.append("]").toString(); 93 } 94 } 95 private String text; 96 private SpanObject[] spans; 97 98 EditedText(Spanned spanned) { 99 text = spanned.toString(); 100 Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); 101 this.spans = new SpanObject[spans.length]; 102 for(int i=0; i<spans.length; i++) { 103 SpanObject span = new SpanObject(); 104 if(spans[i] instanceof AbsoluteSizeSpan) { 105 span.what = new SpanObject.What((AbsoluteSizeSpan)spans[i]); 106 } else if(spans[i] instanceof ForegroundColorSpan) { 107 span.what = new SpanObject.What((ForegroundColorSpan)spans[i]); 108 } 109 span.start = spanned.getSpanStart(spans[i]); 110 span.end = spanned.getSpanEnd(spans[i]); 111 span.flags = spanned.getSpanFlags(spans[i]); 112 this.spans[i] = span; 113 } 114 } 115 116 Spanned toSpanned() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException { 117 SpannableStringBuilder ssb = new SpannableStringBuilder(text); 118 for(SpanObject span : spans) ssb.setSpan(span.what.generate(), span.start, span.end, span.flags); 119 return ssb; 120 } 121 122 @Override 123 public String toString() { 124 StringBuilder sb = new StringBuilder("["); 125 sb.append("text=").append(text); 126 sb.append(",spans=["); 127 for(int i=0; i<spans.length; i++) sb.append(i > 0 ? "," : "").append(spans[i]); 128 sb.append("]"); 129 return sb.append("]").toString(); 130 } 131} 132 133public class MainActivity extends AppCompatActivity { 134 @Override 135 protected void onCreate(Bundle savedInstanceState) { 136 super.onCreate(savedInstanceState); 137 setContentView(R.layout.activity_main); 138 139 EditText editText = findViewById(R.id.editText); 140 TextView textView = findViewById(R.id.textView); 141 142 SpannableStringBuilder ssb = new SpannableStringBuilder("ACDEFG"); 143 ssb.setSpan(new AbsoluteSizeSpan(50, true), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 144 ssb.setSpan(new ForegroundColorSpan(Color.RED), 3, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 145 //editText.setText("1\n2\n\n3\n\n\n4\n\n\n\n5\n\n\n\n\n6"); 146 editText.setText(ssb); 147 148 EditedText editedText = new EditedText(ssb); 149 Log.d("*** toString ***", editedText.toString()); 150 151 Gson gson = new Gson(); 152 String json = gson.toJson(editedText); 153 Log.d("*** toJson ***", json); 154 155 EditedText newEdtedText = gson.fromJson(json, EditedText.class); 156 try { 157 textView.setText(newEdtedText.toSpanned()); 158 } catch(ClassNotFoundException| 159 InvocationTargetException| 160 NoSuchMethodException| 161 IllegalAccessException| 162 InstantiationException e) { 163 e.printStackTrace(); 164 } 165 } 166 167 //コントロールコードを文字化 168 private String toText(Spanned htmlSpanned) { 169 StringBuilder sb = new StringBuilder(); 170 Pattern p = Pattern.compile("[\u0000-\u001F]"); 171 int last = 0; 172 for(Matcher m=p.matcher(htmlSpanned); m.find(last); last=m.end()) { 173 sb.append(htmlSpanned.subSequence(last, m.start())); 174 char c = m.group().charAt(0); 175 sb.append("^").append((char)(c + '@')); 176 } 177 sb.append(htmlSpanned.subSequence(last, htmlSpanned.length())); 178 return sb.toString(); 179 } 180}
res/layout/activity_main.xml
xml
1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context=".MainActivity"> 8 9 <EditText 10 android:id="@+id/editText" 11 android:layout_width="0dp" 12 android:layout_height="0dp" 13 android:textSize="30dp" 14 app:layout_constraintBottom_toTopOf="@id/textView" 15 app:layout_constraintEnd_toEndOf="parent" 16 app:layout_constraintStart_toStartOf="parent" 17 app:layout_constraintTop_toTopOf="parent" /> 18 <TextView 19 android:id="@+id/textView" 20 android:layout_width="0dp" 21 android:layout_height="0dp" 22 android:textSize="30dp" 23 android:background="#e0e0e0" 24 app:layout_constraintBottom_toBottomOf="parent" 25 app:layout_constraintEnd_toEndOf="parent" 26 app:layout_constraintStart_toStartOf="parent" 27 app:layout_constraintTop_toBottomOf="@id/editText" /> 28</androidx.constraintlayout.widget.ConstraintLayout>
plain
1D/*** toString ***: [text=ACDEFG,spans=[[what=[class=android.text.style.AbsoluteSizeSpan,params=[[type=int,value=50],[type=boolean,value=true]]],start=2,end=4,flags=33],[what=[class=android.text.style.ForegroundColorSpan,params=[[type=int,value=-65536]]],start=3,end=5,flags=33]]] 2D/*** toJson ***: {"spans":[{"end":4,"flags":33,"start":2,"object":{"class":"android.text.style.AbsoluteSizeSpan","params":[{"type":"int","value":"50"},{"type":"boolean","value":"true"}]}},{"end":5,"flags":33,"start":3,"object":{"class":"android.text.style.ForegroundColorSpan","params":[{"type":"int","value":"-65536"}]}}],"text":"ACDEFG"}
投稿2023/03/01 20:51
編集2023/03/03 00:12総合スコア13318
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。