2014-09-26 4 views
2

Вопрос, как извлечь текст из HTML с использованием Java просматривалась и дублированные зиллионы раз: Text Extraction from HTML JavaТекст Извлечение из HTML с использованием Java, включая номер строки и код

Thanks to ответов, найденных на Stackoverflow мое современное состояние дела в том, что я использую JSoup

<!-- Jsoup maven dependency --> 
<dependency> 
    <groupId>org.jsoup</groupId> 
    <artifactId>jsoup</artifactId> 
    <version>1.7.3</version> 
</dependency> 

и этот кусок или код:

// parse the html from the givne string 
Document doc = Jsoup.parse(html); 
// loop over children elements of the body tag 
for (Element el:doc.select("body").select("*")) { 
    // loop over all textnodes of these children 
    for (TextNode textNode:el.textNodes()) { 
    // make sure there is some text other than whitespace 
    if (textNode.text().trim().length()>0) { 
     // show: 
     // the original node name 
     // the name of the subnode witht the text 
     // the text 
     System.out.println(el.nodeName()+"."+textNode.nodeName()+":"+textNode.text()); 
    } 
    } 
} 

Теперь я также хотел бы показать номер строки и исходный исходный код html, откуда появился textNode. Я сомневаюсь, что JSoup может сделать это (e.g. see)

и пытаюсь работа вокруг, как:

int pos = html.indexOf(textNode.outerHtml()); 

не надежно найти исходный HTML. Поэтому я предполагаю, что мне, возможно, придется перейти на другую библиотеку или подход. Jericho-html: is it possible to extract text with reference to positions in source file? имеет ответ, в котором говорится, что «Иерихон может это сделать», поскольку ссылка выше также указывает. Но указатель на реальный рабочий код отсутствует.

Whith Jericho я добрался до:

Source htmlSource=new Source(html); 
boolean bodyFound=false; 
// loop over all elements 
for (net.htmlparser.jericho.Element el:htmlSource.getAllElements()) { 
    if (el.getName().equals("body")) { 
     bodyFound=true; 
    } 
    if (bodyFound) { 
     TagType tagType = el.getStartTag().getTagType(); 
     if (tagType==StartTagType.NORMAL) { 
      String text=el.getTextExtractor().toString(); 
      if (!text.trim().equals("")) { 
       int cpos = el.getBegin();    
       System.out.println(el.getName()+"("+tagType.toString()+") line "+ htmlSource.getRow(cpos)+":"+text); 
      } 
     } // if 
    } // if 
} // for 

который довольно хорошо уже, так как это даст вам выход, как:

body(normal) line 91: Some Header. Some Text 
div(normal) line 93: Some Header 
div(normal) line 95: Some Text 

, но теперь проблема Followup что TextExtractor выводит всю текст всех субномов рекурсивно, чтобы текст отображался несколько раз.

Что было бы рабочим решением, которое фильтрует, а также вышеупомянутое решение JSoup (обратите внимание на правильный порядок текстовых элементов), но показывает исходные строки, как это делает фрагмент кода Jericho Code?

ответ

1

Вот тест Junit тестирования ожидаемого результата и на основе SourceTextExtractor Jericho, что делает работу испытания JUnit, которая основана на оригинальном исходном коде Jericho TextExtractor.

@Test 
public void testTextExtract() { 
    // https://github.com/paepcke/CorEx/blob/master/src/extraction/HTMLUtils.java 
    String htmls[] = { 
      "<!DOCTYPE html>\n" + "<html>\n" + "<body>\n" + "\n" 
        + "<h1>My First Heading</h1>\n" + "\n" 
        + "<p>My first paragraph.</p>\n" + "\n" + "</body>\n" + "</html>", 
      "<html>\n" 
        + "<body>\n" 
        + "\n" 
        + "<div id=\"myDiv\" name=\"myDiv\" title=\"Example Div Element\">\n" 
        + " <h5>Subtitle</h5>\n" 
        + " <p>This paragraph would be your content paragraph...</p>\n" 
        + " <p>Here's another content article right here.</p>\n" 
        + "</div>" + "\n" + "Text at end of body</body>\n" + "</html>" }; 
    int expectedSize[] = { 2, 4 }; 
    String expectedInfo[][]={ 
     { 
      "line 5 col 5 to line 5 col 21: My First Heading", 
      "line 7 col 4 to line 7 col 23: My first paragraph." 
     }, 
     { 
      "line 5 col 7 to line 5 col 15: Subtitle", 
      "line 6 col 6 to line 6 col 55: This paragraph would be your content paragraph...", 
      "line 7 col 6 to line 7 col 48: Here's another content article right here.", 
      "line 8 col 7 to line 9 col 20: Text at end of body" 
     } 
    }; 
    int i = 0; 
    for (String html : htmls) { 
     SourceTextExtractor extractor=new SourceTextExtractor(); 
     List<TextResult> textParts = extractor.extractTextSegments(html); 
     // List<String> textParts = HTMLCleanerTextExtractor.extractText(html); 
     int j=0; 
     for (TextResult textPart : textParts) { 
      System.out.println(textPart.getInfo()); 
      assertTrue(textPart.getInfo().startsWith(expectedInfo[i][j])); 
      j++; 
     } 
     assertEquals(expectedSize[i], textParts.size()); 
     i++; 
    } 
} 

