HTML 래퍼없이 DOMDocument의 HTML을 저장하는 방법은 무엇입니까?


116

나는 아래의 기능 이며, 콘텐츠 출력 전에 XML, HTML, bodyp 태그 래퍼를 추가하지 않고 DOMDocument를 출력하기 위해 고군분투 하고 있습니다. 제안 된 수정 사항 :

$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));

콘텐츠에 블록 수준 요소가없는 경우에만 작동합니다. 그러나 h1 요소가있는 아래 예에서와 같이이 경우 saveXML의 결과 출력이 다음으로 잘립니다.

<p> 원하는 경우 </ p>

가능한 해결 방법으로이 게시물을 언급했지만이 솔루션에 구현하는 방법을 이해할 수 없습니다 (아래 주석 처리 된 시도 참조).

어떤 제안?

function rseo_decorate_keyword($postarray) {
    global $post;
    $keyword = "Jasmine Tea"
    $content = "If you like <h1>jasmine tea</h1> you will really like it with Jasmine Tea flavors. This is the last ocurrence of the phrase jasmine tea within the content. If there are other instances of the keyword jasmine tea within the text what happens to jasmine tea."
    $d = new DOMDocument();
    @$d->loadHTML($content);
    $x = new DOMXpath($d);
    $count = $x->evaluate("count(//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and (ancestor::b or ancestor::strong)])");
    if ($count > 0) return $postarray;
    $nodes = $x->query("//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and not(ancestor::h1) and not(ancestor::h2) and not(ancestor::h3) and not(ancestor::h4) and not(ancestor::h5) and not(ancestor::h6) and not(ancestor::b) and not(ancestor::strong)]");
    if ($nodes && $nodes->length) {
        $node = $nodes->item(0);
        // Split just before the keyword
        $keynode = $node->splitText(strpos($node->textContent, $keyword));
        // Split after the keyword
        $node->nextSibling->splitText(strlen($keyword));
        // Replace keyword with <b>keyword</b>
        $replacement = $d->createElement('strong', $keynode->textContent);
        $keynode->parentNode->replaceChild($replacement, $keynode);
    }
$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->item(1));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->childNodes);
return $postarray;
}

답변:


217

PHP 5.4 및 Libxml 2.6 부터는 이제 콘텐츠를 구문 분석하는 방법에 대해 Libxml에 지시 하는 매개 변수 가 있기 때문에이 모든 답변은 이제 잘못 되었습니다 .loadHTML$option

따라서 이러한 옵션을 사용하여 HTML을로드하면

$html->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

할 때 saveHTML()no doctype, no <html>, no <body>.

LIBXML_HTML_NOIMPLIED암시 적 html / body 요소의 자동 추가를 해제하면 LIBXML_HTML_NODEFDTD기본 doctype이 발견되지 않을 때 추가되는 것을 방지합니다.

Libxml 매개 변수에 대한 전체 문서는 여기에 있습니다.

( loadHTML문서에 따르면 Libxml 2.6이 필요하지만 LIBXML_HTML_NODEFDTDLibxml 2.7.8에서만 사용할 LIBXML_HTML_NOIMPLIED수 있으며 Libxml 2.7.7에서 사용할 수 있습니다.)


10
이것은 매력처럼 작동합니다. 받아 들여진 대답이어야합니다. ;-) 난 그냥 하나 개의 플래그를 추가 내 모든 두통은 도망 갔어요
그냥 일반 높음

