使用 XSLT 保留特殊字符作为输出
Retaining special characters as output using XSLT
我面临的问题是关于即使在 XSLT 转换后仍保留特殊字符的问题。我的源 XHTML 文件包含几个特殊字符,如
、—
、’
;在 XSLT 转换时被忽略。
我尝试了各种答案,例如 this and this。
如果我手动将特殊字符的值更改为相应的 Unicode 表示形式,这些字符将保留在输出中。
例如将
更改为  
,它会在输出中产生 space。请参考以下一些样本文件:
源 XHTML :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:text="http://giraffe.wkle.com/text" xmlns:epub="http://www.idpf.org/2007/ops">
<body>
<div class="section" id="section_1">
<p id="para_1" class="para">Content of paragraph—1.</p>
<p id="para_2" class="para">Content of paragraph—2.</p>
</div>
</body>
</html>
XSL 模板:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='p']/text()">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
预期输出:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:text="http://giraffe.wkle.com/text">
<body>
<div class="section" id="section_1">
<p class="para" id="para_1">Content of paragraph—1.</p>
<p class="para" id="para_2">Content of paragraph—2.</p>
</div>
</body>
</html>
实际输出:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:text="http://giraffe.wkle.com/text">
<body>
<div class="section" id="section_1">
<p class="para" id="para_1">Contentofparagraph1.</p>
<p class="para" id="para_2">Contentofparagraph2.</p>
</div>
</body>
</html>
限制:
- 我无权修改源 XHTML 内容或其 DTD。
- XSLT 版本为 1.0。
如果有任何方法可以使用它们的 Unicode 值转换特殊字符并将它们保留在我的输出 XML 文档中,请告诉我。
更新:
我使用这段 Java 代码来调用转换:
public class XSLTUtil {
public static String processXHTML(String sourceFileName, String outputXhtml, String xslFilePath) throws ParserConfigurationException, SAXException, IOException, TransformerException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docbuilder = factory.newDocumentBuilder();
Document doc = docbuilder.parse(new FileInputStream(sourceFileName));
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fos = new FileOutputStream(outputXhtml);
fis = new FileInputStream(xslFilePath);
TransformerFactory transformfactory = TransformerFactory.newInstance();
Templates xsl = transformfactory.newTemplates(new StreamSource(fis));
Transformer transformer = xsl.newTransformer();
transformer.transform(new DOMSource(doc.getDocumentElement()),new StreamResult(fos));
return outputXhtml;
} finally {
if(fos != null) {
fos.close();
}
if(fis != null) {
fis.close();
}
}
}
public static void main(String args[]){
String sourceFileName = "C:\source.xhtml";
String outputXhtml = "C:\output.xhtml";
String xslFilePath = "C:\xslTemplate.xsl";
String result = "-1";
try {
result = processXHTML(sourceFileName, outputXhtml, xslFilePath);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
System.out.println("Result : "+ result);
}
}
[问题早期版本的过时答案]
考虑使用 Andrew Welch 的 Lexev 实用程序。它基本上预处理 XML 将实体引用变成将通过 XML 解析保留的东西,然后对转换结果进行后处理以将实体引用放回原处。
我会去掉 DOM 代码。当您只想将其转换为其他内容时创建 DOM 是笨拙且低效的。如果您提供 StreamSource 或 SAXSource 并让它们决定自己的树表示,Saxon 和 Xalan 都 运行 更快。使用 Saxon,速度可以提高 5-10 倍,并且使用的内存会更少。
我不知道为什么 DOM 会丢失实体引用。您提供了有关您正在使用的 XSLT 处理器的相互矛盾的信息,因此调查起来并不容易,但因为 DOM 数据模型与 XSLT/XPath 数据模型不同(特别是在处理实体扩展方面) 摆脱 DOM 应该可以解决它。
使用 Apache Commons Lang3 StringEscapUtils 的解决方案。
需要图书馆:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
先阅读内容并将所有实体替换为真实文本。
public static String processXHTML(String sourceFileName, String outputXhtml,
String xslFilePath) throws ParserConfigurationException, SAXException, IOException,
TransformerException {
Charset charset = StandardCharsets.UTF_8;
Path path = Paths.get(sourceFileName);
String source = new String(Files.readAllBytes(path), charset);
source = source.replaceAll("\&(amp|lt|gt|quot);", "\u0001;");
source = StringEscapeUtils.unescapeHtml4(source);
source = source.replace('\u0001', '&');
byte[] bytes = source.getBytes(charset);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docbuilder = factory.newDocumentBuilder();
docbuilder.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
System.out.printf("resolveEntity PUBLIC %s SYSTEM %s%n", publicId, systemId);
return new InputSource(new StringReader(""));
}
});
//Document doc = docbuilder.parse(new FileInputStream(sourceFileName));
Document doc = docbuilder.parse(bais);
我跳过了 XML 个实体。
我对 EntityResolver 进行了短路连接,因为(通过网络)读取和处理实体需要很长时间。您似乎已经安装了类似的东西,因为实体没有被替换。
在 resolveEntity return null 中查看递归加载的 DTD。
或者,您可以安装 XML 目录,HTML 实体 DTD 的本地缓存,其中有一些。
我面临的问题是关于即使在 XSLT 转换后仍保留特殊字符的问题。我的源 XHTML 文件包含几个特殊字符,如
、—
、’
;在 XSLT 转换时被忽略。
我尝试了各种答案,例如 this and this。
如果我手动将特殊字符的值更改为相应的 Unicode 表示形式,这些字符将保留在输出中。
例如将
更改为  
,它会在输出中产生 space。请参考以下一些样本文件:
源 XHTML :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:text="http://giraffe.wkle.com/text" xmlns:epub="http://www.idpf.org/2007/ops">
<body>
<div class="section" id="section_1">
<p id="para_1" class="para">Content of paragraph—1.</p>
<p id="para_2" class="para">Content of paragraph—2.</p>
</div>
</body>
</html>
XSL 模板:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='p']/text()">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
预期输出:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:text="http://giraffe.wkle.com/text">
<body>
<div class="section" id="section_1">
<p class="para" id="para_1">Content of paragraph—1.</p>
<p class="para" id="para_2">Content of paragraph—2.</p>
</div>
</body>
</html>
实际输出:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:text="http://giraffe.wkle.com/text">
<body>
<div class="section" id="section_1">
<p class="para" id="para_1">Contentofparagraph1.</p>
<p class="para" id="para_2">Contentofparagraph2.</p>
</div>
</body>
</html>
限制:
- 我无权修改源 XHTML 内容或其 DTD。
- XSLT 版本为 1.0。
如果有任何方法可以使用它们的 Unicode 值转换特殊字符并将它们保留在我的输出 XML 文档中,请告诉我。
更新:
我使用这段 Java 代码来调用转换:
public class XSLTUtil {
public static String processXHTML(String sourceFileName, String outputXhtml, String xslFilePath) throws ParserConfigurationException, SAXException, IOException, TransformerException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docbuilder = factory.newDocumentBuilder();
Document doc = docbuilder.parse(new FileInputStream(sourceFileName));
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fos = new FileOutputStream(outputXhtml);
fis = new FileInputStream(xslFilePath);
TransformerFactory transformfactory = TransformerFactory.newInstance();
Templates xsl = transformfactory.newTemplates(new StreamSource(fis));
Transformer transformer = xsl.newTransformer();
transformer.transform(new DOMSource(doc.getDocumentElement()),new StreamResult(fos));
return outputXhtml;
} finally {
if(fos != null) {
fos.close();
}
if(fis != null) {
fis.close();
}
}
}
public static void main(String args[]){
String sourceFileName = "C:\source.xhtml";
String outputXhtml = "C:\output.xhtml";
String xslFilePath = "C:\xslTemplate.xsl";
String result = "-1";
try {
result = processXHTML(sourceFileName, outputXhtml, xslFilePath);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
System.out.println("Result : "+ result);
}
}
[问题早期版本的过时答案]
考虑使用 Andrew Welch 的 Lexev 实用程序。它基本上预处理 XML 将实体引用变成将通过 XML 解析保留的东西,然后对转换结果进行后处理以将实体引用放回原处。
我会去掉 DOM 代码。当您只想将其转换为其他内容时创建 DOM 是笨拙且低效的。如果您提供 StreamSource 或 SAXSource 并让它们决定自己的树表示,Saxon 和 Xalan 都 运行 更快。使用 Saxon,速度可以提高 5-10 倍,并且使用的内存会更少。
我不知道为什么 DOM 会丢失实体引用。您提供了有关您正在使用的 XSLT 处理器的相互矛盾的信息,因此调查起来并不容易,但因为 DOM 数据模型与 XSLT/XPath 数据模型不同(特别是在处理实体扩展方面) 摆脱 DOM 应该可以解决它。
使用 Apache Commons Lang3 StringEscapUtils 的解决方案。 需要图书馆:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
先阅读内容并将所有实体替换为真实文本。
public static String processXHTML(String sourceFileName, String outputXhtml,
String xslFilePath) throws ParserConfigurationException, SAXException, IOException,
TransformerException {
Charset charset = StandardCharsets.UTF_8;
Path path = Paths.get(sourceFileName);
String source = new String(Files.readAllBytes(path), charset);
source = source.replaceAll("\&(amp|lt|gt|quot);", "\u0001;");
source = StringEscapeUtils.unescapeHtml4(source);
source = source.replace('\u0001', '&');
byte[] bytes = source.getBytes(charset);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docbuilder = factory.newDocumentBuilder();
docbuilder.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
System.out.printf("resolveEntity PUBLIC %s SYSTEM %s%n", publicId, systemId);
return new InputSource(new StringReader(""));
}
});
//Document doc = docbuilder.parse(new FileInputStream(sourceFileName));
Document doc = docbuilder.parse(bais);
我跳过了 XML 个实体。
我对 EntityResolver 进行了短路连接,因为(通过网络)读取和处理实体需要很长时间。您似乎已经安装了类似的东西,因为实体没有被替换。
在 resolveEntity return null 中查看递归加载的 DTD。
或者,您可以安装 XML 目录,HTML 实体 DTD 的本地缓存,其中有一些。