Это адаптированный TextExtractor см http://grepcode.com/file_/repo1.maven.org/maven2/net.htmlparser.jericho/jericho-html/3.3/net/htmlparser/jericho/TextExtractor.java/?v=source

/** 
* TextExtractor that makes source line and col references available 
* http://grepcode.com/file_/repo1.maven.org/maven2/net.htmlparser.jericho/jericho-html/3.3/net/htmlparser/jericho/TextExtractor.java/?v=source 
*/ 
public class SourceTextExtractor { 

    public static class TextResult { 
     private String text; 
     private Source root; 
     private Segment segment; 
     private int line; 
     private int col; 

     /** 
     * get a textResult 
     * @param root 
     * @param segment 
     */ 
     public TextResult(Source root,Segment segment) { 
      this.root=root; 
      this.segment=segment; 
      final StringBuilder sb=new StringBuilder(segment.length()); 
      sb.append(segment); 
      setText(CharacterReference.decodeCollapseWhiteSpace(sb)); 
      int spos = segment.getBegin(); 
      line=root.getRow(spos); 
      col=root.getColumn(spos); 

     } 

     /** 
     * gets info about this TextResult 
     * @return 
     */ 
     public String getInfo() { 
      int epos=segment.getEnd(); 

      String result= 
        " line "+ line+" col "+col+ 
        " to "+ 
        " line "+ root.getRow(epos)+" col "+root.getColumn(epos)+ 
        ":"+getText(); 
      return result; 
     } 

     /** 
     * @return the text 
     */ 
     public String getText() { 
      return text; 
     } 

     /** 
     * @param text the text to set 
     */ 
     public void setText(String text) { 
      this.text = text; 
     } 

     public int getLine() { 
      return line; 
     } 

     public int getCol() { 
      return col; 
     } 

    } 

    /** 
    * extract textSegments from the given html 
    * @param html 
    * @return 
    */ 
    public List<TextResult> extractTextSegments(String html) { 
     Source htmlSource=new Source(html); 
     List<TextResult> result = extractTextSegments(htmlSource); 
     return result; 
    } 

    /** 
    * get the TextSegments from the given root segment 
    * @param root 
    * @return 
    */ 
    public List<TextResult> extractTextSegments(Source root) { 
     List<TextResult> result=new ArrayList<TextResult>(); 
     for (NodeIterator nodeIterator=new NodeIterator(root); nodeIterator.hasNext();) { 
      Segment segment=nodeIterator.next(); 
      if (segment instanceof Tag) { 
       final Tag tag=(Tag)segment; 
       if (tag.getTagType().isServerTag()) { 
        // elementContainsMarkup should be made into a TagType property one day. 
        // for the time being assume all server element content is code, although this is not true for some Mason elements. 
        final boolean elementContainsMarkup=false; 
        if (!elementContainsMarkup) { 
         final net.htmlparser.jericho.Element element=tag.getElement(); 
         if (element!=null && element.getEnd()>tag.getEnd()) nodeIterator.skipToPos(element.getEnd()); 
        } 
        continue; 
       } 
       if (tag.getTagType()==StartTagType.NORMAL) { 
        final StartTag startTag=(StartTag)tag; 
        if (tag.name==HTMLElementName.SCRIPT || tag.name==HTMLElementName.STYLE || (!HTMLElements.getElementNames().contains(tag.name))) { 
         nodeIterator.skipToPos(startTag.getElement().getEnd()); 
         continue; 
        } 

       } 
       // Treat both start and end tags not belonging to inline-level elements as whitespace: 
       if (tag.getName()==HTMLElementName.BR || !HTMLElements.getInlineLevelElementNames().contains(tag.getName())) { 
        // sb.append(' '); 
       } 
      } else { 
       if (!segment.isWhiteSpace()) 
        result.add(new TextResult(root,segment)); 
      } 
     } 
     return result; 
    } 

