/*
  * @see org.rssowl.core.interpreter.IFormatInterpreter#interpret(org.jdom.Document,
  * org.rssowl.core.interpreter.types.IFeed)
  */
 public void interpret(Document document, IFeed feed) {
   Element root = document.getRootElement();
   setDefaultNamespaceUri(root.getNamespace().getURI());
   setRootElementName(root.getName());
   feed.setFormat("RSS"); // $NON-NLS-1$
   processFeed(root, feed);
 }
  private void processFeed(Element element, IFeed feed) {

    /* Interpret Attributes */
    List<?> attributes = element.getAttributes();
    for (Iterator<?> iter = attributes.iterator(); iter.hasNext(); ) {
      Attribute attribute = (Attribute) iter.next();
      String name = attribute.getName();

      /* Check wether this Attribute is to be processed by a Contribution */
      if (processAttributeExtern(attribute, feed)) continue;

      /* Version */
      else if ("version".equals(name)) // $NON-NLS-1$
      feed.setFormat(buildFormat("RSS", attribute.getValue())); // $NON-NLS-1$
    }

    /* Interpret Children */
    List<?> feedChildren = element.getChildren();
    for (Iterator<?> iter = feedChildren.iterator(); iter.hasNext(); ) {
      Element child = (Element) iter.next();
      String name = child.getName().toLowerCase();

      /* Check wether this Element is to be processed by a Contribution */
      if (processElementExtern(child, feed)) continue;

      /* Process Channel */
      else if ("channel".equals(name)) // $NON-NLS-1$
      processChannel(child, feed);
    }
  }
  private void processChannel(Element element, IFeed feed) {

    /* Interpret Attributes */
    List<?> attributes = element.getAttributes();
    for (Iterator<?> iter = attributes.iterator(); iter.hasNext(); ) {
      Attribute attribute = (Attribute) iter.next();

      /* Check wether this Attribute is to be processed by a Contribution */
      processAttributeExtern(attribute, feed);
    }

    /* Interpret Children */
    List<?> channelChildren = element.getChildren();
    for (Iterator<?> iter = channelChildren.iterator(); iter.hasNext(); ) {
      Element child = (Element) iter.next();
      String name = child.getName().toLowerCase();

      /* Check wether this Element is to be processed by a Contribution */
      if (processElementExtern(child, feed)) continue;

      /* Item */
      else if ("item".equals(name)) // $NON-NLS-1$
      processItems(child, feed);

      /* Title */
      else if ("title".equals(name)) { // $NON-NLS-1$
        feed.setTitle(child.getText());
        processNamespaceAttributes(child, feed);
      }

      /* Link */
      else if ("link".equals(name)) { // $NON-NLS-1$
        URI uri = URIUtils.createURI(child.getText());

        /*
         * Do not use the URI if it is empty. This is a workaround for
         * FeedBurner feeds that use a Atom 1.0 Link Element in place of an RSS
         * feed which RSSOwl 2 is not yet able to handle on this scope.
         */
        if (uri != null && StringUtils.isSet(uri.toString())) feed.setHomepage(uri);
        processNamespaceAttributes(child, feed);
      }

      /* Description */
      else if ("description".equals(name)) { // $NON-NLS-1$
        feed.setDescription(child.getText());
        processNamespaceAttributes(child, feed);
      }

      /* Publish Date */
      else if ("pubdate".equals(name)) { // $NON-NLS-1$
        feed.setPublishDate(DateUtils.parseDate(child.getText()));
        processNamespaceAttributes(child, feed);
      }

      /* Image */
      else if ("image".equals(name)) // $NON-NLS-1$
      processImage(child, feed);

      /* Language */
      else if ("language".equals(name)) { // $NON-NLS-1$
        feed.setLanguage(child.getText());
        processNamespaceAttributes(child, feed);
      }

      /* Copyright */
      else if ("copyright".equals(name)) { // $NON-NLS-1$
        feed.setCopyright(child.getText());
        processNamespaceAttributes(child, feed);
      }

      /* Webmaster */
      else if ("webmaster".equals(name)) { // $NON-NLS-1$
        feed.setWebmaster(child.getText());
        processNamespaceAttributes(child, feed);
      }

      /* Managing Editor */
      else if ("managingeditor".equals(name)) { // $NON-NLS-1$
        IPerson person = Owl.getModelFactory().createPerson(null, feed);
        person.setName(child.getText());

        processNamespaceAttributes(child, person);
      }

      /* Last Build Date */
      else if ("lastbuilddate".equals(name)) { // $NON-NLS-1$
        feed.setLastBuildDate(DateUtils.parseDate(child.getText()));
        processNamespaceAttributes(child, feed);
      }

      /* Category */
      else if ("category".equals(name)) // $NON-NLS-1$
      processCategory(child, feed);

      /* Generator */
      else if ("generator".equals(name)) { // $NON-NLS-1$
        feed.setGenerator(child.getText());
        processNamespaceAttributes(child, feed);
      }

      /* Docs */
      else if ("docs".equals(name)) { // $NON-NLS-1$
        URI uri = URIUtils.createURI(child.getText());
        if (uri != null) feed.setDocs(uri);
        processNamespaceAttributes(child, feed);
      }

      /* Rating */
      else if ("rating".equals(name)) { // $NON-NLS-1$
        feed.setRating(child.getText());
        processNamespaceAttributes(child, feed);
      }

      /* TTL */
      else if ("ttl".equals(name)) { // $NON-NLS-1$
        int ttl = StringUtils.stringToInt(child.getTextNormalize());
        if (ttl >= 0) feed.setTTL(ttl);
        processNamespaceAttributes(child, feed);
      }

      /* Skip Hours */
      else if ("skiphours".equals(name)) { // $NON-NLS-1$
        processNamespaceAttributes(child, feed);
        List<?> skipHoursChildren = child.getChildren("hour"); // $NON-NLS-1$

        /* For each <hour> Element */
        for (Iterator<?> iterator = skipHoursChildren.iterator(); iterator.hasNext(); ) {
          Element skipHour = (Element) iterator.next();
          processNamespaceAttributes(skipHour, feed);

          int hour = StringUtils.stringToInt(skipHour.getTextNormalize());
          if (0 <= hour && hour < 24) feed.addHourToSkip(hour);
        }
      }

      /* Skip Days */
      else if ("skipdays".equals(name)) { // $NON-NLS-1$
        processNamespaceAttributes(child, feed);
        List<?> skipDaysChildren = child.getChildren("day"); // $NON-NLS-1$

        /* For each <day> Element */
        for (Iterator<?> iterator = skipDaysChildren.iterator(); iterator.hasNext(); ) {
          Element skipDay = (Element) iterator.next();
          processNamespaceAttributes(skipDay, feed);

          String day = skipDay.getText().toLowerCase();
          int index = IFeed.DAYS.indexOf(day);
          if (index >= 0) feed.addDayToSkip(index);
        }
      }

      /* TextInput */
      else if ("textinput".equals(name)) // $NON-NLS-1$
      processTextInput(child, feed);

      /* Cloud */
      else if ("cloud".equals(name)) // $NON-NLS-1$
      processCloud(child, feed);
    }
  }
  private void processItems(Element element, IFeed feed) {
    INews news =
        Owl.getModelFactory()
            .createNews(null, feed, new Date(System.currentTimeMillis() - (fNewsCounter++ * 1)));
    news.setBase(feed.getBase());

    /* Check wether the Attributes are to be processed by a Contribution */
    processNamespaceAttributes(element, news);

    /* Interpret Children */
    List<?> newsChilds = element.getChildren();
    for (Iterator<?> iter = newsChilds.iterator(); iter.hasNext(); ) {
      Element child = (Element) iter.next();
      String name = child.getName().toLowerCase();

      /* Check wether this Element is to be processed by a Contribution */
      if (processElementExtern(child, news)) continue;

      /* Title */
      else if ("title".equals(name)) { // $NON-NLS-1$
        news.setTitle(child.getText());
        processNamespaceAttributes(child, news);
      }

      /* Link */
      else if ("link".equals(name)) { // $NON-NLS-1$
        URI uri = URIUtils.createURI(child.getText());
        if (uri != null) {
          news.setLink(uri);
          itemUrl = child.getText();
        }
        processNamespaceAttributes(child, news);
      }

      /* Description */
      else if ("description".equals(name)) { // $NON-NLS-1$
        if (!Owl.getPreferenceService().getGlobalScope().getBoolean(DefaultPreferences.HIDE_LIKE)) {
          news.setDescription(child.getText() + FacebookLikeUtil.getButtonCode(itemUrl));
          processNamespaceAttributes(child, news);
        } else {
          news.setDescription(child.getText());
          processNamespaceAttributes(child, news);
        }
      }

      /* Publish Date */
      else if ("pubdate".equals(name)) { // $NON-NLS-1$
        news.setPublishDate(DateUtils.parseDate(child.getText()));
        processNamespaceAttributes(child, news);
      }

      /* Author */
      else if ("author".equals(name)) { // $NON-NLS-1$
        IPerson person = Owl.getModelFactory().createPerson(null, news);
        person.setName(child.getText());

        processNamespaceAttributes(child, person);
      }

      /* Comments */
      else if ("comments".equals(name)) { // $NON-NLS-1$
        news.setComments(child.getText());
        processNamespaceAttributes(child, news);
      }

      /* Attachment */
      else if ("enclosure".equals(name)) // $NON-NLS-1$
      processEnclosure(child, news);

      /* Category */
      else if ("category".equals(name)) // $NON-NLS-1$
      processCategory(child, news);

      /* GUID */
      else if ("guid".equals(name)) // $NON-NLS-1$
      processGuid(child, news);

      /* Source */
      else if ("source".equals(name)) // $NON-NLS-1$
      processSource(child, news);
    }
  }
  private void showFeed(final IFeed feed) {
    if (feed != null && !fBrowser.getControl().isDisposed()) {
      List<INews> news = feed.getNewsByStates(INews.State.getVisible());
      int newsCount = news.size();
      if (news.size() > MAX_NEWS_SHOWN) news = news.subList(0, MAX_NEWS_SHOWN);

      /* Start HTML */
      StringBuilder html = new StringBuilder();
      html.append(
          "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"); //$NON-NLS-1$

      /* Windows only: Mark of the Web */
      if (Application.IS_WINDOWS) {
        html.append(IE_MOTW);
        html.append("\n"); // $NON-NLS-1$
      }

      /* Head */
      html.append("<html>\n  <head>\n"); // $NON-NLS-1$

      /* Append Base URI if available */
      URI base = (feed.getBase() != null) ? feed.getBase() : feed.getLink();
      if (base != null) {
        html.append("  <base href=\""); // $NON-NLS-1$
        html.append(base);
        html.append("\">"); // $NON-NLS-1$
      }

      /* Meta */
      html.append(
          "\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"); //$NON-NLS-1$

      /* CSS */
      try {
        StringWriter writer = new StringWriter();
        fLabelProvider.writeCSS(writer, false);
        html.append(writer.toString());
      } catch (IOException e) {
        /* Will Never Happen */
      }

      /* Open Body */
      html.append("  </head>\n  <body id=\"owlbody\">\n"); // $NON-NLS-1$

      /* Title */
      if (StringUtils.isSet(fBookmark.getName()))
        html.append("<div class=\"group\">")
            .append(fBookmark.getName())
            .append("</div>"); // $NON-NLS-1$ //$NON-NLS-2$

      /* Write News */
      for (INews item : news) {
        html.append(fLabelProvider.getText(item, false));
      }

      /* End HTML */
      html.append("\n  </body>\n</html>"); // $NON-NLS-1$

      /* Apply to Browser */
      fBrowser.getControl().setText(html.toString());

      /* Also Update Status */
      if (StringUtils.isSet(fBookmark.getName())) {
        StringBuilder str = new StringBuilder();
        if (feed.getHomepage() != null) {
          str.append(
              NLS.bind(
                  Messages.PreviewFeedDialog_FOUND_N_NEWS_HOMEPAGE,
                  newsCount,
                  fBookmark.getName()));
          fStatusLabel.addSelectionListener(
              new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                  new OpenInBrowserAction(new StructuredSelection(feed.getHomepage())).run();
                }
              });
        } else
          str.append(
              NLS.bind(Messages.PreviewFeedDialog_FOUND_N_NEWS, newsCount, fBookmark.getName()));

        fStatusLabel.setText(str.toString());
      }
    }
  }
  private void loadFeed() {

    /* Show Info that Feed is loading */
    if (fLoadedFeed == null || fLoadedFeed.getVisibleNews().isEmpty()) {
      if (StringUtils.isSet(fBookmark.getName()))
        showMessage(
            NLS.bind(Messages.PreviewFeedDialog_LOAD_FEED_N, fBookmark.getName()), false, true);
      else showMessage(Messages.PreviewFeedDialog_LOAD_FEED, false, true);
    }

    /* Load Feed in Background */
    JobRunner.runUIUpdater(
        new UIBackgroundJob(fBrowser.getControl()) {
          private IFeed feed;
          private Exception error;

          @Override
          protected void runInBackground(IProgressMonitor monitor) {

            /* First Check if a Feed was already provided */
            if (fLoadedFeed != null && !fLoadedFeed.getVisibleNews().isEmpty()) {
              feed = fLoadedFeed;
              return;
            }

            /* Otherwise Load Feed */
            try {

              /* Resolve Feed if existing */
              if (fFeedReference != null) feed = fFeedReference.resolve();

              /* Create Temporary Feed */
              if (feed == null || feed.getVisibleNews().isEmpty()) {
                feed =
                    Owl.getModelFactory()
                        .createFeed(null, fBookmark.getFeedLinkReference().getLink());

                /* Return if dialog closed */
                if (monitor.isCanceled()
                    || getShell().isDisposed()
                    || fBrowser.getControl().isDisposed()) return;

                /* Retrieve Stream */
                IProtocolHandler handler = Owl.getConnectionService().getHandler(feed.getLink());
                InputStream inS = handler.openStream(feed.getLink(), monitor, null);

                /* Return if dialog closed */
                if (monitor.isCanceled()
                    || getShell().isDisposed()
                    || fBrowser.getControl().isDisposed()) return;

                /* Interpret Feed */
                Owl.getInterpreter().interpret(inS, feed, null);
              }
            } catch (ConnectionException e) {
              error = e;
              Activator.safeLogError(e.getMessage(), e);
            } catch (ParserException e) {
              error = e;
              Activator.safeLogError(e.getMessage(), e);
            } catch (InterpreterException e) {
              error = e;
              Activator.safeLogError(e.getMessage(), e);
            }
          }

          @Override
          protected void runInUI(IProgressMonitor monitor) {
            if (feed != null && error == null) showFeed(feed);
            else if (error != null) {
              String errorMessage = CoreUtils.toMessage(error);
              if (StringUtils.isSet(errorMessage))
                showMessage(
                    NLS.bind(Messages.PreviewFeedDialog_UNABLE_LOAD_FEED, errorMessage),
                    true,
                    false);
            }
          }
        });
  }