【Hbuilder】用Java开发两个生成代码插件,耗费了我一天半的时间

【Hbuilder】用Java开发两个生成代码插件,耗费了我一天半的时间

缘起

某一天在用Hbuilder编写vue页面的时候,发现无论是方法还是css,都需要自己定义后再去编写相应代码

有没有一个插件能自动根据template里的html代码来生成对应的scss层级结构和方法呢?

寻了半天无果,于是决定自己动手造一个轮子

以下是最终效果图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

开发后的插件jar包

在这里插入图片描述

在Hbuilder里的配置

[{
		"name": "格式化css",
		"command": ["java", "-Dfile.encoding=utf-8", "-jar", "D:\\css-formatter.jar", "${fileDir}\\${fileName}"],
		"type": "process",
		"key": "ctrl+alt+c"
	},

	{
		"name": "格式化js",
		"command": ["java", "-Dfile.encoding=utf-8", "-jar", "D:\\js-formatter.jar", "${fileDir}\\${fileName}"],
		"type": "process",
		"key": "ctrl+alt+m"
	}
]

唤出Hbuilder设置界面的方式为:

在任意一个vue文件上右键>外部命令/插件>自定义命令

将上面的内容粘贴并保存,然后将两个jar包放在D盘根目录下

jar包可在github项目主页找到,我已将两个插件开源

css代码生成插件

js代码生成插件

如果只是使用插件看到这里就可以,以下是各个插件核心代码讲解

scss代码生成插件源码讲解

扫盲:

Sass(scss)是成熟、稳定、强大的CSS预处理器

为Css提供了层级结构和变量定义

让css看起来更像一门编程语言(虽然并不是)

生成Scss核心逻辑

首先对template里的DOM树进行解析,生成对应的json文件(因为json和scss语法很接近)

然后利用jsoup解析文档,并替换style标签中的内容(需要用到正则)

最后得对html文档格式化,去除不必要的标签对

  • 首先我们需要创建一个SpringBoot项目,但是不添加任何starter

  • 在启动类CssFormatterApplication中获取传入的文件路径(文件路径是以命令行,也就是main方法的args方式传入)

    //将启动参数存起来
    public static String path;
    
    public static void main(String[] args) {
        log.info("项目启动...");
        //判断是否传入文件路径
        if(args.length==0){
            log.info("找不到文件...");
            log.info("终止程序!");
            //退出程序
            System.exit(-1);
        }
        log.info("文件路径为:"+args[0]);
        path = args[0];
        SpringApplication.run(CssFormatterApplication.class, args);
    }
    

    通过将传入的路径存入静态变量中为我们下一步做准备

  • 利用@PostConstruct注解在项目启动时就去执行生成代码的操作

    首先我们需要将文件路径下的文件读出来并交给jsoup解析,将template和style标签下的内容都解析出来

    log.info("开始处理文件...");
    log.info("获得文件路径为"+path);
    File file = new File(path);
    Document document = Jsoup.parse(file, "UTF-8");
    Element template = document.getElementsByTag("template").get(0);
    Element style = document.getElementsByTag("style").get(0);
    
  • 接下来就是检查用户有没有设置lang属性,并且对style标签中是否存在内容进行判断

    //判断style有没有设置lang属性
    String lang = style.attr("lang");
    if(StringUtils.isEmpty(lang)){
        log.info("用户没有设置scss属性");
        style.attr("lang","scss");
    }
    if(!StringUtils.isEmpty(style.text())){
        log.info("style中已有内容,终止程序!");
        //退出程序
        System.exit(-1);
    }
    
  • 接下来就是整个程序最复杂的步骤

    因为我们不知道用户的标签嵌套了多少层,所以我们需要设计一个递归方法去递归template里的层级,并生成对应的json,有关于怎么写递归我会在后面的文章中讲到,这里直接上递归方法

    /**
    	 * @Description TODO 生成Scss
    	 * @author LaoQin
    	 * @date 2020/03/20
    	 * @param result 生成结果
    	 * @param element 待生成元素
    	 * @return void
    	 */
    private JSONObject generateScss(JSONObject result, Element element) {
        Elements children = element.children();
        for(Element child : children){
            JSONObject childObj = generateScss(new JSONObject(), child);
            String classStr = child.attr("class");
            if(StringUtils.isEmpty(classStr)){
                //没有class默认用标签名
                result.put(child.tagName(),childObj);
                continue;
            }
            String[] splitedClassStr = classStr.split(" ");
            result.put("."+splitedClassStr[0],childObj);
        }
        return result;
    }
    
  • 然后我们就可以使用我们创建的递归方法了

    String result = JSON.toJSONString(generateScss(new JSONObject(),template));
    
  • 然后我们需要对多余标签和json进行过滤,让它成为一个标准的vue模板

    /**
    * @Description TODO 过滤最后json结果
    * @author LaoQin
    * @date 2020/03/20
    * @param result 返回的结果
    * @return java.lang.String
    */
    private String resultFilter(String result) {
    return result.replace("\"","").replace(":","").replace(",","");
    }
    
    /**
    * @Description TODO 过滤某特定标签
    * @author LaoQin
    * @date 2020/03/20
    * @param str 待过滤标签
    * @param tagNames 过滤标签集合
    * @return java.lang.String 过滤后的标签
    */
    private String tagFilter(String str,String ... tagNames){
    for(String tagName: tagNames){
    str = str.replace("<" + tagName + ">", "")
    .replace("</" + tagName + ">", "");
    }
    return str;
    }
    
  • 最后就是将其写入文件了

    //转码
    String encodedStr = new String(tempStr.getBytes(), "UTF-8");
    FileWriter writer = new FileWriter(file);
    writer.write(encodedStr);
    writer.flush();
    writer.close();
    