    /** 
    * extract the text from the given segment 
    * @param segment 
    * @return 
    */ 
    public String extractText(net.htmlparser.jericho.Segment pSegment) { 

     // http://grepcode.com/file_/repo1.maven.org/maven2/net.htmlparser.jericho/jericho-html/3.3/net/htmlparser/jericho/TextExtractor.java/?v=source 
     // this would call the code above 
     // String result=segment.getTextExtractor().toString(); 
     final StringBuilder sb=new StringBuilder(pSegment.length()); 
     for (NodeIterator nodeIterator=new NodeIterator(pSegment); nodeIterator.hasNext();) { 
      Segment segment=nodeIterator.next(); 
      if (segment instanceof Tag) { 
       final Tag tag=(Tag)segment; 
       if (tag.getTagType().isServerTag()) { 
        // elementContainsMarkup should be made into a TagType property one day. 
        // for the time being assume all server element content is code, although this is not true for some Mason elements. 
        final boolean elementContainsMarkup=false; 
        if (!elementContainsMarkup) { 
         final net.htmlparser.jericho.Element element=tag.getElement(); 
         if (element!=null && element.getEnd()>tag.getEnd()) nodeIterator.skipToPos(element.getEnd()); 
        } 
        continue; 
       } 
       if (tag.getTagType()==StartTagType.NORMAL) { 
        final StartTag startTag=(StartTag)tag; 
        if (tag.name==HTMLElementName.SCRIPT || tag.name==HTMLElementName.STYLE || (!HTMLElements.getElementNames().contains(tag.name))) { 
         nodeIterator.skipToPos(startTag.getElement().getEnd()); 
         continue; 
        } 

       } 
       // Treat both start and end tags not belonging to inline-level elements as whitespace: 
       if (tag.getName()==HTMLElementName.BR || !HTMLElements.getInlineLevelElementNames().contains(tag.getName())) { 
        sb.append(' '); 
       } 
      } else { 
       sb.append(segment); 
      } 
     } 
     final String result=net.htmlparser.jericho.CharacterReference.decodeCollapseWhiteSpace(sb); 
     return result; 
    } 
} 
2

Отсутствие функции, которая вам нужна, и jsoup - это ИМХО сложнее реализовать. Идите с Иерихоном и реализуйте что-то вроде этого, чтобы найти немедленные текстовые узлы.

package main.java.com.adacom.task; 

import java.util.Iterator; 
import java.util.LinkedList; 
import java.util.List; 

import net.htmlparser.jericho.Element; 
import net.htmlparser.jericho.EndTag; 
import net.htmlparser.jericho.Segment; 
import net.htmlparser.jericho.Source; 
import net.htmlparser.jericho.StartTag; 
import net.htmlparser.jericho.StartTagType; 
import net.htmlparser.jericho.Tag; 
import net.htmlparser.jericho.TagType; 

public class MainParser { 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 

     String html = "<body><div>divtextA<span>spanTextA<p>pText</p>spanTextB</span>divTextB</div></body>"; 

     Source htmlSource=new Source(html); 
     boolean bodyFound=false; 
     // loop over all elements 
     for (net.htmlparser.jericho.Element el:htmlSource.getAllElements()) { 
      if (el.getName().equals("body")) { 
       bodyFound=true; 
      } 
      if (bodyFound) { 
       TagType tagType = el.getStartTag().getTagType(); 
       if (tagType==StartTagType.NORMAL) { 
        String text = getOwnTextSegmentsString(el); 
        if (!text.trim().equals("")) { 
         int cpos = el.getBegin();    
         System.out.println(el.getName()+"("+tagType.toString()+") line "+ htmlSource.getRow(cpos)+":"+text); 
        } 
       } // if 
      } // if 
     } // for 

    } 

    /** 
    * this function is not used it's shown here only for reference 
    */ 
    public static Iterator<Segment> getOwnTextSegmentsIterator(Element elem) { 
     final Iterator<Segment> it = elem.getContent().getNodeIterator(); 
     final List<Segment> results = new LinkedList<Segment>(); 
     int tagCounter = 0; 
     while (it.hasNext()) { 
      Segment cur = it.next();    
      if(cur instanceof StartTag) 
       tagCounter++; 
      else if(cur instanceof EndTag) 
       tagCounter--; 

      if (!(cur instanceof Tag) && tagCounter == 0) { 
       System.out.println(cur); 
       results.add(cur); 
      } 
     } 
     return results.iterator(); 
    } 

    public static String getOwnTextSegmentsString(Element elem) { 
     final Iterator<Segment> it = elem.getContent().getNodeIterator(); 
     StringBuilder strBuilder = new StringBuilder(); 
     int tagCounter = 0; 
     while (it.hasNext()) { 
      Segment cur = it.next();    
      if(cur instanceof StartTag) 
       tagCounter++; 
      else if(cur instanceof EndTag) 
       tagCounter--; 

      if (!(cur instanceof Tag) && tagCounter == 0) { 
       strBuilder.append(cur.toString() + ' '); 
      } 
     } 
     return strBuilder.toString().trim(); 
    } 

} 
+0

Я хотел бы присудить награду вам за ваши усилия. Я не понимаю, найдет ли ваше решение текст в конце тела, как в тесте JUnit моего ответа. Я проверю и вернусь. –

+0

Я рассмотрел ваш вопрос после редактирования. К сожалению, он, похоже, не возвращает текст в правильном порядке. Я реорганизовал его для реализации пакета com.bitplan.texttools; импорт java.util.Список; public interface TextExtractor { \t Список extractTextSegments (String html); }, и он не будет создавать отдельные сегменты, но попытаться объединить весь текст элемента. В этом случае текст в начале тела или div будет помещен вместе с текстом в конце тела или div, а текст в подносах последует позже. Это не то, что сделал фильтр Jsoup. –

+0

Я понял, что у него были ошибки, но, как я упоминаю, это всего лишь пример, чтобы вы начали. Я рад, что вы нашли решение. Повеселись. – alkis