[jdom-interest] Helper methods [eg]

Rosen, Alex arosen at silverstream.com
Thu Apr 26 17:15:26 PDT 2001


Here's first attempt at some helper classes for jdom-contrib. The methods fall
into 3 categories:

(1) "Required" helpers, that throw an exception if the requested data is not
there, rather than returning null. (The list archives are down right now, but
see the discussion about "NoSuchChildException" in July 2000 for why this is
needed.)

(2) Modification methods which preserve formatting. addChild() adds any
necessary indentation so the XML continues to look nice, and removeChild()
removes the extra whitespace so there isn't a big empty hole left in the
document.

(3) Methods that are currently in the core, but could be put in helpers
instead. I added getChildText() and fullTrim(), which are designed to replace
the getChildText(), getTextTrim(), and getChildTextTrim() on Element. I think
these work better here than cluttering up Element, since they are just helpers
(they don't provide any fundamental functionality). If people agree, we should
remove them from Element; otherwise, we should remove these helpers.

Any comments or suggestions (or contributions) are welcome!

Alex Rosen
SilverStream Software

P.S. To get addChild() to work, I had to fix a bug in PartialList to get add(0,
obj) to work. I added this to the beginning of both addFirst() and addLast():

        if (size() == 0) {
            add(o);
            return;
        }

-------------- next part --------------
/*-- 

 Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact license at jdom.org.
 
 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management (pm at jdom.org).
 
 In addition, we request (but do not require) that you include in the 
 end-user documentation provided with the redistribution and/or in the 
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos 
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many 
 individuals on behalf of the JDOM Project and was originally 
 created by Brett McLaughlin <brett at jdom.org> and 
 Jason Hunter <jhunter at jdom.org>.  For more information on the 
 JDOM Project, please see <http://www.jdom.org/>.
 
 */

package org.jdom.contrib.helpers;

import org.jdom.*;

/** <p>
 *    Used by RequiredHelper. If a requested piece of data is missing
 *    or empty, a MissingDataException is thrown.
 *    </p>
 *    @author Alex Rosen
 */
public class MissingDataException extends JDOMException {
    public MissingDataException(String element, String missingType, 
                    String missingName, Namespace missingNamespace) {
        super("The element \"" + element + "\" does not contain the " 
                + missingType + " named \"" + missingName 
                + "\" in namespace \"" + missingNamespace.getURI() + "\".");
    }

    public MissingDataException(String element, String missingType, 
                    String missingName) {
        super("The element \"" + element + "\" does not contain the " 
                + missingType + " named \"" + missingName + "\".");
    }

    public MissingDataException(String element) {
        super("The element \"" + element 
                + "\" does not contain any text content.");
    }
}
-------------- next part --------------
/*-- 

 Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact license at jdom.org.
 
 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management (pm at jdom.org).
 
 In addition, we request (but do not require) that you include in the 
 end-user documentation provided with the redistribution and/or in the 
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos 
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many 
 individuals on behalf of the JDOM Project and was originally 
 created by Brett McLaughlin <brett at jdom.org> and 
 Jason Hunter <jhunter at jdom.org>.  For more information on the 
 JDOM Project, please see <http://www.jdom.org/>.
 
 */

package org.jdom.contrib.helpers;

import org.jdom.*;

/** <p>
 *    This class contains static helper methods to access data that is required
 *    to exist. If the data is missing, or empty (i.e. the empty string),
 *    then a MissingDataException is thrown. 
 *    </p>
 *    @author Alex Rosen
 */
public class RequiredHelper {
    /** Same as Element.getText(), but throws an exception if the text is
    missing or empty. */
    public static String getRequiredText(Element element)
                throws MissingDataException {
        String s = element.getText();
        if (s == null || s.length() == 0) {
            throw new MissingDataException(element.getName());
        }
        return s;
    }

    /** Same as TextHelper.getChildText(), but throws an exception if the 
    text is missing or empty. */
    public static String getRequiredChildText(Element parent, String name)
                throws MissingDataException {
        Element child = parent.getChild(name);
        if (child == null) {
            throw new MissingDataException(parent.getName(), "child", name);
        }
        return getRequiredText(child);
    }

    /** Same as TextHelper.getChildText(), but throws an exception if the 
    text is missing or empty. */
    public static String getRequiredChildText(
                Element parent, String name, Namespace ns)
                throws MissingDataException {
        Element child = parent.getChild(name, ns);
        if (child == null) {
            throw new MissingDataException(parent.getName(), "child", name, ns);
        }
        return getRequiredText(child);
    }

    /** Same as Element.getAttributeValue(), but throws an exception if the 
    attribute is missing or empty. */
    public static String getRequiredAttributeValue(Element element, String name)
                throws MissingDataException {
        String s = element.getAttributeValue(name);
        if (s == null || s.length() == 0) {
            throw new MissingDataException(element.getName(), "attribute", name);
        }
        return s;
    }

    /** Same as Element.getAttributeValue(), but throws an exception if the 
    attribute is missing or empty. */
    public static String getRequiredAttributeValue(
                Element element, String name, Namespace ns)
                throws MissingDataException {
        String s = element.getAttributeValue(name, ns);
        if (s == null || s.length() == 0) {
            throw new MissingDataException(element.getName(), "attribute", 
                    name, ns);
        }
        return s;
    }
}
-------------- next part --------------
/*-- 

 Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact license at jdom.org.
 
 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management (pm at jdom.org).
 
 In addition, we request (but do not require) that you include in the 
 end-user documentation provided with the redistribution and/or in the 
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos 
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many 
 individuals on behalf of the JDOM Project and was originally 
 created by Brett McLaughlin <brett at jdom.org> and 
 Jason Hunter <jhunter at jdom.org>.  For more information on the 
 JDOM Project, please see <http://www.jdom.org/>.
 
 */

package org.jdom.contrib.helpers;

import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import java.util.*;
import java.io.*;

/** <p>
 *    This class contains static helper methods. 
 *    </p>
 *    @author Alex Rosen
 */
public class TextHelper
{
    /**
     * <p>
     *  This convenience method returns the textual content of the named
     *    child element, or returns an empty <code>String</code> ("")
     *    if the child has no textual content. However, if the child does
     *    not exist, <code>null</code> is returned.
     * </p>
     *
     * @param parent the parent element
     * @param name the name of the child
     * @return text content for the named child, or null if none
     */
    public static String getChildText(Element parent, String name) {
        Element child = parent.getChild(name);
        if (child == null) {
            return null;
        }
        return child.getText();
    }

    /**
     * <p>
     *  This convenience method returns the textual content of the named
     *    child element, or returns an empty <code>String</code> ("")
     *    if the child has no textual content. However, if the child does
     *    not exist, <code>null</code> is returned.
     * </p>
     *
     * @param parent the parent element
     * @param name the name of the child
     * @param ns the namespace of the child
     * @return text content for the named child, or null if none
     */
    public static String getChildText(Element parent, String name, Namespace ns) {
        Element child = parent.getChild(name, ns);
        if (child == null) {
            return null;
        }
        return child.getText();
    }

    /**
     * <p>
     * This returns the text with surrounding whitespace removed and 
     * internal whitespace normalized to a single space.  
     * </p>
     */
    public static String fullTrim(String text) {
        if (text == null) {
            return null; // just in case.
        }

        StringBuffer textContent = new StringBuffer();
        StringTokenizer tokenizer = new StringTokenizer(text);
        while (tokenizer.hasMoreTokens()) {
            String str = tokenizer.nextToken();
            textContent.append(str);
            if (tokenizer.hasMoreTokens()) {
                textContent.append(" ");  // separator
            }
        }

        return textContent.toString();
    }

    /** <p> 
     * Adds an object as the last child of the parent element.
     * </p>
     * <p>
     * This method does its best to make the resulting XML nicely indented. 
     * If the parent doesn't already have any children, the child element is 
     * indented by one level more than the parent. If the parent does have
     * existing children, the new element is indented by the same amount
     * as the child it's being inserted after.
     * </p>
     * <p>
     * This method is not designed to be called on a parent that may
     * contain mixed content (i.e. meaningful text content).
     * </p>
     *
     * @param parent Element to add the child to
     * @param child Element, Entity, Comment, ProcessingInstruction, 
     * or CDATA to add
     * @param indent the indent string, usually some number of spaces or a tab.
     * When adding to the head of the list, we indent the child by this much
     * more than the parent.
     */
    public static void addChild(Element parent, Object child, String indent) {
        Object insertAfter = null;

        // Find the last non-String child, to insert the new child after.
        List contents = parent.getMixedContent();
        for (int i = contents.size() - 1; i >= 0; i--) {
            Object obj = contents.get(i);
            if (!(obj instanceof String)) {
                insertAfter = obj;
                break;
            }
        }

        addChild(parent, child, insertAfter, indent);
    }

    /** <p> 
     * Adds an object as a child of the parent element, at the specified
     * location. If "insertAfter" is not null, the child is inserted after
     * this object. Otherwise, it is inserted at the head of the child list.
     * </p>
     * <p>
     * This method does its best to make the resulting XML nicely indented. 
     * If we're adding at the head of the list, the child element is indented
     * by one more level than the parent. Otherwise, the new element is 
     * indented by the same amount as the "insertAfter" object. 
     * </p>
     * <p>
     * This method is not designed to be called on a parent that may
     * contain mixed content (i.e. meaningful text content). 
     * </p>
     *
     * @param parent Element to add the child to
     * @param child Element, Entity, Comment, ProcessingInstruction, 
     * or CDATA to add
     * @param insertAfter Element, Entity, Comment, ProcessingInstruction, 
     * or CDATA to insert the new child after. If null, the new child is 
     * added at the end. This parameter must not be a String.
     * @param indent the indent string, usually some number of spaces or a tab.
     * When adding to the head of the list, we indent the child by this much
     * more than the parent.
     */
    public static void addChild(Element parent, Object child, 
                                Object insertAfter, String indent) {
        // It's not really meaningful to have "insertAfter" be a String.
        if (insertAfter instanceof String) {
            throw new RuntimeException("\"insertAfter\" cannot be a String.");
        }
        // Make sure that "insertAfter" really is a chid of the parent element.
        // Note that this check also means that the type of "insertAfter" is
        // reasonable (e.g. it's not a JButton). To make sure that the type of
        // "child" is reasonable, we rely on the add() methods below to 
        // check for us.
        List contents = parent.getMixedContent();
        if (insertAfter != null && !contents.contains(insertAfter)) {
            throw new RuntimeException(
                "Parent element does not contain the \"insertAfter\" object.");
        }

        int size = contents.size();
        if (size == 0 || insertAfter == null) {
            // We're adding to the head of the list.
            // Get the parent's indentation.
            List siblings = getSiblings(parent);
            String indentation = getIndentation(parent, siblings);
            // Add the child to the head of the list, indented by one more level 
            // than the parent. The indentation goes in front of the new child.
            contents.add(0, indentation + indent);
            contents.add(1, child);
            // If this parent has no other children, we need indentation after
            // the new child too.
            if (size == 0) {
                contents.add(2, indentation);
            }
        }
        else {
            // We're not adding to the head of the list.
            // Get the indentation of the previous element.
            String indentation = getIndentation(insertAfter, contents);
            // Find the location of the previous element in the 
            // mixed content list.
            int insertAt = contents.indexOf(insertAfter) + 1;
            // Add the child with the same indentation as the previous element.
            // The indentation goes after the new child.
            contents.add(insertAt, indentation);
            contents.add(insertAt + 1, child);
        }

        // ** Workaround ** 
        // The current implementation of PartialList gets confused, 
        // because several of the Strings in the mixed content are likely 
        // to be .equals() with each other. To work around this, we 
        // explicitly remove all children and then add them back as a list.
        parent.setMixedContent(new ArrayList());
        parent.setMixedContent(contents);
    }

    // Returns the mixed-content siblings of the specified element. 
    private static List getSiblings(Element element) {
        // Need to handle the root element separately.    
        Element parent = element.getParent();
        if (parent != null) {
            return parent.getMixedContent();
        } else {
            return element.getDocument().getMixedContent();
        }
    }

    // Returns the indentation of the specified element. "siblings" is
    // the mixed-content siblings of the specified element.
    private static String getIndentation(Object obj, List siblings) {
        // Starting with the object just before the target element,
        // we'll concatentate all the Strings we see, until we hit
        // a non-String (e.g. an Element, Comment, CDATA, etc).
        // (Usually we'll only find one String.)
        
        int i = siblings.indexOf(obj) - 1;
        String result = null;
        while (i >= 0) {
            Object sibling = siblings.get(i);

            // If this object isn't text content, we're done.
            if (!(sibling instanceof String))
                break;
        
            // Prepend this text to the results so far. 
            if (result == null) {
                result = (String)sibling;
            } else {
                result = (String)sibling + result;
            }

            i--;
        }

        if (result == null) {
            result = "";
        }
    
        return result;
    }
}

-------------- next part --------------
/*-- 

 Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact license at jdom.org.
 
 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management (pm at jdom.org).
 
 In addition, we request (but do not require) that you include in the 
 end-user documentation provided with the redistribution and/or in the 
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos 
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many 
 individuals on behalf of the JDOM Project and was originally 
 created by Brett McLaughlin <brett at jdom.org> and 
 Jason Hunter <jhunter at jdom.org>.  For more information on the 
 JDOM Project, please see <http://www.jdom.org/>.
 
 */

package org.jdom.contrib.helpers;

import org.jdom.*;
import java.util.*;

/** <p>
 *    This class contains static helper methods. 
 *    </p>
 *    @author Alex Rosen
 */
public class TextHelper
{
    /**
     * <p>
     *  This convenience method returns the textual content of the named
     *    child element, or returns an empty <code>String</code> ("")
     *    if the child has no textual content. However, if the child does
     *    not exist, <code>null</code> is returned.
     * </p>
     *
     * @param parent the parent element
     * @param name the name of the child
     * @return text content for the named child, or null if none
     */
    public static String getChildText(Element parent, String name) {
        Element child = parent.getChild(name);
        if (child == null) {
            return null;
        }
        return child.getText();
    }

    /**
     * <p>
     *  This convenience method returns the textual content of the named
     *    child element, or returns an empty <code>String</code> ("")
     *    if the child has no textual content. However, if the child does
     *    not exist, <code>null</code> is returned.
     * </p>
     *
     * @param parent the parent element
     * @param name the name of the child
     * @param ns the namespace of the child
     * @return text content for the named child, or null if none
     */
    public static String getChildText(Element parent, String name, Namespace ns) {
        Element child = parent.getChild(name, ns);
        if (child == null) {
            return null;
        }
        return child.getText();
    }

    /**
     * <p>
     * This returns the text with surrounding whitespace removed and 
     * internal whitespace normalized to a single space.  
     * </p>
     */
    public static String fullTrim(String text) {
        if (text == null) {
            return null; // just in case.
        }

        StringBuffer textContent = new StringBuffer();
        StringTokenizer tokenizer = new StringTokenizer(text);
        while (tokenizer.hasMoreTokens()) {
            String str = tokenizer.nextToken();
            textContent.append(str);
            if (tokenizer.hasMoreTokens()) {
                textContent.append(" ");  // separator
            }
        }

        return textContent.toString();
    }

    /** <p> 
     * Adds an object as the last child of the parent element.
     * </p>
     * <p>
     * This method does its best to make the resulting XML nicely indented. 
     * If the parent doesn't already have any children, the child element is 
     * indented by one level more than the parent. If the parent does have
     * existing children, the new element is indented by the same amount
     * as the child it's being inserted after.
     * </p>
     * <p>
     * This method is not designed to be called on a parent that may
     * contain mixed content (i.e. meaningful text content).
     * </p>
     *
     * @param parent Element to add the child to
     * @param child Element, Entity, Comment, ProcessingInstruction, 
     * or CDATA to add
     * @param indent the indent string, usually some number of spaces or a tab.
     * When adding to the head of the list, we indent the child by this much
     * more than the parent.
     */
    public static void addChild(Element parent, Object child, String indent) {
        Object insertAfter = null;

        // Find the last non-String child, to insert the new child after.
        List contents = parent.getMixedContent();
        for (int i = contents.size() - 1; i >= 0; i--) {
            Object obj = contents.get(i);
            if (!(obj instanceof String)) {
                insertAfter = obj;
                break;
            }
        }

        addChild(parent, child, insertAfter, indent);
    }

    /** <p> 
     * Adds an object as a child of the parent element, at the specified
     * location. If "insertAfter" is not null, the child is inserted after
     * this object. Otherwise, it is inserted at the head of the child list.
     * </p>
     * <p>
     * This method does its best to make the resulting XML nicely indented. 
     * If we're adding at the head of the list, the child element is indented
     * by one more level than the parent. Otherwise, the new element is 
     * indented by the same amount as the "insertAfter" object. 
     * </p>
     * <p>
     * This method is not designed to be called on a parent that may
     * contain mixed content (i.e. meaningful text content). 
     * </p>
     *
     * @param parent Element to add the child to
     * @param child Element, Entity, Comment, ProcessingInstruction, 
     * or CDATA to add
     * @param insertAfter Element, Entity, Comment, ProcessingInstruction, 
     * or CDATA to insert the new child after. If null, the new child is 
     * added at the end. This parameter must not be a String.
     * @param indent the indent string, usually some number of spaces or a tab.
     * When adding to the head of the list, we indent the child by this much
     * more than the parent.
     */
    public static void addChild(Element parent, Object child, 
                                Object insertAfter, String indent) {
        // It's not really meaningful to have "insertAfter" be a String.
        if (insertAfter instanceof String) {
            throw new RuntimeException("\"insertAfter\" cannot be a String.");
        }
        // Make sure that "insertAfter" really is a chid of the parent element.
        // Note that this check also means that the type of "insertAfter" is
        // reasonable (e.g. it's not a JButton). To make sure that the type of
        // "child" is reasonable, we rely on the add() methods below to 
        // check for us.
        List contents = parent.getMixedContent();
        if (insertAfter != null && !contents.contains(insertAfter)) {
            throw new RuntimeException(
                "Parent element does not contain the \"insertAfter\" object.");
        }

        int size = contents.size();
        if (size == 0 || insertAfter == null) {
            // We're adding to the head of the list.
            // Get the parent's indentation.
            List siblings = getSiblings(parent);
            String indentation = getIndentation(parent, siblings);
            // Add the child to the head of the list, indented by one more level 
            // than the parent. The indentation goes in front of the new child.
            contents.add(0, indentation + indent);
            contents.add(1, child);
            // If this parent has no other children, we need indentation after
            // the new child too.
            if (size == 0) {
                contents.add(2, indentation);
            }
        }
        else {
            // We're not adding to the head of the list.
            // Get the indentation of the previous element.
            String indentation = getIndentation(insertAfter, contents);
            // Find the location of the previous element in the 
            // mixed content list.
            int insertAt = contents.indexOf(insertAfter) + 1;
            // Add the child with the same indentation as the previous element.
            // The indentation goes after the new child.
            contents.add(insertAt, indentation);
            contents.add(insertAt + 1, child);
        }

        // ** Workaround ** 
        // The current implementation of PartialList gets confused, 
        // because several of the Strings in the mixed content are likely 
        // to be .equals() with each other. To work around this, we 
        // explicitly remove all children and then add them back as a list.
        parent.setMixedContent(new ArrayList());
        parent.setMixedContent(contents);
    }

    // Returns the mixed-content siblings of the specified element. 
    private static List getSiblings(Element element) {
        // Need to handle the root element separately.    
        Element parent = element.getParent();
        if (parent != null) {
            return parent.getMixedContent();
        } else {
            return element.getDocument().getMixedContent();
        }
    }

    // Returns the indentation of the specified element. "siblings" is
    // the mixed-content siblings of the specified element.
    private static String getIndentation(Object obj, List siblings) {
        // Starting with the object just before the target element,
        // we'll concatentate all the Strings we see, until we hit
        // a non-String (e.g. an Element, Comment, CDATA, etc).
        // (Usually we'll only find one String.)
        
        int i = siblings.indexOf(obj) - 1;
        String result = null;
        while (i >= 0) {
            Object sibling = siblings.get(i);

            // If this object isn't text content, we're done.
            if (!(sibling instanceof String))
                break;
        
            // Prepend this text to the results so far. 
            if (result == null) {
                result = (String)sibling;
            } else {
                result = (String)sibling + result;
            }

            i--;
        }

        if (result == null) {
            result = "";
        }
    
        return result;
    }

    /** <p> 
     * Removes an object from a parent element.
     * </p>
     * <p>
     * This method does its best to make the resulting XML nicely indented. 
     * Any whitespace between this child and its preceeding sibling is
     * removed. If this is the last child of the parent element, then
     * all whitespace is removed.
     * </p>
     * <p>
     * This method is not designed to be called on a parent that may
     * contain mixed content (i.e. meaningful text content).
     * </p>
     *
     * @param parent Element to add the child to
     * @param child Element, Entity, Comment, ProcessingInstruction, 
     * or CDATA to remove
     */
    public static void removeChild(Element parent, Object child) {
        // Make sure that "child" really is a chid of the parent element.
        List contents = parent.getMixedContent();
        int i = contents.indexOf(child);
        if (i < 0)
            throw new RuntimeException(
                "Parent element does not contain the \"child\" object.");

        // Remove the requested child, and all whitespace before it.
        do {
            contents.remove(i--);
        }
        while (i >= 0 && contents.get(i) instanceof String);

        // If only Strings are left, we can remove everything.
        if (isOnlyStrings(contents)) {
            contents.clear();
        }

        // ** Workaround ** 
        // The current implementation of PartialList gets confused, 
        // because several of the Strings in the mixed content are likely 
        // to be .equals() with each other. To work around this, we 
        // explicitly remove all children and then add them back as a list.
        parent.setMixedContent(new ArrayList());
        parent.setMixedContent(contents);
    }

    // Returns true if the List contains only String elements.
    private static boolean isOnlyStrings(List contents) {
        Iterator iter = contents.iterator();
        while(iter.hasNext())
        {
            Object obj = iter.next();
            if (!(obj instanceof String))
                return false;
        }        
        return true;
    }
}

-------------- next part --------------
/*-- 

 Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact license at jdom.org.
 
 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management (pm at jdom.org).
 
 In addition, we request (but do not require) that you include in the 
 end-user documentation provided with the redistribution and/or in the 
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos 
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many 
 individuals on behalf of the JDOM Project and was originally 
 created by Brett McLaughlin <brett at jdom.org> and 
 Jason Hunter <jhunter at jdom.org>.  For more information on the 
 JDOM Project, please see <http://www.jdom.org/>.
 
 */

package org.jdom.contrib.helpers;

import org.jdom.*;

/** <p>
 *    This class contains static helper methods to access data that is required
 *    to exist. If the data is missing, or empty (i.e. the empty string),
 *    then a MissingDataException is thrown. 
 *    </p>
 *    @author Alex Rosen
 */
public class RequiredHelper {
    /** Same as Element.getText(), but throws an exception if the text is
    missing or empty. */
    public static String getText(Element element)
                throws MissingDataException {
        String s = element.getText();
        if (s == null || s.length() == 0) {
            throw new MissingDataException(element.getName());
        }
        return s;
    }

    /** Same as TextHelper.getChildText(), but throws an exception if the 
    text is missing or empty. */
    public static String getChildText(Element parent, String name)
                throws MissingDataException {
        Element child = parent.getChild(name);
        if (child == null) {
            throw new MissingDataException(parent.getName(), "child", name);
        }
        return getText(child);
    }

    /** Same as TextHelper.getChildText(), but throws an exception if the 
    text is missing or empty. */
    public static String getChildText(
                Element parent, String name, Namespace ns)
                throws MissingDataException {
        Element child = parent.getChild(name, ns);
        if (child == null) {
            throw new MissingDataException(parent.getName(), "child", name, ns);
        }
        return getText(child);
    }

    /** Same as Element.getAttributeValue(), but throws an exception if the 
    attribute is missing or empty. */
    public static String getAttributeValue(Element element, String name)
                throws MissingDataException {
        String s = element.getAttributeValue(name);
        if (s == null || s.length() == 0) {
            throw new MissingDataException(element.getName(), "attribute", name);
        }
        return s;
    }

    /** Same as Element.getAttributeValue(), but throws an exception if the 
    attribute is missing or empty. */
    public static String getAttributeValue(
                Element element, String name, Namespace ns)
                throws MissingDataException {
        String s = element.getAttributeValue(name, ns);
        if (s == null || s.length() == 0) {
            throw new MissingDataException(element.getName(), "attribute", 
                        name, ns);
        }
        return s;
    }
}
-------------- next part --------------
/*-- 

 Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact license at jdom.org.
 
 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management (pm at jdom.org).
 
 In addition, we request (but do not require) that you include in the 
 end-user documentation provided with the redistribution and/or in the 
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos 
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many 
 individuals on behalf of the JDOM Project and was originally 
 created by Brett McLaughlin <brett at jdom.org> and 
 Jason Hunter <jhunter at jdom.org>.  For more information on the 
 JDOM Project, please see <http://www.jdom.org/>.
 
 */

package org.jdom.contrib.helpers;

import org.jdom.*;

/** <p>
 *    Used by RequiredHelper. If a requested piece of data is missing
 *    or empty, a MissingDataException is thrown.
 *    </p>
 *    @author Alex Rosen
 */
public class MissingDataException extends JDOMException {
    public MissingDataException(String element, String missingType, 
                    String missingName, Namespace missingNamespace) {
        super("The element \"" + element + "\" does not contain a " 
                + missingType + " named \"" + missingName 
                + "\" in namespace \"" + missingNamespace.getURI() + "\".");
    }

    public MissingDataException(String element, String missingType, 
                    String missingName) {
        super("The element \"" + element + "\" does not contain a " 
                + missingType + " named \"" + missingName + "\".");
    }

    public MissingDataException(String element) {
        super("The element \"" + element 
                + "\" does not contain any text content.");
    }
}


More information about the jdom-interest mailing list