8
이것은 PHP 5.4 및 Libxml 2.9에서는 작동하지 않습니다. loadHTML은 어떤 옵션도 허용하지 않습니다 :(
Acyra

11
이것은 완벽하지 않습니다. 참조 stackoverflow.com/questions/29493678/...
조쉬 레빈슨

4
죄송합니다. 그러나 이것은 전혀 좋은 해결책이 아닌 것 같습니다 (적어도 실제로는 그렇지 않습니다). 정말 받아 들여진 대답이되어서는 안됩니다. 언급 된 문제 외에, 또한 거기에 불쾌한 인코딩 문제DOMDocument그 또한이 답변의 코드에 영향을 미친다. Afaik 은 입력이 다른 charset을 지정하지 않는 한DOMDocument 항상 입력 데이터를 latin-1로 해석합니다 . 즉, latin-1이 아닌 입력 데이터에 태그가 필요한 것 같습니다. 그렇지 않으면 UTF-8 멀티 바이트 문자와 같은 출력이 깨집니다. <meta charset="…">
mermshaus

1
LIBXML_HTML_NOIMPLIED 또한 탭, 들여 쓰기 및 줄 바꿈 제거하여 HTML 코드를 망쳐 놨
졸탄 술레을

72

loadHTML ()로 문서를로드 한 후 바로 노드를 제거하면됩니다.

# remove <!DOCTYPE 
$doc->removeChild($doc->doctype);           

# remove <html><body></body></html> 
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

이것은 나에게 더 깨끗한 대답입니다.
KnF 2013 년

39
이는 <body>에 자식 노드가 하나만있는 경우 작동합니다.
Yann Milin

훌륭하게 일했습니다. 감사합니다! 다른 preg 답변보다 훨씬 깨끗하고 빠릅니다.
Ligemer 2014

감사합니다! 빈 노드를 처리하기 위해 하단에 또 다른 캡처를 추가했습니다.
redaxmedia

2
제거하는 코드가 <!DOCTYPE 작동합니다. 두 <body>개 이상의 하위 메모 가있는 경우 두 번째 줄이 끊어집니다 .
Free Radical

21

saveXML()대신 사용 하고 documentElement를 인수로 전달하십시오.

$innerHTML = '';
foreach ($document->getElementsByTagName('p')->item(0)->childNodes as $child) {
    $innerHTML .= $document->saveXML($child);
}
echo $innerHTML;

http://php.net/domdocument.savexml


더 낫지 만 여전히 내용을 <html> <body> <p> 감싸고 있습니다.
Scott B


2
saveXML ()은 HTML이 아닌 XHTML을 저장한다는 점에 유의해야합니다.
alexantd

@Scott : 정말 이상합니다. 예제 섹션에서 바로 수행하려는 작업을 보여줍니다. DOM에 HTML이없는 것이 확실합니까? DOMDocument에 HTML이 정확히 무엇입니까? 자식 노드에 액세스해야 할 수도 있습니다.
Jonah

@Jonah 이상하지 않습니다. 당신이 할 때 loadHTMLlibxml 사용하는 HTML 파서 모듈과 그 누락 된 HTML 골격을 삽입됩니다. 결과적으로 $dom->documentElement루트 HTML 요소가됩니다. 예제 코드를 수정했습니다. 이제 Scott이 요청한 작업을 수행해야합니다.
Gordon

19

최상위 답변의 문제는 그것이 LIBXML_HTML_NOIMPLIED불안정 하다는 것 입니다 .

요소의 순서를 변경할 수 있습니다 (특히 맨 위 요소의 닫는 태그를 문서 맨 아래로 이동), 임의의 p태그 추가 및 기타 다양한 문제 [1] . htmlbody태그를 제거 할 수 있지만 불안정한 동작이 발생합니다. 프로덕션에서는 이는 위험 신호입니다. 요컨대 :

사용하지 마십시오LIBXML_HTML_NOIMPLIED . 대신substr .


생각해보십시오. <html><body>및 의 길이는 </body></html>문서의 양쪽 끝에 고정되어 있습니다. 크기는 변경되지 않으며 위치도 변경되지 않습니다. 이것은 우리가 substr그들을 잘라내 는 데 사용할 수 있습니다.

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

echo substr($dom->saveHTML(), 12, -15); // the star of this operation

( 이것은 최종 솔루션이 아닙니다! 전체 답변은 아래를 참조 하고 컨텍스트를 계속 읽으십시오)

우리는 잘라 12때문에 문서의 선두에서 떨어져 <html><body>= 12 자 ( <<>>+html+body= 4 + 4 + 4), 우리는 뒤로 가서 컷 (15) 마지막을 지나고 있기 때문에 \n</body></html>(= 15 자\n+//+<<>>+body+html = 1 + 2 + 4 + 4 + 4)

나는 여전히 포함되는 것을 LIBXML_HTML_NODEFDTD생략합니다 !DOCTYPE. 첫째, 이것은 substrHTML / BODY 태그 의 제거를 단순화합니다 . 둘째, substr' default doctype'가 항상 고정 된 길이 인지 알 수 없기 때문에 doctype을 제거 하지 않습니다 . 그러나 가장 중요한 것은 LIBXML_HTML_NODEFDTDDOM 파서가 HTML5가 아닌 문서 유형을 문서에 적용하지 못하도록하는 것입니다. 이는 최소한 파서가 느슨한 텍스트로 인식하지 않는 요소를 처리하는 것을 방지합니다.

우리는 HTML / BODY 태그는 고정 길이와 위치로되어 있다는 사실을 알고, 우리는 상수가 좋아 것을 알고 LIBXML_HTML_NODEFDTD위의 방법은 미래에 잘 굴러해야하므로, 사용 중단 통지의 몇 가지 유형없이 제거되지 않습니다, 하지만 ...


... 유일한주의 사항은 DOM 구현 HTML / BODY 태그가 문서 내에 배치되는 방식을 변경할 수 있다는 것입니다. 예를 들어 문서 끝에서 줄 바꿈을 제거하거나 태그 사이에 공백을 추가하거나 줄 바꿈을 추가 할 수 있습니다.

이 문제는에 대한 여는 태그와 닫는 태그의 위치를 ​​검색 body하고 잘라낼 길이에 대한 오프셋을 사용하여 해결할 수 있습니다 . 및를 사용 strpos하여 strrpos각각 앞과 뒤에서 오프셋을 찾습니다.

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

$trim_off_front = strpos($dom->saveHTML(),'<body>') + 6;
// PositionOf<body> + 6 = Cutoff offset after '<body>'
// 6 = Length of '<body>'

$trim_off_end = (strrpos($dom->saveHTML(),'</body>')) - strlen($dom->saveHTML());
// ^ PositionOf</body> - LengthOfDocument = Relative-negative cutoff offset before '</body>'

echo substr($dom->saveHTML(), $trim_off_front, $trim_off_end);

마지막으로 미래를 보장하는 최종 답변을 반복합니다 .

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

$trim_off_front = strpos($dom->saveHTML(),'<body>') + 6;
$trim_off_end = (strrpos($dom->saveHTML(),'</body>')) - strlen($dom->saveHTML());

echo substr($dom->saveHTML(), $trim_off_front, $trim_off_end);

doctype, html 태그, body 태그가 없습니다. DOM 파서가 곧 새로운 페인트 칠을 받기를 바라며 원하지 않는 태그를보다 직접적으로 제거 할 수 있습니다.


좋은 대답, 작은 의견 하나, 반복적으로 $html = $dom -> saveHTML();대신 하지 않는 이유 는 $dom -> saveHTML();무엇입니까?
Steven

15

깔끔한 트릭이 사용하는 loadXML다음과 saveHTML. htmlbody태그가 삽입되는 load단계가 아닌 save단계.

$dom = new DOMDocument;
$dom->loadXML('<p>My DOMDocument contents are here</p>');
echo $dom->saveHTML();

이것은 약간 엉망이며 작동하도록 할 수 있다면 Jonah의 대답을 사용해야합니다.


4
하지만 잘못된 HTML에서는 실패합니다.
Gordon

1
@Gordon 정확히 내가 면책 조항을 맨 아래에 두는 이유!
lonesomeday

1
이것을 시도하고 $ dom-> saveHTML ()을 echo하면 빈 문자열이 반환됩니다. loadXML ($ content)이 비어있는 것처럼. $ dom-> loadHTML ($ content)와 동일한 작업을 수행하면 echo $ dom-> saveXML () 예상대로 내용이 표시됩니다.
Scott B

HTMl을로드하려고 할 때 loadXML을 사용하는 것은 엄지입니다. 특히 LoadXML은 HTML을 처리하는 방법을 모르기 때문입니다.
botenvouwer

15

DOMDocumentFragment 사용

$html = 'what you want';
$doc = new DomDocument();
$fragment = $doc->createDocumentFragment();
$fragment->appendXML($html);
$doc->appendChild($fragment);
echo $doc->saveHTML();

3
php5.4 이전에 대한 가장 깨끗한 대답.
Nick Johnson

이것은 버전 Libxml 2.7.7보다 이전 버전과 최신 버전 모두 나를 위해 작동합니다. 왜 이것이 php5.4 이전에만 해당됩니까?
RobbertT 2015

더 많은 표가 있어야합니다. LIBXML_HTML_NOIMPLIED를 지원하지 않는 libxml 버전을위한 훌륭한 옵션 | LIBXML_HTML_NODEFDTD. 감사!
Marty Mulligan 2015 년

13

2017 년이고 2011 년 질문에 대한 답이 마음에 들지 않습니다. 많은 정규식, 큰 클래스, loadXML 등 ...

알려진 문제를 해결하는 쉬운 솔루션 :

$dom = new DOMDocument();
$dom->loadHTML( '<html><body>'.mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8').'</body></html>' , LIBXML_HTML_NODEFDTD);
$html = substr(trim($dom->saveHTML()),12,-14);

쉽고, 간단하고, 견고하고, 빠릅니다. 이 코드는 HTML 태그 및 인코딩과 관련하여 다음과 같이 작동합니다.

$html = '<p>äöü</p><p>ß</p>';

누구든지 오류를 발견하면 알려주십시오. 직접 사용하겠습니다.

편집 , 오류없이 작동하는 기타 유효한 옵션 (이미 제공된 것과 매우 유사 함) :

@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$saved_dom = trim($dom->saveHTML());
$start_dom = stripos($saved_dom,'<body>')+6;
$html = substr($saved_dom,$start_dom,strripos($saved_dom,'</body>') - $start_dom );

Furure에 이상한 일이 생기지 않도록 몸을 직접 추가 할 수 있습니다.

Thirt 옵션 :

 $mock = new DOMDocument;
 $body = $dom->getElementsByTagName('body')->item(0);
  foreach ($body->childNodes as $child){
     $mock->appendChild($mock->importNode($child, true));
  }
$html = trim($mock->saveHTML());

3
더 비싼 것을 피하고 그에 따라 mb_convert_encoding추가 <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>하고 수정 하여 답을 개선해야합니다 substr. Btw, 당신의 것이 여기에서 가장 우아한 솔루션입니다. 찬성.
Hlsg

10

나는 클럽에서 조금 늦었지만 내가 찾은 방법을 공유하고 싶지 않았습니다 . 우선, 이러한 멋진 옵션을 수용 할 수있는 loadHTML ()에 대한 올바른 버전이 있지만 LIBXML_HTML_NOIMPLIED내 시스템에서 작동하지 않았습니다. 또한 사용자는 파서 관련 문제를보고합니다 (예 : herehere ).

내가 만든 솔루션은 실제로 매우 간단합니다.

로드 할 HTML은로드 <div>할 모든 노드를 포함하는 컨테이너를 갖도록 요소에 배치됩니다.

그런 다음이 컨테이너 요소는 문서에서 제거됩니다 (하지만 DOMElement 는 여전히 존재합니다).

그런 다음 문서에서 모든 직계 자식이 제거됩니다. 여기에는 추가 된 <html>, 태그 (효과적으로 옵션) <head>및 선언 (효과적으로 )이 포함됩니다.<body>LIBXML_HTML_NOIMPLIED<!DOCTYPE html ... loose.dtd">LIBXML_HTML_NODEFDTD

그런 다음 컨테이너의 모든 직계 자식이 문서에 다시 추가되고 출력 될 수 있습니다.

$str = '<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>';

$doc = new DOMDocument();

$doc->loadHTML("<div>$str</div>");

$container = $doc->getElementsByTagName('div')->item(0);

$container = $container->parentNode->removeChild($container);

while ($doc->firstChild) {
    $doc->removeChild($doc->firstChild);
}

while ($container->firstChild ) {
    $doc->appendChild($container->firstChild);
}

$htmlFragment = $doc->saveHTML();

XPath는 평소와 같이 작동합니다. 단일 루트 노드가 아닌 여러 문서 요소가 있는지 확인하십시오.

$xpath = new DOMXPath($doc);
foreach ($xpath->query('/p') as $element)
{   #                   ^- note the single slash "/"
    # ... each of the two <p> element

  • PHP 5.4.36-1 + deb.sury.org ~ precise + 2 (cli) (빌드 : 2014 년 12 월 21 일 20:28:53)

더 복잡한 HTML 소스에서는 작동하지 않았습니다. 또한 HTML의 특정 부분을 제거했습니다.
Zoltán Süle

4

이 글을 쓰는 당시 (2012 년 6 월) 다른 솔루션 중 어느 것도 내 요구를 완전히 충족 할 수 없었기 때문에 다음 사례를 처리하는 솔루션을 작성했습니다.

  • 태그가없는 일반 텍스트 콘텐츠와 HTML 콘텐츠를 허용합니다.
  • (모든 태그를 추가하지 않습니다를 포함 <doctype>, <xml>, <html>, <body>, 및<p> 태그)
  • 포장 된 것을 남겨 둡니다 <p> 둡니다.
  • 빈 텍스트 만 남겨 둡니다.

따라서 다음은 이러한 문제를 해결하는 솔루션입니다.

class DOMDocumentWorkaround
{
    /**
     * Convert a string which may have HTML components into a DOMDocument instance.
     *
     * @param string $html - The HTML text to turn into a string.
     * @return \DOMDocument - A DOMDocument created from the given html.
     */
    public static function getDomDocumentFromHtml($html)
    {
        $domDocument = new DOMDocument();

        // Wrap the HTML in <div> tags because loadXML expects everything to be within some kind of tag.
        // LIBXML_NOERROR and LIBXML_NOWARNING mean this will fail silently and return an empty DOMDocument if it fails.
        $domDocument->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING);

        return $domDocument;
    }

    /**
     * Convert a DOMDocument back into an HTML string, which is reasonably close to what we started with.
     *
     * @param \DOMDocument $domDocument
     * @return string - The resulting HTML string
     */
    public static function getHtmlFromDomDocument($domDocument)
    {
        // Convert the DOMDocument back to a string.
        $xml = $domDocument->saveXML();

        // Strip out the XML declaration, if one exists
        $xmlDeclaration = "<?xml version=\"1.0\"?>\n";
        if (substr($xml, 0, strlen($xmlDeclaration)) == $xmlDeclaration) {
            $xml = substr($xml, strlen($xmlDeclaration));
        }

        // If the original HTML was empty, loadXML collapses our <div></div> into <div/>. Remove it.
        if ($xml == "<div/>\n") {
            $xml = '';
        }
        else {
            // Remove the opening <div> tag we previously added, if it exists.
            $openDivTag = "<div>";
            if (substr($xml, 0, strlen($openDivTag)) == $openDivTag) {
                $xml = substr($xml, strlen($openDivTag));
            }

            // Remove the closing </div> tag we previously added, if it exists.
            $closeDivTag = "</div>\n";
            $closeChunk = substr($xml, -strlen($closeDivTag));
            if ($closeChunk == $closeDivTag) {
                $xml = substr($xml, 0, -strlen($closeDivTag));
            }
        }

        return $xml;
    }
}

나는 또한 같은 클래스에 살 수있는 몇 가지 테스트를 작성했습니다.

public static function testHtmlToDomConversions($content)
{
    // test that converting the $content to a DOMDocument and back does not change the HTML
    if ($content !== self::getHtmlFromDomDocument(self::getDomDocumentFromHtml($content))) {
        echo "Failed\n";
    }
    else {
        echo "Succeeded\n";
    }
}

public static function testAll()
{
    self::testHtmlToDomConversions('<p>Here is some sample text</p>');
    self::testHtmlToDomConversions('<div>Lots of <div>nested <div>divs</div></div></div>');
    self::testHtmlToDomConversions('Normal Text');
    self::testHtmlToDomConversions(''); //empty
}

직접 작동하는지 확인할 수 있습니다. DomDocumentWorkaround::testAll()다음을 반환합니다.

    Succeeded
    Succeeded
    Succeeded
    Succeeded

1
HTML = / = XML, HTML 용 HTML 로더를 사용해야합니다.
hakre apr.

4

좋아, 더 우아한 해결책을 찾았지만 지루합니다.

$d = new DOMDocument();
@$d->loadHTML($yourcontent);
...
// do your manipulation, processing, etc of it blah blah blah
...
// then to save, do this
$x = new DOMXPath($d);
$everything = $x->query("body/*"); // retrieves all elements inside body tag
if ($everything->length > 0) { // check if it retrieved anything in there
      $output = '';
      foreach ($everything as $thing) {
           $output .= $d->saveXML($thing);
      }
      echo $output; // voila, no more annoying html wrappers or body tag
}

좋아, 바라건대 이것은 아무것도 생략하지 않고 누군가를 돕는다?


2
loadHTML이 마크 업없이 문자열을로드 할 때 케이스를 처리하지 않습니다
copndz

3

이 기능 사용

$layout = preg_replace('~<(?:!DOCTYPE|/?(?:html|head|body))[^>]*>\s*~i', '', $layout);

13
이 게시물을 통해이 게시물 을 우연히 발견 하고 HTML을 구문 분석하는 데 정규식을 사용하지 않고 대신 DOM 파서를 사용하기로 결정한 독자가있을 수 있으며 , 결국 완전한 솔루션을 얻기 위해 정규식 응답 이 필요할 수 있습니다 . 아이러니
Robbie Averill

noboy가 BODY의 내용을 반환하는 이유를 이해하지 못합니다. 파서가 전체 문서 헤더 / 문서 유형을 추가 할 때 해당 태그가 항상 존재한다고 가정하지 않습니까? 위의 정규식은 더 짧을 것입니다.
세르지오

@boksiora "그것이 일을합니다"-그렇다면 왜 우리는 처음에 DOM 파서 메소드를 사용합니까?
감사합니다

@naomik 나는 DOM 파서를 사용하지 말라고 말하지 않았습니다. 물론 동일한 결과를 얻는 데는 여러 가지 방법이 있습니다.이 기능을 사용했을 때 내장 php dom에 문제가있었습니다. 파서, html5를 올바르게 구문 분석하지 않았습니다.
boksiora

1
preg_replacehtml 및 body 태그를 제거하는 DOMDocument 기반 방법을 사용하면 UTF-8 인코딩이 보존되지 않았기 때문에 사용해야했습니다. :(
wizonesolutions

3

Alessandro Vendruscolo 가 답변 한 플래그 솔루션 이 작동하지 않으면 다음을 시도해 볼 수 있습니다.

$dom = new DOMDocument();
$dom->loadHTML($content);

//do your stuff..

$finalHtml = '';
$bodyTag = $dom->documentElement->getElementsByTagName('body')->item(0);
foreach ($bodyTag->childNodes as $rootLevelTag) {
    $finalHtml .= $dom->saveHTML($rootLevelTag);
}
echo $finalHtml;

$bodyTag<body>콘텐츠의 루트 인 태그를 제외하고 모든 HTML 래핑없이 전체 처리 된 HTML 코드를 포함합니다 . 그런 다음 정규식 또는 트림 함수를 사용하여 최종 문자열에서 제거 saveHTML하거나 (이후 ) 위의 경우와 같이 모든 하위 항목을 반복하여 콘텐츠를 임시 변수에 저장 $finalHtml하고 반환 할 수 있습니다 (내가 믿는 더 안전한).


3

PHP 5.6.25 및 LibXML 2.9를 실행하는 RHEL7에서이 문제로 어려움을 겪고 있습니다. (2018 년의 오래된 것, 알고 있지만 이것이 바로 Red Hat입니다.)

Alessandro Vendruscolo 가 제안한 많은 찬성 솔루션이 태그를 재 배열하여 HTML을 깨뜨리는 것을 발견했습니다 . 즉 :

<p>First.</p><p>Second.</p>'

된다 :

<p>First.<p>Second.</p></p>'

이것은 그가 사용하도록 제안한 옵션 LIBXML_HTML_NOIMPLIEDLIBXML_HTML_NODEFDTD.

Alex 가 제안한 솔루션은 문제를 해결하는 데 절반 정도가 걸리지 만 다음과 같은 경우 작동하지 않습니다.<body> 자식 노드가 두 개 이상 .

나를 위해 작동하는 솔루션은 다음과 같습니다.

먼저 DOMDocument를로드하기 위해 다음을 사용합니다.

$doc = new DOMDocument()
$doc->loadHTML($content);

DOMDocument를 마사지 한 후 문서를 저장하려면 다음을 사용합니다.

// remove <!DOCTYPE 
$doc->removeChild($doc->doctype);  
$content = $doc->saveHTML();
// remove <html><body></body></html> 
$content = str_replace('<html><body>', '', $content);
$content = str_replace('</body></html>', '', $content);

이것이 매우 우아한 해결책은 아니지만 작동한다는 데 처음으로 동의합니다.


2

<meta>태그를 추가하면DOMDocument . 좋은 점은 태그를 전혀 추가 할 필요가 없다는 것입니다. 선택한 인코딩을 사용하지 않으려면 생성자 인수로 전달하십시오.

http://php.net/manual/en/domdocument.construct.php

$doc = new DOMDocument('1.0', 'UTF-8');
$node = $doc->createElement('div', 'Hello World');
$doc->appendChild($node);
echo $doc->saveHTML();

산출

<div>Hello World</div>

@Bart 덕분에


2

나도이 요구 사항이 있었고 위의 Alex가 게시 한 솔루션이 마음에 들었습니다. 하지만 몇 가지 문제가 있습니다. <body>요소에 두 개 이상의 하위 요소가 포함 된 경우 결과 문서에는의 첫 번째 하위 요소 만 포함되고 <body>모든 항목이 포함되지 않습니다. 또한 조건부로 처리하기 위해 스트리핑이 필요했습니다. HTML 제목이있는 문서가있을 때만 가능합니다. 그래서 다음과 같이 다듬 었습니다. 제거하는 대신으로 <body>변환 <div>하고 XML 선언 및 <html>.

function strip_html_headings($html_doc)
{
    if (is_null($html_doc))
    {
        // might be better to issue an exception, but we silently return
        return;
    }

    // remove <!DOCTYPE 
    if (!is_null($html_doc->firstChild) &&
        $html_doc->firstChild->nodeType == XML_DOCUMENT_TYPE_NODE)
    {
        $html_doc->removeChild($html_doc->firstChild);     
    }

    if (!is_null($html_doc->firstChild) &&
        strtolower($html_doc->firstChild->tagName) == 'html' &&
        !is_null($html_doc->firstChild->firstChild) &&
        strtolower($html_doc->firstChild->firstChild->tagName) == 'body')
    {
        // we have 'html/body' - replace both nodes with a single "div"        
        $div_node = $html_doc->createElement('div');

        // copy all the child nodes of 'body' to 'div'
        foreach ($html_doc->firstChild->firstChild->childNodes as $child)
        {
            // deep copies each child node, with attributes
            $child = $html_doc->importNode($child, true);
            // adds node to 'div''
            $div_node->appendChild($child);
        }

        // replace 'html/body' with 'div'
        $html_doc->removeChild($html_doc->firstChild);
        $html_doc->appendChild($div_node);
    }
}

2

다른 회원들과 마찬가지로 저는 @Alessandro Vendruscolo 답변의 단순성과 놀라운 힘을 처음으로 느꼈습니다. 플래그가 지정된 일부 상수를 생성자에 전달하는 기능은 너무 좋아서 사실이 아닙니다. 저에게는 그랬습니다. LibXML과 PHP의 올바른 버전이 있지만 문서 객체의 노드 구조에 HTML 태그를 추가하는 것이 무엇이든 상관 없습니다.

내 솔루션은 사용하는 것보다 더 잘 작동했습니다 ...

$html->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

플래그 또는 ....

# remove <!DOCTYPE 
$doc->removeChild($doc->firstChild);            

# remove <html><body></body></html>
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

DOM에서 구조화 된 순서없이 지저분 해지는 Node Removal. 다시 코드 조각은 DOM 구조를 미리 결정할 방법이 없습니다.

저는 JQuery가 수행하는 DOM 탐색을 수행하는 간단한 방법을 원하거나 적어도 단일 연결, 이중 연결 또는 트리 노드 탐색 중 하나의 구조화 된 데이터 세트가있는 방식으로이 여정을 시작했습니다. HTML처럼 문자열을 구문 분석 할 수 있고 그 과정에서 사용할 수있는 노드 엔터티 클래스 속성의 놀라운 힘을 가지고있는 한 저는 신경 쓰지 않았습니다.

지금까지 DOMDocument 객체는 저를 원하게 만들었습니다 ... 다른 많은 프로그래머들과 마찬가지로 ... 저는이 질문에 대해 많은 좌절감을 느꼈다는 것을 알고 있습니다. 그래서 저는 마침내 .... (약 30 시간의 시도와 실패 후 유형 테스트) 모든 것을 얻을 수있는 방법을 찾았습니다. 누군가에게 도움이되기를 바랍니다.

우선, 나는 모든 것에 냉소적입니다 ... 웃음 ...

이 사용 사례에서 어쨌든 타사 클래스가 필요하다는 것에 동의하기 전에 평생을 갔을 것입니다. 나는 타사 클래스 구조를 사용하는 팬이 아니었고 훌륭한 파서를 우연히 발견했습니다. (내가 포기하기 전에 Google에서 약 30 번 정도 비공식적으로 보였기 때문에 피했다면 외롭지 마십시오 ...)

코드 조각을 사용하고 있고 추가 태그를 사용하지 않고 어떤 식 으로든 파서의 영향을받지 않고 코드를 정리해야한다면 simplePHPParser 를 사용 하세요 .

놀랍고 JQuery와 비슷하게 작동합니다. 나는 종종 인상적이지는 않지만이 수업은 많은 좋은 도구를 사용하며 아직까지 구문 분석 오류가 없었습니다. 저는이 수업이하는 일을 할 수 있다는 것을 매우 좋아합니다.

여기 에서 다운로드 할 파일 , 여기 에서 시작 지침 및 여기 에서 API를 찾을 수 있습니다 . 이 클래스를 .find(".className")JQuery find 메서드를 사용하는 것과 같은 방식으로 수행 할 수있는 간단한 메서드 getElementByTagName()나 또는 getElementById()... 와 같은 익숙한 메서드와 함께 사용하는 것이 좋습니다 .

이 클래스에서 노드 트리를 저장하면 아무것도 추가하지 않습니다. 간단히 말할 수 있으며 $doc->save();번거 로움없이 전체 트리를 문자열로 출력합니다.

이제 향후 모든 비제 한 대역폭 프로젝트에이 파서를 사용할 것입니다.


2

나는 PHP 5.3을 가지고 있으며 여기에 대한 답변은 나를 위해 작동하지 않았습니다.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);모든 문서를 첫 번째 자식으로 만 바꿨고 단락이 많았고 첫 번째 만 저장되었지만 솔루션은 regex몇 가지 의견을 남기지 않고 무언가를 작성할 수있는 좋은 출발점 이되었고 이것이 개선 될 수 있다고 확신합니다. 누군가 나와 같은 문제를 가지고있는 것이 좋은 출발점이 될 수 있습니다.

function extractDOMContent($doc){
    # remove <!DOCTYPE
    $doc->removeChild($doc->doctype);

    // lets get all children inside the body tag
    foreach ($doc->firstChild->firstChild->childNodes as $k => $v) {
        if($k !== 0){ // don't store the first element since that one will be used to replace the html tag
            $doc->appendChild( clone($v) ); // appending element to the root so we can remove the first element and still have all the others
        }
    }
    // replace the body tag with the first children
    $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
    return $doc;
}

그런 다음 다음과 같이 사용할 수 있습니다.

$doc = new DOMDocument();
$doc->encoding = 'UTF-8';
$doc->loadHTML('<p>Some html here</p><p>And more html</p><p>and some html</p>');
$doc = extractDOMContent($doc);

appendChild허용 DOMNode하므로 새 요소를 만들 필요가 없습니다. 여러 HTML / XML 문서를 조작 할 때 코드를 "정상"으로 유지하기 위해 DOMNode이와 같이 구현하는 기존 요소를 재사용 DOMElement할 수 있습니다.


이것은 문서의 첫 번째 자식으로 만들려는 단일 자식 요소에 대해서만 작동합니다. 이것은 매우 제한적이며 LIBXML_HTML_NOIMPLIED부분적으로 만 수행하는 작업을 효과적으로 수행 하지 않습니다. doctype을 효과적으로 제거하는 것입니다 LIBXML_HTML_NODEFDTD.
hakre

2

HTML 래퍼를 제거하는 방법을 찾기 위해이 주제를 발견했습니다. 사용LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD 좋은 작품,하지만 난 UTF-8에 문제가 있습니다. 많은 노력 끝에 해결책을 찾았습니다. 나는 누구에게나 동일한 문제가 있음을 위해 그것을 게시합니다.

원인으로 인한 문제 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

문제 :

$dom = new DOMDocument();
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$dom->saveHTML();

해결책 1 :

$dom->loadHTML(mb_convert_encoding($document, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    $dom->saveHTML($dom->documentElement));

해결 방법 2 :

$dom->loadHTML($document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
utf8_decode($dom->saveHTML($dom->documentElement));

1
발견 한 내용을 공유하는 것은 좋지만 솔루션 2는 이미 여기에 정확한 질문과 함께 존재하고 솔루션 1은 다른 곳에 있습니다. 또한 솔루션 1의 문제에 대해 주어진 답이 명확하지 않습니다. 나는 당신의 좋은 의도를 존중합니다. 그러나 그것은 당신이 당신의 대답으로 성취하고자하는 것과 약간 반대 인 것 같은 그들이 찾고있는 해결책을 찾도록 다른 사람들을 방해 할뿐만 아니라 많은 소음을 만들 수 있다는 것을 알아 두십시오. Stackoverflow는 한 번에 하나의 질문을 처리 할 때 가장 잘 작동합니다. 힌트입니다.
hakre

2

저는 DOMDocument수업에서 3 가지 문제에 직면 합니다.

1-이 클래스는 ISO 인코딩 및 utf-8 문자가 출력에 표시되지 않는 html을로드합니다.

2- 우리가 ‍‍‍를 주더라도LIBXML_HTML_NOIMPLIED loadHtml 방법에

3-이 클래스는 HTML5 태그가 유효하지 않은 것으로 간주합니다.

그래서 저는이 문제를 해결하기 위해이 클래스를 재정의했고 몇 가지 방법을 변경했습니다.

class DOMEditor extends DOMDocument
{
    /**
     * Temporary wrapper tag , It should be an unusual tag to avoid problems
     */
    protected $tempRoot = 'temproot';

    public function __construct($version = '1.0', $encoding = 'UTF-8')
    {
        //turn off html5 errors
        libxml_use_internal_errors(true);
        parent::__construct($version, $encoding);
    }

    public function loadHTML($source, $options = LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD)
    {
        // this is a bitwise check if LIBXML_HTML_NOIMPLIED is set
        if ($options & LIBXML_HTML_NOIMPLIED) {
            // it loads the content with a temporary wrapper tag and utf-8 encoding
            parent::loadHTML("<{$this->tempRoot}>" . mb_convert_encoding($source, 'HTML', 'UTF-8') . "</{$this->tempRoot}>", $options);
        } else {
            // it loads the content with utf-8 encoding and default options
            parent::loadHTML(mb_convert_encoding($source, 'HTML', 'UTF-8'), $options);
        }
    }

    private function unwrapTempRoot($output)
    {
        if ($this->firstChild->nodeName === $this->tempRoot) {
            return substr($output, strlen($this->tempRoot) + 2, -strlen($this->tempRoot) - 4);
        }
        return $output;
    }

    public function saveHTML(DOMNode $node = null)
    {
        $html = html_entity_decode(parent::saveHTML($node));
        if (is_null($node)) {
            $html = $this->unwrapTempRoot($html);
        }
        return $html;
    }

    public function saveXML(DOMNode $node = null, $options = null)
    {
        if (is_null($node)) {
            return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . PHP_EOL . $this->saveHTML();
        }
        return parent::saveXML($node);
    }

}

이제 DOMEditor대신 사용 DOMDocument하고 있으며 지금까지 잘 작동했습니다.

        $editor = new DOMEditor();
        $editor->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        // works like a charm!
        echo $editor->saveHTML();

당신의 요점 1.은 mb_convert_encoding ($ string, 'HTML-ENTITIES', 'UTF-8'); loadHTML () 및 2.nd를 사용하기 전에 도우미 함수에서 DIV 태그를 사용하여 예를 들어 mb_convert_encoding () 주위에 사용합니다. 나를 위해 충분히 운동했습니다. 실제로 DIV가 없으면 내 경우에는 일반적으로 약간의 여백이 적용되어 불편한 단락이 자동으로 추가됩니다 (bootstrap ..)
trainoasis

0

나도이 문제를 다뤘다.

안타깝게도이 스레드에서 제공하는 솔루션을 사용하는 것이 편하게 느껴지지 않아 만족할만한 솔루션을 확인했습니다.

다음은 내가 구성한 내용이며 문제없이 작동합니다.

$domxpath = new \DOMXPath($domDocument);

/** @var \DOMNodeList $subset */
$subset = $domxpath->query('descendant-or-self::body/*');

$html = '';
foreach ($subset as $domElement) {
    /** @var $domElement \DOMElement */
    $html .= $domDocument->saveHTML($domElement);
}

본질적으로 여기에 제공된 대부분의 솔루션과 유사한 방식으로 작동하지만 수동 작업을 수행하는 대신 xpath 선택기를 사용하여 본문 내의 모든 요소를 ​​선택하고 html 코드를 연결합니다.


여기에있는 모든 솔루션과 마찬가지로 모든 경우에 작동하지 않습니다.로드 된 문자열이 마크 업으로 시작하지 않은 경우 <p> </ p>가 추가 된 경우 코드가 작동하지 않습니다. 저장된 콘텐츠의 <p> </ p> 마크 업
copndz

공정하게 말하면, 원시 텍스트로 테스트하지는 않았지만 이론적으로는 작동합니다. 특정 경우에 xpath를 descendant-or-self::body/p/*.
Nikola Petkanski 2013 년

0

내 서버에 PHP 5.3이 있고 업그레이드 할 수 없으므로 해당 옵션

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

나를위한 것이 아닙니다.

이 문제를 해결하기 위해 SaveXML 함수에 Body 요소를 인쇄 한 다음 "body"를 "div"로 바꿉니다.

여기 내 코드가 있습니다. 누군가를 돕고 있기를 바랍니다.

<? 
$html = "your html here";
$tabContentDomDoc = new DOMDocument();
$tabContentDomDoc->loadHTML('<?xml encoding="UTF-8">'.$html);
$tabContentDomDoc->encoding = 'UTF-8';
$tabContentDomDocBody = $tabContentDomDoc->getElementsByTagName('body')->item(0);
if(is_object($tabContentDomDocBody)){
    echo (str_replace("body","div",$tabContentDomDoc->saveXML($tabContentDomDocBody)));
}
?>

utf-8은 히브리어 지원용입니다.


0

Alex 대답은 정확하지만 빈 노드에서 다음 오류가 발생할 수 있습니다.

DOMNode :: removeChild ()에 전달 된 인수 1은 DOMNode의 인스턴스 여야합니다.

여기 내 작은 모드가 있습니다.

    $output = '';
    $doc = new DOMDocument();
    $doc->loadHTML($htmlString); //feed with html here

    if (isset($doc->firstChild)) {

        /* remove doctype */

        $doc->removeChild($doc->firstChild);

        /* remove html and body */

        if (isset($doc->firstChild->firstChild->firstChild)) {
            $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
            $output = trim($doc->saveHTML());
        }
    }
    return $output;

trim ()을 추가하는 것도 공백을 제거하는 좋은 방법입니다.


0

너무 늦었을 수도 있습니다. 하지만 (나와 같은) 누군가가 여전히이 문제를 가지고있을 수 있습니다.
따라서 위의 어느 것도 나를 위해 일하지 않았습니다. $ dom-> loadHTML도 열려있는 태그를 닫기 때문에 html 및 body 태그를 추가 할뿐만 아니라.
그래서 html 조각에 3-4 개의 닫히지 않은 div가 있기 때문에 <div> 요소를 추가하면 작동하지 않습니다.
내 솔루션 :

1.) 잘라낼 마커를 추가 한 다음 html 조각을로드합니다.

$html_piece = "[MARK]".$html_piece."[/MARK]";
$dom->loadHTML($html_piece);

2.) 문서로 원하는대로 수행하십시오.
3.) html 저장

$new_html_piece = $dom->saveHTML();

4.) 반환하기 전에 마커에서 <p> </ p> 태그를 제거합니다. 이상하게도 [MARK]에만 표시되고 [/ MARK]에는 표시되지 않습니다 ...!?

