001: //plasmaParserDocument.java
002: //------------------------
003: //part of YaCy
004: //(C) by Michael Peter Christen; mc@anomic.de
005: //first published on http://www.anomic.de
006: //Frankfurt, Germany, 2005
007: //
008: //last major change: 24.04.2005
009: //
010: //This program is free software; you can redistribute it and/or modify
011: //it under the terms of the GNU General Public License as published by
012: //the Free Software Foundation; either version 2 of the License, or
013: //(at your option) any later version.
014: //
015: //This program is distributed in the hope that it will be useful,
016: //but WITHOUT ANY WARRANTY; without even the implied warranty of
017: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
018: //GNU General Public License for more details.
019: //
020: //You should have received a copy of the GNU General Public License
021: //along with this program; if not, write to the Free Software
022: //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
023: //
024: //Using this software in any meaning (reading, learning, copying, compiling,
025: //running) means that you agree that the Author(s) is (are) not responsible
026: //for cost, loss of data or any harm that may be caused directly or indirectly
027: //by usage of this softare or this documentation. The usage of this software
028: //is on your own risk. The installation and usage (starting/running) of this
029: //software may allow other people or application to access your computer and
030: //any attached devices and is highly dependent on the configuration of the
031: //software which must be done by the user of the software; the author(s) is
032: //(are) also not responsible for proper configuration and usage of the
033: //software, even if provoked by documentation provided together with
034: //the software.
035: //
036: //Any changes to this file according to the GPL as documented in the file
037: //gpl.txt aside this file in the shipment you received can be done to the
038: //lines that follows this copyright notice here, but changes must not be
039: //done inside the copyright notive above. A re-distribution must contain
040: //the intact and unchanged copyright notice.
041: //Contributions and changes to the program code must be marked as such.
042:
043: package de.anomic.plasma;
044:
045: import java.io.BufferedInputStream;
046: import java.io.ByteArrayInputStream;
047: import java.io.File;
048: import java.io.FileInputStream;
049: import java.io.IOException;
050: import java.io.InputStream;
051:
052: import de.anomic.server.serverCachedFileOutputStream;
053: import de.anomic.server.serverFileUtils;
054: import de.anomic.yacy.yacyURL;
055:
056: import java.util.Arrays;
057: import java.util.HashMap;
058: import java.util.Iterator;
059: import java.util.LinkedList;
060: import java.util.List;
061: import java.util.Map;
062: import java.util.TreeSet;
063:
064: import de.anomic.htmlFilter.htmlFilterImageEntry;
065: import de.anomic.plasma.parser.Parser;
066:
067: public class plasmaParserDocument {
068:
069: private yacyURL source; // the source url
070: private String mimeType; // mimeType as taken from http header
071: private String charset; // the charset of the document
072: private List<String> keywords; // most resources provide a keyword field
073: private StringBuffer title; // a document title, taken from title or h1 tag; shall appear as headline of search result
074: private StringBuffer creator; // author or copyright
075: private List<String> sections; // if present: more titles/headlines appearing in the document
076: private StringBuffer description; // an abstract, if present: short content description
077: private Object text; // the clear text, all that is visible
078: private Map<yacyURL, String> anchors; // all links embedded as clickeable entities (anchor tags)
079: private TreeSet<htmlFilterImageEntry> images; // all visible pictures in document
080: // the anchors and images - Maps are URL-to-EntityDescription mappings.
081: // The EntityDescription appear either as visible text in anchors or as alternative
082: // text in image tags.
083: private Map<yacyURL, String> hyperlinks, audiolinks, videolinks,
084: applinks;
085: private Map<String, String> emaillinks;
086: private yacyURL favicon;
087: private boolean resorted;
088: private InputStream textStream;
089:
090: protected plasmaParserDocument(yacyURL location, String mimeType,
091: String charset, String[] keywords, String title,
092: String author, String[] sections, String abstrct,
093: Object text, Map<yacyURL, String> anchors,
094: TreeSet<htmlFilterImageEntry> images) {
095: this .source = location;
096: this .mimeType = (mimeType == null) ? "application/octet-stream"
097: : mimeType;
098: this .charset = charset;
099: this .keywords = (keywords == null) ? new LinkedList<String>()
100: : Arrays.asList(keywords);
101: this .title = (title == null) ? new StringBuffer()
102: : new StringBuffer(title);
103: this .creator = (author == null) ? new StringBuffer()
104: : new StringBuffer(author);
105: this .sections = (sections == null) ? new LinkedList<String>()
106: : Arrays.asList(sections);
107: this .description = (abstrct == null) ? new StringBuffer()
108: : new StringBuffer(abstrct);
109: this .anchors = (anchors == null) ? new HashMap<yacyURL, String>(
110: 0)
111: : anchors;
112: this .images = (images == null) ? new TreeSet<htmlFilterImageEntry>()
113: : images;
114: this .hyperlinks = null;
115: this .audiolinks = null;
116: this .videolinks = null;
117: this .applinks = null;
118: this .emaillinks = null;
119: this .resorted = false;
120:
121: if (text == null)
122: try {
123: this .text = new serverCachedFileOutputStream(
124: Parser.MAX_KEEP_IN_MEMORY_SIZE);
125: } catch (IOException e) {
126: e.printStackTrace();
127: this .text = new StringBuffer();
128: }
129: else {
130: this .text = text;
131: }
132: }
133:
134: public plasmaParserDocument(yacyURL location, String mimeType,
135: String charset) {
136: this (location, mimeType, charset, null, null, null, null, null,
137: (Object) null, null, null);
138: }
139:
140: public plasmaParserDocument(yacyURL location, String mimeType,
141: String charset, String[] keywords, String title,
142: String author, String[] sections, String abstrct,
143: byte[] text, Map<yacyURL, String> anchors,
144: TreeSet<htmlFilterImageEntry> images) {
145: this (location, mimeType, charset, keywords, title, author,
146: sections, abstrct, (Object) text, anchors, images);
147: }
148:
149: public plasmaParserDocument(yacyURL location, String mimeType,
150: String charset, String[] keywords, String title,
151: String author, String[] sections, String abstrct,
152: File text, Map<yacyURL, String> anchors,
153: TreeSet<htmlFilterImageEntry> images) {
154: this (location, mimeType, charset, keywords, title, author,
155: sections, abstrct, (Object) text, anchors, images);
156: }
157:
158: public plasmaParserDocument(yacyURL location, String mimeType,
159: String charset, String[] keywords, String title,
160: String author, String[] sections, String abstrct,
161: serverCachedFileOutputStream text,
162: Map<yacyURL, String> anchors,
163: TreeSet<htmlFilterImageEntry> images) {
164: this (location, mimeType, charset, keywords, title, author,
165: sections, abstrct, (Object) text, anchors, images);
166: }
167:
168: /*
169: DC according to rfc 5013
170:
171: * dc_title
172: * dc_creator
173: * dc_subject
174: * dc_description
175: * dc_publisher
176: dc_contributor
177: dc_date
178: dc_type
179: * dc_format
180: * dc_identifier
181: * dc_source
182: dc_language
183: dc_relation
184: dc_coverage
185: dc_rights
186: */
187:
188: public String dc_title() {
189: return title.toString();
190: }
191:
192: public String dc_creator() {
193: if (creator != null)
194: return creator.toString();
195: else
196: return new String();
197: }
198:
199: public String dc_subject(char separator) {
200: // sort out doubles and empty words
201: TreeSet<String> hs = new TreeSet<String>();
202: String s;
203: for (int i = 0; i < this .keywords.size(); i++) {
204: if (this .keywords.get(i) == null)
205: continue;
206: s = ((String) this .keywords.get(i)).trim();
207: if (s.length() > 0)
208: hs.add(s.toLowerCase());
209: }
210: if (hs.size() == 0)
211: return "";
212: // generate a new list
213: StringBuffer sb = new StringBuffer(this .keywords.size() * 6);
214: Iterator<String> i = hs.iterator();
215: while (i.hasNext())
216: sb.append(i.next()).append(separator);
217: return sb.substring(0, sb.length() - 1);
218: }
219:
220: public String dc_description() {
221: if (description != null)
222: return description.toString();
223: else
224: return dc_title();
225: }
226:
227: public String dc_publisher() {
228: // if we don't have a publisher, simply return the host/domain name
229: return this .source.getHost();
230: }
231:
232: public String dc_format() {
233: return this .mimeType;
234: }
235:
236: public String dc_identifier() {
237: return "yacy.net:" + this .source.hash();
238: }
239:
240: public yacyURL dc_source() {
241: return this .source;
242: }
243:
244: /**
245: * @return the supposed charset of this document or <code>null</code> if unknown
246: */
247: public String getCharset() {
248: return this .charset;
249: }
250:
251: public String[] getSectionTitles() {
252: if (sections != null) {
253: return (String[]) sections.toArray(new String[this .sections
254: .size()]);
255: } else {
256: return new String[] { dc_title() };
257: }
258: }
259:
260: public InputStream getText() {
261: try {
262: if (this .text == null)
263: return null;
264:
265: if (this .text instanceof File) {
266: this .textStream = new BufferedInputStream(
267: new FileInputStream((File) this .text));
268: } else if (this .text instanceof byte[]) {
269: this .textStream = new ByteArrayInputStream(
270: (byte[]) this .text);
271: } else if (this .text instanceof serverCachedFileOutputStream) {
272: return ((serverCachedFileOutputStream) this .text)
273: .getContent();
274: }
275: return this .textStream;
276: } catch (Exception e) {
277: e.printStackTrace();
278: }
279: return null;
280: }
281:
282: public byte[] getTextBytes() {
283: try {
284: if (this .text == null)
285: return new byte[0];
286:
287: if (this .text instanceof File) {
288: return serverFileUtils.read((File) this .text);
289: } else if (this .text instanceof byte[]) {
290: return (byte[]) this .text;
291: } else if (this .text instanceof serverCachedFileOutputStream) {
292: serverCachedFileOutputStream ffbaos = (serverCachedFileOutputStream) this .text;
293: if (ffbaos.isFallback()) {
294: return serverFileUtils.read(ffbaos.getContent());
295: } else {
296: return ffbaos.getContentBAOS();
297: }
298: }
299: } catch (Exception e) {
300: e.printStackTrace();
301: }
302: return new byte[0];
303: }
304:
305: public long getTextLength() {
306: if (this .text == null)
307: return 0;
308: if (this .text instanceof File)
309: return ((File) this .text).length();
310: else if (this .text instanceof byte[])
311: return ((byte[]) this .text).length;
312: else if (this .text instanceof serverCachedFileOutputStream) {
313: return ((serverCachedFileOutputStream) this .text)
314: .getLength();
315: }
316:
317: return -1;
318: }
319:
320: public Iterator<StringBuffer> getSentences(boolean pre) {
321: if (this .text == null)
322: return null;
323: plasmaCondenser.sentencesFromInputStreamEnum e = plasmaCondenser
324: .sentencesFromInputStream(getText(), this .charset);
325: e.pre(pre);
326: return e;
327: }
328:
329: public List<String> getKeywords() {
330: return this .keywords;
331: }
332:
333: public Map<yacyURL, String> getAnchors() {
334: // returns all links embedded as anchors (clickeable entities)
335: // this is a url(String)/text(String) map
336: return anchors;
337: }
338:
339: // the next three methods provide a calculated view on the getAnchors/getImages:
340:
341: public Map<yacyURL, String> getHyperlinks() {
342: // this is a subset of the getAnchor-set: only links to other hyperrefs
343: if (!resorted)
344: resortLinks();
345: return hyperlinks;
346: }
347:
348: public Map<yacyURL, String> getAudiolinks() {
349: if (!resorted)
350: resortLinks();
351: return this .audiolinks;
352: }
353:
354: public Map<yacyURL, String> getVideolinks() {
355: if (!resorted)
356: resortLinks();
357: return this .videolinks;
358: }
359:
360: public TreeSet<htmlFilterImageEntry> getImages() {
361: // returns all links enbedded as pictures (visible in document)
362: // this resturns a htmlFilterImageEntry collection
363: if (!resorted)
364: resortLinks();
365: return images;
366: }
367:
368: public Map<yacyURL, String> getApplinks() {
369: if (!resorted)
370: resortLinks();
371: return this .applinks;
372: }
373:
374: public Map<String, String> getEmaillinks() {
375: // this is part of the getAnchor-set: only links to email addresses
376: if (!resorted)
377: resortLinks();
378: return emaillinks;
379: }
380:
381: private synchronized void resortLinks() {
382:
383: // extract hyperlinks, medialinks and emaillinks from anchorlinks
384: yacyURL url;
385: String u;
386: int extpos, qpos;
387: String ext = null;
388: Iterator<Map.Entry<yacyURL, String>> i = anchors.entrySet()
389: .iterator();
390: hyperlinks = new HashMap<yacyURL, String>();
391: videolinks = new HashMap<yacyURL, String>();
392: audiolinks = new HashMap<yacyURL, String>();
393: applinks = new HashMap<yacyURL, String>();
394: emaillinks = new HashMap<String, String>();
395: TreeSet<htmlFilterImageEntry> collectedImages = new TreeSet<htmlFilterImageEntry>(); // this is a set that is collected now and joined later to the imagelinks
396: Map.Entry<yacyURL, String> entry;
397: while (i.hasNext()) {
398: entry = i.next();
399: url = entry.getKey();
400: if (url == null)
401: continue;
402: u = url.toNormalform(true, false);
403: if ((u != null) && (u.startsWith("mailto:"))) {
404: emaillinks.put(u.substring(7), entry.getValue());
405: } else {
406: extpos = u.lastIndexOf(".");
407: if (extpos > 0) {
408: if (((qpos = u.indexOf("?")) >= 0)
409: && (qpos > extpos)) {
410: ext = u.substring(extpos + 1, qpos)
411: .toLowerCase();
412: } else {
413: ext = u.substring(extpos + 1).toLowerCase();
414: }
415: if (plasmaParser.mediaExtContains(ext)) {
416: // this is not a normal anchor, its a media link
417: if (plasmaParser.imageExtContains(ext)) {
418: collectedImages
419: .add(new htmlFilterImageEntry(url,
420: (String) entry.getValue(),
421: -1, -1));
422: } else if (plasmaParser.audioExtContains(ext))
423: audiolinks.put(url, (String) entry
424: .getValue());
425: else if (plasmaParser.videoExtContains(ext))
426: videolinks.put(url, (String) entry
427: .getValue());
428: else if (plasmaParser.appsExtContains(ext))
429: applinks
430: .put(url, (String) entry.getValue());
431: } else {
432: hyperlinks.put(url, (String) entry.getValue());
433: }
434: }
435: }
436: }
437:
438: // add image links that we collected from the anchors to the image map
439: Iterator<htmlFilterImageEntry> j = collectedImages.iterator();
440: htmlFilterImageEntry iEntry;
441: while (j.hasNext()) {
442: iEntry = (htmlFilterImageEntry) j.next();
443: if (!images.contains(iEntry))
444: images.add(iEntry);
445: }
446:
447: // expand the hyperlinks:
448: // we add artificial hyperlinks to the hyperlink set
449: // that can be calculated from given hyperlinks and imagelinks
450:
451: hyperlinks.putAll(plasmaParser.allReflinks(images));
452: hyperlinks
453: .putAll(plasmaParser.allReflinks(audiolinks.keySet()));
454: hyperlinks
455: .putAll(plasmaParser.allReflinks(videolinks.keySet()));
456: hyperlinks.putAll(plasmaParser.allReflinks(applinks.keySet()));
457: hyperlinks
458: .putAll(plasmaParser.allSubpaths(hyperlinks.keySet()));
459: hyperlinks.putAll(plasmaParser.allSubpaths(images));
460: hyperlinks
461: .putAll(plasmaParser.allSubpaths(audiolinks.keySet()));
462: hyperlinks
463: .putAll(plasmaParser.allSubpaths(videolinks.keySet()));
464: hyperlinks.putAll(plasmaParser.allSubpaths(applinks.keySet()));
465:
466: // don't do this again
467: this .resorted = true;
468: }
469:
470: public void addSubDocument(plasmaParserDocument doc)
471: throws IOException {
472: this .sections.addAll(Arrays.asList(doc.getSectionTitles()));
473:
474: if (this .title.length() > 0)
475: this .title.append('\n');
476: this .title.append(doc.dc_title());
477:
478: this .keywords.addAll(doc.getKeywords());
479:
480: if (this .description.length() > 0)
481: this .description.append('\n');
482: this .description.append(doc.dc_description());
483:
484: if (!(this .text instanceof serverCachedFileOutputStream)) {
485: this .text = new serverCachedFileOutputStream(
486: Parser.MAX_KEEP_IN_MEMORY_SIZE);
487: serverFileUtils.copy(getText(),
488: (serverCachedFileOutputStream) this .text);
489: }
490: serverFileUtils.copy(doc.getText(),
491: (serverCachedFileOutputStream) this .text);
492:
493: anchors.putAll(doc.getAnchors());
494: images.addAll(doc.getImages());
495: }
496:
497: /**
498: * @return the {@link URL} to the favicon that belongs to the document
499: */
500: public yacyURL getFavicon() {
501: return this .favicon;
502: }
503:
504: /**
505: * @param faviconURL the {@link URL} to the favicon that belongs to the document
506: */
507: public void setFavicon(yacyURL faviconURL) {
508: this .favicon = faviconURL;
509: }
510:
511: public void close() {
512: // try close the output stream
513: if (this .textStream != null) {
514: try {
515: this .textStream.close();
516: } catch (Exception e) {
517: /* ignore this */
518: } finally {
519: this .textStream = null;
520: }
521: }
522:
523: // delete the temp file
524: if ((this .text != null) && (this .text instanceof File)) {
525: try {
526: ((File) this .text).delete();
527: } catch (Exception e) {
528: /* ignore this */
529: } finally {
530: this .text = null;
531: }
532: }
533: }
534:
535: protected void finalize() throws Throwable {
536: this.close();
537: super.finalize();
538: }
539:
540: }
|