From 5b432488c635fe7a669cb66cbe2e35ed91a42bdb Mon Sep 17 00:00:00 2001 From: lijiarao Date: Mon, 25 Mar 2024 15:56:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 33 + pom.xml | 102 + .../mathtoimg/MathToImgApplication.java | 13 + .../controller/mathToImgController.java | 640 +++++ .../example/mathtoimg/utils/ImageParse.java | 45 + .../example/mathtoimg/utils/MinioConfig.java | 35 + .../example/mathtoimg/utils/MinioUtil.java | 368 +++ .../example/mathtoimg/utils/OmmlUtils.java | 94 + .../com/example/mathtoimg/utils/TestMain.java | 21 + .../example/mathtoimg/utils/W3cNodeUtil.java | 115 + src/main/resources/application.properties | 11 + src/main/resources/xsl/OMML2MML.XSL | 2068 +++++++++++++++++ .../mathtoimg/MathToImgApplicationTests.java | 13 + xsl/OMML2MML.XSL | 2068 +++++++++++++++++ 14 files changed, 5626 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/example/mathtoimg/MathToImgApplication.java create mode 100644 src/main/java/com/example/mathtoimg/controller/mathToImgController.java create mode 100644 src/main/java/com/example/mathtoimg/utils/ImageParse.java create mode 100644 src/main/java/com/example/mathtoimg/utils/MinioConfig.java create mode 100644 src/main/java/com/example/mathtoimg/utils/MinioUtil.java create mode 100644 src/main/java/com/example/mathtoimg/utils/OmmlUtils.java create mode 100644 src/main/java/com/example/mathtoimg/utils/TestMain.java create mode 100644 src/main/java/com/example/mathtoimg/utils/W3cNodeUtil.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/xsl/OMML2MML.XSL create mode 100644 src/test/java/com/example/mathtoimg/MathToImgApplicationTests.java create mode 100644 xsl/OMML2MML.XSL diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0786182 --- /dev/null +++ b/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.5 + + + com.example + mathToImg + 0.0.1-SNAPSHOT + mathToImg + Demo project for Spring Boot + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.apache.poi + poi + 4.1.2 + + + org.apache.poi + poi-ooxml + 4.1.2 + + + org.apache.poi + poi-ooxml-schemas + 4.1.2 + + + org.apache.poi + ooxml-schemas + 1.4 + + + org.apache.xmlgraphics + batik-codec + 1.7 + + + net.sourceforge.jeuclid + jeuclid-core + 3.1.9 + + + org.apache.commons + commons-lang3 + 3.9 + + + io.minio + minio + 8.0.3 + + + okio + com.squareup.okio + + + okhttp + com.squareup.okhttp3 + + + + + com.squareup.okhttp3 + okhttp + 4.4.1 + + + org.springframework + spring-test + 5.3.9 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/com/example/mathtoimg/MathToImgApplication.java b/src/main/java/com/example/mathtoimg/MathToImgApplication.java new file mode 100644 index 0000000..f13faae --- /dev/null +++ b/src/main/java/com/example/mathtoimg/MathToImgApplication.java @@ -0,0 +1,13 @@ +package com.example.mathtoimg; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MathToImgApplication { + + public static void main(String[] args) { + SpringApplication.run(MathToImgApplication.class, args); + } + +} diff --git a/src/main/java/com/example/mathtoimg/controller/mathToImgController.java b/src/main/java/com/example/mathtoimg/controller/mathToImgController.java new file mode 100644 index 0000000..84855fa --- /dev/null +++ b/src/main/java/com/example/mathtoimg/controller/mathToImgController.java @@ -0,0 +1,640 @@ +package com.example.mathtoimg.controller; + +import com.example.mathtoimg.utils.ImageParse; +import com.example.mathtoimg.utils.MinioUtil; +import com.example.mathtoimg.utils.OmmlUtils; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.net.URLDecoder; +import java.util.Map; + +@RestController +public class mathToImgController { + private static final Logger log = LoggerFactory.getLogger(mathToImgController.class); + + @Value("${file.mathToImgFilePath}") + private String mathToImgFilePath; + @Value("${xsl.omml2mmlFilePath}") + private String omml2mmlFilePath; + + + @PostMapping("/mathToImg") + public String mathToImg(@RequestBody String oriMathXmlStr) throws XmlException, UnsupportedEncodingException { + String result = ""; + //String oriMathXmlStr = decoder((String) map.get("encode")); + XmlObject object = XmlObject.Factory.parse(oriMathXmlStr); + ImageParse imageParser = new ImageParse(); + File file = OmmlUtils.convertOmathToPng(object, imageParser,mathToImgFilePath,omml2mmlFilePath); + try { + MultipartFile multipartFile = new MockMultipartFile(file.getName(), new FileInputStream(file)); + String upload = MinioUtil.upload(multipartFile, "1"); + file.delete(); + return upload; + } catch (IOException e) { + log.error("",e); + return "公式转换图片失败"; + } + } + + public String decoder(String encode){ + String decode = null; + try { + decode = URLDecoder.decode(encode, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return decode; + } + + public static String oriMathXmlStr = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " f\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " x\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " =\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " a\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 0\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " +\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " n=1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " a\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " n\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " cos\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " nπx\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " L\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " +\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " b\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " n\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " sin\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " nπx\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " L\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + +} diff --git a/src/main/java/com/example/mathtoimg/utils/ImageParse.java b/src/main/java/com/example/mathtoimg/utils/ImageParse.java new file mode 100644 index 0000000..fa66832 --- /dev/null +++ b/src/main/java/com/example/mathtoimg/utils/ImageParse.java @@ -0,0 +1,45 @@ +package com.example.mathtoimg.utils; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Value; + +import java.io.*; +import java.util.UUID; + +public class ImageParse { + + private int number = 0; +// private String targetDir; + + public ImageParse() { + super(); +// this.targetDir = targetDir; + } + + public String parse(byte[] data, String extName,String mathToImgFilePath) { + return parse(new ByteArrayInputStream(data), extName,mathToImgFilePath); + } + + public String parse(InputStream in, String extName,String mathToImgFilePath) { + if (extName.lastIndexOf(".") > -1) { + extName = extName.substring(extName.lastIndexOf(".") + 1); + } + + mathToImgFilePath = mathToImgFilePath + "/" + UUID.randomUUID().toString().replaceAll("-",""); + + File target = new File(mathToImgFilePath); + if (!target.exists()) { + target.mkdirs(); + } + String filename = mathToImgFilePath + "/" + "image_" + (number++) + "." + extName; + try { + IOUtils.copy(in, new FileOutputStream(filename)); + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return filename; + } + + +} diff --git a/src/main/java/com/example/mathtoimg/utils/MinioConfig.java b/src/main/java/com/example/mathtoimg/utils/MinioConfig.java new file mode 100644 index 0000000..463e096 --- /dev/null +++ b/src/main/java/com/example/mathtoimg/utils/MinioConfig.java @@ -0,0 +1,35 @@ +package com.example.mathtoimg.utils; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Minio文件上传配置文件 + */ +@Configuration +public class MinioConfig { + @Value(value = "${jero.minio.minio_url}") + private String minioUrl; + @Value(value = "${jero.minio.minio_name}") + private String minioName; + @Value(value = "${jero.minio.minio_pass}") + private String minioPass; + @Value(value = "${jero.minio.bucketName}") + private String bucketName; + + @Bean + public void initMinio(){ + if(!minioUrl.startsWith("http")){ + minioUrl = "http://" + minioUrl; + } + if(!minioUrl.endsWith("/")){ + minioUrl = minioUrl.concat("/"); + } + MinioUtil.setMinioUrl(minioUrl); + MinioUtil.setMinioName(minioName); + MinioUtil.setMinioPass(minioPass); + MinioUtil.setBucketName(bucketName); + } + +} diff --git a/src/main/java/com/example/mathtoimg/utils/MinioUtil.java b/src/main/java/com/example/mathtoimg/utils/MinioUtil.java new file mode 100644 index 0000000..00a16d9 --- /dev/null +++ b/src/main/java/com/example/mathtoimg/utils/MinioUtil.java @@ -0,0 +1,368 @@ +package com.example.mathtoimg.utils; + +import io.minio.*; +import io.minio.errors.*; +import io.minio.http.Method; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * minio文件上传工具类 + */ +public class MinioUtil { + private static final Logger log = LoggerFactory.getLogger(MinioUtil.class); + private MinioUtil(){ + + } + private static String minioUrl; + private static String minioName; + private static String minioPass; + private static String bucketName; + + public static void setMinioUrl(String minioUrl) { + MinioUtil.minioUrl = minioUrl; + } + + public static void setMinioName(String minioName) { + MinioUtil.minioName = minioName; + } + + public static void setMinioPass(String minioPass) { + MinioUtil.minioPass = minioPass; + } + + public static void setBucketName(String bucketName) { + MinioUtil.bucketName = bucketName; + } + + public static String getMinioUrl() { + return minioUrl; + } + + public static String getBucketName() { + return bucketName; + } + + private static MinioClient minioClient = null; + + /** + * 上传文件 + * @param file + * @return + */ + public static String upload(MultipartFile file, String bizPath, String customBucket) { + String fileName = ""; + //update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击 + //update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击 + String newBucket = bucketName; + if(isNotEmpty(customBucket)){ + newBucket = customBucket; + } + try { + initMinio(minioUrl, minioName,minioPass); + // 检查存储桶是否已经存在 + if(!minioClient.bucketExists(BucketExistsArgs.builder().bucket(newBucket).build())) { + // 创建一个名为ota的存储桶 + minioClient.makeBucket(MakeBucketArgs.builder().bucket(newBucket).build()); + } + //update-end-author:liusq date:20210809 for: 过滤上传文件类型 + InputStream stream = file.getInputStream(); + // 获取文件名 + String orgName = file.getOriginalFilename(); + if("".equals(orgName)){ + orgName=file.getName(); + } + orgName = getFileName(orgName); + fileName = (!orgName.contains(".") + ? orgName + "_" + System.currentTimeMillis() + : orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."))); + String objectName = bizPath + "/" + fileName; + // 使用putObject上传一个本地文件到存储桶中。 + if(objectName.startsWith("/")){ + objectName = objectName.substring(1); + } + PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName) + .bucket(newBucket) + .contentType("application/octet-stream") + .stream(stream,stream.available(),-1).build(); + minioClient.putObject(objectArgs); + stream.close(); + }catch (Exception e){ + log.error(e.getMessage(), e); + } + return fileName; + } + + public static String getFileName(String fileName) { + if (StringUtils.isBlank(fileName)) { + return ""; + } + //判断是否带有盘符信息 + // Check for Unix-style path + int unixSep = fileName.lastIndexOf('/'); + // Check for Windows-style path + int winSep = fileName.lastIndexOf('\\'); + // Cut off at latest possible point + int pos = (winSep > unixSep ? winSep : unixSep); + if (pos != -1) { + // Any sort of path separator found... + fileName = fileName.substring(pos + 1); + } + //替换上传文件名字的特殊字符 + fileName = fileName.replace("=", "").replace(",", "").replace("&", "") + .replace("#", "").replace("“", "").replace("”", ""); + //替换上传文件名字中的空格 + fileName = fileName.replaceAll("\\s", ""); + return fileName; + } + + public static boolean isNotEmpty(Object object) { + if (object != null && !object.equals("") && !object.equals("null")) { + return (true); + } + return (false); + } + + /** + * 文件上传 + * @param file + * @param bizPath + * @return + */ + public static String upload(MultipartFile file, String bizPath) { + return upload(file,bizPath,null); + } + + /** + * 判断文件是否存在 + * @param objectName 文件名 + * @return + */ + public static boolean doesObjectExist(String objectName) { + boolean exist = true; + try { + initMinio(minioUrl, minioName,minioPass); + minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()); + } catch (Exception e) { + exist = false; + } + return exist; + } + + /** + * 获取文件流 + * @param bucketName + * @param objectName + * @return + */ + public static InputStream getMinioFile(String bucketName, String objectName){ + InputStream inputStream = null; + try { + initMinio(minioUrl, minioName, minioPass); + GetObjectArgs objectArgs = GetObjectArgs.builder().object(objectName) + .bucket(bucketName).build(); + inputStream = minioClient.getObject(objectArgs); + } catch (Exception e) { + log.info("文件获取失败" + e.getMessage()); + } + return inputStream; + } + + /** + * 获取文件流 + * @param objectName + * @return + */ + public static InputStream download(String objectName){ + InputStream inputStream = null; + try { + initMinio(minioUrl, minioName, minioPass); + GetObjectArgs objectArgs = GetObjectArgs.builder().object(objectName) + .bucket(bucketName).build(); + inputStream = minioClient.getObject(objectArgs); + } catch (Exception e) { + log.info("文件获取失败" + e.getMessage()); + } + return inputStream; + } + + /** + * 删除文件 + * @param objectName + * @throws Exception + */ + public static void removeObject(String objectName) { + try { + initMinio(minioUrl, minioName,minioPass); + RemoveObjectArgs objectArgs = RemoveObjectArgs.builder().object(objectName) + .bucket(bucketName).build(); + minioClient.removeObject(objectArgs); + }catch (Exception e){ + log.info("文件删除失败" + e.getMessage()); + } + } + + /** + * 删除文件 + * @param objectName + * @throws Exception + */ + public static void delete(String objectName) { + try { + initMinio(minioUrl, minioName,minioPass); + RemoveObjectArgs objectArgs = RemoveObjectArgs.builder().object(objectName) + .bucket(bucketName).build(); + minioClient.removeObject(objectArgs); + }catch (Exception e){ + log.info("文件删除失败" + e.getMessage()); + } + } + + /** + * 获取文件外链 + * @param bucketName + * @param objectName + * @param expires + * @return + */ + public static String getObjectURL(String bucketName, String objectName, Integer expires) { + initMinio(minioUrl, minioName,minioPass); + try{ + GetPresignedObjectUrlArgs objectArgs = GetPresignedObjectUrlArgs.builder().object(objectName) + .bucket(bucketName) + .expiry(expires).build(); + String url = minioClient.getPresignedObjectUrl(objectArgs); + return URLDecoder.decode(url,"UTF-8"); + }catch (Exception e){ + log.info("文件路径获取失败" + e.getMessage()); + } + return null; + } + + /** + * 初始化客户端 + * @param minioUrl + * @param minioName + * @param minioPass + * @return + */ + private static MinioClient initMinio(String minioUrl, String minioName,String minioPass) { + if (minioClient == null) { + try { + minioClient = MinioClient.builder() + .endpoint(minioUrl) + .credentials(minioName, minioPass) + .build(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return minioClient; + } + + /** + * 上传文件到minio + * @param stream + * @param relativePath + * @return + */ + public static String upload(InputStream stream,String relativePath) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException { + initMinio(minioUrl, minioName,minioPass); + if(minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { + log.info("Bucket already exists."); + } else { + // 创建一个名为ota的存储桶 + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + log.info("create a new bucket."); + } + PutObjectArgs objectArgs = PutObjectArgs.builder().object(relativePath) + .bucket(bucketName) + .contentType("application/octet-stream") + .stream(stream,stream.available(),-1).build(); + minioClient.putObject(objectArgs); + stream.close(); + return minioUrl+bucketName+"/"+relativePath; + } + + /** + * cos 上传 + * 相同key 文件会覆盖 + * @param bytev + * @param key + */ + public static void uploadByte(byte[] bytev, String key){ + initMinio(minioUrl, minioName,minioPass); + try { + // 生成预签名上传 URL + Map reqParams = new HashMap(); + reqParams.put("response-content-type", "application/json"); + String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .method(Method.PUT) + .bucket(bucketName) + .object(key) + .expiry(1, TimeUnit.DAYS) + .extraQueryParams(reqParams) + .build()); + log.info("生成预签名上传URL:" + url); + int responseCode = upLoadFileByte(url.toString(), bytev); + if (responseCode == 200) { + log.info("上传成功!"); + } else { + log.info("上传失败!"); + } + } catch (ErrorResponseException | InsufficientDataException | InternalException | ServerException | XmlParserException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + private static int upLoadFileByte(String singnedurl,byte[] bytev){ + InputStream inputStream= null; + DataOutputStream out = null; + int responseCode = 0; + try { + URL url= null; + url = new URL(singnedurl); + inputStream= new ByteArrayInputStream(bytev); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoOutput(true); + connection.setRequestMethod("PUT"); + out = new DataOutputStream(connection.getOutputStream()); + + // 写入要上传的数据 + IOUtils.copy(inputStream, out); + responseCode = connection.getResponseCode(); + log.info("Service returned response code " + responseCode); + } catch (IOException e) { + log.error(e.getMessage(), e); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + try { + out.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return responseCode; + } + } + +} diff --git a/src/main/java/com/example/mathtoimg/utils/OmmlUtils.java b/src/main/java/com/example/mathtoimg/utils/OmmlUtils.java new file mode 100644 index 0000000..b59b21d --- /dev/null +++ b/src/main/java/com/example/mathtoimg/utils/OmmlUtils.java @@ -0,0 +1,94 @@ +package com.example.mathtoimg.utils; + +import com.example.mathtoimg.controller.mathToImgController; +import net.sourceforge.jeuclid.context.LayoutContextImpl; +import net.sourceforge.jeuclid.context.Parameter; +import net.sourceforge.jeuclid.converter.Converter; +import org.apache.xmlbeans.XmlObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamSource; +import java.io.*; +import java.net.URL; + +public class OmmlUtils { + private static final Logger log = LoggerFactory.getLogger(OmmlUtils.class); + public static File convertOmathToPng(XmlObject xmlObject, ImageParse imageParser,String mathToImgFilePath,String omml2mmlFilePath) { + Document document = null; + try { + String mathMLStr = getMathMLFromNode(xmlObject.getDomNode(),omml2mmlFilePath); + System.out.println("xxxxxxxxxxxxxxxxxxxxxxx : " + mathMLStr); + document = W3cNodeUtil.xmlStr2Node(mathMLStr, "utf-16"); + String imageName = documentToImageHTML(document, imageParser,mathToImgFilePath); + if (imageName != null) { + File tf = new File(imageName); + System.out.println("图片是否存在 ======== " + tf.exists() + "Name : " + tf.getName()); + return tf; + } + } catch (Exception e) { + log.error("",e); + } + return null; + } + + + + /** + * 直接转node有等号会出问题,先转成xml的string,再转成mathML的字符串 + * + * @param node + * @return + * @throws IOException + * @throws TransformerException + */ + private static String getMathMLFromNode(Node node,String omml2mmlFilePath) throws IOException, TransformerException { + ClassLoader classLoader = OmmlUtils.class.getClassLoader(); + URL url = classLoader.getResource("xsl/OMML2MML.XSL"); + File xslFile = null; + if (url != null) { + try { + xslFile = new File(url.toURI()); + } catch (Exception e) { + e.printStackTrace(); + } + } + +// final File xslFile = new File("/omml2mml.xsl"); +// final File xslFile = new File("D:\\MyCode\\omml-test\\omml2mml.xsl"); +// boolean exists = xslFile.exists(); + + //final File xslFile = new File(omml2mmlFilePath); + + StreamSource streamSource = new StreamSource(xslFile); + String s = W3cNodeUtil.node2XmlStr(node); + // encoding utf-16 + String mathML = W3cNodeUtil.xml2Xml(s, streamSource); + if (mathML != null) { + mathML = mathML.replaceAll("xmlns:m=\"http://schemas.openxmlformats.org/officeDocument/2006/math\"", ""); + mathML = mathML.replaceAll("xmlns:mml", "xmlns"); + mathML = mathML.replaceAll("mml:", ""); + } + return mathML; + } + + private static String documentToImageHTML(Document document, ImageParse imageParser,String mathToImgFilePath) { + try { + Converter mathMLConvert = Converter.getInstance(); + LayoutContextImpl localLayoutContextImpl = new LayoutContextImpl(LayoutContextImpl.getDefaultLayoutContext()); + localLayoutContextImpl.setParameter(Parameter.MATHSIZE, 50); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + mathMLConvert.convert(document, os, "image/png", localLayoutContextImpl); + String pngName = imageParser.parse(os.toByteArray(), ".png",mathToImgFilePath); + os.close(); + return pngName; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/main/java/com/example/mathtoimg/utils/TestMain.java b/src/main/java/com/example/mathtoimg/utils/TestMain.java new file mode 100644 index 0000000..58af6cf --- /dev/null +++ b/src/main/java/com/example/mathtoimg/utils/TestMain.java @@ -0,0 +1,21 @@ +package com.example.mathtoimg.utils; + +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlObject; + +import java.io.File; +import java.io.UnsupportedEncodingException; + +public class TestMain { + + public static void main(String[] args) throws XmlException, UnsupportedEncodingException { + String oriMathXmlStr = "fx=a0+n=1ancosnπxL+bnsinnπxL"; + oriMathXmlStr = new String(oriMathXmlStr.getBytes("gbk"), "utf-8"); + + XmlObject object = XmlObject.Factory.parse(oriMathXmlStr); + ImageParse imageParser = new ImageParse(); + File file = OmmlUtils.convertOmathToPng(object, imageParser,"",""); + System.out.printf(file.toString()); + } + +} diff --git a/src/main/java/com/example/mathtoimg/utils/W3cNodeUtil.java b/src/main/java/com/example/mathtoimg/utils/W3cNodeUtil.java new file mode 100644 index 0000000..428d692 --- /dev/null +++ b/src/main/java/com/example/mathtoimg/utils/W3cNodeUtil.java @@ -0,0 +1,115 @@ +package com.example.mathtoimg.utils; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; + +public class W3cNodeUtil { + + public static String node2XmlStr(Node node) { + Transformer transformer = null; + if (node == null) { + throw new IllegalArgumentException("node 不能为空.."); + } + try { + transformer = TransformerFactory.newInstance().newTransformer(); + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + if (transformer != null) { + try { + StringWriter sw = new StringWriter(); + transformer.transform(new DOMSource(node), new StreamResult(sw)); + return sw.toString(); + } catch (TransformerException te) { + throw new RuntimeException(te.getMessage()); + } + } + return null; + } + + public static String xml2Xml(String xml, Source XSLSource) { + Transformer transformer = null; + if (xml == null) { + throw new IllegalArgumentException("node 不能为空.."); + } + try { + if (XSLSource == null) { + transformer = TransformerFactory.newInstance().newTransformer(); + } else { + transformer = TransformerFactory.newInstance().newTransformer(XSLSource); + } + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + if (transformer != null) { + try { + Source source = new StreamSource(new StringReader(xml)); + StringWriter sw = new StringWriter(); + transformer.transform(source, new StreamResult(sw)); + return sw.toString(); + } catch (TransformerException te) { + throw new RuntimeException(te.getMessage()); + } + } + return null; + } + + //MathML转成Document + public static Document xmlStr2Node(String xmlString, String encoding) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + Document doc = null; + try { + InputStream is = new ByteArrayInputStream(xmlString.getBytes(encoding)); + doc = dbf.newDocumentBuilder().parse(is); + is.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return doc; + } + + private static Node getChildNode(Node node, String nodeName) { + if (!node.hasChildNodes()) { + return null; + } + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + if (nodeName.equals(childNode.getNodeName())) { + return childNode; + } + childNode = getChildNode(childNode, nodeName); + if (childNode != null) { + return childNode; + } + } + return null; + } + + public static Node getChildChainNode(Node node, String... nodeName) { + Node childNode = node; + for (int i = 0; i < nodeName.length; i++) { + String tmp = nodeName[i]; + childNode = getChildNode(childNode, tmp); + if (childNode == null) { + return null; + } + } + return childNode; + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..b42af5e --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,11 @@ + +server.port = 9998 +# \u751F\u6210\u7684\u7279\u6B8A\u516C\u5F0F\u6587\u4EF6\u5B58\u50A8\u8DEF\u5F84 +file.mathToImgFilePath = D://opt//mathToImg +# \u5904\u7406\u7279\u6B8A\u516C\u5F0F\u6240\u9700\u6587\u4EF6\u8DEF\u5F84 +xsl.omml2mmlFilePath = D://workspace//laws_biyadi//mathToImg//xsl//OMML2MML.XSL +# minio\u6587\u4EF6\u4E0A\u4F20 +jero.minio.minio_url = 10.10.10.45:9000 +jero.minio.minio_name = admin +jero.minio.minio_pass = hzwlsoft.com +jero.minio.bucketName = laws-sinotruk diff --git a/src/main/resources/xsl/OMML2MML.XSL b/src/main/resources/xsl/OMML2MML.XSL new file mode 100644 index 0000000..b46ae33 --- /dev/null +++ b/src/main/resources/xsl/OMML2MML.XSL @@ -0,0 +1,2068 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ˘ + + ¸ + + ` + + - + + + + . + + ˙ + + ˝ + + ´ + + ~ + + ˜ + + ¨ + + ˇ + + ^ + + ¯ + + _ + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + + + right + + + + + + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0in + + + 0in + + + 0in + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + + + + + + + + + + + + + + + + + off + + + + + + + + + + + off + + + + + + + + + + + + + + off + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + 0pt + + + + right + + + left + + + + + right + + + left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + + ¯ + + + + + + + + + + _ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + normal + + + + + + + + monospace + sans-serif-italic + bold-sans-serif + sans-serif-bold-italic + sans-serif + bold-fraktur + fraktur + double-struck + bold-script + script + bold + italic + normal + bold-italic + + + + + + bold + normal + + + + + normal + italic + + + + + + + + + + + + + + + + + + + + + + + + italic + + + bold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + box + + + + + + + + + + + + + + + + + left right top bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + -1 + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + 1 + 0 + + + diff --git a/src/test/java/com/example/mathtoimg/MathToImgApplicationTests.java b/src/test/java/com/example/mathtoimg/MathToImgApplicationTests.java new file mode 100644 index 0000000..a2b5120 --- /dev/null +++ b/src/test/java/com/example/mathtoimg/MathToImgApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.mathtoimg; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class MathToImgApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/xsl/OMML2MML.XSL b/xsl/OMML2MML.XSL new file mode 100644 index 0000000..b46ae33 --- /dev/null +++ b/xsl/OMML2MML.XSL @@ -0,0 +1,2068 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ˘ + + ¸ + + ` + + - + + + + . + + ˙ + + ˝ + + ´ + + ~ + + ˜ + + ¨ + + ˇ + + ^ + + ¯ + + _ + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + + + right + + + + + + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0in + + + 0in + + + 0in + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + + + + + + + + + + + + + + + + + off + + + + + + + + + + + off + + + + + + + + + + + + + + off + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + 0pt + + + + right + + + left + + + + + right + + + left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + + ¯ + + + + + + + + + + _ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + normal + + + + + + + + monospace + sans-serif-italic + bold-sans-serif + sans-serif-bold-italic + sans-serif + bold-fraktur + fraktur + double-struck + bold-script + script + bold + italic + normal + bold-italic + + + + + + bold + normal + + + + + normal + italic + + + + + + + + + + + + + + + + + + + + + + + + italic + + + bold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + box + + + + + + + + + + + + + + + + + left right top bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + -1 + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + 1 + 0 + + +