My Schema class RE: [jdom-interest] Schema

Kenworthy, Edward edward.kenworthy at exchange.co.uk
Mon Jun 4 23:41:33 PDT 2001


hi

I've had some emails asking me to post how I have been doing schema
enforcement and could I post some code.

Basically I wrap the JDom document inside an XMLDocument, this principally
adds support for document listeners. It also contains an instance of a
Schema object (see below) which it uses to validate any changes to the
underlying Document. It also exposes the Schema object so that, for example,
UI's can query it to find out which child elements it should allow to be
added to any particular element.

Note that a) it is NOT a full schema implementation - I'm basically only
implementing what I need as I need it - however the basic structure seems to
be holding up. b) as you can see from the scattering of @todos I'm still
working on it :)

Edward

ps

for those of you who are curious, I'm writing a requirements management
tool.

##
## Schema.java
##

package reqxml;

import java.util.Map;
import java.util.Collection;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.text.MessageFormat;
import java.io.File;
import org.jdom.*;
import org.jdom.input.SAXBuilder;

import org.apache.log4j.Category;

/**
 * Description:
 * @author Edward Kenworthy
 * @version 1.0
 */

class Schema
{
// property keys for messages
  private final static String BAD_SCHEMA_BAD_ROOT="BadSchemaRootNoName";
  private final static String
BAD_SCHEMA_BAD_REQ_TYPE_FMT="BadSchemReqTypeFmt";
  private final static String
BAD_SCHEMA_NO_NAME_ATT_FMT="BadSchemaNoNameAttFmt";
  private final static String
BAD_SCHEMA_BAD_MINOCCURS_FMT="BadSchemaBadMinOccursFmt";
  private final static String
INVALID_REQUIREMENT_TYPE="InvalidRequirementType";
  private final static String
BAD_SCHEMA_ELEMENT_LACKS_NAME_REF="ElementLacksnameAndRef";

// XML Schema magic strings
  public final static String XML_SCHEMA_ATT_NAME = "name"; // attribute of
element "attribute" that defines the name of the attribute
  public final static String XML_SCHEMA_ATT_TYPE = "type"; // attribute of
element "attribute" that defines the type of the attribute
  public final static String XML_SCHEMA_ATT_SIMPLETYPE = "simpleType"; //
attribute of element "attribute" that defines the type of the attribute
  public final static String XML_SCHEMA_ATT_SIMPLETYPE_BASE = "base";
  public final static String XML_SCHEMA_ATT_DEFAULT = "default";  //
attribute of element "attribute" that defines the default value of the
attribute

  public final static String XML_SCHEMA_COMPLEXTYPE = "complexType";
  public final static String XML_SCHEMA_ELEMNAME = "name";
  public final static String XML_SCHEMA_MINOCCURS = "minOccurs";
  public final static String XML_SCHEMA_SIMPLETYPE = "element";
  public final static String XML_SCHEMA_ELEMREF = "ref";
  public final static String XML_SCHEMA_ATTRIB = "attribute";

// Logging
  private static final Category logger =
Category.getInstance(Schema.class.getName());

  private final static SAXBuilder _builder = new SAXBuilder();

  private Document _schema;
  private String _fileName;
  public Schema(String fileName) throws JDOMException
  {
    _fileName = fileName;
    _schema = _builder.build(_fileName);
  }
  public String getFileName()
  {
    return _fileName;
  }
  public String getDirectory()
  {
    return (new File(_fileName)).getParentFile().getAbsolutePath();
  }

/** @todo need to take account of maxOccurs ? Might be too much of a pain
for the user though. */
  /*package*/ boolean isElementAddable(String candidateChildType, Element
parent) throws SchemaException
  {
    Collection validChildTypes = getAllValidChildTypes(parent);

    boolean result = false;
    Iterator iter = validChildTypes.iterator();
    while (iter.hasNext())
    {
      if (((String)iter.next()).equals(candidateChildType))
      {
	result = true;
	break;
      }
    }
//    logger.debug("isElementAddable() he say " + (result ? "Yes!" :
"No."));
    return result;
  }

  public String getProjectRootType() throws SchemaException
  {
    Element schemaElement = _schema.getRootElement();
    Element rootElement = schemaElement.getChild(XML_SCHEMA_COMPLEXTYPE);
    Attribute projectRootType =
rootElement.getAttribute(XML_SCHEMA_ELEMNAME);
    if (null != projectRootType)
    {
      return projectRootType.getValue();
    }
    throw new
SchemaException(ReqXML.getStringProperty(BAD_SCHEMA_BAD_ROOT));
  }

  public Collection generateMandatoryRootChildren(XMLDocument doc) throws
