Live a simple life

          沉默(zhu_xing@live.cn)
          隨筆 - 48, 文章 - 0, 評論 - 132, 引用 - 0
          數(shù)據(jù)加載中……

          【Eclipse插件開發(fā)】基于WTP開發(fā)自定義的JSP編輯器(八):定制StructuredTextEditor自動提示

                      前面介紹的內(nèi)容集中在兩點:StructuredTextEditor框架和WTP數(shù)據(jù)模型,在本節(jié)中就可以定制一個我們最常用的WTP StructuredTextEditor的功能,那就是自動提示。

                      【W(wǎng)TP StructuredTextEditor提示功能實現(xiàn)分析】
                      有關(guān)Eclipse文本編輯器框架、JFace Text Framework和WTP StructuredTextEditor的簡要知識,參見:
                     【Eclipse插件開發(fā)】基于WTP開發(fā)自定義的JSP編輯器(二):基于WTP StructuredTextEditor創(chuàng)建自己的JSPEditor 
                      
                      【SourceViewer提示策略配置】
                      在章節(jié)二中,我們說過如果要對一個ISourceViewer進行自動提示策略的定制,在ISourceViewer對應(yīng)的SourceViewerConfiguration中配置就可以了。對于WTP JSP StructuredTextEditor而言,這里的ISourceViewer就是StructuredTextViewer,這里的SourceViewerConfiguration就是StructuredTextViewerConfigurationJSP。那我們來看一下WTP StructuredTextViewerConfigurationJSP中對自動提示策略的配置:
                     (以下代碼摘取子StructuredTextViewerConfigurationJSP類中):
          protected IContentAssistProcessor[] getContentAssistProcessors(ISourceViewer sourceViewer, String partitionType) {
                  IContentAssistProcessor[] processors 
          = null;
                  
                  //其他代碼省略......
                  
          else if ((partitionType == IXMLPartitions.XML_DEFAULT) || (partitionType == IHTMLPartitions.HTML_DEFAULT) || (partitionType == IHTMLPartitions.HTML_COMMENT) || (partitionType == IJSPPartitions.JSP_DEFAULT) || (partitionType == IJSPPartitions.JSP_DIRECTIVE) || (partitionType == IJSPPartitions.JSP_CONTENT_DELIMITER) || (partitionType == IJSPPartitions.JSP_CONTENT_JAVASCRIPT) || (partitionType == IJSPPartitions.JSP_COMMENT)) {
                      
          // jsp
                      processors = new IContentAssistProcessor[]{new JSPContentAssistProcessor()};
                  }
                  
          else if ((partitionType == IXMLPartitions.XML_CDATA) || (partitionType == IJSPPartitions.JSP_CONTENT_JAVA)) {
                      
          // jsp java
                      processors = new IContentAssistProcessor[]{new JSPJavaContentAssistProcessor()};
                  }
                  //其他代碼省略......

                  
          return processors;
              }
                      以上代碼,我們可以看的出來,IContentAssistProcessor是和具體分區(qū)類型(partition type)相關(guān)聯(lián)的。想搞懂這個問題,就需要看一下這個具體分區(qū)類型(partition type)是怎么計算出來的。
                      PS:分區(qū)類型是JFace Text Framework中的概念,相關(guān)的知識大家有興趣可以進一步去了解一下JFace Text Framework。

                      【分區(qū)類型(partition type)】
                      我們先來看一下JFace Text Framework中的基礎(chǔ)知識吧。再JFace Text Framework中有個分區(qū)劃分器的角色(org.eclipse.jface.text.IDocumentPartitioner),這個角色中一個核心操作就是判斷文檔(org.eclipse.jface.text.IDocument)中特定位置所在區(qū)域(region)的分區(qū)類型是什么,其實這里的分區(qū)類型說白了就是在一定程度上反應(yīng)了該區(qū)域(region)的內(nèi)容是什么語義性質(zhì)的^_^。    
                      我們接著看一下,WTP提供了什么樣的IDocumentPartitioner呢?
                      
                      
                     上圖中的org.eclipse.jst.jsp.core.internal.text.StructuredTextPartitionerForJSP就是我們針對jsp文件類型的分區(qū)器了,看一下相應(yīng)的實現(xiàn)代碼:    
          public String getPartitionType(ITextRegion region, int offset) {
                  String result 
          = null;
                  
          final String region_type = region.getType();
                  
          if (region_type == DOMJSPRegionContexts.JSP_CONTENT) {
                      result 
          = getPartitionTypeForDocumentLanguage();
                  }
                  
          else if (region_type == DOMJSPRegionContexts.JSP_COMMENT_TEXT || region_type == DOMJSPRegionContexts.JSP_COMMENT_OPEN || region_type == DOMJSPRegionContexts.JSP_COMMENT_CLOSE)
                      result 
          = IJSPPartitions.JSP_COMMENT;
                  
          else if (region_type == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME || region_type == DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN || region_type == DOMJSPRegionContexts.JSP_DIRECTIVE_CLOSE)
                      result 
          =
           IJSPPartitions.JSP_DIRECTIVE
          ;
                  
          else if (region_type == DOMJSPRegionContexts.JSP_CLOSE || region_type == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN || region_type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN || region_type == DOMJSPRegionContexts.JSP_DECLARATION_OPEN)
                      result 
          = IJSPPartitions.JSP_CONTENT_DELIMITER;
                  
          else if (region_type == DOMJSPRegionContexts.JSP_ROOT_TAG_NAME)
                      result 
          = IJSPPartitions.JSP_DEFAULT;
                  
          else if (region_type == DOMJSPRegionContexts.JSP_EL_OPEN || region_type == DOMJSPRegionContexts.JSP_EL_CONTENT || region_type == DOMJSPRegionContexts.JSP_EL_CLOSE || region_type == DOMJSPRegionContexts.JSP_EL_DQUOTE || region_type == DOMJSPRegionContexts.JSP_EL_SQUOTE || region_type == DOMJSPRegionContexts.JSP_EL_QUOTED_CONTENT)
                      result 
          = IJSPPartitions.JSP_DEFAULT_EL;
                  
                      //其他代碼省略。。。
                  
          else {
                      result 
          = getEmbeddedPartitioner().getPartitionType(region, offset);
                  }
                  
          return result;
              }
                      
                      我們可以看到,對于WTP結(jié)構(gòu)化文本(當然包括JSP)而言,分區(qū)類型(partition type)基本上是根據(jù)ITextRegion的type信息確定的。有關(guān)ITextRegion的type相關(guān)知識,也是我們前面在介紹語法Document(IStructuredDocument)的時候重點內(nèi)容之一,忘記的話,去看一下。

                      【自動提示流程】
                      既然在StructuredTextViewerConfigurationJSP中根據(jù)分區(qū)類型(partition type)對提示進行了配置,那么是如何來利用這個配置的呢?
                      我們在source viewer中觸發(fā)提示的時候,會有一個相應(yīng)的offset信息,前面也說過IDocumentPartitioner(WTP 對應(yīng)于JSP的實現(xiàn)為StructuredTextPartitionerForJSP)提供了根據(jù)offset判斷相應(yīng)區(qū)域分區(qū)類型(partition type)的接口操作,那提示的流程也就出來了:
                      
                      
                                 上圖中的WTP StructuredTextViewerConfigurationJSP對應(yīng)于我們自己的jsp編輯器中的類型為jspeditor.configuration.JSPStructuredTextViewerConfiguration,這個我們在前面第二節(jié)中就定義了。

                  【定制WTP StructuredTextEditor的提示功能】
                          通過上面的自動提示流程的分析,我們可以看的出來,如果想在我們自己的JSP編輯器中定制WTP提供的特定分區(qū)類型下的自動提示,只要覆寫WTP StructuredTextViewerConfigurationJSP中的getContentAssistProcessors實現(xiàn),用我們自定義的IContentAssistProcessor實現(xiàn)和特定的分區(qū)類型想綁定就可以了
                         【需求】
                          1、提供針對標簽屬性值提示的定制。
                          2、提供屬性值提示擴展點,允許用戶以動態(tài)掛入的方式提供特定標簽的屬性值提示

                          【實現(xiàn)摘要】
                          1、定義自己的IContentAssistProcessor實現(xiàn)
                              
          public class CustomizedJSPContentAssistantProcessor extends AbstractContentAssistProcessor{
              
              
          /* 
               * 定制自動提示策略:提供自定義的屬性值提示。
               * 
               * @see org.eclipse.wst.xml.ui.internal.contentassist.AbstractContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
               
          */
              
          public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
                  
          if (!this.isOffsetValid(viewer, offset))
                      
          return new ICompletionProposal[0];
                  
                  
          //利用IModelManager獲取對應(yīng)的模型
                  IStructuredDocument structuredDocument = (IStructuredDocument)viewer.getDocument();
                  IStructuredModel structuredModel 
          = StructuredModelManager.getModelManager().getModelForRead(structuredDocument);
                  
          if (structuredModel == null)
                      
          return new ICompletionProposal[0];
                  
                  
          try {
                      
          //如果當前offset不是位于屬性區(qū)域
                      IDOMAttr attrNode = StructuredModelUtil.getAttrAtOffset(structuredModel, offset);
                      
          if (attrNode != null)
                          
          return this.computeCustomizedCompletionProposals(viewer, offset, attrNode);
                      
          else
                          
          return new ICompletionProposal[0];
                  } 
          catch (Exception e) {
                      
          //log exception
                      IStatus status = new Status(IStatus.ERROR, "jspeditor"100"自動提示失敗", e);
                      Activator.getDefault().getLog().log(status);
                      
                      
          return new ICompletionProposal[0];
                  } 
          finally {
                      
          //注意,削減引用計數(shù)
                      if (structuredModel != null)
                          structuredModel.releaseFromRead();
                  }
              }
              
              
          /**
               * 判斷當前位置是否需要啟動我們自定義的JSP標簽屬性值自動提示,標準:
               * 1、當前offset對應(yīng)的區(qū)域的分區(qū)類型(partition type)為IJSPPartitions.JSP_DIRECTIVE
               * 2、當前offset對應(yīng)的text region的類型為DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE
               * 
               * 
          @param viewer
               * 
          @param offset
               * 
          @return
               
          */
              
          private boolean isOffsetValid(ITextViewer viewer, int offset) {
                  
          try {
                      IStructuredDocument structuredDocument 
          = (IStructuredDocument)viewer.getDocument();
                      
                      
          //判斷分區(qū)類型
                      if (IJSPPartitions.JSP_DIRECTIVE != structuredDocument.getPartition(offset).getType()) 
                          
          return false;
                      
          //判斷葉子text region對應(yīng)的region type信息
                      ITextRegion textRegion = StructuredDocumentUtil.getTextRegion(structuredDocument, offset);
                      
          return DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE == textRegion.getType();
                  } 
          catch (Exception e) {
                      
          return false;
                  }
              }
              
              
          /**
               * 自定義提示結(jié)果:自定義屬性值提示
               * 
               * 
          @param viewer
               * 
          @param offset
               * 
          @param attrNode
               * 
          @return
               * 
          @throws Exception
               
          */
              
          private ICompletionProposal[] computeCustomizedCompletionProposals(ITextViewer viewer, int offset, IDOMAttr attrNode) throws Exception{
                  
          //準備上下文數(shù)據(jù)
                  String tagName = attrNode.getOwnerElement().getNodeName();
                  String attrbuteName 
          = attrNode.getName();
                  String inputText 
          = attrNode.getStructuredDocument().get(attrNode.getValueRegionStartOffset() + 1, offset - attrNode.getValueRegionStartOffset() - 1);
                  
                  
          //獲取相應(yīng)通過擴展點注冊的屬性值提示擴展
                  IAssistantContributor contributor = AssistantContributorManager.getInstance().getAssistantContributor(tagName);
                  
          return contributor.computeProposals(attrbuteName, inputText, viewer, offset);
              }
              
          }
             
                        關(guān)于細節(jié)實現(xiàn)暫且不說,后面會附上相應(yīng)的源碼。目前只需要了解大致的算法流程就可以:1、如果offset位于特定的JSP標簽屬性值范圍內(nèi),則去獲取對應(yīng)的屬性值自定義提示。

                      2、將自定義的IContentAssistProcessor配置到自定義的SourceViewerConfiguration中。再配置之前,我們首先看一下如果offset位于一個屬性值的ITextRegion中,那么分區(qū)類型(partition type)是怎樣的。回過頭看一下,上面WTP StructuredTextPartitionerForJSP代碼黑體加粗部分,我們的屬性值區(qū)域?qū)?yīng)的分區(qū)類型為org.eclipse.jst.jsp.JSP_DIRECTIVE(IJSPPartitions.JSP_DIRECTIVE)。所以在我們自定義的JSPStructuredTextViewerConfiguration中覆寫WTP提供的StructuredTextViewerConfigurationJSP中的相應(yīng)方法:
          public class JSPStructuredTextViewerConfiguration extends
                  StructuredTextViewerConfigurationJSP {
              
          /* 
               * 
               * 
               * @see org.eclipse.jst.jsp.ui.StructuredTextViewerConfigurationJSP#getContentAssistProcessors(org.eclipse.jface.text.source.ISourceViewer, java.lang.String)
               
          */
              
          protected IContentAssistProcessor[] getContentAssistProcessors(ISourceViewer sourceViewer, String partitionType) {
                  
          //我們目前只自定義JSP標簽屬性值自動提示的情況
                  if (partitionType == IJSPPartitions.JSP_DIRECTIVE) {
                      
          return new IContentAssistProcessor[]{new CustomizedJSPContentAssistantProcessor(), new JSPContentAssistProcessor()};
                  }
                  
                  
          return super.getContentAssistProcessors(sourceViewer, partitionType);
              }
          }
                          由于我們只想定制JSP標簽的屬性值提示,所以我們將我們自定義的Content Assistant Processor配置為和IJSPPartitions.JSP_DIRECTIVE分區(qū)類型相綁定。

                          說明:如果你想定制類型的自動提示呢? 前面已經(jīng)說過了^_^

                      3、定義相關(guān)抽象接口
                            由于針對不同JSP標簽的屬性值自動提示的邏輯是不同的,因為提示的內(nèi)容相同,但是屬性值提示這一概念是一致的。所以,我們就定義了IAssistantContributor接口來代表這一抽象概念,行為的變化通過對該接口的繼承來封裝。
          public interface IAssistantContributor {
              
          /**
               * 提供屬性值內(nèi)容提示
               * 
               * 
          @param attrbuteName 屬性名
               * 
          @param inputText    已輸入屬性值
               * 
          @param viewer       structured text viewer
               * 
          @param offset       光標位置
               * 
          @return
               
          */
              
          public ICompletionProposal[] computeProposals(String attributeName, String inputText, ITextViewer viewer, int offset);
          }
                          上面接口一看就知道是用的策略模式的手法,我在博客的前面的文章中說明了在使用策略模式時候的注意點,其中重點之一就是要關(guān)注上下文信息。  我們在本接口中提供了屬性名(attributeName)和用戶已經(jīng)輸入的屬性值(inputText),這兩個上下文信息對于一般的簡單提示情況下已經(jīng)足夠了。但是,也有可能有比較為復(fù)雜的情況,例如要處理嵌套標簽或者標簽之間有依賴等等情況,通過viewer參數(shù)可以獲取到對應(yīng)的IStructuredDocument和IStructuredModel,在加上offset信息,用戶可以利用這兩個信息自己去分析出進一步的上下文信息^_^。

                         PS:我們在代碼中針對IAssistantContributor接口提供了一個默認適配器類,針對的也就是簡單的提示情況,即只提供屬性名(attributeName)和用戶已經(jīng)輸入的屬性值(inputText)就可以完成提示的情況了。
                          
          public abstract class AbstractAssistantContributor implements
                  IAssistantContributor {
              
              
          /* 
               * 子類可以覆寫,提供較為簡單的模版方法,分為計算替代字符串和構(gòu)建completion proposals兩步。
               * 
               * @see jspeditor.assist.contributor.IAssistantContributor#computeProposals(java.lang.String, java.lang.String, org.eclipse.jface.text.ITextViewer, int)
               
          */
              
          public ICompletionProposal[] computeProposals(String attrbuteName, String inputText, ITextViewer viewer, int offset) {
                  String[] replaceStrings 
          = this.computeReplaceStrings(attrbuteName, inputText, viewer, offset);
                  
          return this.buildCompletionProposals(replaceStrings, inputText, viewer, offset);
              } 
              
              
          /**
               * 子類可以覆寫.
               * 說明:如果需要提供自定義的display string、image等信息,請直接覆寫computeProposals方法。
               * 
               * 
          @param attrbuteName
               * 
          @param inputText
               * 
          @return
               
          */
              
          protected String[] computeReplaceStrings(String attrbuteName, String inputText, ITextViewer viewer, int offset) {
                  
          return new String[0];
              }
              
              
          protected final IDOMAttr getDOMAttr(ITextViewer viewer, int offset) {
                  IStructuredDocument structuredDocument 
          = (IStructuredDocument)viewer.getDocument();
                  IStructuredModel structuredModel 
          = StructuredModelManager.getModelManager().getModelForRead(structuredDocument);
                  
                  IDOMAttr attr 
          = StructuredModelUtil.getAttrAtOffset(structuredModel, offset);
                  
                  structuredModel.releaseFromRead();
                  
          return attr;
              }
              

              
          /**
               * 構(gòu)造ICompletionProposal實例,只提供簡單的ICompletionProposal實例
               * 
               * 
          @param replaceStrings
               * 
          @param inputText
               * 
          @param viewer
               * 
          @param offset
               * 
          @return
               
          */
              
          protected ICompletionProposal[] buildCompletionProposals(String[] replaceStrings, String inputText, ITextViewer viewer, int offset) {
                  
          if (replaceStrings == null || replaceStrings.length == 0)
                      
          return new ICompletionProposal[0];
                  
                  
          //計算ICompletionProposal相關(guān)參數(shù)
                  IDOMAttr attrNode = getDOMAttr(viewer, offset);
                  
          int replaceOffset = attrNode.getValueRegionStartOffset() + 1;
                  
          int replaceLength = attrNode.getValueSource().length();
                  
                  List
          <ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
                  
          for (int i = 0; i < replaceStrings.length; i++) {
                      
          //根據(jù)用戶輸入執(zhí)行過濾
                      if (replaceStrings[i].toLowerCase().startsWith(inputText.trim().toLowerCase())) {
                          
          int cursorPosition = replaceStrings[i].length();
                          ICompletionProposal proposal 
          = new CompletionProposal(replaceStrings[i], replaceOffset, replaceLength, cursorPosition);
                          proposals.add(proposal);
                      }
                  }
                  
          return proposals.toArray(new ICompletionProposal[proposals.size()]);
              }
          }
                      可以看的出來,我們這個默認適配器類簡單的應(yīng)用了模版方法的手法去處理簡單情景下的自動提示。
                      
                    4、定義屬性值自動提示擴展點
                      由于JSP默認提供的一些標簽實際業(yè)務(wù)意義不強,而我們自己在開發(fā)應(yīng)用的時候,往往會不斷提供有業(yè)務(wù)意義的標簽供用戶使用,所以我們假設(shè)我們要處理的屬性值自動提示需要允許后續(xù)開發(fā)人員以動態(tài)開發(fā)的方式掛入,支持方便擴展。我們想到了擴展點機制^_^。
                       
                      上面的擴展點定義其實很簡單,就是針對特定的JSP tag id(也就是tag名稱,這是唯一的)提供一個 IAssistantContributor接口的實現(xiàn)。
                      
                        我們針對用戶掛入的擴展提供了一個管理器AssistantContributorManager,能夠支持以tag id獲取對應(yīng)的IAssistantContributor實現(xiàn),看一下上面我們定義的content assistant processor實現(xiàn)中黑體部分代碼就利用了該manager實例。具體請在附件源碼中看吧^_^

                      5、示例
                       我們首先提供一個非常簡單的tld(test.tld),里面包含了一個簡單的測試標簽test,如下
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
          <taglib>
              
          <tlibversion>1.0</tlibversion>
              
          <jspversion>1.0</jspversion>
              
          <shortname>test</shortname>
              
          <uri>http://www.aygfsteel.com/zhuxing/tags/test</uri>
              <tag>
                  
          <name>test</name>
                  
          <tagclass>any</tagclass>
                  
          <bodycontent>empty</bodycontent>
                  
          <attribute>
                      
          <name>scope</name>
                      
          <required>true</required>
                      
          <rtexprvalue>true</rtexprvalue>
                  
          </attribute>
              
          </tag>
          </taglib>
                       我們在其中定義了一個scope屬性,下面我們就以擴展點的方式掛入我們的擴展。
                      
                       首先實現(xiàn)提供一個IAssistantContributor接口實現(xiàn):
          public class TestTagAssistantContributor extends AbstractAssistantContributor {
              
          /**
               * scope attribute name
               
          */
              
          private static final String ATTR_NAME_SCOPE = "scope";
              
              
          /**
               * scope attribute value
               
          */
              
          private static final String[] ATTR_VALUE_SCOPE = {"request""session"};
              
              
          /* (non-Javadoc)
               * @see jspeditor.assist.contributor.AbstractAssistantContributor#computeReplaceStrings(java.lang.String, java.lang.String, org.eclipse.jface.text.ITextViewer, int)
               
          */
              
          protected String[] computeReplaceStrings(String attrbuteName, String inputText, ITextViewer viewer, int offset) {
                  
          if (ATTR_NAME_SCOPE.equals(attrbuteName)) {
                      
          return ATTR_VALUE_SCOPE;
                  }
                  
                  
          return new String[0];
              }

          }
                      通過上面簡單的代碼可以看的出來,我們的提示邏輯很簡單,如果是scope屬性,就提供“requst”和“session”兩種選擇。

                      下面,我們將我們提供的針對test標簽的屬性值提示擴展掛入:  

                      6、效果演示

                      看到了嗎,我們?yōu)閠est標簽掛入了自動提示實現(xiàn)之后,還真的提示了哈^_^            
                      
                      【后記】
                        其實本節(jié)中的內(nèi)容為定制自動提示提供了一個完整解決方案的雛形^_^               

                     【源碼下載】

                        源碼下載(自動提示定制)

          本博客中的所有文章、隨筆除了標題中含有引用或者轉(zhuǎn)載字樣的,其他均為原創(chuàng)。轉(zhuǎn)載請注明出處,謝謝!

          posted on 2008-09-22 18:17 zhuxing 閱讀(3515) 評論(4)  編輯  收藏 所屬分類: Eclipse Plug-in & OSGIWTP(Web Tools Platform)

          評論

          # re: 【Eclipse插件開發(fā)】基于WTP開發(fā)自定義的JSP編輯器(八):定制StructuredTextEditor自動提示  回復(fù)  更多評論   

          下班了,先把源碼附上,里面有測試用test.tld
          2008-09-22 18:36 | zhuxing

          # re: 【Eclipse插件開發(fā)】基于WTP開發(fā)自定義的JSP編輯器(八):定制StructuredTextEditor自動提示  回復(fù)  更多評論   

          期待作者的下文
          2008-09-22 21:54 | 冷月

          # re: 【Eclipse插件開發(fā)】基于WTP開發(fā)自定義的JSP編輯器(八):定制StructuredTextEditor自動提示  回復(fù)  更多評論   

          內(nèi)容已經(jīng)補充完畢,并更新了代碼,希望對大家有幫助

          下一節(jié)內(nèi)容:為我們的編輯器配置自定義即時校驗
          2008-09-23 15:42 | zhuxing

          # re: 【Eclipse插件開發(fā)】基于WTP開發(fā)自定義的JSP編輯器(八):定制StructuredTextEditor自動提示  回復(fù)  更多評論   

          我喜歡這篇, zhuxing 哥,這兩天我要好好看看。
          2008-09-24 17:28 | srdrm
          主站蜘蛛池模板: 英德市| 阜康市| 芦山县| 平安县| 漠河县| 镇赉县| 承德市| 昔阳县| 水富县| 鄂托克前旗| 英德市| 新河县| 会同县| 靖安县| 博湖县| 泰州市| 资中县| 乌苏市| 林口县| 枣庄市| 高唐县| 丰城市| 赣榆县| 凤城市| 承德市| 林周县| 蓬莱市| 奈曼旗| 平泉县| 措勤县| 长泰县| 江都市| 荃湾区| 昌宁县| 绩溪县| 叙永县| 阿合奇县| 瑞丽市| 正蓝旗| 泗水县| 肇庆市|