$new_html_piece = preg_replace( "/<p[^>]*?>(\[MARK\]|\s)*?<\/p>/", "[MARK]" , $new_html_piece );

5.) 마커 앞뒤의 모든 항목 제거

$pattern_contents = '{\[MARK\](.*?)\[\/MARK\]}is';
if (preg_match($pattern_contents, $new_html_piece, $matches)) {
    $new_html_piece = $matches[1];
}

6.) 반환

return $new_html_piece;

LIBXML_HTML_NOIMPLIED가 저에게 효과적이라면 훨씬 쉬울 것입니다. schould하지만 그렇지 않습니다. PHP 5.4.17, libxml 버전 2.7.8.
나는 정말 이상하다고 생각한다. 나는 HTML DOM 파서를 사용한다. 그리고 나서이 "것"을 고치기 위해 나는 정규식을 사용해야한다. 요점은 정규식을 사용하는 것이 아니라,


당신이 여기서하는 일은 위험 해 보입니다. stackoverflow.com/a/29499718/367456 이 당신을 위해 일을해야합니다.
hakre

불행히도 이것은 ( stackoverflow.com/questions/4879946/… ) 나를 위해 작동하지 않습니다. 내가 말했듯이 : "그래서 html 조각에 3-4 개의 닫히지 않은 div가 가끔씩 있기 때문에 <div> 요소를 추가하는 것이 작동하지 않습니다."어떤 이유로 DOMDocument는 모든 "닫히지 않은"요소를 닫으려고합니다. 경우에 따라 단축 코드 또는 기타 마커 내에서 프래그먼트를 얻고 프래그먼트를 제거하고 문서의 다른 부분을 조작하고 싶습니다. 작업이 끝나면 프래그먼트를 다시 삽입합니다.
Joe

