设计思路
根据共享文档规范设定进行数据库规范维护,即当国家规范文档版本变更也可根据数据库动态维护进行升级更新,在规范文档结构数据维护时标记什么节点和属性为变量,并设定好对应的变量名。在前端呈现完整的规范文档结构并进行变量的SQL查询定制,定制好后一MAP进行查询结果数据接受即可实现动态化的文档生成。
所需资料
所需SQL
供参考的表结构设计,根据国家标准文档规范进行设计,此处仅展示对应文档的节点和属性表结构的设计。在界面上进行维护对应文档的节点和属性。
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 '共享文档元素属性';
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;
public class Dom4JUtil {
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());
}
}
}
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);
}
}
}
}
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;
}
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;
}
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;
}
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;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);
}
}
}
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);
}
}
}
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);
}
}
}
}
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;
}
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;
}
public static String formatXml(Document document) throws IOException {
OutputFormat format = OutputFormat.createPrettyPrint();
format.setNewLineAfterDeclaration(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();
}
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());
}
}
}
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());
}
}
}
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数据表结构进行文档信息的查询:
@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} ")
List<GxwdNewItems> getAllIterms(String gxwdId);
@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;
@Api(tags="docSample")
@RestController
@Slf4j
public class DocSampleController {
@Resource
private DSMSService dsmsService;
@PostMapping("/creatDocSample")
@ApiOperation(value="生成指定文档模板")
@ApiImplicitParam(name="docName",value="个人基本健康信息登记",paramType="form")
public String creatDocSample(String docName) throws IOException {
docName = "个人基本健康信息登记";
Map sortMap = dsmsService.getGxwdIdByName(docName);
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);
}
@PostMapping("/creatDoc")
@ApiOperation(value="生成指定文档")
@ApiImplicitParam(name="docName",value="个人基本健康信息登记",paramType="form")
public String creatDoc(String docName) throws IOException {
docName = "个人基本健康信息登记";
Map sortMap = dsmsService.getGxwdIdByName(docName);
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);
}
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);
}
@PostMapping("/defDataMap")
@ApiOperation(value="模拟数据填充数据结构")
public Map<String, String> defDataMap() {
Map<String, String> dataMap = new HashMap<>();
dataMap.put("title-txt","个人基本健康信息登记");
dataMap.put("id-txt","202111120911");
dataMap.put("versionNo-txt","1.1");
dataMap.put("effectiveTime-value","202111120938");
dataMap.put("id-extension","340421_3404211092020");
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","云鹏科技");
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");
dataMap.put("assPersonName-txt","王医生");
dataMap.put("assOrgName-txt","达实旗云");
dataMap.put("assAddrName-txt","四川成都");
dataMap.put("authorTime","202111110938");
dataMap.put("authorId","e5d6a289c099459b8113");
dataMap.put("reOrgId-extension","3404112392111001");
dataMap.put("custOrgName-txt","达实旗云");
dataMap.put("custOrgAddr-txt","四川成都");
dataMap.put("custOrgid","3404112392111001");
dataMap.put("custTel","13698762311");
dataMap.put("particName-txt","联系人老王");
dataMap.put("particTel","13698762311");
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练手。不喜勿喷!
|