js代码生成插件源码讲解

核心思路

这个相对会简单一些,只需要分析代码里形如 @methodType="methodName"的代码,然后替换到methods里面即可

这个主要是考察正则表达式的编写

别的就不多说了,直接上整套代码

package com.zfbgt.js.formatter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author LaoQin
 * @Description TODO 启动类
 * @date 2020/03/23
 */
@Slf4j
@SpringBootApplication
public class JsFormatterApplication {

    /**
     * @Description TODO 文件路径
     * @author LaoQin
     * @date 2020/03/23
     */
    private static String path;

    public static void main(String[] args) {
        log.info("项目启动...");
        //判断是否传入文件路径
        if (args.length == 0) {
            log.info("找不到文件...");
            log.info("终止程序!");
            //退出程序
            System.exit(-1);
        }
        log.info("文件路径为:" + args[0]);
        path = args[0];
        SpringApplication.run(JsFormatterApplication.class, args);
    }

    /**
     * @Description TODO 预编译Patterns
     * @author LaoQin
     * @date 2020/03/23
     */
    private static Pattern SCRIPT_PATTERN = Pattern.compile("}$");
    private static Pattern METHOD_PATTERN = Pattern.compile("(\\s+)(@)(\\w+)(=\")(.*)(\")");
    private static Pattern INNER_METHOD_PATTERN = Pattern.compile("(.*)(\\s*)([:\\(])");
    private static Pattern METHOD_TEMPLATE_PATTERN = Pattern.compile("methods\\s*:\\s*\\{[\\s\\S]*");
    private static Pattern METHOD_NAME_PATTERN = Pattern.compile("(\\S*)\\(");

    /**
     * @Description TODO 预编译常量列表
     * @author LaoQin
     * @date 2020/03/23
     */
    private static String METHODS = "methods";
    private static String METHOD_TEMPLATE = ",methods:{}}";

    @PostConstruct
    public void formatFile() throws IOException {
        log.info("开始处理文件...");
        log.info("获得文件路径为" + path);
        File file = new File(path);
        Document document = Jsoup.parse(file, "UTF-8");
        //获取模板代码
        Element template = document.getElementsByTag("template").get(0);
        //获取js代码
        Element script = document.getElementsByTag("script").get(0);
        //没有methods节点则创建节点
        String replacedScript = script.html();
        TreeSet<String> nowMethodSet = new TreeSet<>();
        if (!script.html().contains(METHODS)) {
            Matcher matcher = SCRIPT_PATTERN.matcher(script.html());
            if (matcher.find()) {
                replacedScript = matcher.replaceAll(METHOD_TEMPLATE);
            }
        }else {
            //如果存在methods节点则维护一个现有method set
            Matcher matcher = METHOD_TEMPLATE_PATTERN.matcher(replacedScript);
            if(matcher.find()){
                String methodTemplateGroup = matcher.group();
                Matcher innerMatcher = INNER_METHOD_PATTERN.matcher(methodTemplateGroup);
                while (innerMatcher.find()){
                    String trimStr = innerMatcher.group(1).trim();
                    if(trimStr.contains(":")){
                        trimStr = trimStr.split(":")[0].trim();
                    }
                    //除了methods本身以外的都加入nowMethodSet
                    if(!METHODS.equals(trimStr)){
                        nowMethodSet.add(trimStr);
                    }
                }
            }
        }

        //匹配方法的正则表达式
        Matcher matcher = METHOD_PATTERN.matcher(template.html());
        Set methodSet = new TreeSet<String>();
        while (matcher.find()) {
            //获取方法定义
            String method = matcher.group(5);
            Matcher methodNameMatcher = METHOD_NAME_PATTERN.matcher(method);
            String methodName = null;
            if(methodNameMatcher.find()){
                methodName = methodNameMatcher.group(1);
            }
            if(nowMethodSet.contains(methodName==null?method:methodName)){
                continue;
            }
            //补全方法定义
            if(!method.contains("(")){
                method+="()";
            }
            //补全方法体
            method+="{}";
            //添加至方法列表
            methodSet.add(method);
        }
        //处理待添加队列
        String methodStr = methodSet.toString();
        String replacedMethodStr = methodStr.replace("[", "").replace("]", "");
        if(nowMethodSet.size()>0){
            replacedMethodStr = ","+replacedMethodStr;
        }
        replacedMethodStr+="}}";
        StringBuffer sb = new StringBuffer(replacedScript);
        sb.replace(sb.lastIndexOf("}"),sb.length(),"");
        sb.replace(sb.lastIndexOf("}"),sb.length(),"");
        sb.append(replacedMethodStr);
        script.text(sb.toString().replaceAll("\n","&huanhang"));
        //过滤多余标签
        String tempStr = tagFilter(document.html(), "html","head","body");
        tempStr = tempStr.replaceAll("&amp;huanhang","\n").replaceAll("&gt;",">");
        FileWriter writer = new FileWriter(file);
        writer.write(tempStr);
        writer.flush();
        writer.close();
    }
    /**
     * @Description TODO 过滤某特定标签
     * @author LaoQin
     * @date 2020/03/20
     * @param str 待过滤标签
     * @param tagNames 过滤标签集合
     * @return java.lang.String 过滤后的标签
     */
    private String tagFilter(String str,String ... tagNames){
        for(String tagName: tagNames){
            str = str.replace("<" + tagName + ">", "")
                    .replace("</" + tagName + ">", "");
        }
        return str;
    }
}

以上就是全部内容,感兴趣的小伙伴可以Git下来学习

也请大佬不吝赐教

~ from 老邋遢

发布了52 篇原创文章 · 获赞 150 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/itkfdektxa/article/details/105076690
今日推荐