当前位置:网站首页>Generate XML based on annotations and reflection
Generate XML based on annotations and reflection
2022-06-10 18:08:00 【llp1110】
Generate based on annotations and reflection xml
Hello everyone , I am a llp. Recently, when interfacing with third-party interfaces , Found that they are all xml A message of form , And there are differences. Some collections contain parent tags while others do not , So I want to do a generation based on annotation reflection xml The plan . I just finished today , Make a record and share , There must be many deficiencies in the program , Your suggestions are very welcome .
1.XMl grammar
Before that, we need to know XML The document is divided into the following parts
1. The document statement
2. Elements
3. attribute
4. notes
5.CDATA District 、 Special characters
Specific details , You can refer to the official website documentation
2. Define your own set of annotations
Here I divide annotations into the following categories :
1.@XmlBeanElement Used to decorate a class
2.@XmlPropertyELement Used to decorate fields of common types
3.@XmlBeanElement Used to modify bean Type field
4.@XmlCDATA Used to decorate and generate CDATA Field of zone
5.@XmlGatherElement Fields used to decorate collection types List<String>、List<Person>、List<Map>
6.@XmlMapElement Used to modify Map Type field Map<String,String>
here Map Only ordinary types of key,val The way
[email protected]
/** * xml Annotate with the label (xml Elements ) * ElementType.TYPE decorator * @Retention(RetentionPolicy.RUNTIME) Run time takes effect */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlRootElement {
/** * Alias */
String name() default "";
/** * Namespace */
String[] namespace() default "";
/** * Version number For document declaration */
String version() default "v1.0";
/** * code For document declaration */
String encoding() default "utf-8";
}
[email protected]
/** * xml Attribute annotation * @Target(ElementType.FIELD) Decorate fields */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlPropertyELement {
/** * Alias */
String name() default "";
/** * Namespace */
String namespace() default "";
/** * Whether to resolve null value */
boolean analysisEmpty() default false;
}
[email protected]
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlBeanElement {
/** * Alias */
String name() default "";
/** * Namespace */
String namespace() default "";
/** * Whether to resolve null value * @return */
boolean analysisEmpty() default false;
}
[email protected]
/** * CDATA District * <![CDATA[ * Here you can display the characters you entered as they are , No resolution xml * ]]> * 1. Because the design here @XmlCDATA It's cooperation @XmlPropertyELement Use annotations * Whether to resolve null value 、 Namespace 、 Aliases, etc. are handed over to @XmlPropertyELement control * [email protected] Annotations focus only on xml in CDATA Treatment of the area */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlCDATA {
/** * <code> * <!-- If you want to put some strings , As plain text , Use CDATA Include --> * <![CDATA[ * <script data-compress=strip> * function h(obj){ * obj.style.behavior='url(#default#homepage)'; * var a = obj.setHomePage('//www.baidu.com/'); * } * </script> * ]]> * </code> */
}
[email protected]
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlGatherElement {
/** * Alias */
String name() default "";
/** * Namespace */
String namespace() default "";
/** * Child tags */
String childName() default "";
/** * Whether to resolve null value */
boolean analysisEmpty() default false;
/** * Whether to display parent signature */
boolean showName() default true;
/** * Whether it is bean */
boolean isBean() default false;
}
[email protected]
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlMapElement {
/** * Tag alias */
String name() default "";
/** * Namespace */
String namespace() default "";
/** * Whether to resolve null value */
boolean analysisEmpty() default false;
}
3. Generate XML Tool class of
public class XmlUtil {
/** * Generate xml character string * * @param obj xml The object decorated by the element annotation * @return */
public static String generateXml(Object obj) {
//1. Get class Object's
Class<?> clazz = obj.getClass();
//2. obtain xml Element annotation
XmlRootElement xmlRootElement = clazz.getAnnotation(XmlRootElement.class);
//3. Define a StringBuffer Object is used to splice xml character string
StringBuffer xml = new StringBuffer();
//4. The document statement assemble <?xml version="1.0" encoding="UTF-8"?>
xml.append("<?xml ")
.append("version=" + xmlRootElement.version())
.append(" ")
.append("encoding=" + xmlRootElement.encoding())
.append("?>");
//5.xml Elements ( Follow the label ) Splicing
String name = xmlRootElement.name();
// If the alias is empty , Take the class name as the label
if ("".equals(name) || name == null) {
name = clazz.getSimpleName();
}
xml.append("<")
.append(name)
.append(" ");
String[] namespace = xmlRootElement.namespace();
//6. Traversing namespaces , Splicing xml Element namespace
for (int i = 0; i < namespace.length; i++) {
if (StringUtils.isNotBlank(namespace[i])) {
xml.append(namespace[i])
.append(" ");
}
}
// Remove extra space at the end
xml.deleteCharAt(xml.lastIndexOf(" "));
xml.append(">");
//7. Splicing xml attribute
element(xml, clazz, obj);
//8.xml End of element
xml.append("</")
.append(xmlRootElement.name())
.append(">");
return xml.toString();
}
/** * assemble xml Attribute tag content * * @param xml * @param clazz * @param obj */
private static void element(StringBuffer xml, Class<?> clazz, Object obj) {
//1. Get all the fields of the class
Field[] fields = clazz.getDeclaredFields();
//2. Traverse all fields
for (Field field : fields) {
Object val = getFieldVal(field, obj);
//3. Determine what type of annotation each field is decorated with
if (field.isAnnotationPresent(XmlPropertyELement.class)) {
// Basic type field content processing
propertyElement(field, xml, val);
} else if (field.isAnnotationPresent(XmlBeanElement.class)) {
// Object field content processing
beanELement(field, xml, val);
} else if (field.isAnnotationPresent(XmlGatherElement.class)) {
// Set field content processing
gatherELement(field, xml, val);
} else if (field.isAnnotationPresent(XmlMapElement.class)) {
//map Field content processing
mapELement(field, xml, val);
}
}
}
/** * map Field content processing * * @param field * @param xml * @param val */
private static void mapELement(Field field, StringBuffer xml, Object val) {
//1. obtain @XmlMapElement annotation
XmlMapElement xmlMapElement = field.getAnnotation(XmlMapElement.class);
//2. Determine whether to resolve null values
if (!xmlMapElement.analysisEmpty() && isEmpty(val)) {
return;
}
//3. Get alias , If the alias is empty, the field name is used as the label
String name = xmlMapElement.name();
if ("".equals(name) || name == null) {
name = field.getName();
}
xml.append("<").append(name);
//4. Get namespace , Splice if present
if (!"".equals(xmlMapElement.namespace()) && xmlMapElement.namespace() != null) {
xml.append(" ").append(xmlMapElement.namespace());
}
xml.append(">");
// Traverse map Splicing
if (val instanceof Map) {
mapToXmlString(xml, (Map) val);
} else {
throw new RuntimeException("@XmlMapElement The identity field must be Map type ");
}
// End label splicing
xml.append("</").append(name).append(">");
}
/** * Set field content processing * * @param field * @param xml * @param val */
private static void gatherELement(Field field, StringBuffer xml, Object val) {
//1. obtain @XmlGatherElement annotation
XmlGatherElement gatherElement = field.getAnnotation(XmlGatherElement.class);
// Determine whether to process null data
if (!gatherElement.analysisEmpty() && isEmpty(val)) {
return;
}
String name = gatherElement.name();
// If the alias is empty, assemble by name
if (gatherElement == null || name.length() == 0) {
name = field.getName();
}
// Determine whether to assemble the parent label
boolean showName = gatherElement.showName();
if (showName) {
xml.append("<").append(name);
if (gatherElement.namespace() != null && !"".equals(gatherElement.namespace()))
xml.append(" ").append(gatherElement.namespace());
xml.append(">");
}
// Get sub tag name
String childName = gatherElement.childName();
// If the child tag is empty, the parent tag will be used as the child tag
if (isEmpty(childName))
childName = name;
if (val instanceof Array) {
// Processing array data
// Determine whether the sub tag needs to be parsed
if (!gatherElement.isBean()) {
// Not for bean Object directly traverses the array , Splice each element
for (Array a : (Array[]) val) {
xml.append("<").append(childName).append(">").append(a).append("</").append(childName).append(">");
}
} else {
// Every element is bean An array of objects
for (Object ob : (Array[]) val) {
// Assemble sub tag data
beanOrMapEment(childName, xml, ob);
}
}
} else if (val instanceof Collection) {
// Processing set data
Collection list = (Collection) val;
// Determine whether the aggregate sub tag needs to be parsed
if (!gatherElement.isBean()) {
for (Object ob : list) {
xml.append("<").append(childName).append(">").append(ob).append("</").append(childName).append(">");
}
} else {
for (Object ob : list) {
// Resolve child Tags
beanOrMapEment(childName, xml, ob);
}
}
}
// If the parent label is displayed , Splice parent tag end tag
if (showName) {
xml.append("</").append(name).append(">");
}
}
/** * Each element is bean or map Data splicing * * @param childName * @param xml * @param ob */
private static void beanOrMapEment(String childName, StringBuffer xml, Object ob) {
xml.append("<").append(childName).append(">");
// Do not process when empty
if (ob != null) {
// Determine whether the elements in the set are map
if (ob instanceof Map) {
// analysis map data
mapToXmlString(xml, (Map) ob);
} else {
// analysis bean, Fetch element class object
Class clazz = ob.getClass();
// Assembly element xml message
element(xml, clazz, ob);
}
}
xml.append("</").append(childName).append(">");
}
/** * map Traversal assembly * * @param xml * @param map */
private static void mapToXmlString(StringBuffer xml, Map map) {
if (map == null || map.size() == 0) {
return;
}
for (Object key : map.keySet()) {
xml.append("<").append(key).append(">").append(map.get(key)).append("</").append(key).append(">");
}
}
/** * bean Attribute field content processing * * @param field * @param xml * @param val */
private static void beanELement(Field field, StringBuffer xml, Object val) {
//1. obtain @XmlBeanElement annotation
XmlBeanElement xmlBeanElement = field.getAnnotation(XmlBeanElement.class);
//2. Determine whether to parse the null value
if (!xmlBeanElement.analysisEmpty() && isEmpty(val)) {
// If you do not parse the null value and the attribute value is null, you can directly return
return;
}
//3. Splicing bean Object start label
String name = xmlBeanElement.name();
// If the alias is empty, the field name has been spliced
if (name == null || "".equals(name)) {
name = field.getName();
}
xml.append("<").append(name);
//4. Determine whether the attribute has a namespace set
if (!"".equals(xmlBeanElement.namespace()) && xmlBeanElement.namespace() != null) {
xml.append(" ").append(xmlBeanElement.namespace());
}
xml.append(">");
//5. Determine whether the field is @XmlCDATA To modify
if (field.isAnnotationPresent(XmlCDATA.class)) {
// Wrap the content
xml.append("<![CDATA[").append(val).append("]]>");
}
//6. Get the... Of the object field class object , Yes bean Object properties , If the field also contains bean Objects are processed recursively , By analogy
Class<?> fieldClass = field.getType();
element(xml, fieldClass, val);
//7. Splicing bean Object end tag
xml.append("</").append(xmlBeanElement.name()).append(">");
}
/** * Basic type field content processing * * @param field * @param xml * @param val */
private static void propertyElement(Field field, StringBuffer xml, Object val) {
//1. obtain @XmlPropertyELement
XmlPropertyELement xmlPropertyELement = field.getAnnotation(XmlPropertyELement.class);
//2/ Determine whether to resolve null values , If it is not resolved and the value is null, it returns
if (!xmlPropertyELement.analysisEmpty() && isEmpty(val)) {
return;
}
//3. Attribute tag name splicing
// If the alias is empty, the field name has been spliced
String name = xmlPropertyELement.name();
if (name == null || "".equals(name)) {
name = field.getName();
}
xml.append("<").append(name);
//4. Determine whether the attribute has a namespace set , Splice if present
if (!"".equals(xmlPropertyELement.namespace()) && xmlPropertyELement.name() != null) {
xml.append(" ").append(xmlPropertyELement.namespace());
}
xml.append(">");
/** * * <![CDATA[ * Here you can display the characters you entered as they are , No resolution xml * ]]> */
//5. Splice attribute tag content
if (field.isAnnotationPresent(XmlCDATA.class)) {
// If the field is @XmlCDATA Just use... For annotation <![CDATA[ Label content ]]> Wrap the label body
xml.append("<![CDATA[").append(val).append("]]>");
} else {
// If the field is not @XmlCDATA Annotation decoration directly splices the label body
xml.append(val);
}
//6. Splice attribute tag end tag
xml.append("</").append(xmlPropertyELement.name()).append(">");
}
/** * Judge whether the object value is empty * * @param val * @return */
private static boolean isEmpty(Object val) {
return val == null || "".equals(val);
}
/** * Reflection gets the field value * * @param field * @param obj * @return */
private static Object getFieldVal(Field field, Object obj) {
try {
// Allow calling private methods
field.setAccessible(true);
// Reflection gets the field value
return field.get(obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
4. Test use
1. Use steps
1. First of all, we need to use annotations to modify the content of xml Field bean
test01—FileDto
@Data
@XmlRootElement(name = "file",
namespace = {
"xmlns=\"urn:gsma:params:xml:ns:rcs:rcs:fthttp\"",
"xmlns:x=\"urn:gsma:params:xml:ns:rcs:rcs:up:fthttpext\""})
public class FileDto {
@XmlBeanElement(name = "file-info" ,namespace = "type=\"thumbnail\"")
private FileInfoDto thumbFileInfoDto;
@XmlBeanElement(name = "file-info", namespace = "file-disposition=\"file\"")
private FileInfoDto fileInfoDto;
}
@Data
public class FileInfoDto {
@XmlPropertyELement(name = "file-size")
private String fileSize;
@XmlPropertyELement(name = "file-name")
private String fileName;
@XmlPropertyELement(name = "content-type")
private String contentType;
@XmlPropertyELement(name = "x:branded-url")
private String brandedUrl;
}
test02—OutboundMessageRequestDto
/** * <?xml version="1.0" encoding="UTF-8"?> * <msg:outboundMessageRequest xmlns:msg="urn:oma:xml:rest:netapi:messaging:1"> * <messageId>5eae954c-42ca-4181-9ab4-9c0ef2e2ac66</messageId> * <clientCorrelator>567895</clientCorrelator> * </msg:outboundMessageRequest> */
@Data
@XmlRootElement(name = "msg:outboundMessageRequest",namespace = "xmlns:msg=\"urn:oma:xml:rest:netapi:messaging:1\"")
public class OutboundMessageRequestDto {
@XmlPropertyELement(name = "messageId")
private String messageId;
@XmlPropertyELement(name = "clientCorrelator")
private String clientCorrelator;
@XmlCDATA
@XmlPropertyELement(name = "bodyText",analysisEmpty = true)
private String bodyText;
}
test03-RequestDto
@Data
@XmlRootElement(name = "Request",namespace = "xmlns:msg=\"urn:oma:xml:rest:netapi:messaging:1\"")
public class RequestDto {
@XmlPropertyELement(name = "address")
private String address;
@XmlGatherElement(name = "destinationAddress",childName = "phone")
private List<String> destinationAddress;
@XmlPropertyELement(name = "senderAddress")
private String senderAddress;
@XmlBeanElement(name = "Message")
private MessageDto messageDto;
@XmlPropertyELement(name = "clientCorrelator")
private String clientCorrelator;
}
@Data
public class MessageDto {
@XmlPropertyELement(name = "contentType")
private String contentType;
@XmlPropertyELement(name = "conversationID")
private String conversationID;
@XmlPropertyELement(name = "contributionID")
private String contributionID;
@XmlGatherElement(name = "reportRequest",showName = true,analysisEmpty = true)
private List<String> reportRequest;
@XmlBeanElement(name = "serviceCapability")
private ServiceCapabilityDto serviceCapabilityDto;
@XmlPropertyELement(name = "messageId")
private String messageId;
@XmlPropertyELement(name = "bodyText")
private String bodyText;
}
@Data
public class ServiceCapabilityDto {
@XmlPropertyELement(name = "capabilityId")
private String capabilityId;
@XmlPropertyELement(name = "version")
private String version;
}
2. Test cases
public class MyTest {
/** One XML The document is divided into the following parts 1. The document statement 2. Elements 3. attribute 4. notes 5.CDATA District 、 Special characters * <?xml version="1.0" encoding="UTF-8"?> * <file * xmlns="urn:gsma:params:xml:ns:rcs:rcs:fthttp" * xmlns:x="urn:gsma:params:xml:ns:rcs:rcs:up:fthttpext"> * <file-info type="thumbnail"> * <file-size>2048</file-size> * <content-type>png</content-type> * </file-info> * <file-info type="file" file-disposition="file"> * <file-size>1024</file-size> * <file-name>1.jpg</file-name> * <content-type>jpeg</content-type> * <x:branded-url>[alternative branded HTTP URL of the file]</x:branded-url> * </file-info> * </file> */
@Test
public void test01(){
FileDto fileDto = new FileDto();
FileInfoDto thumbFileInfo = new FileInfoDto();
thumbFileInfo.setFileSize("2048");
thumbFileInfo.setContentType("png");
FileInfoDto fileInfoDto = new FileInfoDto();
fileInfoDto.setFileSize("1024");
fileInfoDto.setFileName("1.jpg");
fileInfoDto.setContentType("jpeg");
fileDto.setThumbFileInfoDto(thumbFileInfo);
fileDto.setFileInfoDto(fileInfoDto);
String xml = XmlUtil.generateXml(fileDto);
System.out.println(xml);
}
/** * <?xml version=v1.0 encoding=utf-8?> * <msg:outboundMessageRequest * xmlns:msg="urn:oma:xml:rest:netapi:messaging:1"> * <messageId>c8c43a68-c7dd-44c6-b047-2e7e43d5d1b9</messageId> * <clientCorrelator>123456</clientCorrelator> * <bodyText> * <![CDATA[null]]> * </bodyText> * </msg:outboundMessageRequest> */
@Test
public void test02(){
OutboundMessageRequestDto outboundMessageRequestDto = new OutboundMessageRequestDto();
outboundMessageRequestDto.setMessageId(UUID.randomUUID().toString());
outboundMessageRequestDto.setClientCorrelator("123456");
outboundMessageRequestDto.setBodyText(null);
String xml = XmlUtil.generateXml(outboundMessageRequestDto);
System.out.println(xml);
}
/** * <?xml version="1.0" encoding="UTF-8"?> * <Request * xmlns:msg="urn:oma:xml:rest:netapi:messaging:1"> * <address>tel:+8619585550103</address> * <destinationAddress> * <phone>tel:+8619585550103</phone> * <phone>tel:+8619585550104</phone> * </destinationAddress> * <senderAddress>sip:[email protected]</senderAddress> * <Message> * <contentType> text/plain * </contentType> * <conversationID>XSFDEREW#%$^$%^^&^% * </conversationID> * <contributionID>SFF$#%$%%^%&^%THT * </contributionID> * <reportRequest>Delivered</reportRequest> * <reportRequest>Failed</reportRequest> * <serviceCapability> * <capabilityId>ChatbotSA</capabilityId> * <version>+g.gsma.rcs.botversion="#=1"</version> * </serviceCapability> * <messageId>5eae954c-42ca-4181-9ab4-9c0ef2e2ac66</messageId> * <bodyText>Hello * </bodyText> * </Message> * <clientCorrelator>567895</clientCorrelator> * </Request> */
@Test
public void test03(){
RequestDto requestDto = new RequestDto();
requestDto.setAddress("tel:+8619585550103");
List<String> destinationAddress = new ArrayList<String>(2);
destinationAddress.add("tel:+8619585550103");
destinationAddress.add("tel:+8619585550104");
requestDto.setDestinationAddress(destinationAddress);
requestDto.setSenderAddress("sip:[email protected]");
MessageDto messageDto = new MessageDto();
messageDto.setContentType("text/plain");
messageDto.setConversationID(UUID.randomUUID().toString());
messageDto.setContributionID(UUID.randomUUID().toString());
List<String> reportRequest = null;
// reportRequest.add("Delivered");
// reportRequest.add("Failed");
messageDto.setReportRequest(reportRequest);
ServiceCapabilityDto serviceCapabilityDto = new ServiceCapabilityDto();
serviceCapabilityDto.setCapabilityId("ChatbotSA");
serviceCapabilityDto.setVersion("+g.gsma.rcs.botversion="#=1"");
messageDto.setServiceCapabilityDto(serviceCapabilityDto);
messageDto.setMessageId(UUID.randomUUID().toString());
messageDto.setBodyText("Hello");
requestDto.setMessageDto(messageDto);
requestDto.setClientCorrelator("567895");
String xml = XmlUtil.generateXml(requestDto);
System.out.println(xml);
}
}
3. test result
test01
<?xml version=v1.0 encoding=utf-8?>
<file xmlns="urn:gsma:params:xml:ns:rcs:rcs:fthttp" xmlns:x="urn:gsma:params:xml:ns:rcs:rcs:up:fthttpext">
<file-info type="thumbnail">
<file-size>2048</file-size>
<content-type>png</content-type>
</file-info>
<file-info file-disposition="file">
<file-size>1024</file-size>
<file-name>1.jpg</file-name>
<content-type>jpeg</content-type>
</file-info>
</file>
test02
<?xml version=v1.0 encoding=utf-8?>
<msg:outboundMessageRequest xmlns:msg="urn:oma:xml:rest:netapi:messaging:1">
<messageId>50e1ba7a-dbb5-4f2f-8811-d81859d029ff</messageId>
<clientCorrelator>123456</clientCorrelator>
<bodyText>
<![CDATA[null]]>
</bodyText>
</msg:outboundMessageRequest>
test03
<?xml version=v1.0 encoding=utf-8?>
<Request xmlns:msg="urn:oma:xml:rest:netapi:messaging:1">
<address>tel:+8619585550103</address>
<destinationAddress>
<phone>tel:+8619585550103</phone>
<phone>tel:+8619585550104</phone>
</destinationAddress>
<senderAddress>sip:[email protected]</senderAddress>
<Message>
<contentType>text/plain</contentType>
<conversationID>3869f2e2-aedf-424f-8928-3082dc284104</conversationID>
<contributionID>f9d6b351-a245-4e79-b850-0700114361dd</contributionID>
<reportRequest></reportRequest>
<serviceCapability>
<capabilityId>ChatbotSA</capabilityId>
<version>+g.gsma.rcs.botversion="#=1"</version>
</serviceCapability>
<messageId>35a43af0-df1d-4a0d-94a0-60492e520bc4</messageId>
<bodyText>Hello</bodyText>
</Message>
<clientCorrelator>567895</clientCorrelator>
</Request>
边栏推荐
猜你喜欢

云计算搭建全部内容总结,保证可以搭建一个完整的云计算服务器,包括节点安装、实例的分配和网络的配置等内容

One of the Taobao short video pit avoidance Guide Series -- thoroughly understand Taobao short video

Penguin E-sports stops, and tiger teeth are hard to walk

mmcv之Registry类解读

线性移动棋

训练时添加进度条的库--tqdm

MYSQL开窗函数详解

【技术分析】探讨大世界游戏的制作流程及技术——前期流程篇

Abbexa 无细胞 DNA 试剂盒说明书

The short ticket hypothesis: finding sparse, trainable neural networks
随机推荐
MMdetection之build_optimizer模块解读
Chunk extend: hit training lab13
AOE网关键路径
图像搜索是什么
canvas发散的粒子h5动画js特效
Record of cmake and GCC installation
如何定位游戏发热问题
js手机端复制文本到剪切板代码
CUDA编程(一):实现两个数组相加
mmcv之Config类介绍
第七部分:第二课 顾问通用技能-如何安装和卸载SAP ERP系统客户端
项目中常用的19条MySQL优化
Abbexa 8-OHdG CLIA 试剂盒解决方案
玩转Pytorch的Function类
关于cmake和gcc的安装的记录
Leetcode 875. 爱吃香蕉的珂珂
THE LOTTERY TICKET HYPOTHESIS: FINDING SPARSE, TRAINABLE NEURAL NETWORKS论文笔记
.NET 开源的免费午餐结束了?
IP summary (tcp/ip volumes 1 and 2)
Memory pool principle I (based on the whole block)