Idea plugin之Xml文件读取和操作

文章内容

在Idea插件开发过程中,如何方便的读取和修改xml文件,包含自定义的xml文件和部分第三方xml配置。官网知识点介绍及示例

相关知识点及示例

在idea plugin中,一个xml文件对应一个com.intellij.psi.xml.XmlFile。
在ideal plugin中,提供了两种方式操作xml内容:
以如下xml文件为例:

<root xmlns="http://x.y.z/xml/4.0.0">
    <foo>
        <bar>42</bar>
        <bar>239</bar>
    </foo>
</root>
  1. xml相关的api查询和操作xml内容
xmlFile.getDocument().getRootTag()
            .findFirstSubTag("foo")
      .findSubTags("bar")[1]
      .getValue()
      .getTrimmedText()
  1. Idea plugin中使用动态代理技术,提供了一种自定义xml内容层级结构的方式操作xml内容。
    2.1定义interface:
interface Root extends com.intellij.util.xml.DomElement {
    Foo getFoo();
}
interface Foo extends com.intellij.util.xml.DomElement {
    List<Bar> getBars();
}
interface Bar extends com.intellij.util.xml.DomElement {
    String getValue();
}

2.2 创建并注册DomFileDescription:
创建DomFileDescription:

public class CustomDomFileDescription extends DomFileDescription<Root> {

    public CustomDomFileDescription() {
        super(Root.class, "root", "http://x.y.z/xml/4.0.0");
    }
}

注册DomFileDescription:

<idea-plugin>
    <extensions defaultExtensionNs="com.intellij">
    <dom.fileMetaData implementation="x.y.z.CustomDomFileDescription" 
    rootTagName="root" />
  </extensions>
</idea-plugin>

2.3 操作代码:

DomManager manager = DomManager.getDomManager(e.getProject());
XmlFile file = // 读取的file;      
Root root = manager.getFileElement(file, Root.class).getRootElement();
List<Bar> bars = root.getFoo().getBars();
if (bars.size() > 1) {
    String s = bars.get(1).getValue();
  Messages.showInfoMessage("值为:" + s, "提示");
}   

两种方式对比:

第一种方式每个api都可能返回null,当层级结构比较多是,需要很多判断null。而第二种方式则不会。

第二种方式的相关知识点:

  • xml标签对应一个interface,继承:com.intellij.util.xml.DomElement
  • 对于<bar>42</bar>这种,可以定义如下方法来获取和设置标签内的内容:
String getValue();
void setValue(String s);

也可以使用@TagValue标注获取和设置标签内的内容的方法:

@TagValue
String getTagValue();
@TagValue
void setTagValue(String s);

之上的例子返回值和设置值的类型都是String。这是很自然的,因为XML表示文本格式,而标记内容总是文本。但有时您可能希望使用整数、布尔值、枚举甚至类名(当然,它们将被表示为PsiClass)和更泛型的Java类型(PsiType)进行操作。在这种情况下,您只需要将方法中的类型更改为您需要的类型,一切都会正常工作。

对于一些自定义类型T,需要在标签内内容相互转换时,需要用@Convert指定转换器,转换器继承Converter<T>

比如在xml文件中增加beanTag:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://x.y.z/xml/4.0.0">
    <foo>
        <bar>42</bar>
        <bar>239</bar>
    </foo>
    <beanTag>{"id":1,"name":"zhu"}</beanTag>
</root>

增加类:Bean.class