SchemaException
  {
    Collection mandatoryRootChildren = new Vector();
    Element schemaElement = _schema.getRootElement();
    Element rootElement = schemaElement.getChild(XML_SCHEMA_COMPLEXTYPE); //
note the name
    List children = rootElement.getChildren();
    logger.debug("Schema says there are " + children.size() + " child
elements of root " + rootElement.getName() + ", sorting out which are
mandatory.");
    Iterator iter = children.iterator();
    while(iter.hasNext())
    {
      Element currentElement = (Element)iter.next();
      Attribute minOccursAtt =
currentElement.getAttribute(XML_SCHEMA_MINOCCURS);
      if (null != minOccursAtt)
      {
	try
	{
	  int minOccurs = minOccursAtt.getIntValue();
	  Attribute requirementTypeAtt =
currentElement.getAttribute(XML_SCHEMA_ELEMNAME);
	  if (null != requirementTypeAtt)
	  {
	    String requirementType = requirementTypeAtt.getValue();
	    try
	    {
	      for (int i = 0; i < minOccurs; i++)
	      {
/** @todo handle mandatory children of mandatory elements */
		logger.debug("Adding a " + requirementType + " to the list
of mandatory root elements");
		mandatoryRootChildren.add(new Requirement(doc,
requirementType));
	      }
	    }
	    catch (IllegalNameException ine)
	    {
	      throw new
SchemaException(MessageFormat.format(ReqXML.getStringProperty(BAD_SCHEMA_BAD
_REQ_TYPE_FMT), new Object[]{requirementType}));
	    }
	  }
	  else
	  {
	    throw new
SchemaException(MessageFormat.format(ReqXML.getStringProperty(BAD_SCHEMA_NO_
NAME_ATT_FMT), new Object[]{currentElement.getName()}));
	  }
	}
	catch (DataConversionException dce)
	{
	  throw new
SchemaException(MessageFormat.format(ReqXML.getStringProperty(BAD_SCHEMA_BAD
_MINOCCURS_FMT), new Object[]{currentElement.getName(),
minOccursAtt.getValue()}));
	}
      }
    } // while
    logger.debug("Schema says there are " + mandatoryRootChildren.size() + "
mandatory root children.");
    return mandatoryRootChildren;
  }

  private final Map _cacheOfValidChildTypes = new HashMap(); // cache of
results from getAllValidChildTypes()

/**
 * If it returns null then that means ANY element can be child of this
parent
 */
  /*package*/ Collection getAllValidChildTypes(Element parent) throws
SchemaException
  {
    logger.debug("Checking allowed children for " + parent.getName());
//    if (null != _schema)
//    {
    // try and find it in the cache first
      List allowedChildren =
(List)_cacheOfValidChildTypes.get(parent.getName());
      if (null == allowedChildren)
      {
	logger.debug("Not found in the cache, looking it up.");
	allowedChildren = new Vector();
	Element parentInSchema = findRequirementInSchema(parent.getName());
	if (null != parentInSchema)
	{
	  List allChildren = parentInSchema.getChildren();
	  Iterator iter = allChildren.iterator();
// the structure used in the following code is straight from the XML schema
spec.
	  while(iter.hasNext())
	  {
	    Element current = (Element)iter.next();
	    if ((current.getName().equals(XML_SCHEMA_SIMPLETYPE)) ||
(current.getName().equals(XML_SCHEMA_COMPLEXTYPE)))
	    {
	      String elementType;
	      Attribute nameAtt = current.getAttribute(XML_SCHEMA_ELEMNAME);
	      if (null != nameAtt)
	      {
		elementType = nameAtt.getValue();
	      }
	      else
	      {
		Attribute refAtt = current.getAttribute(XML_SCHEMA_ELEMREF);
		if (null != refAtt)
		{
		  elementType = refAtt.getValue();
		}
		else
		{
		  throw new
SchemaException(ReqXML.getStringProperty(BAD_SCHEMA_ELEMENT_LACKS_NAME_REF))
;
		}
	      }
	      logger.debug("Adding " + elementType + " to the list.");
	      allowedChildren.add(elementType);
	    }
	  } // while
	  // cache the result
	  _cacheOfValidChildTypes.put(parent.getName(), allowedChildren);
	}
	else
	{
	  throw new
SchemaException(ReqXML.getStringProperty(INVALID_REQUIREMENT_TYPE));
	}
      }
//      else
//      {
//	logger.debug("Found list in cache, returning that.");
//      }
      return allowedChildren; // note that if (null == parentInSchema) then
allowedChildren is empty
//    }
//    return null;
  }

  class SchemaAttribute
  {
// Logging
    private final Category logger =
Category.getInstance(SchemaAttribute.class.getName());

    private String _name;
    private String _type;
    private String _defaultValue;
    public SchemaAttribute(Element attribute)
    {
// name
      Attribute nameAttribute =
attribute.getAttribute(Schema.XML_SCHEMA_ATT_NAME);
      if (null != nameAttribute)
      {
	_name = nameAttribute.getValue();
// type
	Attribute typeAttribute =
attribute.getAttribute(Schema.XML_SCHEMA_ATT_TYPE);
	if (null != typeAttribute)
	{
	  _type = typeAttribute.getValue();
	}
	else
	{
	  // could be a simpleType
	  Element simpleType =
attribute.getChild(Schema.XML_SCHEMA_ATT_SIMPLETYPE);
	  if (null != simpleType)
	  {
	    Attribute base =
simpleType.getAttribute(Schema.XML_SCHEMA_ATT_SIMPLETYPE_BASE);
	    if (null != base)
	    {
	      _type = Schema.XML_SCHEMA_ATT_SIMPLETYPE;
//Schema.XML_SCHEMA_ATT_SIMPLETYPE + ":" + base.getValue();
	    }
	    else
	    {
/** @todo text to resource file */
	      logger.error("Bad schema. simpleType has no \"" +
Schema.XML_SCHEMA_ATT_SIMPLETYPE_BASE + "\" attribute !");
	      throw new RuntimeException("Bad schema. simpleType has no \""
+ Schema.XML_SCHEMA_ATT_SIMPLETYPE_BASE + "\" attribute !");
	    }
	  }
	  else
	  {
/** @todo text to resource file */
	    logger.error("Bad schema. No \"" + Schema.XML_SCHEMA_ATT_TYPE +
"\" or \"" + Schema.XML_SCHEMA_ATT_SIMPLETYPE + "\" on attribute.");
	    throw new RuntimeException("Bad schema. No \"" +
Schema.XML_SCHEMA_ATT_TYPE + "\" or \"" + Schema.XML_SCHEMA_ATT_SIMPLETYPE +
"\" on attribute.");
	  }
	}
// default value
	Attribute defaultAttribute =
attribute.getAttribute(Schema.XML_SCHEMA_ATT_DEFAULT);
	if (null != defaultAttribute)
	{
	  _defaultValue = defaultAttribute.getValue();
	}
	else
	{
	  _defaultValue="";
	}
      }
      else
      {
/** @todo text to resource file */
	logger.error("Bad schema. No \"" + Schema.XML_SCHEMA_ATT_NAME + "\"
on attribute.");
	throw new RuntimeException("Bad schema. No \"" +
Schema.XML_SCHEMA_ATT_NAME + "\" on attribute.");
      }
    }
    public String getName()
    {
      return _name;
    }
    public String getDefaultValue()
    {
      return _defaultValue;
    }
    public String getType()
    {
      return _type;
    }
  }

/**
 * Returns a list of SchemaAttributes that describe the attributes for the
given requirement type.
 */
  /*package*/ Collection getAttributes(String requirementType)
  {
    Vector attributes = new Vector();
    Element reqTypeElement = findRequirementInSchema(requirementType);
    if (reqTypeElement != null)
    {
      List children = reqTypeElement.getChildren();
      Iterator iter = children.iterator();
      while(iter.hasNext())
      {
	Element child = (Element)iter.next();
	if (child.getName().equals(XML_SCHEMA_ATTRIB))
	{
	  attributes.add(new SchemaAttribute(child));
	}
      }
      return attributes;
    }
    return null;
  }

  private Element findRequirementInSchema(String reqName)
  {
    return findRequirementInSchema(_schema.getRootElement(), reqName);
  }
/**
 * Recursively performs a depth first search of the schema.
 */
  private Element findRequirementInSchema(Element searchRoot, String
reqName)
  {
    List children = searchRoot.getChildren();
    Iterator iter = children.iterator();
    while (iter.hasNext())
    {
      Element current = (Element)iter.next();
      Attribute nameAtt = current.getAttribute(XML_SCHEMA_ATT_NAME);
      if (null != nameAtt)
      {
	String name = nameAtt.getValue();
	if (name.equals(reqName))
	{
	  return current; // found it
	}
	else
	{
	  Element found = findRequirementInSchema(current, reqName);
	  if (null != found)
	  {
	    return found;
	  }
	}
      }
    } // while
    return null;
  }

  class SchemaException extends Exception
  {
    public SchemaException()
    {
    }
    public SchemaException(String message)
    {
      super(message);
    }
  }
} // class schema

-----Original Message-----
From: Yoichi Takayama [mailto:yoichi at webmcq.com]
Sent: 05 June 2001 00:41
To: Kenworthy, Edward
Subject: RE: [jdom-interest] Schema


Dear Edward,

I am interested in this topic, too.

I would like the user to be able to edit the XML file via web interface - 
either via HTML FORM (maybe it has to be DHTML) or XForm or Applet or an 
application using Java Web Start GUI and probably the GUI shows the current 
tree and values.  User should be able to pick an Element or an Attribute 
and change the values, add or delete Element or Attribute, only if all of 
these conform to its Schema.

Could you post your findings to the list?

Thanks,
Yoichi


At 08:35 AM 04/06/2001 +0100, you wrote:
>Load the schema as another instance of a JDom Document and use that.
>
>-----Original Message-----
>From: Kiss Gábor [mailto:kissg at freemail.hu]
>Sent: 30 May 2001 10:58
>To: jdom-interest at jdom.org
>Subject: [jdom-interest] Schema
>
>
>Hello everyone!
>
>I again turn to the list with a Validation question.
>I write an XML editor in which the user can only edit the content and
>attributes of an element. No modifications to the structure are allowed.
>I would like to read -for example- the restrictions from the schema, so
that
>user could choose among those restrictions, and couldn't type in anything
>else.
>For example for a postition attribute one could choose center, left or
right
>from a drop-down list.
>
>I'm looking for the simplest way to do it.
>
>Thanks a lot,
>
>Gabor Kiss
>
>_______________________________________________
>To control your jdom-interest membership:
>http://lists.denveronline.net/mailman/options/jdom-interest/youraddr@yourho
st.com 
>



More information about the jdom-interest mailing list