Java tutorial
/* * Leech - crawling capabilities for Apache Tika * * Copyright (C) 2012 DFKI GmbH, Author: Christian Reuschling * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. * * Contact us by mail: christian.reuschling@dfki.de */ package de.dfki.km.leech.parser.wikipedia; import info.bliki.wiki.filter.PlainTextConverter; import info.bliki.wiki.model.WikiModel; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.rmi.server.UID; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; import org.apache.tika.sax.XHTMLContentHandler; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import de.dfki.inquisition.collections.MultiValueBalancedTreeMap; import de.dfki.inquisition.collections.MultiValueHashMap; import de.dfki.inquisition.text.StringUtils; import de.dfki.km.leech.metadata.LeechMetadata; import de.dfki.km.leech.util.TikaUtils; /** * A parser implementation that can deal with mediawiki xml dump files, downloadable e.g. under:<br> * <br> * German wikipedia <br> * http://dumps.wikimedia.org/dewiki/<br> * http://dumps.wikimedia.org/dewiki/latest/dewiki-latest-pages-articles.xml.bz2<br> * English wikipedia:<br> * http://dumps.wikimedia.org/enwiki/<br> * http://dumps.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles.xml.bz2 <br> * <br> * Configure this parser inside the ParseContext: ParseContext.set(WikipediaDumpParserConfig.class, wikipediaDumpParserConfig); * * * @author Christian Reuschling, Dipl.Ing.(BA) */ public class WikipediaDumpParser implements Parser { public static class WikipediaDumpParserConfig { protected boolean determinePageRedirects = true; protected boolean parseGeoCoordinates = true; protected boolean parseInfoBoxes = false; protected boolean parseLinksAndCategories = false; public boolean getDeterminePageRedirects() { return determinePageRedirects; } public boolean getParseGeoCoordinates() { return parseGeoCoordinates; } public boolean getParseInfoBoxes() { return parseInfoBoxes; } public boolean getParseLinksAndCategories() { return parseLinksAndCategories; } public WikipediaDumpParserConfig setDeterminePageRedirects(boolean determinePageRedirects) { this.determinePageRedirects = determinePageRedirects; return this; } public WikipediaDumpParserConfig setParseGeoCoordinates(boolean parseGeoCoordinates) { this.parseGeoCoordinates = parseGeoCoordinates; return this; } public WikipediaDumpParserConfig setParseInfoBoxes(boolean parseInfoBoxes) { this.parseInfoBoxes = parseInfoBoxes; return this; } public WikipediaDumpParserConfig setParseLinksAndCategories(boolean parseLinksAndCategories) { this.parseLinksAndCategories = parseLinksAndCategories; return this; } } public static final String externalLink = "externalLink"; public static final String infobox = "infobox"; public static final String internalLink = "internalLink"; static protected final WikiModel m_wikiModel = new WikiModel("http://www.mywiki.com/wiki/${image}", "http://www.mywiki.com/wiki/${title}"); private static final long serialVersionUID = -7801896202662990477L; /** * Reads all next character events from an xmlEventReader and concatenate their data into one String * * @param xmlEventReader the xmlEventReader to get the events * * @return the data of the character events, concatenated into one String * * @throws XMLStreamException */ static protected String readNextCharEventsText(XMLEventReader xmlEventReader) throws XMLStreamException { StringBuilder strbText = new StringBuilder(""); while (xmlEventReader.hasNext()) { XMLEvent nextEvent = xmlEventReader.peek(); if (!nextEvent.isCharacters()) break; nextEvent = xmlEventReader.nextEvent(); strbText.append(nextEvent.asCharacters().getData()); } return strbText.toString(); } protected Pattern dmsCoordinatePattern = Pattern .compile("(\\d+\\.?\\d*)/(\\d*+\\.?\\d*)/(\\d*\\.?\\d*)/([NESW])"); protected String cleanAttValue(String strAttName, String strAttValue) { if (strAttValue == null) strAttValue = ""; // Angaben in Klammern kommen weg strAttValue = strAttValue.replaceAll("\\(.*?\\)", ""); // Angaben in geschweiften Klammern kommen weg strAttValue = strAttValue.replaceAll("\\{\\{.*?\\}\\}", "").trim(); if ("longitude".equals(strAttName) || "latitude".equals(strAttName)) { Matcher degreeMatcher = dmsCoordinatePattern.matcher(strAttValue); if (degreeMatcher.find()) { double dInDezimal = dmsToDecCoordinate(degreeMatcher.group(1), degreeMatcher.group(2), degreeMatcher.group(3)); if ("E".equals(degreeMatcher.group(4))) strAttValue = String.valueOf(dInDezimal); if ("W".equals(degreeMatcher.group(4))) strAttValue = String.valueOf(dInDezimal * -1); if ("N".equals(degreeMatcher.group(4))) strAttValue = String.valueOf(dInDezimal); if ("S".equals(degreeMatcher.group(4))) strAttValue = String.valueOf(dInDezimal * -1); } else { // wenn es keine Gradzahl ist, mssen wir schauen, ob es auch eine Zahl ist - es gibt auch fehlerhafte Eintrge in der Wikipedia... try { Double.valueOf(strAttValue); } catch (NumberFormatException e) { return null; } } } return strAttValue; } /** * Converts DMS ( Degrees / minutes / seconds ) to decimal format longitude / latitude * * @param strDegree * @param strMinutes * @param strSeconds * * @return the doordinate in decimal format (longitude / latitude) */ public double dmsToDecCoordinate(String strDegree, String strMinutes, String strSeconds) { double degree = 0; if (!StringUtils.nullOrWhitespace(strDegree)) degree = Double.valueOf(strDegree); double minutes = 0; if (!StringUtils.nullOrWhitespace(strMinutes)) minutes = Double.valueOf(strMinutes); double seconds = 0; if (!StringUtils.nullOrWhitespace(strSeconds)) seconds = Double.valueOf(strSeconds); return degree + (((minutes * 60) + (seconds)) / 3600); } public MultiValueHashMap<String, String> getPageTitle2Redirects(InputStream sWikipediaDump) throws FileNotFoundException, XMLStreamException { // <text xml:space="preserve">#REDIRECT [[Autopoiesis]]</text> // <text xml:space="preserve">#REDIRECT:[[Hans Leo Haler]]</text> // <text xml:space="preserve">#redirect [[Weier Hai]]</text> // #weiterleitung // <page> // <title>Autopoiesis</title> Logger.getLogger(WikipediaDumpParser.class.getName()).info("will collect redirects from wikipedia dump..."); MultiValueHashMap<String, String> hsPageTitle2Redirects = new MultiValueBalancedTreeMap<String, String>(); String strCurrentTitle = ""; XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); XMLEventReader xmlEventReader = xmlInputFactory.createXMLEventReader(sWikipediaDump, "Utf-8"); int iTitlesRead = 0; while (xmlEventReader.hasNext()) { XMLEvent xmlEvent = xmlEventReader.nextEvent(); if (!xmlEvent.isStartElement()) continue; // wenn wir einen Title haben, dann merken wir uns den, falls wir ihn brauchen if (xmlEvent.asStartElement().getName().getLocalPart().equals("title")) { strCurrentTitle = readNextCharEventsText(xmlEventReader); iTitlesRead++; if (iTitlesRead % 200000 == 0) Logger.getLogger(WikipediaDumpParser.class.getName()) .info("read doc #" + StringUtils.beautifyNumber(iTitlesRead)); continue; } if (!xmlEvent.asStartElement().getName().getLocalPart().equals("text")) continue; // jetzt haben wir ein text-tag. Wir schauen, ob jetzt ein redirect kommt // entweder kommt ein charEvent oder ein EndEvent. Leere Texte gibts wohl auch XMLEvent nextEvent = xmlEventReader.peek(); if (!nextEvent.isCharacters()) continue; String strCharEventData = readNextCharEventsText(xmlEventReader); if (strCharEventData == null) continue; strCharEventData = strCharEventData.trim(); boolean bRedirect = false; if (strCharEventData.length() >= 9 && strCharEventData.substring(0, 9).equalsIgnoreCase("#redirect")) bRedirect = true; if (!bRedirect && strCharEventData.length() >= 8 && strCharEventData.substring(0, 8).equalsIgnoreCase("redirect") && !strCharEventData.contains("\n")) bRedirect = true; if (!bRedirect && strCharEventData.length() >= 14 && strCharEventData.substring(0, 14).equalsIgnoreCase("#weiterleitung")) bRedirect = true; if (!bRedirect && strCharEventData.length() >= 13 && strCharEventData.substring(0, 13).equalsIgnoreCase("weiterleitung") && !strCharEventData.contains("\n")) bRedirect = true; if (!bRedirect) continue; // wir haben einen redirect - der wird in unsere Datenstruktur eingetragen int iStart = strCharEventData.indexOf("[["); int iEnd = strCharEventData.indexOf("]]"); if (iStart < 0 || iEnd < 0) continue; if (iEnd <= iStart) continue; if ((iStart + 2) > strCharEventData.length() || iEnd > strCharEventData.length()) continue; String strRedirectTarget = strCharEventData.substring(iStart + 2, iEnd).trim(); hsPageTitle2Redirects.add(strRedirectTarget, strCurrentTitle); // if("Venceslav Konstantinov".equalsIgnoreCase(strCurrentTitle) || "Venceslav Konstantinov".equalsIgnoreCase(strRedirectTarget)) // System.out.println("redirect found: (" + hsPageTitle2Redirects.keySize() + ") " + strCurrentTitle + " => '" + strRedirectTarget + "'"); } Logger.getLogger(WikipediaDumpParser.class.getName()) .info("Redirects found: " + StringUtils.beautifyNumber(hsPageTitle2Redirects.valueSize())); return hsPageTitle2Redirects; } @Override public Set<MediaType> getSupportedTypes(ParseContext context) { return Collections.singleton(MediaType.application("wikipedia+xml")); } @Override public void parse(InputStream stream, ContentHandler handler, Metadata metadata, ParseContext context) throws IOException, SAXException, TikaException { try { // wir iterieren schn ber die page-Eintrge. Darin gibt es dann title, timestamp, <contributor> => <username> und text. den text mssen // wir noch bereinigen. dazu nehmen wir eine Vorverarbeitung mit bliki - dazu mssen wir aber selbst nochmal den String vorbereiten und // nachbereinigen. Leider. WikipediaDumpParserConfig wikipediaDumpParserConfig = context.get(WikipediaDumpParserConfig.class); if (wikipediaDumpParserConfig == null) { Logger.getLogger(WikipediaDumpParser.class.getName()) .info("No wikipedia parser config found. Will take the default one."); wikipediaDumpParserConfig = new WikipediaDumpParserConfig(); } TikaInputStream tikaStream = TikaInputStream.get(stream); File fWikipediaDumpFile4Stream = tikaStream.getFile(); MultiValueHashMap<String, String> hsPageTitle2Redirects = new MultiValueHashMap<String, String>(); if (wikipediaDumpParserConfig.determinePageRedirects) hsPageTitle2Redirects = getPageTitle2Redirects(new FileInputStream(fWikipediaDumpFile4Stream)); HashSet<String> hsRedirectPageTitles = new HashSet<String>(hsPageTitle2Redirects.values()); String strCleanedText = ""; String strBaseURL = null; XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); XMLEventReader xmlEventReader = xmlInputFactory .createXMLEventReader(new FileInputStream(fWikipediaDumpFile4Stream), "Utf-8"); while (xmlEventReader.hasNext()) { XMLEvent xmlEvent = xmlEventReader.nextEvent(); if (xmlEvent.isEndElement() && xmlEvent.asEndElement().getName().getLocalPart().equals("page")) { if (metadata.size() == 0) continue; // den mimetype wollen wir auch noch in den Metadaten haben metadata.add(Metadata.CONTENT_TYPE, "application/wikipedia+xml"); XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata); xhtml.startDocument(); xhtml.startElement("p"); xhtml.characters(strCleanedText.toCharArray(), 0, strCleanedText.length()); xhtml.endElement("p"); xhtml.endDocument(); } if (!xmlEvent.isStartElement()) continue; // ##### die siteinfo if (strBaseURL == null && xmlEvent.asStartElement().getName().getLocalPart().equals("base")) { // http://de.wikipedia.org/wiki/Wikipedia:Hauptseite =>http://de.wikipedia.org/wiki/ strBaseURL = readNextCharEventsText(xmlEventReader); strBaseURL = strBaseURL.substring(0, strBaseURL.lastIndexOf("/") + 1); } // ##### die page if (xmlEvent.asStartElement().getName().getLocalPart().equals("page")) { for (String strKey : metadata.names()) metadata.remove(strKey); } // ##### der Title if (xmlEvent.asStartElement().getName().getLocalPart().equals("title")) { // wir merken uns immer den aktuellen Titel String strCurrentTitle = readNextCharEventsText(xmlEventReader); if (strCurrentTitle.equalsIgnoreCase("DuckDuckGo")) { int fasd = 8; } if (strCurrentTitle.toLowerCase().contains("duck") && strCurrentTitle.toLowerCase().contains("go")) { int is = 666; } // wenn der Titel eine redirect-Page ist, dann tragen wir die ganze Page aus der EventQueue aus, springen an das endPage, und // haben somit diese Seite ignoriert. Ferner ignorieren wir auch spezielle wikipedia-Seiten String strSmallTitle = strCurrentTitle.trim().toLowerCase(); if (hsRedirectPageTitles.contains(strCurrentTitle) || hsRedirectPageTitles.contains(strSmallTitle) || hsRedirectPageTitles.contains(strCurrentTitle.trim()) || strSmallTitle.startsWith("category:") || strSmallTitle.startsWith("kategorie:") || strSmallTitle.startsWith("vorlage:") || strSmallTitle.startsWith("template:") || strSmallTitle.startsWith("hilfe:") || strSmallTitle.startsWith("help:") || strSmallTitle.startsWith("wikipedia:") || strSmallTitle.startsWith("portal:") || strSmallTitle.startsWith("mediawiki:")) { while (true) { XMLEvent nextXmlEvent = xmlEventReader.nextEvent(); if (nextXmlEvent.isEndElement() && nextXmlEvent.asEndElement().getName().getLocalPart().equals("page")) break; } } else { metadata.add(Metadata.TITLE, strCurrentTitle); metadata.add(Metadata.SOURCE, strBaseURL + strCurrentTitle); for (String strRedirect : hsPageTitle2Redirects.get(strCurrentTitle)) { // wir ignorieren Titel, die sich lediglich durch gro/kleinschreibung unterscheiden if (!StringUtils.containsIgnoreCase(strRedirect, metadata.getValues(Metadata.TITLE))) metadata.add(Metadata.TITLE, strRedirect); } } continue; } // ##### der text if (xmlEvent.asStartElement().getName().getLocalPart().equals("text")) { String strText = readNextCharEventsText(xmlEventReader); if (wikipediaDumpParserConfig.parseLinksAndCategories) parseLinksAndCategories(strText, strBaseURL, metadata, handler); if (wikipediaDumpParserConfig.parseInfoBoxes) parseInfoBox(strText, metadata, handler); if (wikipediaDumpParserConfig.parseGeoCoordinates) parseGeoCoordinates(strText, metadata); // aufgrund einiger Defizite in dem verwendeten cleaner mssen wir hier leider noch zu-und nacharbeiten strText = strText.replaceAll("==\n", "==\n\n"); strText = strText.replaceAll("\n==", "\n\n=="); strCleanedText = m_wikiModel.render(new PlainTextConverter(), strText); strCleanedText = strCleanedText.replaceAll("\\{\\{", " "); strCleanedText = strCleanedText.replaceAll("\\}\\}", " "); strCleanedText = StringEscapeUtils.unescapeHtml4(strCleanedText); continue; } // ##### der timestamp if (xmlEvent.asStartElement().getName().getLocalPart().equals("timestamp")) { String strTimestamp = readNextCharEventsText(xmlEventReader); metadata.add(Metadata.MODIFIED, strTimestamp); continue; } // ##### der username if (xmlEvent.asStartElement().getName().getLocalPart().equals("username")) { String strUsername = readNextCharEventsText(xmlEventReader); metadata.add(Metadata.CREATOR, strUsername); continue; } } } catch (Exception e) { Logger.getLogger(WikipediaDumpParser.class.getName()).log(Level.SEVERE, "Error", e); } } protected void parseGeoCoordinates(String strText, Metadata metadata) { Matcher matcher = Pattern.compile("(?s)\\{\\{Coordinate (.*?)\\}\\}").matcher(strText); coord: while (matcher.find()) { String strCoordinates = matcher.group(1); String[] straCoordinates = strCoordinates.split("\\|"); for (String strPiece : straCoordinates) { if (strPiece.contains("text=")) continue coord; Matcher degreeMatcher = dmsCoordinatePattern.matcher(strPiece); if (degreeMatcher.find()) { // wir haben eine Angabe in DMS double dInDezimal = dmsToDecCoordinate(degreeMatcher.group(1), degreeMatcher.group(2), degreeMatcher.group(3)); if ("E".equals(degreeMatcher.group(4))) metadata.add("longitude", String.valueOf(dInDezimal)); if ("W".equals(degreeMatcher.group(4))) metadata.add("longitude", String.valueOf(dInDezimal * -1)); if ("N".equals(degreeMatcher.group(4))) metadata.add("latitude", String.valueOf(dInDezimal)); if ("S".equals(degreeMatcher.group(4))) metadata.add("latitude", String.valueOf(dInDezimal * -1)); } else { // wir haben eine Angabe in dezimal - wenn alles stimmt if (strPiece.contains("EW=") || strPiece.contains("NS=")) { String strAttValue = strPiece.substring(3).trim(); try { Double.valueOf(strAttValue); if (strPiece.contains("EW=")) metadata.add("longitude", strAttValue); if (strPiece.contains("NS=")) metadata.add("latitude", strAttValue); } catch (NumberFormatException e) { // NOP - the geo coordinate entry is broken } } } } } } protected void parseInfoBox(String strText, Metadata metadata, ContentHandler handler) throws SAXException { // att-value paare mit | getrennt. Innerhalb eines values gibt es auch Zeilenumbrche (mit '<br />') - dies gilt als Aufzhlung // |Single1 |Datum1 , Besetzung1a Besetzung1b, Sonstiges1Titel |Sonstiges1Inhalt , Coverversion3 |Jahr3 // | 1Option = 3 // | 1Option Name = Demos // | 1Option Link = Demos // | 1Option Color = // als erstes schneiden wir mal die Infobox raus. (?m) ist multiline und (?s) ist dotall ('.' matcht auch line breaks) int iStartInfoBox = -1; int iEndInfoBox = -1; MatchResult infoMatch = StringUtils.findFirst("\\{\\{\\s*Infobox", strText); if (infoMatch != null) { iStartInfoBox = infoMatch.start(); iEndInfoBox = StringUtils.findMatchingBracket(iStartInfoBox, strText) + 1; } else return; if (strText.length() < 3 || strText.length() < iEndInfoBox || iEndInfoBox <= 0 || (iStartInfoBox + 2) > iEndInfoBox) return; String strInfoBox = ""; strInfoBox = strText.substring(iStartInfoBox + 2, iEndInfoBox); if (strInfoBox.length() < 5) return; String strCleanedInfoBox = m_wikiModel.render(new PlainTextConverter(), strInfoBox.replaceAll("<br />", "<br />")); // da wir hier eigentlich relationierte Datenstze haben, machen wir auch einzelne, separierte Dokumente draus // System.out.println(strCleanedInfoBox); // System.out.println(strCleanedInfoBox.substring(0, strCleanedInfoBox.indexOf("\n")).trim()); // erste Zeile bezeichnet die InfoBox int iIndex = strCleanedInfoBox.indexOf("|"); if (iIndex == -1) iIndex = strCleanedInfoBox.indexOf("\n"); if (iIndex == -1) return; String strInfoBoxName = strCleanedInfoBox.substring(7, iIndex).trim(); metadata.add(infobox, strInfoBoxName); String[] straCleanedInfoBoxSplit = strCleanedInfoBox.split("\\s*\\|\\s*"); HashMap<String, MultiValueHashMap<String, String>> hsSubDocId2AttValuePairsOfSubDoc = new HashMap<String, MultiValueHashMap<String, String>>(); for (String strAttValuePair : straCleanedInfoBoxSplit) { // System.out.println("\nattValPair unsplittet " + strAttValuePair); // die Dinger sind mit einem '=' getrennt String[] straAtt2Value = strAttValuePair.split("="); if (straAtt2Value.length == 0 || straAtt2Value[0] == null) continue; if (straAtt2Value.length < 2 || straAtt2Value[1] == null) continue; String strAttName = straAtt2Value[0].trim(); String strAttValues = straAtt2Value[1]; if (StringUtils.nullOrWhitespace(strAttValues)) continue; // Innerhalb eines values gibt es auch Zeilenumbrche (mit '<br />' bzw. '<br />') - dies gilt als Aufzhlung String[] straAttValues = strAttValues.split(Pattern.quote("<br />")); // XXX wir werfen zusatzangaben in Klammern erst mal weg - man knnte sie auch als attnameAddInfo in einem extra Attribut speichern - // allerdings mu man dann wieder aufpassen, ob nicht ein subDocument entstehen mu (Bsp. mehrere Genre-entries mit jeweiliger // Jahreszahl) // der Attributname entscheidet nun, ob ein Dokument ausgelagert werden soll oder nicht. Ist darin eine Zahl enthalten, dann entfernen // wir diese und gruppieren alle att-value-paare mit dieser Zahl in einen extra Datensatz (MultiValueHashMap) Matcher numberMatcher = Pattern.compile("([\\D]*)(\\d+)([\\D]*)").matcher(strAttName); if (!numberMatcher.find()) { // wir haben keine Zahl im AttNamen - wir tragen diesen Wert einfach in die Metadaten ein. for (String strAttValue : straAttValues) { String strCleanedAttValue = cleanAttValue(strAttName, strAttValue); if (strCleanedAttValue != null) metadata.add(strAttName, strCleanedAttValue); } } else { // wir haben eine Zahl im Namen - wir tragen den Wert in einem SubDocument unter der Id <zahl> ein String strPrefix = numberMatcher.group(1); String strNumber = numberMatcher.group(2); String strSuffix = numberMatcher.group(3); String strDataSetId = strPrefix + strNumber; String strFinalAttName = strPrefix + strSuffix; // wenn wir noch mehr Zahlen haben, dann haben wir geloost - und tragen es einfach ein if (numberMatcher.find()) { for (String strAttValue : straAttValues) { String strCleanedAttValue = cleanAttValue(strFinalAttName, strAttValue); if (strCleanedAttValue != null) metadata.add(strFinalAttName, strCleanedAttValue); } } // System.out.println("prefix " + strPrefix); // System.out.println("num " + strDataSetId); // System.out.println("suffix " + strSuffix); MultiValueHashMap<String, String> hsAttname2ValueOfSubDoc = hsSubDocId2AttValuePairsOfSubDoc .get(strDataSetId); if (hsAttname2ValueOfSubDoc == null) { hsAttname2ValueOfSubDoc = new MultiValueHashMap<String, String>(); hsSubDocId2AttValuePairsOfSubDoc.put(strDataSetId, hsAttname2ValueOfSubDoc); } for (String strAttValue : straAttValues) hsAttname2ValueOfSubDoc.add(strFinalAttName, strAttValue.replaceAll("\\(.*?\\)", "").trim()); } } String strPageId = new UID().toString(); metadata.add(LeechMetadata.id, strPageId); // we have to use the same metadata Object Metadata metadataBackup4ParentPage = TikaUtils.copyMetadata(metadata); for (MultiValueHashMap<String, String> hsAttValuePairsOfSubDoc : hsSubDocId2AttValuePairsOfSubDoc .values()) { TikaUtils.clearMetadata(metadata); // die Referenz zu meinem parent metadata.add(LeechMetadata.parentId, strPageId); metadata.add(infobox, strInfoBoxName); String strChildId = new UID().toString(); metadata.add(LeechMetadata.id, strChildId); // zum rckreferenzieren geben wir dem parent auch noch unsere id metadataBackup4ParentPage.add(LeechMetadata.childId, strChildId); for (Entry<String, String> attName2Value4SubDoc : hsAttValuePairsOfSubDoc.entryList()) { String strAttName = attName2Value4SubDoc.getKey(); String strAttValue = attName2Value4SubDoc.getValue(); String strCleanedAttValue = cleanAttValue(strAttName, strAttValue); if (strCleanedAttValue != null) metadata.add(strAttName, strCleanedAttValue); } metadata.add(Metadata.CONTENT_TYPE, "application/wikipedia-meta+xml"); // so erreichen wir, da im bergeordneten ContentHandler mehrere Docs ankommen :) XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata); xhtml.startDocument(); xhtml.endDocument(); } TikaUtils.clearMetadata(metadata); TikaUtils.copyMetadataFromTo(metadataBackup4ParentPage, metadata); } protected void parseLinksAndCategories(String strText, String strBaseURL, Metadata metadata, ContentHandler handler) throws SAXException { // wir parsen jetzt auch noch die Infoboxen, und tragen auch die Kategorien mit in die Metadaten ein. Kinga fnde auch noch die // Links aus dem Text ganz hbsch // Syntax: // interne links: [[Zielartikel|alternativer Text]] (alternativer text optional) es gibt auch noch den hop mit der raute:[[Titel#berschrift // des Abschnitts]], wenn vor der Raute nix steht, dann wird innerhalb der Seite referenziert // externe links: [http://wissen.dradio.de/index.40.de.html?dram:article_id=6990 "Portrt" ber Alan Smithee, DRadio Wissen] Trennung durch // Leerzeichen // Kategorien: [[Category:Category name|Sortkey]] [[Kategorie:Xyz]] // Infoboxen: // http://de.wikipedia.org/wiki/Vorlage:Infobox_Festival // http://de.wikipedia.org/wiki/Kategorie:Vorlage:Infobox_Musik // [ nicht gefolgt von [ (beliebige Zeichen non-greedy) gefolgt von ] // String strRegExp = "\\[(?!\\[)(.*?)\\]"; String strRegExp = "\\[(.*?)\\]"; HashSet<String> hsInternalLinks = new HashSet<String>(); HashSet<String> hsExternalLinks = new HashSet<String>(); Matcher matcher = Pattern.compile(strRegExp).matcher(strText); while (matcher.find()) { String strMatch = matcher.group(1); // die mit vorangestelltem '[' sind die internen links, sowie die Kategorien. Die ohne sind die externen links. if (strMatch.startsWith("[")) { // wir haben einen internen link oder eine Kategorie String strLinkOrCategory = strMatch.substring(1); int iIndex = strLinkOrCategory.indexOf("|"); if (iIndex != -1) strLinkOrCategory = strLinkOrCategory.substring(0, iIndex); iIndex = strLinkOrCategory.indexOf(":"); if (iIndex == -1) { // wir haben einen internen link - wenn wir keine vorangestellte Raute haben, dann bernehmen wir ihn. Wenn wir eine Raute in der // Mitte haben, dann bernehmen wir lediglich den ersten Teil iIndex = strLinkOrCategory.indexOf("#"); if (iIndex == 0) continue; else if (iIndex == -1) hsInternalLinks.add(strBaseURL + strLinkOrCategory); else hsInternalLinks.add(strBaseURL + strLinkOrCategory.substring(0, iIndex)); } else { // wir haben eine Variable - wenn es eine Kategorie ist, dann bernehmen wir sie..oder wir bernehmen erst mal alle String strAttName = strLinkOrCategory.substring(0, iIndex); String strCleanedAttValue = cleanAttValue(strAttName, strLinkOrCategory.substring(iIndex + 1)); if (strCleanedAttValue != null) metadata.add(strAttName, strCleanedAttValue); } } else { // wir haben einen externen link - wir werfen das label weg String strExternalLink = strMatch; int iIndex = strMatch.indexOf(" "); if (iIndex != -1) strExternalLink = strMatch.substring(0, iIndex); hsExternalLinks.add(strExternalLink); } } for (String strInternalLink : hsInternalLinks) metadata.add(internalLink, strInternalLink); for (String strExternalLink : hsExternalLinks) metadata.add(externalLink, strExternalLink); } }