|
|
|
@ -141,13 +141,10 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
checkSemanticFor: for (CheckSemanticModel value : checkSemanticModelMap.values()) {
|
|
|
|
|
String field = value.getField();
|
|
|
|
|
String fieldName = value.getFieldName();//校验的字段名称
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String ruleInfo = value.getRuleInfo();//是否绝对判断 0-绝对判断,1-不绝对判断
|
|
|
|
|
String inputText = value.getInputText();//校验文本 ,ocr识别的文本如果不包含该内容,则算作失败.
|
|
|
|
|
String inputText = value.getInputText();//校验文本 ,ocr识别的文本如果不包含该内容,则算作失败
|
|
|
|
|
List<String> fieldNameList = Arrays.asList(fieldName.split(","));
|
|
|
|
|
text = null;
|
|
|
|
|
boolean b = ArrayOUtils.containsStringList(fieldNameList, semanticResult.keySet().stream().collect(Collectors.toList()));
|
|
|
|
|
//查看ocr识别返回的字段名称中是否有当前这个字段名称
|
|
|
|
|
if (b) {
|
|
|
|
@ -162,29 +159,64 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
if (ocrArray.size()>0) {
|
|
|
|
|
ocrArrayFor: for (int i = 0; i < ocrArray.size(); i++) {
|
|
|
|
|
JSONObject ocrItem = ocrArray.get(i);
|
|
|
|
|
text = null;
|
|
|
|
|
text = ocrItem.getString("text");//ocr 识别的文本
|
|
|
|
|
probability = ocrItem.getDouble("probability");//置信度
|
|
|
|
|
log.info("ocrItem:");
|
|
|
|
|
log.info(ocrItem.toJSONString());
|
|
|
|
|
if (StringUtils.isBlank(text)) {
|
|
|
|
|
//ocr识别参数为空,不通过
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数未获取到结果<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList, field,value.getFieldName(), inputText, null, probability, imgPath, value.getFieldName() + "参数未获取到结果", false);
|
|
|
|
|
} else if ("1".equals(ruleInfo) && StringUtils.isNotBlank(text)) {
|
|
|
|
|
//不必校验,有值就行,通过
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true);
|
|
|
|
|
} else if ("0".equals(ruleInfo)) {
|
|
|
|
|
//必定验证参数,必须有值且匹配
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (StringUtils.isBlank(inputText) || text.contains(inputText) || inputText.contains(text)) {
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
} else {
|
|
|
|
|
rMessage.append(value.getFieldName() + "不匹配<br>");
|
|
|
|
|
if ("101".equals(ruleInfo)) {
|
|
|
|
|
if (StringUtils.isBlank(text) || StringUtils.isBlank(inputText)) {
|
|
|
|
|
//没识别值
|
|
|
|
|
mapPutIfTrue(fieldRightMap,field,true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "参数不判断", true);
|
|
|
|
|
}else{
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (text.contains(inputText)) {
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
}else{
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数不匹配<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "参数不匹配", false,v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
if (StringUtils.isBlank(text)) {
|
|
|
|
|
//ocr识别参数为空,不通过
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数未获取到结果<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "不匹配", false,v);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "参数未获取到结果", false);
|
|
|
|
|
} else if(StringUtils.isBlank(inputText)){
|
|
|
|
|
//没有输入值.
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList, value.getFieldName(), field, inputText, text, probability, imgPath, "没有输入值不做匹配", true);
|
|
|
|
|
} else if ("0".equals(ruleInfo)) {
|
|
|
|
|
//不必校验,有识别到就行,通过
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList, value.getFieldName(), field, inputText, text, probability, imgPath, "", true);
|
|
|
|
|
} else if(Double.valueOf(ruleInfo)>=1 && Double.valueOf(ruleInfo)<=99){
|
|
|
|
|
//在1~99之间,根据精准度匹配
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (v>=Double.valueOf(ruleInfo)) {
|
|
|
|
|
//准确度 可靠
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList, value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
}else{
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数不匹配<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "参数不匹配", false,v);
|
|
|
|
|
}
|
|
|
|
|
} else if ("100".equals(ruleInfo)) {
|
|
|
|
|
//必定验证参数,必须有值且匹配
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (text.equals(inputText)) {
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
} else {
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数不匹配<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "参数不匹配", false,v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -193,7 +225,6 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "参数未获取到结果", false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数未获取到结果<br>");
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, 0d, imgPath, value.getFieldName() + "参数未获取到结果", false);
|
|
|
|
@ -324,7 +355,7 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
OcrIdentifyVo ocrIdentifyVo = this.findById(id);
|
|
|
|
|
List<OcrIdentifyDetail> identifyDetails = ocrIdentifyDetailService.listByIdentifyId(ocrIdentifyVo.getId());
|
|
|
|
|
|
|
|
|
|
/*for (OcrIdentifyDetail identifyDetail : identifyDetails) {
|
|
|
|
|
for (OcrIdentifyDetail identifyDetail : identifyDetails) {
|
|
|
|
|
JSONObject semanticResult = JSONObject.parseObject(identifyDetail.getSemanticResult());
|
|
|
|
|
String imgPath = identifyDetail.getImageUrl();
|
|
|
|
|
// 进行数据化 结构
|
|
|
|
@ -344,7 +375,7 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
String field = value.getField();
|
|
|
|
|
String fieldName = value.getFieldName();//校验的字段名称
|
|
|
|
|
String ruleInfo = value.getRuleInfo();//是否绝对判断 0-绝对判断,1-不绝对判断
|
|
|
|
|
String inputText = value.getInputText();//校验文本 ,ocr识别的文本如果不包含该内容,则算作失败.
|
|
|
|
|
String inputText = value.getInputText();//校验文本 ,ocr识别的文本如果不包含该内容,则算作失败
|
|
|
|
|
List<String> fieldNameList = Arrays.asList(fieldName.split(","));
|
|
|
|
|
text = null;
|
|
|
|
|
boolean b = ArrayOUtils.containsStringList(fieldNameList, semanticResult.keySet().stream().collect(Collectors.toList()));
|
|
|
|
@ -366,37 +397,70 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
probability = ocrItem.getDouble("probability");//置信度
|
|
|
|
|
log.info("ocrItem:");
|
|
|
|
|
log.info(ocrItem.toJSONString());
|
|
|
|
|
if (StringUtils.isBlank(text)) {
|
|
|
|
|
//ocr识别参数为空,不通过
|
|
|
|
|
rMessage.append(value.getFieldName() + "_参数未获取到结果<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "_参数未获取到结果", false);
|
|
|
|
|
} else if ("1".equals(ruleInfo) && StringUtils.isNotBlank(text)) {
|
|
|
|
|
//不必校验,有值就行,通过
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true);
|
|
|
|
|
} else if ("0".equals(ruleInfo)) {
|
|
|
|
|
//必定验证参数,必须有值且匹配
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (StringUtils.isBlank(inputText) || text.contains(inputText) || inputText.contains(text)) {
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
} else {
|
|
|
|
|
rMessage.append(value.getFieldName() + "_参数不匹配<br>");
|
|
|
|
|
if ("101".equals(ruleInfo)) {
|
|
|
|
|
if (StringUtils.isBlank(text) || StringUtils.isBlank(inputText)) {
|
|
|
|
|
//没识别值
|
|
|
|
|
mapPutIfTrue(fieldRightMap,field,true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "参数不判断", true);
|
|
|
|
|
}else{
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (text.contains(inputText)) {
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
}else{
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数不匹配<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "参数不匹配", false,v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
if (StringUtils.isBlank(text)) {
|
|
|
|
|
//ocr识别参数为空,不通过
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数未获取到结果<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "_参数不匹配", false,v);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "参数未获取到结果", false);
|
|
|
|
|
} else if(StringUtils.isBlank(inputText)){
|
|
|
|
|
//没有输入值.
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList, value.getFieldName(), field, inputText, text, probability, imgPath, "没有输入值不做匹配", true);
|
|
|
|
|
} else if ("0".equals(ruleInfo)) {
|
|
|
|
|
//不必校验,有识别到就行,通过
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList, value.getFieldName(), field, inputText, text, probability, imgPath, "", true);
|
|
|
|
|
} else if(Double.valueOf(ruleInfo)>=1 && Double.valueOf(ruleInfo)<=99){
|
|
|
|
|
//在1~99之间,根据精准度匹配
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (v>=Double.valueOf(ruleInfo)) {
|
|
|
|
|
//准确度 可靠
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList, value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
}else{
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数不匹配<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "参数不匹配", false,v);
|
|
|
|
|
}
|
|
|
|
|
} else if ("100".equals(ruleInfo)) {
|
|
|
|
|
//必定验证参数,必须有值且匹配
|
|
|
|
|
double v = StrCharUtil.similarityRatio(inputText, text);
|
|
|
|
|
if (text.equals(inputText)) {
|
|
|
|
|
fieldRightMap.put(field, true);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, "", true,v);
|
|
|
|
|
} else {
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数不匹配<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, text, probability, imgPath, value.getFieldName() + "参数不匹配", false,v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
rMessage.append(value.getFieldName() + "_参数未获取到结果<br>");
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数未获取到结果<br>");
|
|
|
|
|
mapPutIfTrue(fieldRightMap, field, false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "_参数未获取到结果", false);
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, probability, imgPath, value.getFieldName() + "参数未获取到结果", false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
rMessage.append(value.getFieldName() + "_参数未获取到结果<br>");
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, 0d, imgPath, value.getFieldName() + "_参数未获取到结果", false);
|
|
|
|
|
rMessage.append(value.getFieldName() + "参数未获取到结果<br>");
|
|
|
|
|
ocrResultAdd(ocrResultList,value.getFieldName(), field, inputText, null, 0d, imgPath, value.getFieldName() + "参数未获取到结果", false);
|
|
|
|
|
fieldRightMap.put(field, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -418,7 +482,7 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
identifyDetail.setDataStructured(JSONArray.toJSONString(ocrResultList));//数据结构化
|
|
|
|
|
ocrIdentifyDetailService.updateById(identifyDetail);
|
|
|
|
|
}
|
|
|
|
|
}*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//=======规则检查配置
|
|
|
|
|
OcrRuleCheckVo ocrRuleCheckVo = ocrIdentifyVo.getOcrRuleCheckVo();
|
|
|
|
@ -426,7 +490,7 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
|
|
|
|
|
//===================
|
|
|
|
|
//4.更新主任务状态
|
|
|
|
|
LambdaUpdateWrapper<OcrIdentify> updateWrapper = new LambdaUpdateWrapper<OcrIdentify>();
|
|
|
|
|
/*LambdaUpdateWrapper<OcrIdentify> updateWrapper = new LambdaUpdateWrapper<OcrIdentify>();
|
|
|
|
|
updateWrapper.eq(OcrIdentify::getId, id);
|
|
|
|
|
List<OcrIdentifyDetail> identifyDetailList = ocrIdentifyDetailService.listByIdentifyId(id);
|
|
|
|
|
if(true) {
|
|
|
|
@ -475,7 +539,7 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
updateWrapper.set(OcrIdentify::getTaskResultInfo,taskResultInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
super.update(updateWrapper);
|
|
|
|
|
super.update(updateWrapper);*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
@ -656,7 +720,6 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
*/
|
|
|
|
|
public Map<String, CheckSemanticModel> getCheckSemanticModelMap(Map<String, String> configRuleMap, Map<String, String> fieldMap, List<JSONObject> sourceJsonObjects) {
|
|
|
|
|
Map<String, CheckSemanticModel> checkSemanticModelMap = new LinkedHashMap<>();
|
|
|
|
|
|
|
|
|
|
//校验正确的值
|
|
|
|
|
Map<String, String> inputMap = new LinkedHashMap<>();
|
|
|
|
|
if (sourceJsonObjects != null && sourceJsonObjects.size() > 0) {
|
|
|
|
@ -671,28 +734,43 @@ public class OcrIdentifyServiceImpl extends ServiceImpl<OcrIdentifyMapper, OcrId
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CheckSemanticModel copyEntity = null;
|
|
|
|
|
String configRule = null, fieldName = null, inputText;
|
|
|
|
|
if (fieldMap != null) {
|
|
|
|
|
for (String field : fieldMap.keySet()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//判断 规则检查配置 是不是 isrule=1
|
|
|
|
|
if (configRuleMap.keySet().contains("isrule")&&"1".equals((String)configRuleMap.get("isrule"))) {
|
|
|
|
|
//isrule=1
|
|
|
|
|
for (String field : inputMap.keySet()) {
|
|
|
|
|
fieldName = fieldMap.get(field);
|
|
|
|
|
copyEntity = new CheckSemanticModel();
|
|
|
|
|
inputText = inputMap.get(field);
|
|
|
|
|
copyEntity.setField(field);
|
|
|
|
|
copyEntity.setInputText(inputText);
|
|
|
|
|
copyEntity.setRuleInfo("1");
|
|
|
|
|
copyEntity.setFieldName(fieldName);
|
|
|
|
|
checkSemanticModelMap.put(field, copyEntity);
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
if (fieldMap != null) {
|
|
|
|
|
for (String field : fieldMap.keySet()) {
|
|
|
|
|
copyEntity = new CheckSemanticModel();
|
|
|
|
|
copyEntity.setField(field);
|
|
|
|
|
//1/0
|
|
|
|
|
if (configRuleMap != null && configRuleMap.containsKey(field)) {
|
|
|
|
|
configRule = configRuleMap.get(field);
|
|
|
|
|
copyEntity.setRuleInfo(configRule);
|
|
|
|
|
//端字段含义
|
|
|
|
|
fieldName = fieldMap.get(field);
|
|
|
|
|
//检查数据
|
|
|
|
|
inputText = inputMap.get(field);
|
|
|
|
|
|
|
|
|
|
//1/0
|
|
|
|
|
if (configRuleMap != null && configRuleMap.containsKey(field)) {
|
|
|
|
|
configRule = configRuleMap.get(field);
|
|
|
|
|
copyEntity.setRuleInfo(configRule);
|
|
|
|
|
//端字段含义
|
|
|
|
|
fieldName = fieldMap.get(field);
|
|
|
|
|
//检查数据
|
|
|
|
|
inputText = inputMap.get(field);
|
|
|
|
|
|
|
|
|
|
copyEntity.setFieldName(fieldName);
|
|
|
|
|
copyEntity.setFieldName(fieldName);
|
|
|
|
|
|
|
|
|
|
copyEntity.setInputText(inputText);
|
|
|
|
|
copyEntity.setInputText(inputText);
|
|
|
|
|
|
|
|
|
|
checkSemanticModelMap.put(field, copyEntity);
|
|
|
|
|
checkSemanticModelMap.put(field, copyEntity);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|