如何使用 PHP 将 HTML 转换为 XML-TEI?
Posted
技术标签:
【中文标题】如何使用 PHP 将 HTML 转换为 XML-TEI?【英文标题】:How to transform HTML into XML-TEI with PHP? 【发布时间】: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="someidposition()">
<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 感谢您的回复。根据要求,我提供了一些尝试:) 【参考方案1】:我认为您对 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="someidposition()">
<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 中,它就像一个魅力!为其余部分提供了一个很好的起点。我详细编辑了我的问题,我在此过程中发现了关于我的特定案例的信息。谢谢!以上是关于如何使用 PHP 将 HTML 转换为 XML-TEI?的主要内容,如果未能解决你的问题,请参考以下文章
PHP:如何将 ASCII 转换为 HTML 或如何解码字符串
如何使用 PHP 将 html 页面转换为单个 .chm 文件?