リストを処理する HTMLEditorKit の独特な方法について、より具体的な説明が必要な人のために説明すると、すべては生成されるマークアップに帰着します。できるだけシンプルにしようと思います。少し巻き戻して、Swing での HTML ドキュメントについて話しましょう。
Swing は、カーソルの配置とナビゲーションを行うために段落に依存していることがわかりました。たとえば、新しい行に書き込むたびに、新しいページが生成されます。ドキュメントの対応するビューでさえ、適切な場所に段落が存在するかどうかによって異なります。ドキュメントには必ず段落が必要です。そうしないと、奇妙なことが起こり始めます。
では、ドキュメントが完全に空白の場合はどうなるでしょうか? 確かに、そこに段落は必要ありません。まあ、信じられないことに、その場合でも段落があります。これは、ドキュメンテーションがp-impliedまたはImplied paragraphと呼んでいるものの効果の 1 つです。空白のドキュメントに対して生成される HTML は次のとおりです。
<html>
<head></head>
<body>
<p style="margin-top: 0">
</p>
</body>
</html>
予想どおり、リストを挿入すると、段落内に配置されます。
<html>
<head></head>
<body>
<p style="margin-top: 0">
<ul>
<li>
</li>
</ul>
</p>
</body>
</html>
...もちろん、これは無効なマークアップです (頭の中にタイトルがないという理由だけではありません)。ちょっと待って!もっと面白くなります。リストが挿入されると、ドキュメントの「内部ポインター」は、いわば終了</ul>
タグの後に留まります。したがって、「Hello」と入力すると、リストの外に配置されます。
<html>
<head></head>
<body>
<p style="margin-top: 0">
<ul>
<li>
</li>
</ul>
Hello
</p>
</body>
</html>
これが、挿入された箇条書きに対して「Hello」がかなり右に表示される理由です。さて、ステファンが質問で述べたように、setText(getText())
魔法のように問題を解決します。これは、JTextPane インスタンスのコンテンツを手動で設定するとパーサーがトリガーされ、パーサーが「内部ポインター」をあるべき場所に配置するためです。リスト内。これで「Hello」と入力すると、箇条書きの近くに表示されます。HTML にはまだ正しくない点があるため、もっと詳しく言います。
<html>
<head></head>
<body>
<p style="margin-top: 0">
<ul>
<li>
Hello
</li>
</ul>
</p>
</body>
</html>
リストには、新しいテキストを囲む段落がないことに注意してください。そのため、テキストは箇条書きのすぐ隣に表示されません。
このすべてについてどうやって行くのですか?まあ、それはステファンが話していたトリッキーなビットです. これまで見てきたように、バグ (このようなもの)、文書化されていない不具合 (このようなもの)、およびデフォルトの動作の組み合わせに直面することになります。最も簡単な方法は、Stephane のリストにあるソリューションの 1 つを使用することです。Shefが最高であることに同意しますが、2009 年以降はあまり活動していません (!)。個人的には、Stanislav の Web サイトが EditorKit のあらゆる点で非常に役立つことがわかりました。
ADAPROもご覧ください。私が深く関わった、非常に安定したオープンソースの支援エディターです。支援機能にはバグがありますが、コアの編集機能は徹底的にテストされています。次のコードは、そのプロジェクトからのものです。SHEF のnet.atlanticbb.tantlinger.ui.textパッケージのElementWriter クラスが必要です。
//HTML representation of an empty paragraph
private static final String sEmptyParagraph = "<p style=\"margin-top: 0\"></p>";
/**
* Translates into HTML a given element of the document model.
* @param element Element to serialise to a HTML string
* @param out Serialiser to HTML string
* @return HTML string "equivalent" to given element
*/
static String extractHTML (Element element, StringWriter out) {
ElementWriter writer = new ElementWriter (out, element);
try {
writer.write();
} catch (IOException e) {
System.out.println ("Error encountered when serialising element: " +e);
e.printStackTrace();
} catch (BadLocationException e) {
System.out.println ("Error encountered when extracting HTML at the element's position: " +e);
e.printStackTrace();
}
return out.toString();
}
/**
* Determines if the parent element of the current paragraph element is one of a number provided as a list
* of tag names. If so, it returns the parent element.
* @param document Document model of the text
* @param iCaretPos Caret's current position
* @param sTags Possible parent tags
* @return Parent element
*/
static Element getNearestParent (HTMLDocument document, int iCaretPos, String sTags) {
Element root;
root = document.getParagraphElement (iCaretPos);
do {
root = root.getParentElement();
} while (sTags.indexOf (root.getName()) == -1);
return root;
}
/**
* Inserts all HTML tags required to build an ordered/unordered list at the caret's current position.
* If the aim is instead to turn the numbered/bulleted paragraphs into plain ones, it takes care of
* deleting the necessary tags.
* @param sTypeList Type of list to build: "ul" or "ol".
* @param textArea Editable area containing text.
*/
static void insertList (String sTypeList, JTextPane textArea) {
boolean bOnlyListSelected; //selection includes a list exclusively
int iStartIndex, iEndIndex, //element indexes included in selection
iStartSel, iEndSel, //starting and ending offset of selected text
iItemNo, //total number of list items
i;
String sHTML, //HTML code of text represented by a given element
sHTMLBlock, //HTML code block to be inserted into document model
sRest; //part of the text remaining unselected after the selected block
HTML.Tag tag; //current list tag
HTMLDocument document; //data model underlying the typed text
Element root, //root element of the document model tree
section; //element representing a block of text
SimpleAttributeSet attribIns; //backup of current input attributes
//Fetches the current document
document = (HTMLDocument) textArea.getDocument();
//Finds the topmost parent element of the current paragraph (effectively, is the list inside a table?)
root = getNearestParent (document, textArea.getCaretPosition(), "td body");
//Range of elements included in the selection
iStartSel = textArea.getSelectionStart();
iEndSel = textArea.getSelectionEnd();
iStartIndex = root.getElementIndex (iStartSel);
iEndIndex = root.getElementIndex (iEndSel);
//HTML-related initialisations
sHTML = "";
sHTMLBlock = "";
tag = null;
//Checks if selection is comprised of just list items
i = iStartIndex;
bOnlyListSelected = true;
do {
tag = HTML.getTag (root.getElement(i).getName());
//Is it a list tag?
if ((tag == null) || ((!tag.equals (HTML.Tag.OL)) && (!tag.equals (HTML.Tag.UL))))
bOnlyListSelected = false;
i++;
} while (bOnlyListSelected && (i <= iEndIndex));
//Back up current input attributes
attribIns = new SimpleAttributeSet (textArea.getInputAttributes());
try {
//At some point in the selection there is no previous list...
if (!bOnlyListSelected) {
//Inserts <LI> tags for every text block
for (i = iStartIndex; i <= iEndIndex; i++) {
section = root.getElement(i);
tag = HTML.getTag (section.getName());
//Retrieves current HTML
sHTML = extractHTML (section, new StringWriter());
//If it is non-listed text, reconstitute the paragraph
if (tag == null)
sHTML = "<p style=\"margin-top: 0;\">" +sHTML+ "</p>";
//Text in a list already => no nesting (delete <UL>/<OL> tags)
if (sHTML.indexOf("<li>") != -1) {
sHTML = sHTML.substring (sHTML.indexOf("<li>"), sHTML.length());
sHTML = sHTML.substring (0, sHTML.lastIndexOf("</li>") + 5);
//Non-listed text => add <LI> tags
} else sHTML = "<li>" +sHTML+ "</li>";
sHTMLBlock = sHTMLBlock + sHTML;
}
sHTMLBlock = "<"+sTypeList+">" +sHTMLBlock.trim()+ "</"+sTypeList+">";
//Gets the text coming after caret or end of selection
sRest = textArea.getText (iEndSel, document.getLength() - iEndSel);
//Adds an empty paragraph at the end of the list if the latter coincides with the end of the document
//or if the rest of the document is empty. This is to avoid a glitch in the editor kit's write() method.
//http://java-sl.com/tip_html_kit_last_empty_par.html
if ((root.getElement(iEndIndex).getEndOffset() == root.getEndOffset()) ||
sRest.replaceAll ("[\\p{Z}\\s]", "").trim().isEmpty())
sHTMLBlock = sHTMLBlock + sEmptyParagraph;
//Removes the remaining old non-listed text block and saves resulting HTML string to document model
document.setOuterHTML (root.getElement(iEndIndex), sHTMLBlock);
if (iEndIndex > iStartIndex)
document.remove (root.getElement(iStartIndex).getStartOffset(),
root.getElement(iEndIndex - 1).getEndOffset() -
root.getElement(iStartIndex).getStartOffset());
//Selection just includes list items
} else {
//Works out the list's length in terms of element indexes
root = root.getElement (root.getElementIndex (iStartSel));
iItemNo = root.getElementCount();
iStartIndex = root.getElementIndex (textArea.getSelectionStart());
iEndIndex = root.getElementIndex (textArea.getSelectionEnd());
//For everery <LI> block, remove the <LI> tag
for (i = iStartIndex; i <= iEndIndex; i++) {
sHTML = extractHTML (root.getElement(i), new StringWriter());
sHTML = sHTML.substring(sHTML.indexOf("<li>") + 4, sHTML.length());
sHTML = sHTML.substring(0, sHTML.lastIndexOf("</li>"));
sHTMLBlock = sHTMLBlock + sHTML;
}
//List selected partially? => divide list
if (iItemNo > (iEndIndex - iStartIndex + 1)) {
//Saves HTML string to document model
((HTMLEditorKit) textArea.getEditorKit()).insertHTML (document, root.getElement(iEndIndex).getEndOffset(),
sHTMLBlock, 3, 0, HTML.Tag.P);
//Removes the old block
document.remove (root.getElement(iStartIndex).getStartOffset(),
root.getElement(iEndIndex).getEndOffset() -
root.getElement(iStartIndex).getStartOffset());
//Removes the list tag associated with the block
} else document.setOuterHTML (root, sHTMLBlock.trim());
}
} catch (Exception eTexto) {
System.out.println ("Problemas al eliminar/insertar texto: " +eTexto);
eTexto.printStackTrace();
}
//Recover selection. Previous operations displace the cursor and thus selection highlight is lost
textArea.setSelectionStart (iStartSel);
textArea.setSelectionEnd (iEndSel);
//If only one list item has been created and is the first one, copy all previous style information to the list
if ((!bOnlyListSelected) && (iStartSel == iEndSel)) {
textArea.setCharacterAttributes (attribIns, false);
}
}