如何用PHP把HTML变成XML-TEI?

How to transform HTML into XML-TEI with PHP?

我需要将一些 HTML 字符串转换成一个 XML 文件,该文件使用一组特定的 TEI(文本编码倡议)标签编写。然后应将该文件提供给 web-based 学术出版系统 lodel,以便在线出版。

更多上下文:

<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>

我不能做什么:

我尝试了什么:

我设法用上述方法生成了一些 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>&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:

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="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>&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;
}