4. Sample command MakeParagraphsCmd

MakeParagraphsCmd converts text lines contained in the clipboard to a sequence of paragraphs. This is a simplified version of command formatTextAs in XMLmind XML Editor - Commands.

MakeParagraphsCmd shows you how to avoid writing a validating command.

4.1. How it works

Authors often need to paste in their XML documents text blocks copied from a word processor.

Without a special command, doing so is tedious because:

  1. The author must create a blank paragraph in the XML document.

  2. The author must paste text copied from the clipboard inside this paragraph.

  3. If multiple text blocks have been copied, the author must remove open lines and split the paragraph in multiple pieces.

A special command can do all this automatically. Such a command is a validating one because the author must not be allowed to insert captured paragraphs in places which would make the document invalid.

The paste command in XMLmind XML Editor - Commands can be passed plain text or an XML string (a string which starts with "<?xml ") as its parameter. So why not use this standard validating command to do the paragraph insertion?

The idea here is to write a simple command, MakeParagraphsCmd, which returns an XML string containing one or several paragraphs built using clipboard content. This string is then passed to the paste command which inserts the paragraphs (if the XML Schema or the DTD constraining the document allows to do so).

Note that MakeParagraphsCmd cannot be used alone. It must be part of a macro-command such as the following one (DocBook example):

<command name="insertAfterAsSimpara">
  <macro>
    <sequence>
      <command name="MakeParagraphsCmd" parameter="simpara" />
      <command name="paste" parameter="after[implicitElement] %_" />
    </sequence>
  </macro>
</command>

4.2. First step: prepare

Excerpts from MakeParagraphsCmd.java:

public class MakeParagraphsCmd extends CommandBase {
    private Name elementName;
    private boolean blocks;
    private String pastable;

    public MakeParagraphsCmd() {
        super(/*repeatable*/ false, /*recordable*/ true);
    }

    public boolean prepare(DocumentView docView, 
                           String parameter, int x, int y) {
        elementName = null;
        blocks = false;
        pastable = null;

        if (docView.getDocument() == null) {
            return false;
        }
        
        if (parameter == null || parameter.length() == 0) {1
            return false;
        }

        String name = null;
        int pos = parameter.lastIndexOf(']');
        if (pos < 0) {
            name = parameter;
        } else if (pos+1 < parameter.length()) {
            name = parameter.substring(pos+1);
        }

        if (name != null) {
            elementName = Name.fromString(name.trim());2
        }
        if (elementName == null) {
            return false;
        }

        blocks = (parameter.indexOf("[blocks]") >= 0);3

        pastable = docView.getXMLClipboard().get();4
        if (pastable == null || pastable.length() == 0 || 
            pastable.startsWith("<?xml ")) {5
            return false;
       }

        return true;
    }
    ...

1

This command must be passed a parameter.

2

After option [blocks], the parameter must contain a fully qualified element name. This name specifies the kind of paragraph element that MakeParagraphsCmd will create.

  • DocBook 4 examples: para or simpara.

  • DocBook 5 examples: {http://docbook.org/ns/docbook}para or {http://docbook.org/ns/docbook}simpara[3].

  • XHTML example: {http://www.w3.org/1999/xhtml}p.

3

If the [blocks] option has been specified, MakeParagraphsCmd converts multiple text lines separated by open lines to a single paragraph. Without this option, each non-empty line is converted to a paragraph.

4

The clipboard contents must be accessed using DocumentView.getXMLClipboard as the XMLClipboard helper object caches parsed XML nodes when the system clipboard contains some XML in string form.

5

MakeParagraphsCmd cannot be executed if the clipboard is empty or if it contains XML.

4.3. Second step: doExecute

    public CommandResult doExecute(DocumentView docView, 
                                   String parameter, int x, int y) {
        ArrayList<String> textList = new ArrayList<String>();

        StringBuilder block = new StringBuilder();

        LineNumberReader lines = 
            new LineNumberReader(new StringReader(pastable));
        String line;
        while ((line = readLine(lines)) != null) {1
            line = XMLText.collapseWhiteSpace(line);2
            if (line.length() == 0) { 
                // Open line.
                if (blocks && block.length() > 0) {
                    textList.add(block.toString());
                    block = new StringBuilder();
                }

                continue;
            }

            line = XMLText.filterText(line);3

            if (blocks) {
                if (block.length() > 0) {
                    block.append(' ');
                }
                block.append(line);
            } else {
                textList.add(line);
            }
        }

        if (blocks && block.length() > 0) {
            textList.add(block.toString());
        }

        int count = textList.size();
        if (count == 0) {
            return CommandResult.FAILED;
        }

        // The content of the clipboard is fetched as a *Java String*. 
        // Therefore no need to specify encoding in the XML declaration.

        StringBuilder result = new StringBuilder();
        result.append("<?xml version='1.0'?>");4

        openTag(ClipboardFormat.ENVELOPE_NAME, result);5
        for (int i = 0; i < count; ++i) {
            String text = (String) textList.get(i);

            openTag(elementName, result);
            XMLText.escapeXML(text, result);
            closeTag(elementName, result);
        }
        closeTag(ClipboardFormat.ENVELOPE_NAME, result);
        
        return CommandResult.done(result.toString());
    }

1

This loop reads the content of the clipboard line by line and, for each paragraph that needs to be created, adds its textual content to ArrayList textList.

2

Whitespace contained in the textual content of a future paragraph is collapsed using utility XMLText.collapseWhiteSpace.

3

Non-XML characters contained in the textual content of a future paragraph are discarded using utility XMLText.filterText.

4

The content of the clipboard is fetched as a string. Therefore there is no need to specify an encoding in the XML declaration.

5

XXE only supports text inside the clipboard. This text is considered to be XML (that is, not plain text) if it starts with "<?xml " and if it can be parsed as well-formed XML.

As expected, the following text is parsed as a single p element:

<?xml version="1.0"?><p>Paragraph #1.</p>

Using special wrapper element {http://www.xmlmind.com/xmleditor/namespace/clipboard}clipboard, the following text is parsed as multiple p elements and empty text nodes (see ClipboardFormat):

<?xml version="1.0"?>
<ns:clipboard
  xmlns:ns="http://www.xmlmind.com/xmleditor/namespace/clipboard">
  <p>Paragraph #1.</p>
  <p>Paragraph #2.</p>
</ns:clipboard>


[3] Namespace prefixes are not supported inside command parameters.