Apache FOP高级技巧大揭秘:提升转换效果与性能的3大策略
发布时间: 2024-09-29 06:13:33 阅读量: 154 订阅数: 51
fopdf:从头开始使用Apache FOP生成PDF的教程代码
# 1. Apache FOP概览
Apache FOP(Formatting Objects Processor)是一个广泛使用的、开源的XSL-FO(Extensible Stylesheet Language Formatting Objects)格式化处理器,主要用于将XML文档转换成PDF文件。它为文档格式化提供了一种强大的方式,能够处理复杂的排版要求,并且支持多种国际化语言。
本章将介绍Apache FOP的基本概念和特性,包括它的基本架构、使用场景以及如何开始使用Apache FOP进行转换任务。我们还将概述Apache FOP的安装过程和一些基本的配置选项,为读者提供一个对Apache FOP全面认识的基础。
在接下来的章节中,我们将深入探讨Apache FOP的高级功能和优化技巧,帮助读者掌握如何处理XML数据、配置优化、管理图片资源,以及如何在高级转换策略中应对复杂布局和动态内容生成。通过本章内容的学习,读者将能够建立起对Apache FOP的初步理解和运用。
# 2. 基础转换技巧提升
## 2.1 XML数据处理
### 2.1.1 XML数据预处理
在进行FOP转换之前,确保XML数据的准确性和完整性是非常重要的。预处理步骤可能包括数据清洗、格式标准化和数据验证,以保证转换过程中数据的准确性和效率。
数据清洗包括去除多余的空格、规范化的日期和数字格式、确保所有必要的数据字段都已填充。对于大型数据集来说,这一步骤可能需要借助脚本和程序来自动化执行。
在数据标准化方面,定义统一的XML架构(Schema)以规范数据结构和字段类型是推荐的做法。这将有助于确保在转换过程中XML数据按照预期的格式被正确处理。
最后,数据验证是一个关键步骤。通过使用XML验证工具(例如xmllint或集成开发环境内置的验证器),可以确保XML文档符合其定义的架构。错误的数据可能会导致转换失败或者生成不正确的输出文件。
### 2.1.2 XML数据绑定技术
XML数据绑定是将XML文档中的数据与特定编程语言的数据结构相映射的过程。在FOP转换中,可以使用数据绑定技术来简化XML数据的处理过程,提高转换效率。
常用的XML数据绑定技术包括JAXB(Java Architecture for XML Binding),它允许Java开发者将Java对象模型绑定到XML表示,反之亦然。这种绑定是通过注解来实现的,可以方便地生成Java类的getter和setter方法,并允许开发者以面向对象的方式来操作XML数据。
例如,考虑以下XML结构:
```xml
<book>
<title>Effective XML</title>
<author>Eric van der Vlist</author>
<published>2003</published>
</book>
```
使用JAXB注解后,可以生成一个Book类,代码如下:
```java
@XmlRootElement
public class Book {
private String title;
private String author;
private int published;
// getter and setter methods
}
```
这样的绑定技术使得在进行FOP转换时,我们可以直接操作Book对象,而无需手动解析和构建XML节点。
## 2.2 FOP配置优化
### 2.2.1 配置文件的作用与解析
Apache FOP的配置文件(fop.conf)是定义FOP行为和优化转换过程的关键。配置文件中可以设置字体映射、图像处理参数以及其他转换相关的配置选项。
例如,字体映射对于确保在转换过程中使用的字体与原始文档中指定的字体一致是至关重要的。此外,对于图像的处理,配置文件可以指定图像的最大和最小尺寸限制,以及图像质量的调整选项。
```xml
<configuration>
<renderers>
<renderer mime="application/pdf">
<fonts>
<font metricsfile="path/to/font-metrics.xml" kerning="yes">
<font-triplet name="Arial" style="normal" weight="normal"/>
</font>
</fonts>
</renderer>
</renderers>
</configuration>
```
在上面的配置示例中,我们定义了如何处理PDF输出时的Arial字体,包括字体度量文件的位置和是否启用字距调整。
### 2.2.2 高效的格式化对象调用
在FOP转换过程中,使用恰当的格式化对象(Formatting Objects, FO)是至关重要的。格式化对象是XSL-FO标准中的核心,它们定义了页面布局、段落、文本、图像等元素的渲染方式。
优化FO调用涉及到使用合适的属性和值来控制排版,例如控制间距、边距和缩进。了解如何高效地使用FO元素可以减少转换时的性能开销,并提高输出文档的质量。
下面是一个简单的FO布局示例:
```xml
<fo:layout-master-set>
<fo:simple-page-master master-name="first-page">
<fo:region-body margin="1in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="first-page">
<fo:flow flow-name="xsl-region-body">
<fo:block>
<!-- 文本内容 -->
</fo:block>
</fo:flow>
</fo:page-sequence>
```
在这个例子中,我们定义了一个简单的页面主控集,并指定了页面的主体区域的边距。然后在页面序列中使用了这个页面主控,并在页面流中插入了文本块。
## 2.3 图片和资源管理
### 2.3.1 图片嵌入与引用策略
在处理图像时,决定是嵌入图像还是引用外部图像取决于多种因素,如图像的大小、转换的类型和输出格式的要求。嵌入图像可以确保文档在不同环境下的可移植性和完整性,但可能会增加文件大小。引用外部资源则可以减少文件大小,但需要确保文档在渲染时外部资源的可访问性。
```xml
<fo:external-graphic src="url('image.png')" content-width="100%"/>
```
在上面的示例中,我们引用了一个外部图像,`content-width`属性设置为100%,使图像根据可用空间自动调整大小。
### 2.3.2 资源文件的管理和优化
管理资源文件是提高转换效率和输出质量的关键。有效的资源管理包括压缩图像、优化图像质量和调整资源文件大小等策略。
例如,使用图像编辑软件预先压缩图像文件,减少转换时的内存使用。对于PDF输出,可以调整图像的DPI设置,以达到在保持质量的同时减少文件大小的目的。
优化后的资源文件管理流程可以表示为以下步骤:
1. 图像压缩和优化
2. 图像大小和DPI调整
3. 将图像存储在内容管理系统或网络文件系统中
4. 在XSL-FO文档中引用或嵌入图像路径
资源文件的优化是一个持续的过程,需要定期检查和调整以确保最佳性能。
```mermaid
graph TD;
A[图像和资源管理优化]
A --> B[图像压缩和优化]
A --> C[调整图像大小和DPI]
A --> D[存储资源文件]
A --> E[引用或嵌入图像路径]
```
本章节通过阐述XML数据处理、FOP配置优化和图片与资源管理的策略,提供了针对基础转换过程进行提升的具体方法。这些策略不仅有助于提升转换过程的效率,还确保了输出文档的准确性和质量。通过实践这些方法,用户可以显著改进其文档转换工作的结果。
# 3. 高级转换策略
## 3.1 复杂布局的处理方法
### 3.1.1 布局模型与控制
在Apache FOP中处理复杂布局通常涉及到对布局模型的理解和应用。布局模型定义了文档的视觉结构,决定了内容在页面上的排列方式。Apache FOP支持多种布局模型,其中流式布局(Flow Layout)和块级布局(Block Layout)是最常见的两种。
流式布局适用于连续文本的排版,类似于Web页面的文本流动,文本会根据容器的形状自动调整。在处理长文档,如书籍、报告时,流式布局是一种非常实用的模型。
块级布局则更适合需要精确控制元素位置的场景。例如,当你需要在页面上精确地放置标题、图片和表格时,块级布局提供了更细粒度的控制。这种模型允许你指定块元素的宽度、高度以及它们在页面上的位置。
为了控制这些布局模型,Apache FOP提供了丰富的布局控制指令。比如,你可以使用`keep-together`属性来确保表格或段落不会被分割到不同的页面。`break-before`和`break-after`属性则可以控制页面或列的分页行为。
**代码示例:**
```xml
<fo:block break-before="page">这是一个新页面的开始</fo:block>
<fo:block-container overflow="scroll">
<!-- 这里可以放置需要滚动的复杂布局内容 -->
</fo:block-container>
```
在上述代码中,`<fo:block>`元素通过`break-before="page"`属性强制在新的页面开始,而`<fo:block-container>`可以定义一个块容器,在此容器内的内容可以使用滚动条。
### 3.1.2 高级页面布局技巧
在处理更高级的页面布局时,合理地运用Apache FOP的布局指令可以达到意想不到的视觉效果。例如,使用`column-count`属性可以实现多列布局,这对于创建杂志风格的布局非常有用。此外,`float`属性允许内容环绕,这在处理图片和注释时特别有用。
**代码示例:**
```xml
<fo:flow flow-name="xsl-region-body">
<fo:block-container column-count="3">
<fo:block>第一列内容</fo:block>
<fo:block>第二列内容</fo:block>
<fo:block>第三列内容</fo:block>
</fo:block-container>
</fo:flow>
```
在这个示例中,`<fo:block-container>`元素内使用了`column-count="3"`来创建三列布局。每个`<fo:block>`元素代表一列的内容。
当涉及到复杂的布局控制时,建议使用FOP的XSL-FO样式表,并根据实际情况编写相应的XSLT脚本来生成复杂的结构。在XSLT脚本中,可以灵活地使用各种模板和匹配规则来处理复杂的布局逻辑。
**小提示:** 在高级布局中,合理地使用CSS与FOP的布局特性可以极大地优化渲染效率,并确保布局的准确性和灵活性。
## 3.2 动态内容生成
### 3.2.1 数据驱动文档的实现
Apache FOP提供了强大的数据绑定技术,可以将XML数据源与XSL-FO模板结合,实现数据驱动的文档生成。这使得FOP能够动态地根据不同的数据生成个性化的文档,非常适合批量生成报表、发票和其他类型的数据密集型文档。
实现数据驱动文档的基础是将数据源绑定到XSLT转换过程。在XSLT脚本中,使用`<xsl:for-each>`和`<xsl:value-of>`等指令来遍历XML数据并将其填充到FO文档的相应位置。
**代码示例:**
```xml
<xsl:stylesheet version="1.0" xmlns:xsl="***">
<xsl:template match="/">
<fo:root xmlns:fo="***">
<fo:layout-master-set>
<!-- 定义页面布局 -->
</fo:layout-master-set>
<fo:page-sequence>
<fo:flow flow-name="xsl-region-body">
<xsl:for-each select="data-set/record">
<fo:block>
<fo:basic-link internal-destination="{generate-id()}">
<xsl:value-of select="name"/>
</fo:basic-link>
</fo:block>
</xsl:for-each>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
```
在这个XSLT模板中,`<xsl:for-each>`循环遍历XML数据集中的每个记录,并为每个记录生成一个FO块。
### 3.2.2 动态生成表格与列表
动态生成表格和列表是数据驱动文档生成中常见的需求。在Apache FOP中,可以使用`<fo:table>`和`<fo:table-body>`元素来构建动态表格,并通过XSLT脚本中的循环来填充表格数据。
列表的创建与表格类似,可以使用`<fo:list-block>`、`<fo:list-item>`和`<fo:list-item-label>`等元素来构建有序或无序列表,并通过数据绑定技术来动态填充。
**代码示例:**
```xml
<fo:table>
<fo:table-body>
<xsl:for-each select="data-set/records/record">
<fo:table-row>
<fo:table-cell>
<xsl:value-of select="column1"/>
</fo:table-cell>
<fo:table-cell>
<xsl:value-of select="column2"/>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
```
在上述代码中,表格通过`<fo:table-body>`元素中的`<xsl:for-each>`指令被填充数据。每个`<fo:table-row>`代表表格的一行,而`<fo:table-cell>`则代表每个单元格。
## 3.3 性能优化实践
### 3.3.1 批量处理与并发转换
为了提高文档转换的效率,特别是在处理大量文档时,Apache FOP支持批量处理和并发转换。批量处理可以一次性将多个文档转换为PDF或其他格式,而并发转换则可以利用多核处理器的能力来加速转换过程。
批量处理可以通过脚本循环调用FOP命令行工具来实现,或者使用支持批量处理的编程库。并发转换则需要使用支持多线程处理的编程语言,如Java,并合理分配线程资源。
**代码示例:**
```java
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MvcFactory;
import org.xml.sax.XMLReader;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BatchConversion {
public static void main(String[] args) {
// 初始化FOP工厂
FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
File[] files = new File("path/to/your/documents").listFiles();
for (File *** {
// 使用线程池异步执行转换任务
executorService.submit(() -> {
try {
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, result);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(new StreamSource(xsltStylesheet));
Source src = new StreamSource(file);
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(src, res);
} catch (Exception e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
```
在这个Java代码示例中,使用了线程池来并发执行文档转换任务。
### 3.3.2 缓存机制的集成与应用
在Apache FOP中集成缓存机制可以显著提升性能,尤其是在处理大量重复内容或者需要多次引用相同资源时。通过缓存已处理的样式、布局和资源,可以减少每次转换时的计算量和I/O操作。
**mermaid流程图:**
```mermaid
graph LR
A[开始转换] --> B{检查缓存}
B -->|缓存命中| C[加载缓存结果]
B -->|缓存未命中| D[处理内容]
D --> E[更新缓存]
E --> C
C --> F[输出转换结果]
```
在实际应用中,可以在XSLT脚本中预先加载和存储样式信息,或者使用外部缓存服务如Redis来存储渲染过程中生成的中间数据。需要注意的是,使用缓存时必须保证缓存的一致性,避免因为缓存问题导致文档内容不正确。
**小提示:** 缓存机制虽然可以提高转换性能,但也会增加内存的使用量。合理地配置缓存大小和过期策略对于优化整体性能至关重要。
# 4. 故障排除与性能监控
## 4.1 常见问题诊断
### 4.1.1 问题定位工具和方法
在处理Apache FOP转换中遇到的问题时,首先要了解有哪些工具和方法可以帮助我们定位问题。以下是几个关键步骤:
1. **查看日志文件**:FOP转换过程中会生成日志文件,记录了所有转换时发生的事件。通过分析这些日志文件,可以快速找到错误信息和警告信息。
2. **调试模式**:在FOP配置中启用调试模式,可以输出更多的调试信息,有助于深入理解转换过程中每个步骤的状态和问题所在。
3. **单元测试**:为FOP的转换编写单元测试,可以帮助开发者在早期阶段发现和修复问题。单元测试通常关注特定功能或场景的输出结果是否符合预期。
4. **代码审查**:通过代码审查,团队成员之间可以相互检查和交流各自编写的代码,这样不仅可以提前发现问题,还能提高团队整体的编码水平。
### 4.1.2 典型案例分析与解决
下面,让我们通过一个具体的案例分析来了解问题定位的整个过程:
**案例分析**:
假设在转换含有大量表格的复杂XML文档时,FOP抛出了`java.lang.OutOfMemoryError`。我们需要定位问题并解决它。
**问题定位**:
1. 查看日志文件,发现异常发生前有大量内存分配记录。
2. 开启FOP的调试模式,分析调试日志发现内存泄漏迹象。
3. 使用单元测试,重现问题并逐步缩小问题发生的原因范围。
**解决方案**:
1. 根据调试日志中记录的内存使用情况,检查代码中是否有大对象持续引用不释放。
2. 优化代码逻辑,例如,将大表格分成多个小块处理,避免一次性加载整个表格到内存。
3. 调整JVM参数,为FOP进程分配更多的内存,并采用垃圾收集器,如G1 GC,来提高内存管理效率。
## 4.2 性能监控与分析
### 4.2.1 监控工具的选择与部署
为了确保Apache FOP在生产环境中的性能稳定,采用合适的监控工具至关重要。以下是几个推荐的监控工具及其部署步骤:
1. **JConsole**:Java自带的JMX监控工具,可以实时监控JVM性能,包括内存、线程、类使用情况等。部署步骤简单,只需通过JDK自带的`jconsole`命令即可启动。
2. **VisualVM**:一个强大的Java应用程序性能监控工具,支持远程监控和分析,可以安装插件来扩展功能。安装后,连接到FOP服务器,即可开始监控。
3. **Apache FOP自定义监控**:除了通用的JVM监控工具外,还可以根据FOP的运行特点,自定义监控代码,例如监控转换任务的平均执行时间、错误率等指标,并通过定时任务定期上报到监控系统。
### 4.2.2 数据分析与性能瓶颈识别
在获得监控数据后,接下来要进行数据分析,找到性能瓶颈。以下是几个关键的分析步骤:
1. **数据收集**:定期从监控系统收集性能数据,确保数据的连续性和完整性。
2. **数据可视化**:使用图表工具,比如Grafana,将收集的数据以图形形式展示出来,帮助分析人员更直观地识别问题。
3. **趋势分析**:对比历史数据,分析性能变化趋势,识别出随时间变化的潜在问题。
4. **瓶颈识别**:通过监控数据中出现的异常波动,定位到可能的性能瓶颈,比如内存泄漏、CPU过载、I/O阻塞等。
## 4.3 安全性和稳定性保证
### 4.3.1 安全策略与最佳实践
保证Apache FOP的安全性需要遵循一些基本的安全策略与最佳实践:
1. **权限控制**:确保FOP服务器运行的用户只有必要的权限,避免以root或管理员权限运行,防止潜在的安全风险。
2. **输入验证**:对输入的XML文件进行严格验证,防止恶意代码注入。这可以通过编写XSLT样式表来实现,确保只处理已知的和受信任的XML结构。
3. **加密通信**:如果FOP服务器需要远程访问,确保使用加密协议,如SSL/TLS,以防止数据传输过程中的监听和篡改。
4. **及时更新**:定期更新FOP及其依赖库到最新版本,修补已知的安全漏洞。
### 4.3.2 稳定性测试与持续集成
为了确保FOP的稳定性,需要将稳定性测试纳入到持续集成(CI)流程中:
1. **自动化测试**:编写自动化测试脚本,定期检查FOP转换的稳定性和输出结果的一致性。
2. **负载测试**:通过模拟高负载环境,测试FOP在极端情况下的表现,确保转换任务不会因为高并发而失败。
3. **回归测试**:每次对FOP进行升级或修改后,执行回归测试,确保新的变更没有引入新的问题。
4. **持续集成系统**:将所有的测试步骤集成到CI系统中,如Jenkins或Travis CI,使得每次代码提交都会触发一系列测试,及时发现并修复问题。
# 5. Apache FOP的自动化与集成
## 5.1 自动化构建流程
实现Apache FOP转换任务的自动化是提高开发效率和文档生成效率的关键。在本小节中,我们将探讨如何通过集成到现有的构建系统(如Maven或Gradle)来自动化文档转换流程。
### Maven配置示例
在`pom.xml`中添加Apache FOP的依赖,并配置执行转换任务的插件:
```xml
<dependencies>
<!-- Apache FOP dependencies -->
<dependency>
<groupId>org.apache.fop</groupId>
<artifactId>fop</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Apache FOP Maven Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<java classname="org.apache.fop.cli.Main" failonerror="true">
<arg value="-c" />
<arg value="fop-config.xml" />
<arg value="-p" />
<arg value="${project.build.directory}/pdfs" />
<arg value="-s" />
<arg value="source.fo" />
</java>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
这段代码配置了一个Maven插件,它将在打包阶段调用Apache FOP将`.fo`文件转换为PDF格式。`fop-config.xml`是FOP的配置文件,用于指定转换的详细设置。
### Gradle配置示例
在`build.gradle`文件中添加Apache FOP依赖并配置任务:
```groovy
apply plugin: 'java'
apply plugin: 'application'
dependencies {
implementation 'org.apache.fop:fop:2.6'
}
tasks.register('fopConvert', JavaExec) {
main = 'org.apache.fop.cli.Main'
classpath = sourceSets.main.runtimeClasspath
args = [
'-c', 'fop-config.xml',
'-p', 'build/pdf',
'-s', 'src/main/resources/source.fo'
]
}
tasks.named('build') {
finalizedBy tasks.fopConvert
}
```
这段Groovy脚本定义了一个名为`fopConvert`的任务,用于执行FOP转换。在`build`任务完成后,会自动执行`fopConvert`任务。
## 5.2 集成到CI/CD流程
在持续集成和持续部署(CI/CD)流程中,文档的自动转换是不可或缺的一部分。本小节将探讨如何将Apache FOP集成到主流的CI/CD工具中,例如Jenkins。
### Jenkins集成示例
1. **安装必要的插件**:在Jenkins中安装插件,如Git、Maven Integration或Gradle插件。
2. **配置源代码管理**:配置项目的源代码仓库,以便Jenkins能够拉取最新的代码。
3. **配置构建触发器**:设定触发条件,例如基于代码提交的轮询SCM,或Webhook触发。
4. **添加构建步骤**:
- 对于Maven项目,选择“调用顶层Maven目标”并填入`fopConvert`作为目标。
- 对于Gradle项目,选择“执行Gradle脚本”,并指定执行`fopConvert`任务。
5. **构建后操作**:配置邮件通知、日志记录或其他后处理步骤。
### 示例Jenkinsfile
对于Maven项目,Jenkinsfile可能如下所示:
```groovy
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('FOP Conversion') {
steps {
sh 'mvn fopConvert'
}
}
}
post {
always {
archiveArtifacts artifacts: '**/*.pdf', fingerprint: true
}
}
}
```
该流水线脚本定义了三个阶段:检出代码、编译项目和执行FOP转换任务。转换后的PDF文件将被归档。
## 5.3 调度与监控
自动化文档生成的一个关键方面是确保文档能够定时生成,并且转换过程可以被监控和报告。本小节将介绍如何在自动化流程中设置定时任务和监控FOP转换状态。
### 使用cron表达式进行调度
对于基于Unix/Linux的环境,可以使用cron表达式在定时任务中添加Apache FOP的转换任务:
```bash
0 2 *** /usr/bin/java -jar /path/to/fop.jar -c /path/to/fop-config.xml -p /path/to/output -s /path/to/source.fo >> /path/to/fop.log 2>&1
```
这个命令设置了每晚凌晨2点执行一次转换,并将输出重定向到日志文件中。
### 监控与日志分析
监控FOP转换可以通过分析输出日志来完成。可以使用日志管理工具如ELK Stack(Elasticsearch, Logstash, Kibana)进行日志的收集、分析和可视化。
配置Logstash管道(pipeline)来解析FOP日志文件,并使用Kibana创建仪表板来监控转换过程中的错误和性能指标。
```conf
input {
file {
path => "/path/to/fop.log"
start_position => "beginning"
}
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+\[%{WORD:loglevel}\]\s+%{GREEDYDATA:logmessage}" }
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "fop-logs-%{+YYYY.MM.dd}"
}
}
```
通过这种方式,可以实时监控文档生成的状态,并在出现问题时及时响应。
自动化和集成是现代文档处理和发布流程中不可或缺的环节。通过将Apache FOP的文档转换任务与其他工具和服务集成,可以极大地提高工作效率和文档发布的质量。
0
0