package tk.eclipse.plugin.dtdeditor.editors; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import jp.aonir.fuzzyxml.internal.FuzzyXMLUtil; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.CompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposal; import tk.eclipse.plugin.htmleditor.HTMLPlugin; import tk.eclipse.plugin.htmleditor.HTMLUtil; import tk.eclipse.plugin.htmleditor.assist.HTMLAssistProcessor; import tk.eclipse.plugin.htmleditor.editors.HTMLSourceEditor; /** * * @author Naoki Takezoe */ public class DTDAssistProcessor extends HTMLAssistProcessor { private static final int UNDEF = 0; private static final int ELEMENT_ELEMENT = 1; private static final int ELEMENT_TYPE = 2; private static final int ELEMENT_CONTENT = 3; private static final int ATTLIST_ELEMENT = 4; private static final int ATTLIST_ATTRNAME = 5; private static final int ATTLIST_ATTRTYPE = 6; private static final int ATTLIST_ATTROMIT = 7; private static String[] DECLS = {"ELEMENT", "ATTLIST","ENTITY","NOTATION"}; private static String[] ATTR_TYPES = {"CDATA","ID","IDREF","IDREFS","ENTITY","ENTITIES","NMTOKEN","NMTOKENS"}; private static String[] ATTR_OMITS = {"#REQUIRED","#IMPLIED","#FIXED \"\"","\"\""}; private static String[] ELEM_TYPES = {"ANY", "EMPTY", "(#PCDATA)", "()"}; private Pattern elementPattern = Pattern.compile("<!ELEMENT\\s+(.*?)\\s"); private Pattern entityPattern = Pattern.compile("<!ENTITY\\s+%\\s(.*?)\\s"); private List<String> elements = new ArrayList<String>(); private List<String> entities = new ArrayList<String>(); @Override public void update(HTMLSourceEditor editor, String source){ elements.clear(); source = FuzzyXMLUtil.comment2space(source, false); Matcher matcher = elementPattern.matcher(source); while(matcher.find()){ elements.add(matcher.group(1).trim()); } entities.clear(); matcher = entityPattern.matcher(source); while(matcher.find()){ entities.add("%" + matcher.group(1).trim()); } } @Override public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,int documentOffset) { String text = FuzzyXMLUtil.comment2space(viewer.getDocument().get(), false); text = text.substring(0, documentOffset); String word = getWord(text); int context = getContext(text); List<ICompletionProposal> list = new ArrayList<ICompletionProposal>(); if(word.startsWith("<") || context==UNDEF){ for(int i=0;i<DECLS.length;i++){ if(("<!" + DECLS[i]).startsWith(word)){ list.add(createProposal("<!" + DECLS[i] + " >", "<!" + DECLS[i] + " ... >", word, documentOffset, DECLS[i].length() + 3, HTMLPlugin.ICON_TAG)); } } } else if(context==ATTLIST_ELEMENT || context==ELEMENT_CONTENT){ for(int i=0;i<elements.size();i++){ String element = elements.get(i); if(element.startsWith(word)){ list.add(createProposal(element, element, word, documentOffset, element.length(), HTMLPlugin.ICON_ELEMENT)); } } } else if(context==ATTLIST_ATTRTYPE){ for(int i=0;i<ATTR_TYPES.length;i++){ if(ATTR_TYPES[i].startsWith(word)){ list.add(createProposal(ATTR_TYPES[i], ATTR_TYPES[i], word, documentOffset, ATTR_TYPES[i].length(), HTMLPlugin.ICON_VALUE)); } } for(int i=0;i<entities.size();i++){ String value = entities.get(i); if(value.startsWith(word)){ list.add(createProposal(value, value, word, documentOffset, value.length(), HTMLPlugin.ICON_VALUE)); } } } else if(context==ATTLIST_ATTROMIT){ for(int i=0;i<ATTR_OMITS.length;i++){ if(ATTR_OMITS[i].startsWith(word) && !word.startsWith("\"")){ int position = ATTR_OMITS[i].indexOf('"'); if(position < 0){ position = ATTR_OMITS[i].length(); } else { position++; } list.add(createProposal(ATTR_OMITS[i], ATTR_OMITS[i], word, documentOffset, position, HTMLPlugin.ICON_VALUE)); } } } else if(context==ELEMENT_TYPE){ for(int i=0;i<ELEM_TYPES.length;i++){ if(ELEM_TYPES[i].startsWith(word)){ int position = ELEM_TYPES[i].length(); if(ELEM_TYPES[i].equals("()")){ position = 1; } list.add(createProposal(ELEM_TYPES[i], ELEM_TYPES[i], word, documentOffset, position, HTMLPlugin.ICON_VALUE)); } } } HTMLUtil.sortCompilationProposal(list); ICompletionProposal[] prop = list.toArray(new ICompletionProposal[list.size()]); return prop; } private static CompletionProposal createProposal(String text, String display, String word, int offset, int position, String image){ return new CompletionProposal( text, offset - word.length(), word.length(), position, HTMLPlugin.getDefault().getImageRegistry().get(image), display, null, null); } protected String getWord(String text){ StringBuffer sb = new StringBuffer(); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if(Character.isWhitespace(c) || c=='(' || c==')' || c=='|' || c==','){ if(sb.length() > 0){ sb.setLength(0); } } else { sb.append(c); } } return sb.toString(); } protected int getContext(String text){ StringBuffer sb = new StringBuffer(); int context = UNDEF; boolean flag = false; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if((Character.isWhitespace(c) || c=='(' || c==')') && !flag){ if(c=='(' || c==')'){ sb.append(c); } if(sb.length() > 0){ context = judgeContext(sb.toString(), context); sb.setLength(0); } } else { if(c=='"'){ flag = !flag; } sb.append(c); } } return context; } private int judgeContext(String word, int prev){ if(word.equals("<!ELEMENT")){ return ELEMENT_ELEMENT; } else if(word.equals("<!ATTLIST")){ return ATTLIST_ELEMENT; } else if(word.startsWith("<!")){ return UNDEF; } else if(prev==ELEMENT_ELEMENT){ return ELEMENT_TYPE; } else if(prev==ELEMENT_TYPE && word.equals("(")){ return ELEMENT_CONTENT; } else if(prev==ELEMENT_CONTENT && !word.endsWith(")")){ return ELEMENT_CONTENT; } else if(prev==ATTLIST_ELEMENT){ return ATTLIST_ATTRNAME; } else if(prev==ATTLIST_ATTRNAME){ return ATTLIST_ATTRTYPE; } else if(prev==ATTLIST_ATTRTYPE){ return ATTLIST_ATTROMIT; } else if(prev==ATTLIST_ATTROMIT){ if(word.startsWith("#FIXED")){ return ATTLIST_ATTROMIT; } else { return ATTLIST_ATTRNAME; } } return UNDEF; } /* (non-Javadoc) * @see tk.eclipse.plugin.htmleditor.assist.HTMLAssistProcessor#enableTemplate() */ @Override public boolean enableTemplate() { return false; } }