public class Bean{
    private Long id;
    private String name;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

新增Converter:Bean和<beanTag>中的String相互转换

public class BeanConverter extends Converter<Bean> {
    @Override
    public @Nullable Bean fromString(@Nullable @NonNls String s, ConvertContext context) {
        if(StringUtils.isNotEmpty(s)){
            return JSON.parseObject(s, Bean.class);
        }
        return null;
    }
    @Override
    public @Nullable String toString(@Nullable Bean bean, ConvertContext context) {
        if(bean == null){
            return "";
        }
        return JSON.toJSONString(bean);
    }
}

配置:

@NameStrategy(JavaNameStrategy.class)
public interface Root extends com.intellij.util.xml.DomElement{
    Foo getFoo();
    @Convert(BeanConverter.class)
    GenericDomValue<Bean> getBeanTag();
}

读取和修改代码:

DomManager manager = DomManager.getDomManager(e.getProject());
WriteCommandAction.runWriteCommandAction(e.getProject(), ()->{
  Root root = manager.getFileElement(xmlFile, Root.class).getRootElement();
  Bean bean = root.getBeanTag().getValue();
  Messages.showInfoMessage(bean.getName(), "自定义bean");
  // 修改值
  bean.setName("zhu-new");
  root.getBeanTag().setValue(bean);
}

xml标签属性操作:<root name="abc">

bean中定义getter方法:返回值类型使用:GenericAttributeValue

@NameStrategy(JavaNameStrategy.class)
public interface Root extends com.intellij.util.xml.DomElement{
    Foo getFoo();
    @Convert(BeanConverter.class)
    GenericDomValue<Bean> getBeanTag();
    @Attribute("name")
    GenericAttributeValue<String> getName();
}

DomNameStrategy:属性名与xml标签之间的转换策略。通过@NameStrategy在定义的interface上指定策略。

@NameStrategy(JavaNameStrategy.class)
public interface Root extends com.intellij.util.xml.DomElement{
}

目前支持两种:

  • HyphenNameStrategy:以中划线【-】分隔。getSomeTag()-><some-tag>
  • JavaNameStrategy:驼峰格式。getSomeTag()-><someTag>

删除标签或属性:.undefine()

// 删除name属性
root.getName().undefine();
// 删除beanTag标签
root.getBeanTag().undefine();
// 删除foo标签
root.getFoo().undefine();

列表相关的操作:

在Foo中新增两个addBar方法,一个是放在最后,一个是指定下标

public interface Foo extends com.intellij.util.xml.DomElement{
    List<Bar> getBars();
    @SubTagList("bar")
    Bar addBar();
    @SubTagList("bar")
    Bar addBar(int idx);
}

使用:

root.getFoo().addBar().setValue("我是新增的");
root.getFoo().addBar(1).setValue("我是插入的");

修改前:

<?xml version="1.0" encoding="UTF-8"?>
<root name="abc" xmlns="http://x.y.z/xml/4.0.0">
    <foo>
        <bar>42</bar>
        <bar>239</bar>
    </foo>
    <beanTag>{"id":1,"name":"zhu-new"}</beanTag>
</roo

修改后:

<?xml version="1.0" encoding="UTF-8"?>
<root name="abc" xmlns="http://x.y.z/xml/4.0.0">
    <foo>
        <bar>42</bar>
        <bar>我是插入的</bar>
        <bar>239</bar>
        <bar>我是新增的</bar>
    </foo>
    <beanTag>{"id":1,"name":"zhu-new"}</beanTag>
</root>

下面将介绍idea中支持的一种工具,用XSD/DTD生成上面用到的一些interface。

步骤:

  • Help | Edit Custom Properties 后添加:idea.is.internal=true 后重启
  • 菜单栏中选择:Tools | Internal Actions | DevKit | Generate DOM Model


    image
  • 选择对应的xsd文件,生成的包名
    image
  • 用这种方式生成会SuperClass,记得删除com.intellij.util.xml.DomElement类。
  • 生成之后可能还需要修改@NameStrategy来指定命名策略

特别之处

  1. Maven的pom.xml文件:
    pom.xml文件已经被idea自带的jar包已经注册了对应的DomFileDescription:org.jetbrains.idea.maven.dom.MavenDomFileDescription
  2. 以下整理了如何操作pom.xml:
  • plugin.xml中增加<depends>:
<idea-plugin>
<depends>org.jetbrains.idea.maven</depends>
</idea-plugin>
  • build.gradle中:
intellij {
    plugins = ["org.jetbrains.idea.maven"]
}
  • 获取根标签:
MavenDomProjectModel mavenProject = DomManager.getDomManager(project)
.getFileElement(pomFile, MavenDomProjectModel.class)

              .getRootElement();
  • MavenDomProjectModel中就有pom.xml中所有的标签。

总结

  • 官网文章
  • 学习最好的方式就是开始写起来,遇到问题后可以尝试debug,看懂其实现原理,之后再分析问题之所在。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,682评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,277评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,083评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,763评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,785评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,624评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,358评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,261评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,722评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,030评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,737评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,360评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,941评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,057评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,237评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,976评论 2 355

推荐阅读更多精彩内容