【发布时间】:2022-01-06 03:56:28
【问题描述】:
我需要将一些 HTML 字符串转换为使用一组特定的 TEI(文本编码倡议)标签编写的 XML 文件。然后应将该文件提供给基于网络的学术出版系统 lodel,以便在线发表。
更多上下文:
- 我使用的是 PHP 7.2。
- HTML 字符串可能格式不正确且复杂(包括表格、图像、块引用、脚注等)。
- 我需要输出的 XML-TEI 是简单节点的混合(使用 SimpleXMLElement 创建它们很简单),以及必须从 HTML 生成的其他节点。
- 从 HTML 到 XML-TEI 的转换意味着一些调整,例如替换
<strong>foo</strong>
与
<hi rend="bold">foo</hi>
或者
<h1>Foo</h1>
some other nodes...
与
<div type="div1">
<head subtype="level1">Foo</head>
some other nodes...
</div>
我不能做什么:
- 包括 libtidy 或其 php 类(这至少有助于清理 HTML)
- 改变技术状况,尽管我知道 XML-TEI 应该用于生成 HTML,而不是相反。
我尝试了什么:
- 将 HTML 字符串加载到 DOMDocument 中,遍历节点并创建一些单独的 XML(使用 XMLSimpleElement、DOM 甚至 XMLWriter)
- 将 HTML 字符串作为 XML (!) 加载到 DOMDocument 中,加载一些 XSLT,然后输出 XML
我设法使用上述方法生成了一些 XML,它适用于标准字段,但每次涉及 HTML 段时,我都会丢失树结构或内容。 我觉得 XSLT 是最好的选择,但我不知道如何使用它。
使用代码示例进行编辑:
SimpleXMLElement 示例:
导出类:
class XMLToLodelService {
$raw_html = '<html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></head><body><h1>Main <em>Title</em></h1><h4>test</h4><p> </p><p></p><p> </p><p>Paragraph</p><p id="foo">Another paragraph</p><h1>And a <strong>second</strong> title</h1><h2>Some subtitle</h2><p>Foobar</p></body></html>';
$string = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.tei-c.org/ns/1.0 http://lodel.org/ns/tei/tei.openedition.1.6.2/document.xsd"></TEI>
XML;
$xml = new SimpleXMLElement($string);
//...
$text = $xml[0]->addChild('text', '');
$this->parseBody($text, $raw_html);
public function parseBody(&$core, $text){
$dom = new DOMDocument;
$dom->formatOutput = true;
$dom->encoding = 'UTF-8';
$dom->loadHTML(mb_convert_encoding($text, 'HTML-ENTITIES', 'UTF-8'));
$body = $dom->getElementsByTagName('body')[0];
$core->addChild('body', '');
$core = $core->body;
// let's loop through nodes with DOM functions
// and add xml step by step in $core
$body->normalize();
$this->parseNodes($core, $body->childNodes);
}
public function parseNodes(&$core, $elements){
foreach($elements as $node){
if($this->isHeading($node)){
$nextNode = $this->translateHeading($core, $node);
}elseif($node->nodeName != '#text'){
$nextNode = $core->addChild($node->nodeName, $node->textContent);
}else{
continue;
}
if($node->hasChildNodes()){
$this->parseNodes($nextNode, $node->childNodes);
}
}
}
public function isHeading($node){
return in_array($node->nodeName, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
}
public function translateHeading(&$core, $node){
$level = str_split($node->nodeName)[1];
$head = new ExSimpleXMLElement('<head subtype="level' . $level . '"></head>');
$div = $core->addChild('div', $head);
$div->addAttribute('subtype', 'div' . $level);
return $div;
}
}
结果:
<TEI xsi:schemaLocation="http://www.tei-c.org/ns/1.0 http://lodel.org/ns/tei/tei.openedition.1.6.2/document.xsd">
<teiHeader>
// well-generated code...
</teiHeader>
<text>
<body>
<div subtype="div1">
<em>Title</em>
</div>
<div subtype="div4"/>
<p> </p>
<p/>
<p> </p>
<p>Paragraph</p>
<p>Another paragraph</p>
<div subtype="div1">
<strong>second</strong>
</div>
<div subtype="div2"/>
<p>Foobar</p>
</body>
</text>
</TEI>
XSLT 示例: 这里我只是尝试为每个h1项添加一个id,只是为了练习XSLT。
导出类:
class XMLToLodelService {
$raw_html = '<html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></head><body><h1>Main <em>Title</em></h1><h4>test</h4><p> </p><p></p><p> </p><p>Paragraph</p><p id="foo">Another paragraph</p><h1>And a <strong>second</strong> title</h1><h2>Some subtitle</h2><p>Foobar</p></body></html>';
$html = new DOMDocument();
$html->loadXML($raw_html);
$html->normalizeDocument();
$xsl = new DOMDocument();
$xsl->load('xslt.xsl');
$xsltProcessor = new XSLTProcessor;
$xsltProcessor->importStylesheet($xsl);
echo $xsltProcessor->transformToXml($html);
}
xslt 文件:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="//h1">
<root>
<xsl:apply-templates select="//h1"/>
</root>
</xsl:template>
<xsl:template match="//h1">
<xsl:element id="someid{position()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
结果:
<TEI xsi:schemaLocation="http://www.tei-c.org/ns/1.0 http://lodel.org/ns/tei/tei.openedition.1.6.2/document.xsd">
<teiHeader>
// well-generated code...
</teiHeader>
<text>
<body/> //shouldn't be empty
</text>
</TEI>
我可能忽略/误解了一些东西。任何帮助将不胜感激。
在 ThW 的回答后编辑:
对于我的大多数用例来说,接受的答案就像一个魅力。我遇到了非常具体的标记问题。我想在这里特别分享一个,以防它对某人有所帮助。
为了变换:
<h1>Title</h1>
//some siblings tags...
进入:
<div type="div1">
<head subtype="level1">Title</head>
//some siblings tags...
</div>
我不得不在我的 xslt 中使用一种特殊的方法。当涉及嵌套标题标签或不同级别的标签(即 h1 然后 h2 等等)时,接受的答案不起作用。我在这个特定案例中使用了这个 xslt 标记:
<xsl:template match="/">
<xsl:apply-templates select="//h1"/>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'h')]">
<xsl:variable name="lvl" select="number(substring-after(local-name(), 'h'))"/>
<div type="div{$lvl}">
<head subtype="level{$lvl}">
<xsl:apply-templates select="text()|./*" mode="richtext"/>
</head>
<xsl:apply-templates select="//following-sibling::*[not(starts-with(local-name(), 'h'))
and preceding-sibling::*[starts-with(local-name(), 'h')][1] = current()]"/>
<xsl:apply-templates select="//following-sibling::*[local-name() = concat('h', $lvl + 1)
and preceding-sibling::*[local-name() = concat('h', $lvl)][1] = current()]"/>
<xsl:apply-templates select="//following-sibling::*[local-name() = concat('h', $lvl + 2)
and preceding-sibling::*[local-name() = concat('h', $lvl)][1] = current()]"/>
<xsl:apply-templates select="//following-sibling::*[local-name() = concat('h', $lvl + 3)
and preceding-sibling::*[local-name() = concat('h', $lvl)][1] = current()]"/>
<xsl:apply-templates select="//following-sibling::*[local-name() = concat('h', $lvl + 4)
and preceding-sibling::*[local-name() = concat('h', $lvl)][1] = current()]"/>
<xsl:apply-templates select="//following-sibling::*[local-name() = concat('h', $lvl + 5)
and preceding-sibling::*[local-name() = concat('h', $lvl)][1] = current()]"/>
</div>
</xsl:template>
这是对这个主题的一个调整:XHTML to Structured XML with XSLT 1.0
感谢您的宝贵时间!
【问题讨论】:
-
你能提供你的代码吗? “将 HTML 字符串加载到 DOMDocument 中,遍历节点并创建一些单独的 XML(使用 XMLSimpleElement、DOM 甚至 XMLWriter)” 和 “将 HTML 字符串加载为 XML(!)进入 DOMDocument,加载一些 XSLT,然后输出 XML"
-
如果我相信the documentation,我认为您可以使用this editor 导入HTML 和convert it to TEI。它的成本为 198 美元,但无需自己完成所有转换 HTML 的工作,可以轻松节省这笔钱。
-
@KIKOSoftware 感谢您的回复。我知道这个软件,但我的经理说我也不能使用它(我正在构建的工具是为一些买不起的人准备的,而且无论如何也不会使用第三方工具:()。
-
@DefinitelynotRafal 感谢您的回复。根据要求,我提供了一些尝试:)