如何使用 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>&nbsp;</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>&nbsp;</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>&nbsp;</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 将 HTML 转换为 XML-TEI?

PHP:如何将 ASCII 转换为 HTML 或如何解码字符串

如何使用 php 将 docx 文档转换为 html?

如何使用 PHP 将 html 页面转换为单个 .chm 文件?

如何使用 JavaScript 将 PHP 代码转换为 HTML?

使用 PHP 将 HTML 转换为 PDF? [复制]