如何用PHP把HTML变成XML-TEI?
How to transform HTML into XML-TEI with PHP?
我需要将一些 HTML 字符串转换成一个 XML 文件,该文件使用一组特定的 TEI(文本编码倡议)标签编写。然后应将该文件提供给 web-based 学术出版系统 lodel,以便在线出版。
更多上下文:
- 我正在使用 PHP 7.2.
- HTML 字符串可能格式错误且复杂(包含表格、图像、块引用、脚注...)。
- 我需要输出的 XML-TEI 是简单节点的混合(使用 SimpleXMLElement 创建它们很简单),以及其他必须从 HTML 生成的节点。
- 从 HTML 到 XML-TEI 的 t运行 形式意味着一些调整,例如替换
<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 class(这至少有助于清理 HTML)
- 更改技术情况,即使我知道 XML-TEI 应该用于生成 HTML 而不是相反。
我尝试了什么:
- 将 HTML 字符串加载到 DOM 文档中,遍历节点并创建一些单独的 XML(使用 XMLSimpleElement,DOM,甚至 XMLWriter)
- 将 HTML 字符串作为 XML (!) 加载到 DOM 文档中,加载一些 XSLT,然后输出 XML
我设法用上述方法生成了一些 XML,它适用于标准字段,但每次涉及到 HTML 段时,我都会丢失树结构或内容。
我感觉 XSLT 是最好的选择,但我不知道如何使用它。
使用代码示例进行编辑:
带有简单XML元素的示例:
导出class:
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:
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>
这是这个主题的一个调整:
感谢您的宝贵时间!
我认为您对 XSLT 的想法是正确的。具体将 HTML 作为 HTML 加载到 DOM 中。这里不用加载为XML。然后为基本结构使用特定的命名模板,为富文本片段使用辅助模式。
然而,将所有 HTML 元素映射到 TEI 元素需要一些工作。
$template = <<<'XSLT'
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.tei-c.org/ns/1.0">
<xsl:output mode="xml" indent="yes"/>
<!-- match the document element (the html element) -->
<xsl:template match="/*">
<!-- add container and header elements -->
<TEI
xsi:schemaLocation="http://www.tei-c.org/ns/1.0 http://lodel.org/ns/tei/tei.openedition.1.6.2/document.xsd">
<xsl:call-template name="tei-header"/>
<text>
<!-- apply richtext fragment templates using a separate mode -->
<xsl:apply-templates select="body" mode="richtext" />
</text>
</TEI>
</xsl:template>
<!-- named header template -->
<xsl:template name="tei-header">
<teiHeader>...</teiHeader>
</xsl:template>
<!-- match h1, add id attribute and remove any descendant except text content -->
<xsl:template match="h1" mode="richtext">
<head id="someid{position()}">
<xsl:value-of select="."/>
</head>
</xsl:template>
<!-- match p, add to output and apply templates to descendants -->
<xsl:template match="p" mode="richtext">
<p>
<!-- apply templates to descendants -->
<xsl:apply-templates mode="richtext"/>
</p>
</xsl:template>
</xsl:stylesheet>
XSLT;
$htmlDocument = new DOMDocument();
@$htmlDocument->loadHTML(getHTML());
$xslDocument = new DOMDocument();
$xslDocument->loadXML($template);
$processor = new XSLTProcessor();
$processor->importStylesheet($xslDocument);
echo $processor->transformToXML($htmlDocument);
function getHTML() {
return <<<'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;
}
我需要将一些 HTML 字符串转换成一个 XML 文件,该文件使用一组特定的 TEI(文本编码倡议)标签编写。然后应将该文件提供给 web-based 学术出版系统 lodel,以便在线出版。
更多上下文:
- 我正在使用 PHP 7.2.
- HTML 字符串可能格式错误且复杂(包含表格、图像、块引用、脚注...)。
- 我需要输出的 XML-TEI 是简单节点的混合(使用 SimpleXMLElement 创建它们很简单),以及其他必须从 HTML 生成的节点。
- 从 HTML 到 XML-TEI 的 t运行 形式意味着一些调整,例如替换
<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 class(这至少有助于清理 HTML)
- 更改技术情况,即使我知道 XML-TEI 应该用于生成 HTML 而不是相反。
我尝试了什么:
- 将 HTML 字符串加载到 DOM 文档中,遍历节点并创建一些单独的 XML(使用 XMLSimpleElement,DOM,甚至 XMLWriter)
- 将 HTML 字符串作为 XML (!) 加载到 DOM 文档中,加载一些 XSLT,然后输出 XML
我设法用上述方法生成了一些 XML,它适用于标准字段,但每次涉及到 HTML 段时,我都会丢失树结构或内容。 我感觉 XSLT 是最好的选择,但我不知道如何使用它。
使用代码示例进行编辑:
带有简单XML元素的示例:
导出class:
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:
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>
这是这个主题的一个调整:
感谢您的宝贵时间!
我认为您对 XSLT 的想法是正确的。具体将 HTML 作为 HTML 加载到 DOM 中。这里不用加载为XML。然后为基本结构使用特定的命名模板,为富文本片段使用辅助模式。
然而,将所有 HTML 元素映射到 TEI 元素需要一些工作。
$template = <<<'XSLT'
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.tei-c.org/ns/1.0">
<xsl:output mode="xml" indent="yes"/>
<!-- match the document element (the html element) -->
<xsl:template match="/*">
<!-- add container and header elements -->
<TEI
xsi:schemaLocation="http://www.tei-c.org/ns/1.0 http://lodel.org/ns/tei/tei.openedition.1.6.2/document.xsd">
<xsl:call-template name="tei-header"/>
<text>
<!-- apply richtext fragment templates using a separate mode -->
<xsl:apply-templates select="body" mode="richtext" />
</text>
</TEI>
</xsl:template>
<!-- named header template -->
<xsl:template name="tei-header">
<teiHeader>...</teiHeader>
</xsl:template>
<!-- match h1, add id attribute and remove any descendant except text content -->
<xsl:template match="h1" mode="richtext">
<head id="someid{position()}">
<xsl:value-of select="."/>
</head>
</xsl:template>
<!-- match p, add to output and apply templates to descendants -->
<xsl:template match="p" mode="richtext">
<p>
<!-- apply templates to descendants -->
<xsl:apply-templates mode="richtext"/>
</p>
</xsl:template>
</xsl:stylesheet>
XSLT;
$htmlDocument = new DOMDocument();
@$htmlDocument->loadHTML(getHTML());
$xslDocument = new DOMDocument();
$xslDocument->loadXML($template);
$processor = new XSLTProcessor();
$processor->importStylesheet($xslDocument);
echo $processor->transformToXML($htmlDocument);
function getHTML() {
return <<<'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;
}