package tk.eclipse.plugin.xmleditor.editors;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import jp.aonir.fuzzyxml.FuzzyXMLAttribute;
import jp.aonir.fuzzyxml.FuzzyXMLElement;
import org.apache.xerces.impl.xs.SchemaGrammar;
import org.apache.xerces.impl.xs.XMLSchemaLoader;
import org.apache.xerces.xni.parser.XMLInputSource;
import org.apache.xerces.xs.XSAttributeDeclaration;
import org.apache.xerces.xs.XSAttributeUse;
import org.apache.xerces.xs.XSComplexTypeDefinition;
import org.apache.xerces.xs.XSConstants;
import org.apache.xerces.xs.XSElementDeclaration;
import org.apache.xerces.xs.XSModelGroup;
import org.apache.xerces.xs.XSNamedMap;
import org.apache.xerces.xs.XSObject;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.XSParticle;
import org.apache.xerces.xs.XSTerm;
import org.apache.xerces.xs.XSTypeDefinition;
import org.eclipse.ui.IFileEditorInput;
import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.assist.AssistInfo;
import tk.eclipse.plugin.htmleditor.assist.AttributeInfo;
import tk.eclipse.plugin.htmleditor.assist.HTMLAssistProcessor;
import tk.eclipse.plugin.htmleditor.assist.TagInfo;
import tk.eclipse.plugin.htmleditor.editors.HTMLSourceEditor;
import com.wutka.dtd.DTD;
import com.wutka.dtd.DTDAttribute;
import com.wutka.dtd.DTDChoice;
import com.wutka.dtd.DTDDecl;
import com.wutka.dtd.DTDElement;
import com.wutka.dtd.DTDEmpty;
import com.wutka.dtd.DTDEnumeration;
import com.wutka.dtd.DTDItem;
import com.wutka.dtd.DTDMixed;
import com.wutka.dtd.DTDName;
import com.wutka.dtd.DTDParseException;
import com.wutka.dtd.DTDParser;
import com.wutka.dtd.DTDSequence;
/**
* The AssistProcessor for the <code>XMLEditor</code>.
*
* @author Naoki Takezoe
* @see XMLEditor
*/
public class XMLAssistProcessor extends HTMLAssistProcessor {
private List<TagInfo> _tagList = new ArrayList<TagInfo>();
private Map<String, ArrayList<TagInfo>> _nsTagListMap = new HashMap<String, ArrayList<TagInfo>>();
private XMLEditor _editor;
private IFileEditorInput _input;
private ClassNameAssistProcessor _classNameAssistant = new ClassNameAssistProcessor();
/**
* The constructor without DTD / XSD.
* <p>
* <code>XMLAssistProcessor</code> that's created by
* this constructor can complete only close tags.
*/
public XMLAssistProcessor() {
super();
}
/**
* Update informations about code-completion.
*
* @param input the <code>XMLEditor</code>
* @param source XML source code
*/
@Override
public void update(HTMLSourceEditor editor, String source) {
if (editor.getEditorInput() instanceof IFileEditorInput) {
this._input = (IFileEditorInput) editor.getEditorInput();
}
if (editor instanceof XMLEditor) {
this._editor = (XMLEditor) editor;
}
}
/**
* Refresh DTD informations.
*
* @param in the <code>InputStream</code> of the DTD
*/
public void updateDTDInfo(Reader in) {
// clear at fisrt
_tagList.clear();
// root = null;
try {
DTDParser parser = new DTDParser(in);
DTD dtd = parser.parse();
Object[] obj = dtd.getItems();
for (int i = 0; i < obj.length; i++) {
if (obj[i] instanceof DTDElement) {
DTDElement element = (DTDElement) obj[i];
String name = element.getName();
DTDItem item = element.getContent();
boolean hasBody = true;
if (item instanceof DTDEmpty) {
hasBody = false;
}
TagInfo tagInfo = new TagInfo(name, hasBody);
Iterator ite = element.attributes.keySet().iterator();
// set child tags
if (item instanceof DTDSequence) {
DTDSequence seq = (DTDSequence) item;
setChildTagName(tagInfo, seq.getItem());
}
else if (item instanceof DTDMixed) {
// #PCDATA
}
while (ite.hasNext()) {
String attrName = (String) ite.next();
DTDAttribute attr = element.getAttribute(attrName);
DTDDecl decl = attr.getDecl();
boolean required = false;
if (decl == DTDDecl.REQUIRED) {
required = true;
}
AttributeInfo attrInfo = new AttributeInfo(attrName, true, AttributeInfo.NONE, required);
tagInfo.addAttributeInfo(attrInfo);
Object attrType = attr.getType();
if (attrType instanceof DTDEnumeration) {
DTDEnumeration dtdEnum = (DTDEnumeration) attrType;
String[] items = dtdEnum.getItems();
for (int j = 0; j < items.length; j++) {
attrInfo.addValue(items[j]);
}
}
else if (attrType.equals("ID")) {
attrInfo.setAttributeType(AttributeInfo.ID);
}
else if (attrType.equals("IDREF")) {
attrInfo.setAttributeType(AttributeInfo.IDREF);
}
else if (attrType.equals("IDREFS")) {
attrInfo.setAttributeType(AttributeInfo.IDREFS);
}
}
_tagList.add(tagInfo);
// TODO root tag is an element that was found at first.
}
}
}
catch (DTDParseException ex) {
// ignore
}
catch (Exception ex) {
HTMLPlugin.logException(ex);
}
}
/**
* Refresh XML schema informations.
*
* @param uri the URI of the XML schema
* @param in the <code>InputStream</code> of XML schema
*/
public void updateXSDInfo(String uri, Reader in) {
try {
SchemaGrammar grammer = (SchemaGrammar) new XMLSchemaLoader().loadGrammar(new XMLInputSource(null, null, null, in, null));
// clear at first
String targetNS = grammer.getTargetNamespace();
_nsTagListMap.put(targetNS, new ArrayList<TagInfo>());
List<TagInfo> tagList = _nsTagListMap.get(targetNS);
// root = null;
XSNamedMap map = grammer.getComponents(XSConstants.ELEMENT_DECLARATION);
for (int i = 0; i < map.getLength(); i++) {
XSElementDeclaration element = (XSElementDeclaration) map.item(i);
parseXSDElement(tagList, element);
}
}
catch (Exception ex) {
}
}
private void parseXSDElement(List<TagInfo> tagList, XSElementDeclaration element) {
TagInfo tagInfo = new TagInfo(element.getName(), true);
if (tagList.contains(tagInfo)) {
return;
}
tagList.add(tagInfo);
XSTypeDefinition type = element.getTypeDefinition();
if (type instanceof XSComplexTypeDefinition) {
XSParticle particle = ((XSComplexTypeDefinition) type).getParticle();
if (particle != null) {
XSTerm term = particle.getTerm();
if (term instanceof XSElementDeclaration) {
parseXSDElement(tagList, (XSElementDeclaration) term);
tagInfo.addChildTagName(((XSElementDeclaration) term).getName());
}
if (term instanceof XSModelGroup) {
parseXSModelGroup(tagInfo, tagList, (XSModelGroup) term);
}
}
XSObjectList attrs = ((XSComplexTypeDefinition) type).getAttributeUses();
for (int i = 0; i < attrs.getLength(); i++) {
XSAttributeUse attrUse = (XSAttributeUse) attrs.item(i);
XSAttributeDeclaration attr = attrUse.getAttrDeclaration();
AttributeInfo attrInfo = new AttributeInfo(attr.getName(), true, AttributeInfo.NONE, attrUse.getRequired());
tagInfo.addAttributeInfo(attrInfo);
}
}
}
private void parseXSModelGroup(TagInfo tagInfo, List<TagInfo> tagList, XSModelGroup term) {
XSObjectList list = (term).getParticles();
for (int i = 0; i < list.getLength(); i++) {
XSObject obj = list.item(i);
if (obj instanceof XSParticle) {
XSTerm term2 = ((XSParticle) obj).getTerm();
if (term2 instanceof XSElementDeclaration) {
parseXSDElement(tagList, (XSElementDeclaration) term2);
tagInfo.addChildTagName(((XSElementDeclaration) term2).getName());
}
if (term2 instanceof XSModelGroup) {
parseXSModelGroup(tagInfo, tagList, (XSModelGroup) term2);
}
}
}
}
/**
* Sets a child tag name to <code>TagInfo</code>.
*
* @param tagInfo the <code>TagInfo</code>
* @param items an array of <code>DTDItem</code>
*/
private void setChildTagName(TagInfo tagInfo, DTDItem[] items) {
for (int i = 0; i < items.length; i++) {
if (items[i] instanceof DTDName) {
DTDName dtdName = (DTDName) items[i];
tagInfo.addChildTagName(dtdName.getValue());
}
else if (items[i] instanceof DTDChoice) {
DTDChoice dtdChoise = (DTDChoice) items[i];
setChildTagName(tagInfo, dtdChoise.getItem());
}
}
}
@Override
protected boolean supportTagRelation() {
return true;
}
/**
* Returns an array of an attribute value proposal to complete an attribute value.
*
* @param tagName the tag name
* @param value the inputed value
* @param attrInfo the attribute information
* @return the array of attribute value proposals
*/
@Override
protected AssistInfo[] getAttributeValues(String tagName, String value, TagInfo tagInfo, AttributeInfo attrInfo) {
if (attrInfo.getAttributeType() == AttributeInfo.IDREF || attrInfo.getAttributeType() == AttributeInfo.IDREFS) {
return super.getAttributeValues(tagName, value, tagInfo, attrInfo);
}
String[] values = attrInfo.getValues();
if (values.length == 0) {
return getClassAttributeValues(value, attrInfo.getAttributeName());
}
AssistInfo[] infos = new AssistInfo[values.length];
for (int i = 0; i < infos.length; i++) {
infos[i] = new AssistInfo(values[i]);
}
return infos;
}
/**
* Provides classname completion.
*
* @param value the inputed value
* @param attrName the attribute name
* @return the array of attribute value proposals
*/
protected AssistInfo[] getClassAttributeValues(String value, String attrName) {
if (_input == null || _editor == null || value.length() == 0) {
return new AssistInfo[0];
}
String[] classNameAttributes = _editor.getClassNameAttributes();
for (int i = 0; i < classNameAttributes.length; i++) {
if (attrName.equals(classNameAttributes[i])) {
return _classNameAssistant.getClassAttributeValues(_input.getFile(), value);
}
}
return new AssistInfo[0];
}
/**
* Returns the <code>List</code> of <code>TagInfo</code>.
*
* @return the <code>List</code> of <code>TagInfo</code>
*/
@Override
protected List<TagInfo> getTagList() {
ArrayList<TagInfo> list = new ArrayList<TagInfo>();
list.addAll(this._tagList);
// get namespace
FuzzyXMLElement element = getOffsetElement();
HashMap<String, String> nsPrefixMap = new HashMap<String, String>();
getNamespace(nsPrefixMap, element);
// add prefix to tag names
Iterator ite = this._nsTagListMap.keySet().iterator();
while (ite.hasNext()) {
String uri = (String) ite.next();
String prefix = nsPrefixMap.get(uri);
if (prefix == null || prefix.equals("")) {
list.addAll(this._nsTagListMap.get(uri));
}
else {
List<TagInfo> nsTagList = this._nsTagListMap.get(uri);
for (int i = 0; i < nsTagList.size(); i++) {
TagInfo tagInfo = nsTagList.get(i);
list.add(createPrefixTagInfo(tagInfo, prefix));
}
}
}
return list;
}
/**
* Adds prefix to <code>TagInfo</code>.
*
* @param tagInfo the <code>TagInfo</code> instance
* @param prefix the prefix to add
* @return the new <code>TagInfo</code> instance that is added the prefix
*/
private TagInfo createPrefixTagInfo(TagInfo tagInfo, String prefix) {
TagInfo newTagInfo = new TagInfo(prefix + ":" + tagInfo.getTagName(), tagInfo.hasBody());
AttributeInfo[] attrInfos = tagInfo.getAttributeInfo();
for (int i = 0; i < attrInfos.length; i++) {
AttributeInfo newAttrInfo = new AttributeInfo(prefix + ":" + attrInfos[i].getAttributeName(), true, AttributeInfo.NONE, attrInfos[i].isRequired());
newTagInfo.addAttributeInfo(newAttrInfo);
}
String[] children = tagInfo.getChildTagNames();
for (int i = 0; i < children.length; i++) {
newTagInfo.addChildTagName(prefix + ":" + children[i]);
}
return newTagInfo;
}
/**
* Returns mapping of namespace and prefix at the calet position.
*
* @param map
* <ul>
* <li>key - namespace</li>
* <li>value - prefix</li>
* </ul>
* @param element the <code>FuzzyXMLElement</code> at the calet position
*/
private void getNamespace(Map<String, String> map, FuzzyXMLElement element) {
FuzzyXMLAttribute[] attrs = element.getAttributes();
for (int i = 0; i < attrs.length; i++) {
if (attrs[i].getName().startsWith("xmlns")) {
String name = attrs[i].getName();
String prefix = "";
if (name.indexOf(":") >= 0) {
prefix = name.substring(name.indexOf(":") + 1);
}
if (map.get(attrs[i].getValue()) == null) {
map.put(attrs[i].getValue(), prefix);
}
}
}
if (element.getParentNode() != null) {
getNamespace(map, (FuzzyXMLElement) element.getParentNode());
}
}
/**
* Returns the <code>TagInfo<code> which has the specified name.
*
* @param name the tag name
* @return the <code>TagInfo</code> which has the specified name,
* or <code>null</code>.
*/
@Override
protected TagInfo getTagInfo(String name) {
List tagList = getTagList();
for (int i = 0; i < tagList.size(); i++) {
TagInfo info = (TagInfo) tagList.get(i);
if (info.getTagName().equals(name)) {
return info;
}
}
return new XMLTagInfo(name);
}
/**
* The <code>TagInfo</code> object which will be returned
* when this processor has no definition for the specified tag.
*/
private class XMLTagInfo extends TagInfo {
public XMLTagInfo(String tagName) {
super(tagName, true);
}
@Override
public AttributeInfo getAttributeInfo(String name) {
AttributeInfo attrInfo = new AttributeInfo(name, true);
return attrInfo;
}
}
}