blob: f07e8ddc77fd2b7041ad60dc04d8a1554058e958 [file] [log] [blame]
/*
* Copyright (c) 2002 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM - Initial API and implementation
* Jens Lukowski/Innoopract - initial renaming/restructuring
*
*/
package org.eclipse.wst.xml.core.internal.contentmodel.util;
import java.util.Hashtable;
import java.util.List;
import java.util.Stack;
import java.util.Vector;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAnyElement;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMContent;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList;
import org.eclipse.wst.xml.core.internal.contentmodel.ContentModelManager;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.util.CMDataTypeValueHelper;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
/**
* todo... common up this code with 'ContentBuilder'
*/
public class DOMContentBuilderImpl extends CMVisitor implements DOMContentBuilder
{
protected int buildPolicy = BUILD_ALL_CONTENT;
protected Hashtable propertyTable = new Hashtable();
protected boolean alwaysVisit = false;
protected List resultList;
protected Document document;
protected Node currentParent;
protected Node topParent;
protected Vector visitedCMElementDeclarationList = new Vector();
protected boolean attachNodesToParent = true;
protected NamespaceTable namespaceTable;
protected List namespaceInfoList;
protected Element rootElement; // this is used only teporarily via createDefaultRootContent
protected ExternalCMDocumentSupport externalCMDocumentSupport;
public boolean supressCreationOfDoctypeAndXMLDeclaration;
protected CMDataTypeValueHelper valueHelper = new CMDataTypeValueHelper();
protected int numOfRepeatableElements = 1;
protected Stack cmGroupStack = new Stack();
public interface ExternalCMDocumentSupport
{
public CMDocument getCMDocument(Element element, String uri);
}
public void setExternalCMDocumentSupport(ExternalCMDocumentSupport externalCMDocumentSupport)
{
this.externalCMDocumentSupport = externalCMDocumentSupport;
}
public DOMContentBuilderImpl(Document document)
{
this.document = document;
namespaceTable = new NamespaceTable(document);
}
public void setBuildPolicy(int buildPolicy)
{
this.buildPolicy = buildPolicy;
}
public int getBuildPolicy()
{
return buildPolicy;
}
protected boolean buildAllContent(int policy)
{
return (policy & BUILD_ALL_CONTENT) == BUILD_ALL_CONTENT;
}
protected boolean buildOptionalElements(int policy)
{
return (policy & BUILD_OPTIONAL_ELEMENTS) == BUILD_OPTIONAL_ELEMENTS;
}
protected boolean buildOptionalAttributes(int policy)
{
return (policy & BUILD_OPTIONAL_ATTRIBUTES) == BUILD_OPTIONAL_ATTRIBUTES;
}
protected boolean buildFirstChoice(int policy)
{
return (policy & BUILD_FIRST_CHOICE) == BUILD_FIRST_CHOICE;
}
protected boolean buildTextNodes(int policy)
{
return (policy & BUILD_TEXT_NODES) == BUILD_TEXT_NODES;
}
protected boolean buildFirstSubstitution(int policy)
{
return (policy & BUILD_FIRST_SUBSTITUTION) == BUILD_FIRST_SUBSTITUTION;
}
public List getResult()
{
return resultList;
}
public void setProperty(String propertyName, Object value)
{
propertyTable.put(propertyName, value);
}
public Object getProperty(String propertyName)
{
return propertyTable.get(propertyName);
}
public void build(Node parent, CMNode child)
{
resultList = new Vector();
topParent = parent;
currentParent = parent;
if (parent instanceof Element)
{
namespaceTable.addElementLineage((Element)parent);
}
attachNodesToParent = false;
alwaysVisit = true;
visitCMNode(child);
}
public void createDefaultRootContent(CMDocument cmDocument, CMElementDeclaration rootCMElementDeclaration, List namespaceInfoList) throws Exception
{
this.namespaceInfoList = namespaceInfoList;
createDefaultRootContent(cmDocument, rootCMElementDeclaration);
}
public void createDefaultRootContent(CMDocument cmDocument, CMElementDeclaration rootCMElementDeclaration) throws Exception
{
String grammarFileName = cmDocument.getNodeName();
if (!supressCreationOfDoctypeAndXMLDeclaration)
{
// TODO cs... investigate to see if this code path is ever used, doesn't seem to be
// for now I'm setting the encoding to UTF-8 just incase this code path is used somewhere
//
String piValue = "version=\"1.0\""; //$NON-NLS-1$
String encoding = "UTF-8"; //$NON-NLS-1$
piValue += " encoding=\"" + encoding + "\""; //$NON-NLS-1$ //$NON-NLS-2$
ProcessingInstruction pi = document.createProcessingInstruction("xml", piValue); //$NON-NLS-1$
document.appendChild(pi);
// if we have a 'dtd' then add a DOCTYPE tag
//
if (grammarFileName != null && grammarFileName.endsWith("dtd")) //$NON-NLS-1$
{
DOMImplementation domImpl = document.getImplementation();
DocumentType documentType = domImpl.createDocumentType(rootCMElementDeclaration.getElementName(), grammarFileName, grammarFileName);
document.appendChild(documentType);
}
}
// if we have a schema add an xsi:schemaLocation attribute
//
if (grammarFileName != null && grammarFileName.endsWith("xsd") && namespaceInfoList != null) //$NON-NLS-1$
{
DOMNamespaceInfoManager manager = new DOMNamespaceInfoManager();
String name = rootCMElementDeclaration.getNodeName();
if (namespaceInfoList.size() > 0)
{
NamespaceInfo info = (NamespaceInfo)namespaceInfoList.get(0);
if (info.prefix != null && info.prefix.length() > 0)
{
name = info.prefix + ":" + name; //$NON-NLS-1$
}
}
rootElement = createElement(rootCMElementDeclaration, name, document);
manager.addNamespaceInfo(rootElement, namespaceInfoList, true);
}
createDefaultContent(document, rootCMElementDeclaration);
}
public void createDefaultContent(Node parent, CMElementDeclaration ed)
{
currentParent = parent;
alwaysVisit = true;
visitCMElementDeclaration(ed);
}
public String computeName(CMNode cmNode, Node parent)
{
String prefix = null;
return DOMNamespaceHelper.computeName(cmNode, parent, prefix, namespaceTable);
}
// overide the following 'create' methods to control how nodes are created
//
protected Element createElement(CMElementDeclaration ed, String name, Node parent)
{
return document.createElement(name);
}
protected Attr createAttribute(CMAttributeDeclaration ad, String name, Node parent)
{
return document.createAttribute(name);
}
protected Text createTextNode(CMDataType dataType, String value, Node parent)
{
return document.createTextNode(value);
}
protected void handlePushParent(Element parent, CMElementDeclaration ed)
{
}
protected void handlePopParent(Element element, CMElementDeclaration ed)
{
}
// The range must be between 1 and 99.
public void setNumOfRepeatableElements(int i)
{
numOfRepeatableElements = i;
}
protected int getNumOfRepeatableElements()
{
return numOfRepeatableElements;
}
public void visitCMElementDeclaration(CMElementDeclaration ed)
{
int forcedMin = (buildOptionalElements(buildPolicy) || alwaysVisit) ? 1 : 0;
int min = Math.max(ed.getMinOccur(), forcedMin);
// Correct the min value if the element is contained in
// a group.
if (!cmGroupStack.isEmpty())
{
CMGroup group = (CMGroup)cmGroupStack.peek();
int gmin = group.getMinOccur();
if (gmin == 0)
if (buildOptionalElements(buildPolicy))
; // min = min
else
min = min * gmin; // min = 0
else
min = min * gmin;
}
int max = Math.min(ed.getMaxOccur(), getNumOfRepeatableElements());
if (max < min)
max = min;
alwaysVisit = false;
// Note - ed may not be abstract but has substitutionGroups
// involved.
if (buildFirstSubstitution(buildPolicy)
|| isAbstract(ed)) // leave this for backward compatibility for now
{
// Note - To change so that if ed is optional, we do not
// generate anything here.
ed = getSubstitution(ed);
// Note - the returned ed may be an abstract element in
// which case the xml will be invalid.
}
if (min > 0 && !visitedCMElementDeclarationList.contains(ed))
{
visitedCMElementDeclarationList.add(ed);
for (int i = 1; i <= max; i++)
{
// create an Element for each
Element element = null;
if (rootElement != null)
{
element = rootElement;
rootElement = null;
}
else
{
element = createElement(ed, computeName(ed, currentParent), currentParent);
}
// visit the children of the GrammarElement
Node oldParent = currentParent;
currentParent = element;
handlePushParent(element, ed);
namespaceTable.addElement(element);
boolean oldAttachNodesToParent = attachNodesToParent;
attachNodesToParent = true;
// instead of calling super.visitCMElementDeclaration()
// we duplicate the code with some minor modifications
CMNamedNodeMap nodeMap = ed.getAttributes();
int size = nodeMap.getLength();
for (int j = 0; j < size; j++)
{
visitCMNode(nodeMap.item(j));
}
CMContent content = ed.getContent();
if (content != null)
{
visitCMNode(content);
}
if (ed.getContentType() == CMElementDeclaration.PCDATA)
{
CMDataType dataType = ed.getDataType();
if (dataType != null)
{
visitCMDataType(dataType);
}
}
// end duplication
attachNodesToParent = oldAttachNodesToParent;
handlePopParent(element, ed);
currentParent = oldParent;
linkNode(element);
}
int size = visitedCMElementDeclarationList.size();
visitedCMElementDeclarationList.remove(size - 1);
}
}
public void visitCMDataType(CMDataType dataType)
{
Text text = null;
String value = null;
// For backward compatibility:
// Previous code uses a property value but new one uses
// buildPolicy.
if (getProperty(PROPERTY_BUILD_BLANK_TEXT_NODES) != null
&& getProperty(PROPERTY_BUILD_BLANK_TEXT_NODES).equals("true")) //$NON-NLS-1$
buildPolicy = buildPolicy ^ BUILD_TEXT_NODES;
if (buildTextNodes(buildPolicy))
{
value = valueHelper.getValue(dataType);
if (value == null)
{
if (currentParent != null && currentParent.getNodeType() == Node.ELEMENT_NODE)
{
value = currentParent.getNodeName();
}
else
{
value = "pcdata"; //$NON-NLS-1$
}
}
}
else
{
value = ""; //$NON-NLS-1$
}
text = createTextNode(dataType, value, currentParent);
linkNode(text);
}
public void visitCMGroup(CMGroup e)
{
cmGroupStack.push(e);
int forcedMin = (buildOptionalElements(buildPolicy) || alwaysVisit) ? 1 : 0;
int min = Math.max(e.getMinOccur(), forcedMin);
int max = 0;
if (e.getMaxOccur() == -1) // unbounded
max = getNumOfRepeatableElements();
else
max = Math.min(e.getMaxOccur(), getNumOfRepeatableElements());
if (max < min)
max = min;
alwaysVisit = false;
for (int i = 1; i <= max; i++)
{
if (e.getOperator() == CMGroup.CHOICE
&& buildFirstChoice(buildPolicy))
{
CMNode hintNode = null;
// todo... the CMGroup should specify the hint... but it seems as though
// the Yamato guys are making the CMElement specify the hint.
// I do it that way for now until... we should fix this post GA
//
int listSize = visitedCMElementDeclarationList.size();
if (listSize > 0)
{
CMElementDeclaration ed = (CMElementDeclaration)visitedCMElementDeclarationList.get(listSize - 1);
Object contentHint = ed.getProperty("contentHint"); //$NON-NLS-1$
if (contentHint instanceof CMNode)
{
hintNode = (CMNode)contentHint;
}
}
// see if this hint corresponds to a valid choice
//
CMNode cmNode = null;
if (hintNode != null)
{
CMNodeList nodeList = e.getChildNodes();
int nodeListLength = nodeList.getLength();
for (int j = 0; j < nodeListLength; j++)
{
if (hintNode == nodeList.item(j))
{
cmNode = hintNode;
}
}
}
// if no cmNode has been determined from the hint, just use the first choice
//
if (cmNode == null)
{
CMNodeList nodeList = e.getChildNodes();
if (nodeList.getLength() > 0)
{
cmNode = nodeList.item(0);
}
}
if (cmNode != null)
{
visitCMNode(cmNode);
}
}
else if (e.getOperator() == CMGroup.ALL // ALL
|| e.getOperator() == CMGroup.SEQUENCE) // SEQUENCE
{
// visit all of the content
super.visitCMGroup(e);
}
}
cmGroupStack.pop();
}
static int count = 0;
public void visitCMAttributeDeclaration(CMAttributeDeclaration ad)
{
if (alwaysVisit ||
buildOptionalAttributes(buildPolicy) ||
ad.getUsage() == CMAttributeDeclaration.REQUIRED)
{
alwaysVisit = false;
String name = computeName(ad, currentParent);
String value = valueHelper.getValue(ad, namespaceTable);
Attr attr = createAttribute(ad, name, currentParent);
attr.setValue(value != null ? value : ""); //$NON-NLS-1$
linkNode(attr);
}
}
protected boolean isAbstract(CMNode ed)
{
boolean result = false;
if (ed != null)
{
Object value = ed.getProperty("Abstract"); //$NON-NLS-1$
result = (value == Boolean.TRUE);
}
return result;
}
protected CMElementDeclaration getSubstitution(CMElementDeclaration ed)
{
CMElementDeclaration result = ed;
CMNodeList l = (CMNodeList)ed.getProperty("SubstitutionGroup"); //$NON-NLS-1$
if (l != null)
{
for (int i=0; i < l.getLength(); i++)
{
CMNode candidate = l.item(i);
if (!isAbstract(candidate) && (candidate instanceof CMElementDeclaration))
{
result = (CMElementDeclaration)candidate;
break;
}
}
}
return result;
}
protected CMElementDeclaration getParentCMElementDeclaration()
{
CMElementDeclaration ed = null;
int listSize = visitedCMElementDeclarationList.size();
if (listSize > 0)
{
ed = (CMElementDeclaration)visitedCMElementDeclarationList.get(listSize - 1);
}
return ed;
}
public void visitCMAnyElement(CMAnyElement anyElement)
{
// ingnore buildPolicy for ANY elements... only create elements if absolutely needed
//
int forcedMin = alwaysVisit ? 1 : 0;
int min = Math.max(anyElement.getMinOccur(), forcedMin);
alwaysVisit = false;
String uri = anyElement.getNamespaceURI();
String targetNSProperty = "http://org.eclipse.wst/cm/properties/targetNamespaceURI"; //$NON-NLS-1$
CMDocument parentCMDocument = (CMDocument)anyElement.getProperty("CMDocument"); //$NON-NLS-1$
CMElementDeclaration ed = null;
//System.out.println("parentCMDocument = " + parentCMDocument); //$NON-NLS-1$
if (parentCMDocument != null)
{
if (uri == null || uri.startsWith("##") || uri.equals(parentCMDocument.getProperty(targetNSProperty))) //$NON-NLS-1$
{
ed = getSuitableElement(getParentCMElementDeclaration(), parentCMDocument);
}
}
if (ed == null && externalCMDocumentSupport != null && uri != null && !uri.startsWith("##") && currentParent instanceof Element) //$NON-NLS-1$
{
CMDocument externalCMDocument = externalCMDocumentSupport.getCMDocument((Element)currentParent, uri);
if (externalCMDocument != null)
{
ed = getSuitableElement(null, externalCMDocument);
}
}
for (int i = 1; i <= min; i++)
{
if (ed != null)
{
visitCMElementDeclaration(ed);
}
else
{
Element element = document.createElement("ANY-ELEMENT"); //$NON-NLS-1$
linkNode(element);
}
}
}
protected CMElementDeclaration getSuitableElement(CMNamedNodeMap nameNodeMap)
{
CMElementDeclaration result = null;
int size = nameNodeMap.getLength();
for (int i = 0; i < size; i++)
{
CMElementDeclaration candidate = (CMElementDeclaration)nameNodeMap.item(i);
if (!visitedCMElementDeclarationList.contains(candidate))
{
result = candidate;
break;
}
}
return result;
}
protected CMElementDeclaration getSuitableElement(CMElementDeclaration ed, CMDocument cmDocument)
{
CMElementDeclaration result = null;
if (ed != null)
{
result = getSuitableElement(ed.getLocalElements());
}
if (result == null && cmDocument != null)
{
result = getSuitableElement(cmDocument.getElements());
}
return result;
}
public void linkNode(Node node)
{
if (attachNodesToParent && currentParent != null)
{
if (node.getNodeType() == Node.ATTRIBUTE_NODE)
{
((Element)currentParent).setAttributeNode((Attr)node);
}
else
{
currentParent.appendChild(node);
}
}
else if (resultList != null)
{
resultList.add(node);
}
}
public static void testPopulateDocumentFromGrammarFile(Document document, String grammarFileName, String rootElementName, boolean hack)
{
try
{
CMDocument cmDocument = ContentModelManager.getInstance().createCMDocument(grammarFileName, null);
CMNamedNodeMap elementMap = cmDocument.getElements();
CMElementDeclaration element = (CMElementDeclaration)elementMap.getNamedItem(rootElementName);
DOMContentBuilderImpl contentBuilder = new DOMContentBuilderImpl(document);
contentBuilder.supressCreationOfDoctypeAndXMLDeclaration = hack;
contentBuilder.createDefaultRootContent(cmDocument, element);
System.out.println();
System.out.println("-----------------------------"); //$NON-NLS-1$
DOMWriter writer = new DOMWriter();
if (hack)
{
writer.print(document, grammarFileName);
}
else
{
writer.print(document);
}
System.out.println("-----------------------------"); //$NON-NLS-1$
}
catch (Exception e)
{
System.out.println("Error: " + e); //$NON-NLS-1$
e.printStackTrace();
}
}
// test
//
/*
public static void main(String arg[])
{
if (arg.length >= 2)
{
try
{
CMDocumentFactoryRegistry.getInstance().registerCMDocumentBuilderWithClassName("org.eclipse.wst.xml.core.internal.contentmodel.mofimpl.CMDocumentBuilderImpl");
String grammarFileName = arg[0];
String rootElementName = arg[1];
Document document = (Document)Class.forName("org.apache.xerces.dom.DocumentImpl").newInstance();
testPopulateDocumentFromGrammarFile(document, grammarFileName, rootElementName, true);
}
catch (Exception e)
{
System.out.println("DOMContentBuilderImpl error");
e.printStackTrace();
}
}
else
{
System.out.println("Usage : java org.eclipse.wst.xml.util.DOMContentBuildingCMVisitor grammarFileName rootElementName");
}
}*/
}