设计思路
根据共享文档规范设定进行数据库规范维护,即当国家规范文档版本变更也可根据数据库动态维护进行升级更新,在规范文档结构数据维护时标记什么节点和属性为变量,并设定好对应的变量名。在前端呈现完整的规范文档结构并进行变量的SQL查询定制,定制好后一MAP进行查询结果数据接受即可实现动态化的文档生成。
所需资料
所需SQL
供参考的表结构设计,根据国家标准文档规范进行设计,此处仅展示对应文档的节点和属性表结构的设计。在界面上进行维护对应文档的节点和属性。
-- ----------------------------
-- Table structure for WSXX_GXWD_ELEMENT_NEW
-- ----------------------------
DROP TABLE "DSMS"."WSXX_GXWD_ELEMENT_NEW";
CREATE TABLE "DSMS"."WSXX_GXWD_ELEMENT_NEW" (
"UUID" VARCHAR2(100 CHAR) NOT NULL,
"ITEM_UUID" VARCHAR2(100 CHAR),
"CODE" VARCHAR2(200 CHAR),
"VAL" VARCHAR2(200 CHAR),
"STANDARD_RANGE" NUMBER DEFAULT 1 NOT NULL,
"CREATED" DATE,
"UPDATED" DATE,
"DELETED" DATE,
"STATUS" NUMBER DEFAULT 1 NOT NULL,
"GXWD_UUID" VARCHAR2(100 CHAR),
"VARIABLE" NUMBER,
"PARAM_NAME" VARCHAR2(255 BYTE)
);
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ELEMENT_NEW"."ITEM_UUID" IS '元素UUID';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ELEMENT_NEW"."STANDARD_RANGE" IS '标准范围,1国标,2省标,3市标,4平台内部,5其他';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ELEMENT_NEW"."STATUS" IS '状态,1启用,2禁用';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ELEMENT_NEW"."GXWD_UUID" IS '共享文档UUID';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ELEMENT_NEW"."VARIABLE" IS '是否为变量: 1是 0否';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ELEMENT_NEW"."PARAM_NAME" IS '属性变量名称';
COMMENT ON TABLE "DSMS"."WSXX_GXWD_ELEMENT_NEW" IS '共享文档元素属性';
-- ----------------------------
-- Table structure for WSXX_GXWD_ITEMS_NEW
-- ----------------------------
DROP TABLE "DSMS"."WSXX_GXWD_ITEMS_NEW";
CREATE TABLE "DSMS"."WSXX_GXWD_ITEMS_NEW" (
"UUID" VARCHAR2(100 CHAR) NOT NULL,
"SECTION_UUID" VARCHAR2(100 CHAR),
"NAME" VARCHAR2(200 CHAR),
"BASE" VARCHAR2(200 CHAR),
"BOUND" VARCHAR2(200 CHAR),
"INFO" VARCHAR2(400 CHAR),
"DE_CODE" VARCHAR2(60 CHAR),
"XPATH" VARCHAR2(400 CHAR),
"STANDARD_RANGE" NUMBER DEFAULT 1 NOT NULL,
"STATUS" NUMBER DEFAULT 1 NOT NULL,
"CREATED" DATE,
"UPDATED" DATE,
"DELETED" DATE,
"PARENT_UUID" VARCHAR2(100 CHAR),
"TAG_TYPE" NUMBER DEFAULT 2 NOT NULL,
"ORDERBY" NUMBER DEFAULT 0 NOT NULL,
"GXWD_UUID" VARCHAR2(100 BYTE),
"VARIABLE" NUMBER,
"DEFAULT_TXT" VARCHAR2(255 BYTE),
"PARAM_NAME" VARCHAR2(255 BYTE)
);
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."SECTION_UUID" IS '章节UUID';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."NAME" IS '元素名称';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."BASE" IS '基数';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."BOUND" IS '约束';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."INFO" IS '说明与描述';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."DE_CODE" IS '对应的数据元标识符';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."XPATH" IS 'xml的xpath';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."STANDARD_RANGE" IS '标准范围,1国标,2省标,3市标,4平台内部,5其他';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."STATUS" IS '状态,1启用,2禁用';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."PARENT_UUID" IS '上级元素id';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."TAG_TYPE" IS '标签类型:1-单标签,2-双标签';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."ORDERBY" IS '顺序';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."GXWD_UUID" IS '共享文档UUID';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."VARIABLE" IS '是否为变量: 1是 0否';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."DEFAULT_TXT" IS '默认文本值';
COMMENT ON COLUMN "DSMS"."WSXX_GXWD_ITEMS_NEW"."PARAM_NAME" IS '对应的变量名称';
COMMENT ON TABLE "DSMS"."WSXX_GXWD_ITEMS_NEW" IS '共享文档元素';
所需依赖
其他程序依赖自选
<!--lang3-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!--end-->
<!--dom4j-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
代码实现
主要围绕DOM4J进行文档模板结构生成、数据填充、格式化和解析。工具类如下:
import com.share.documents.entity.dsms.GxwdNewElement;
import com.share.documents.entity.dsms.GxwdNewItems;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.*;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @Author: zhaoPeng
* @Description: TODO(DOM4J 文档操作工具类)
* @Date: 2021/11/17 10:44
* @Version: 1.0
*/
public class Dom4JUtil {
/***-------------根据文档结构动态添加数据-------------------------------------------***/
/**
* TODO(根据传入的数据进行共享文档生成)
* @author zhaoPeng
* @date 2021/11/16
* @param root 文档根节点对象
* @param items 文档所有节点信息
* @param attrs 文档所有属性信息
* @param dataMap 带填充数据
* @return void
*/
public static void createDoc(Element root, List<GxwdNewItems> items, List<GxwdNewElement> attrs, Map<String, String> dataMap) {
Element childEle = null;
for (GxwdNewItems item:items) {
if(item==null)
break;
if("-1".equals(item.getParent_uuid())){//父节点为根目录
childEle = root.addElement(item.getName());
//当前节点添加属性和节点文本信息
if(addNodeTxt(childEle,item,dataMap,root.getName()) || addElementAttr(childEle,item.getUuid(),attrs,dataMap,root.getName()) ){
//若存在可有可以的节点并被移除后不再进行后续的节点和属性操作
break;
}
//添加当前节点的子节点
addChildElement(childEle,item.getUuid(),items,attrs,dataMap,root.getName());
}
}
}
/**
* TODO(添加对应节点的子节点)
* @author zhaoPeng
* @date 2021/11/16
* @param parentNode 当前节点dom对象
* @param parentId 当前对象数据库ID
* @param items 所有节点信息
* @param attrs 所有属性信息
* @param dataMap 待填充数据
* @param rootEleName 根节点名称
* @return void
*/
public static void addChildElement(Element parentNode, String parentId,List<GxwdNewItems> items,List<GxwdNewElement> attrs,Map<String, String> dataMap,String rootEleName) {
if(items.size()>0){
Element childEle = null;
for (GxwdNewItems child : items){
if(parentId.equals(child.getParent_uuid())){
childEle = parentNode.addElement(child.getName());
//当前节点添加属性和节点文本信息
if(addNodeTxt(childEle,child,dataMap,rootEleName) || addElementAttr(childEle,child.getUuid(),attrs,dataMap,rootEleName)){
//若存在可有可以的节点并被移除后不再进行后续的节点和属性操作
break;
}
//添加当前节点的子节点
addChildElement(childEle,child.getUuid(),items,attrs,dataMap,rootEleName);
}
}
}
}
/**
* TODO(添加当前节点的文本信息)
* @author zhaoPeng
* @date 2021/11/16
* @param elementNode 当前节点dom树对象
* @param child 当前节点数据库对象
* @param dataMap 带填充数据
* @param rootEleName 根节点名
* @return java.lang.Boolean 用于判定是否继续追加后续节点和属性
*/
public static Boolean addNodeTxt(Element elementNode,GxwdNewItems child ,Map<String, String> dataMap,String rootEleName) {
//添加约束属性
if (StringUtils.isBlank(child.getBase())) {//默认为必填
elementNode.addAttribute("base","3");
} else {
elementNode.addAttribute("base",child.getBase());
}
if (!"1".equals(child.getVariable())) { //不为变量 则采用默认值
if(StringUtils.isNotBlank(child.getDefault_txt())){
elementNode.setText(child.getDefault_txt());
}
} else {//如果为设定的变量 则进行变量取值 或采用默认值
if(dataMap.containsKey(child.getParam_name()) && StringUtils.isNotBlank(dataMap.get(child.getParam_name()))){
elementNode.setText(dataMap.get(child.getParam_name()));
}else if(StringUtils.isNotBlank(child.getDefault_txt())){//否 则采用默认值
elementNode.setText(child.getDefault_txt());
}else if(removeElement(rootEleName,elementNode)){//查询结果和默认值均为空值时进行节点移除判定
//移除后则不再进行该节点的子节点操作
return true;
}
}
return false;
}
/**
* TODO(给当前节点添加对应属性)
* @author zhaoPeng
* @date 2021/11/16
* @param elementNode 当前节点
* @param nodeMapId 对应节点的ID--属性的父级ID
* @param attrs 属性map数据
* @param dataMap 带填充数据
* @param rootEleName 文档根节点
* @return boolean 用于判定是否继续追加后续节点和属性
*/
public static Boolean addElementAttr(Element elementNode, String nodeMapId,List<GxwdNewElement> attrs,Map<String, String> dataMap,String rootEleName) {
for (GxwdNewElement attr:attrs) {//当前节点的属性
if(attr==null)
break;
if(nodeMapId.equals(attr.getItem_uuid())){
//不是需替换变量直接取模板中的默认值
if("1".equals(attr.getVariable())){
if(dataMap.containsKey(attr.getParam_name()) && StringUtils.isNotBlank(dataMap.get(attr.getParam_name()))){
elementNode.addAttribute(attr.getCode(),dataMap.get(attr.getParam_name()));
}else if(StringUtils.isNotBlank(attr.getVal())){//否 则采用默认值
elementNode.addAttribute(attr.getCode(),attr.getVal());
}else if(removeElement(rootEleName,elementNode)){//查询结果和默认值均为空值时进行节点移除判定
//移除后则不再进行该节点的属性操作
return true;
}
}else if(StringUtils.isNotBlank(attr.getVal())){//否 则采用默认值
elementNode.addAttribute(attr.getCode(),attr.getVal()==null?"":attr.getVal());
}
}
}
return false;
}
/**
* TODO(移除无数据且为可有可无的节点,移除后 后面的同级节点将不再继续追加)
* @author zhaoPeng
* @date 2021/11/16
* @param rootEleName 根节点名
* @param eleNode 当前节点
* @return boolean 用于判定是否继续追加后续节点和属性
*/
public static Boolean removeElement(String rootEleName,Element eleNode) {
//不为根节点时进行判定
if(!rootEleName.equals(eleNode.getName())){
//判定自身节点是否为必填项
List<Attribute> attrs = eleNode.attributes();
if (attrs != null) {
Element par = null;
for (Attribute attr : attrs) {
if("base".equals(attr.getName())){
if("1".equals(attr.getValue()) || "2".equals(attr.getValue())){
par = eleNode.getParent();
par.remove(eleNode);
return true;
}else{
par = eleNode.getParent();
removeElement(rootEleName,par);
}
}
}
}
}
return false;
}
/**
* TODO(移除自定义属性--如在进行可有可无节点移除时的判定条件)
* @author zhaoPeng
* @date 2021/11/16
* @param root 根节点对象
* @return void
*/
public static void removeAttr(Element root) {
Iterator<Element> iterator = root.elementIterator();
while (iterator.hasNext()) {
Element ele = iterator.next();
if (ele == null) {
continue;
}
//当前节点的属性
List<Attribute> attrs = ele.attributes();
if (attrs.size() > 0) {
Attribute attr = null;
//属性赋值和自定义属性移除
// for (int i = 0,len=attrs.size();i < len; i++) { //由于dom的相关操作是同步机制,这般优化会出现下表越界异常
for (int i = 0;i < attrs.size(); i++) {
attr = attrs.get(i);
if (attr == null)
continue;
if ("base".equals(attr.getName())) {
ele.remove(attr);
i--;
}
}
}
if(ele.elementIterator().hasNext()){
//当前节点的子节点
removeAttr(ele);
}
}
}
/***-------------生成文档模板,用于前端定制化SQL配置----------------------------------***/
/**
* TODO(根据传入的数据进行共享文档生成)
* @author zhaoPeng
* @date 2021/11/16
* @param root 文档根节点对象
* @param items 文档所有节点信息
* @param attrs 文档所有属性信息
* @return void
*/
public static void createDoc(Element root, List<GxwdNewItems> items, List<GxwdNewElement> attrs) {
Element childEle = null;
for (GxwdNewItems item:items) {
if(item==null)
break;
if("-1".equals(item.getParent_uuid())){//父节点为根目录
childEle = root.addElement(item.getName());
//当前节点添加属性和节点文本信息
addNodeTxt(childEle,item);
addElementAttr(childEle,item.getUuid(),attrs);
//添加当前节点的子节点
addChildElement(childEle,item.getUuid(),items,attrs);
}
}
}
/**
* TODO(添加对应节点的子节点)
* @author zhaoPeng
* @date 2021/11/16
* @param parentNode 当前节点dom对象
* @param parentId 当前对象数据库ID
* @param items 所有节点信息
* @param attrs 所有属性信息
* @return void
*/
public static void addChildElement(Element parentNode, String parentId,List<GxwdNewItems> items,List<GxwdNewElement> attrs) {
if(items.size()>0){
Element childEle = null;
for (GxwdNewItems child : items){
if(parentId.equals(child.getParent_uuid())){
childEle = parentNode.addElement(child.getName());
//当前节点添加属性和节点文本信息
addNodeTxt(childEle,child);
addElementAttr(childEle,child.getUuid(),attrs);
//添加当前节点的子节点
addChildElement(childEle,child.getUuid(),items,attrs);
}
}
}
}
/**
* TODO(添加当前节点的文本信息)
* @author zhaoPeng
* @date 2021/11/16
* @param elementNode 当前节点dom树对象
* @param child 当前节点数据库对象
* @return java.lang.Boolean 用于判定是否继续追加后续节点和属性
*/
public static Boolean addNodeTxt(Element elementNode,GxwdNewItems child) {
if (!"1".equals(child.getVariable())) { //不为变量 则采用默认值
elementNode.setText(child.getDefault_txt()==null?"":child.getDefault_txt());
} else {//如果为设定的变量 则进行变量取值 或采用默认值
elementNode.setText("${"+child.getParam_name()+"}");
}
return false;
}
/**
* TODO(给当前节点添加对应属性)
* @author zhaoPeng
* @date 2021/11/16
* @param elementNode 当前节点
* @param nodeMapId 对应节点的ID--属性的父级ID
* @param attrs 属性map数据
* @return boolean 用于判定是否继续追加后续节点和属性
*/
public static Boolean addElementAttr(Element elementNode, String nodeMapId,List<GxwdNewElement> attrs) {
for (GxwdNewElement attr:attrs) {//当前节点的属性
if(attr==null)
break;
if(nodeMapId.equals(attr.getItem_uuid())){
//不是需替换变量直接取模板中的默认值
if("1".equals(attr.getVariable())){ //是变量则显示变量名
elementNode.addAttribute(attr.getCode(),"${"+attr.getParam_name()+"}");
}else if(StringUtils.isNotBlank(attr.getVal())){//否 则采用默认值
elementNode.addAttribute(attr.getCode(),attr.getVal()==null?"":attr.getVal());
}
}
}
return false;
}
/***------------格式化xml Document为string----------------------------------***/
/**
* TODO(格式化xml为string)
* @author zhaoPeng
* @date 2021/11/16
* @param document
* @return java.lang.String
*/
public static String formatXml(Document document) throws IOException {
OutputFormat format = OutputFormat.createPrettyPrint();
format.setNewLineAfterDeclaration(false);
//如果有Element曾经调用setText("")设置元素文本为空字符串的话,输出xml时会报错:
//XMLWriter StringIndexOutOfBoundsException String index out of range: -1
//须设置format.setPadText(false);
format.setPadText(false);
format.setEncoding("UTF-8");
StringWriter stringWriter = new StringWriter();
XMLWriter writer = new XMLWriter(stringWriter, format);
writer.write(document);
writer.close();
return stringWriter.toString();
}
/***------------DOM4J解析XML字符串 解析后数据可自定义返回形式---------------***/
/**
* TODO(dom4j解析xml)
* @author zhaoPeng
* @date 2021/11/16
* @param xmlStr 待解析的xml字符串
* @return void
*/
public static void parseRootEle(String xmlStr) throws DocumentException {
Document doc = DocumentHelper.parseText(xmlStr.replaceAll("\n",""));
Element rootElement = doc.getRootElement();
Iterator<Element> iterator = rootElement.elementIterator();
while (iterator.hasNext()) {
Element element = iterator.next();
System.out.println("-------------------------------");
System.out.println("节点名称:"+element.getName()+"-节点值为:"+element.getText());
parseNodeAttr(element,element.getName());
//存在子节点继续轮询
if(element.elementIterator().hasNext()){
//当前节点的子节点
parseChildEle(element,element.getName());
}
}
}
/**
* TODO(解析子节点)
* @author zhaoPeng
* @date 2021/11/16
* @param element
* @return void
*/
public static void parseChildEle(Element element,String nodeName) {
Iterator<Element> iterator = element.elementIterator();
while (iterator.hasNext()) {
Element childEle = iterator.next();
System.out.println("-------------------------------");
System.out.println("节点名称:"+nodeName+"|"+childEle.getName()+"-节点值为:"+childEle.getText());
parseNodeAttr(childEle,nodeName+"-"+childEle.getName());
//存在子节点继续轮询
if(childEle.elementIterator().hasNext()){
//当前节点的子节点
parseChildEle(childEle,nodeName+"|"+childEle.getName());
}
}
}
/**
* TODO(解析属性)
* @author zhaoPeng
* @date 2021/11/16
* @param element
* @return void
*/
public static void parseNodeAttr(Element element,String nodeName) {
for(Iterator attrIt=element.attributeIterator();attrIt.hasNext();){
Attribute attribute = (Attribute) attrIt.next();
if(attribute!=null){
System.out.println("属性:"+nodeName+"@"+attribute.getName()+"="+attribute.getText());
}
}
}
}
根据上面提供的SQL数据表结构进行文档信息的查询:
/**
* TODO(查询指定文档的所有节点属性)
* @author zhaoPeng
* @date 2021/11/15
* @param
* @return java.util.List<java.util.Map < java.lang.String, java.lang.String>>
*/
@Select("SELECT UUID,SECTION_UUID,NAME,INFO,PARENT_UUID,TAG_TYPE,ORDERBY,BASE,VARIABLE,DEFAULT_TXT,PARAM_NAME FROM WSXX_GXWD_ITEMS_NEW WHERE VARIABLE IN (0,1) AND GXWD_UUID=#{gxwdId} ")//ORDER BY ORDERBY ASC
List<GxwdNewItems> getAllIterms(String gxwdId);
/**
* TODO(查询指定文档的所有节点属性)
* @author zhaoPeng
* @date 2021/11/15
* @param
* @return java.util.List<java.util.Map < java.lang.String, java.lang.String>>
*/
@Select("SELECT UUID,ITEM_UUID,CODE,VAL,VARIABLE,PARAM_NAME FROM WSXX_GXWD_ELEMENT_NEW WHERE GXWD_UUID=#{gxwdId} AND VARIABLE IN (0,1)")
List<GxwdNewElement> getAllAttrs(String gxwdId);
对应对象中的属性为查询结果集中的键。
控制层代码:
import com.share.documents.entity.dsms.GxwdNewElement;
import com.share.documents.entity.dsms.GxwdNewItems;
import com.share.documents.service.DSMSService;
import com.share.documents.utils.Dom4JUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: zp
* @Description: TODO(生成共享文档模板)
* @Date: 2021/11/3 13:44
* @Version: 1.0
*/
@Api(tags="docSample")
@RestController
@Slf4j
public class DocSampleController {
@Resource
private DSMSService dsmsService;
/**
* TODO(生成指定文档模板)
* @author zhaoPeng
* @date 2021/11/16
* @param docName 共享文档名称
* @return String 共享文档xml字符串
*/
@PostMapping("/creatDocSample")
@ApiOperation(value="生成指定文档模板")
@ApiImplicitParam(name="docName",value="个人基本健康信息登记",paramType="form")
public String creatDocSample(String docName) throws IOException {
docName = "个人基本健康信息登记";
//根据文档名称获取文档分类信息
Map sortMap = dsmsService.getGxwdIdByName(docName);
//查询指定的共享文档id
String gxwdId = dsmsService.getGxwdOutline(sortMap.get("UUID").toString());
//查询文档的节点结构和属性信息
List<GxwdNewItems> itemsMap = dsmsService.getAllIterms(gxwdId);
List<GxwdNewElement> attrsMap = dsmsService.getAllAttrs(gxwdId);
//添加根节点
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement("ClinicalDocument","urn:hl7-org:v3");
root.addAttribute("xmlns:mif","urn:hl7-org:v3/mif");
root.addAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
root.addAttribute("xsi:schemaLocation","urn:hl7-org:v3 ..\\sdschemas\\SDA.xsd");
//生成文档
Dom4JUtil.createDoc(root,itemsMap,attrsMap);
return Dom4JUtil.formatXml(doc);
}
/**
* TODO(根据共享文档名称进行文档的生成)
* @author zhaoPeng
* @date 2021/11/16
* @param docName 共享文档名称
* @return String 共享文档xml字符串
*/
@PostMapping("/creatDoc")
@ApiOperation(value="生成指定文档")
@ApiImplicitParam(name="docName",value="个人基本健康信息登记",paramType="form")
public String creatDoc(String docName) throws IOException {
docName = "个人基本健康信息登记";
//根据文档名称获取文档分类信息
Map sortMap = dsmsService.getGxwdIdByName(docName);
//查询指定的共享文档id
String gxwdId = dsmsService.getGxwdOutline(sortMap.get("UUID").toString());
//查询文档的节点结构和属性信息
List<GxwdNewItems> itemsMap = dsmsService.getAllIterms(gxwdId);
List<GxwdNewElement> attrsMap = dsmsService.getAllAttrs(gxwdId);
//添加根节点
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement("ClinicalDocument","urn:hl7-org:v3");
root.addAttribute("xmlns:mif","urn:hl7-org:v3/mif");
root.addAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
root.addAttribute("xsi:schemaLocation","urn:hl7-org:v3 ..\\sdschemas\\SDA.xsd");
//生成文档
Dom4JUtil.createDoc(root,itemsMap,attrsMap,defDataMap());
//移除自定义属性(根据实际情况选用)
Dom4JUtil.removeAttr(root);
return Dom4JUtil.formatXml(doc);
}
/**
* TODO(模拟xml字符串解析)
* @author zhaoPeng
* @date 2021/11/17
* @param args
* @return void
*/
public static void main(String[] args) throws DocumentException {
String xml = "<ClinicalDocument xmlns=\"urn:hl7-org:v3\" xmlns:mif=\"urn:hl7-org:v3/mif\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:hl7-org:v3 ..\\sdschemas\\SDA.xsd\">\n" + " <author typeCode=\"AUT\" contextControlCode=\"OP\">\n" +
" <time xsi:type=\"TS\" value=\"202111110938\"/>\n" +
" <assignedAuthor classCode=\"ASSIGNED\">\n" +
" <id root=\"2.16.156.10011.1.7\" extension=\"e5d6a289c099459b8113\"/>\n" +
" <assignedPerson>\n" +
" <name>王医生</name>\n" +
" </assignedPerson>\n" +
" <representedOrganization>\n" +
" <id root=\"2.16.156.10011.1.5\" extension=\"3404112392111001\"/>\n" +
" <name>GDIGU</name>\n" +
" <addr>四川成都</addr>\n" +
" </representedOrganization>\n" +
" </assignedAuthor>\n" +
" </author>\n" +
"</ClinicalDocument>";
Dom4JUtil.parseRootEle(xml);
}
/**
* @return Map
* @Author zhaopeng
* @Description TODO(模拟数据 map中的key值为自定义sql中指定,对应文档中的变量名)
* @Date: 2021/11/12 10:2
*/
@PostMapping("/defDataMap")
@ApiOperation(value="模拟数据填充数据结构")
public Map<String, String> defDataMap() {
//各章节数据
Map<String, String> dataMap = new HashMap<>();
//模拟文档活动类信息数据
//txt
dataMap.put("title-txt","个人基本健康信息登记");
dataMap.put("id-txt","202111120911");
dataMap.put("versionNo-txt","1.1");
//attr
dataMap.put("effectiveTime-value","202111120938");
dataMap.put("id-extension","340421_3404211092020");
//模拟患者信息数据
//txt
dataMap.put("houseNumber-txt","");
dataMap.put("streetName-txt","");
dataMap.put("township-txt","");
dataMap.put("county-txt","");
dataMap.put("city-txt","成都");
dataMap.put("state-txt","四川");
dataMap.put("postalCode-txt","636154");
dataMap.put("patientName-txt","张三");
dataMap.put("employerName-txt","云鹏科技");
//attr
dataMap.put("patient-id","420106201101011919");
dataMap.put("telecom-value","010-87815102");
dataMap.put("patientId-ext","420106201101012219");
dataMap.put("patSex-code","01");
dataMap.put("patBirth-value","201102113");
dataMap.put("patMar-code","19");
dataMap.put("patMar-value","未婚");
dataMap.put("patEth-code","1");
dataMap.put("patEth-value","汉族");
dataMap.put("patEdu-code","59");
dataMap.put("patOcc-code","37");
//模拟文档创作者信息数据
//txt
dataMap.put("assPersonName-txt","王医生");
dataMap.put("assOrgName-txt","达实旗云");
dataMap.put("assAddrName-txt","四川成都");
//attr
dataMap.put("authorTime","202111110938");
dataMap.put("authorId","e5d6a289c099459b8113");
dataMap.put("reOrgId-extension","3404112392111001");//模拟可有可无的数据进行移除时需要数据库中对应的变量名默认值为空
//模拟数据录入者保管机构信息数据
//txt
dataMap.put("custOrgName-txt","达实旗云");
dataMap.put("custOrgAddr-txt","四川成都");
//attr
dataMap.put("custOrgid","3404112392111001");
dataMap.put("custTel","13698762311");
//模拟文档管理者信息联系人数据
//txt
dataMap.put("particName-txt","联系人老王");
//attr
dataMap.put("particTel","13698762311");
//模拟关联活动 父级文档数据(无) 机构ID模拟数据
dataMap.put("parOrg-id","");//模拟可有可无的数据进行移除时需要数据库中对应的变量名默认值为空
return dataMap;
}
}
此处的模拟数据在实际的使用中替换为实际的查询数据,根据呈现的模板变量名匹配对应的KEY即可。
效果图
本人采用Swagger进行API的测试,如图:
模板生成,用于定制化查询SQL:
使用模拟数据验证文档生成:
总结
代码其实没有难度,稍微复杂点的是共享文档其中的逻辑,感兴趣的可以重点关注一下文章开头中所描述的思路。
避坑
1、数据库中关于贡献文档的结构、节点、属性数据维护好后,避免采用由上几下的轮训查询操作【性能慢】,可采用本文中的方式将对应结构一次性查出,在内存中进行封装。
2、Document树的操作是实时的,移除和添加操作均会同步到当前逻辑。
3、Document中取值会将换行符和空格当做实际的数据值,在解析前可先进行替换操作。
freemarker模板生成XML文档
如果对灵活性要求不高且对freemarker操作熟悉,可采用freemarker进行共享文档的生成。
模板如图:
实现代码:
try {
Template template = configuration.getTemplate("myXml.ftl");
String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
return "生成失败";
}
仅需要模板文件和对应的数据即可。所有的判断、遍历、默认值设置均在ftl模板文件中设定。
很久没有写过代码了,拿Dom4j练手。不喜勿喷!