div 요소를 제외하고 대신 자신의 콘텐츠를로드 한 후 body 요소에서 작동 할 수 있어야합니다. 본문 요소는 조각을로드 할 때 암시 적으로 추가되어야합니다.
hakre

내 문제는 내 프래그먼트에 닫히지 않은 태그가 포함되어 있다는 것입니다. 닫히지 않은 상태로 유지해야하며 DOMDocument는 해당 요소를 닫습니다. 같은 Fregment : < div >< div > ... < /div >. 나는 여전히 해결책을 찾고 있습니다.
Joe

음, div 태그에는 항상 닫는 쌍이 있다고 생각합니다. 아마도 Tidy가 그것을 처리 할 수있을 것이고, 또한 조각과 함께 작동 할 수 있습니다.
hakre

0

Drupal을 사용하는 모든 사람을 위해 다음과 같은 기능이 내장되어 있습니다.

https://api.drupal.org/api/drupal/modules!filter!filter.module/function/filter_dom_serialize/7.x

참조 코드 :

function filter_dom_serialize($dom_document) {
  $body_node = $dom_document->getElementsByTagName('body')->item(0);
  $body_content = '';

  if ($body_node !== NULL) {
    foreach ($body_node->getElementsByTagName('script') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node);
    }

    foreach ($body_node->getElementsByTagName('style') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node, '/*', '*/');
    }

    foreach ($body_node->childNodes as $child_node) {
      $body_content .= $dom_document->saveXML($child_node);
    }
    return preg_replace('|<([^> ]*)/>|i', '<$1 />', $body_content);
  }
  else {
    return $body_content;
  }
}

찬성. Drupal API에서이 기능을 사용하면 Drupal 7 사이트에서 잘 작동합니다. Drupal을 사용하지 않는 사람들은 함수를 자신의 사이트에 복사 할 수 있다고 생각합니다. Drupal과 관련된 것은 없습니다.
Free Radical

0

show-body-only와 함께 tidy를 사용할 수 있습니다.

$tidy = new tidy();
$htmlBody = $tidy->repairString($html, [
  'indent' =>  true,
  'output-xhtml' => true,
  'show-body-only' => true
], 'utf8');

그러나 기억하세요 : Font Awesome icons : Problems Indenting HTML (5) with PHP와 같은 일부 태그를 깔끔하게 제거하세요.


-1
#remove doctype tag
$doc->removeChild($doc->doctype); 

#remove html & body tags
$html = $doc->getElementsByTagName('html')[0];
$body = $html->getElementsByTagName('body')[0];
foreach($body->childNodes as $child) {
    $doc->appendChild($child);
}
$doc->removeChild($html);

-1의 이유를 공유 하시겠습니까?
Dylan Maxey

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.