text: /*
   * Copyright (C) 2006, 2008, 2009, 2010 Apple Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "config.h"
  #include "HTMLViewSourceDocument.h"
  
  #include "Attribute.h"
  #include "DOMImplementation.h"
  #include "HTMLAnchorElement.h"
  #include "HTMLBRElement.h"
  #include "HTMLBaseElement.h"
  #include "HTMLBodyElement.h"
  #include "HTMLDivElement.h"
  #include "HTMLHtmlElement.h"
  #include "HTMLNames.h"
  #include "HTMLTableCellElement.h"
  #include "HTMLTableElement.h"
  #include "HTMLTableRowElement.h"
  #include "HTMLTableSectionElement.h"
  #include "HTMLToken.h"
  #include "HTMLViewSourceParser.h"
  #include "SegmentedString.h"
  #include "Text.h"
  #include "TextViewSourceParser.h"
  
  namespace WebCore {
  
  using namespace HTMLNames;
  
  HTMLViewSourceDocument::HTMLViewSourceDocument(Frame* frame, const KURL& url, const String& mimeType)
      : HTMLDocument(frame, url)
      , m_type(mimeType)
  {
      setUsesBeforeAfterRules(true);
      setIsViewSource(true);
  
      setCompatibilityMode(QuirksMode);
      lockCompatibilityMode();
  }
  
  PassRefPtr<DocumentParser> HTMLViewSourceDocument::createParser()
  {
      if (m_type == "text/html" || m_type == "application/xhtml+xml" || m_type == "image/svg+xml" || DOMImplementation::isXMLMIMEType(m_type))
          return HTMLViewSourceParser::create(this);
  
      return TextViewSourceParser::create(this);
  }
  
  void HTMLViewSourceDocument::createContainingTable()
  {
      RefPtr<HTMLHtmlElement> html = HTMLHtmlElement::create(this);
      parserAddChild(html);
      html->attach();
      RefPtr<HTMLBodyElement> body = HTMLBodyElement::create(this);
      html->parserAddChild(body);
      body->attach();
  
      // Create a line gutter div that can be used to make sure the gutter extends down the height of the whole
      // document.
      RefPtr<HTMLDivElement> div = HTMLDivElement::create(this);
      div->setAttribute(classAttr, "webkit-line-gutter-backdrop");
      body->parserAddChild(div);
      div->attach();
  
      RefPtr<HTMLTableElement> table = HTMLTableElement::create(this);
      body->parserAddChild(table);
      table->attach();
      m_tbody = HTMLTableSectionElement::create(tbodyTag, this);
      table->parserAddChild(m_tbody);
      m_tbody->attach();
      m_current = m_tbody;
  }
  
  void HTMLViewSourceDocument::addSource(const String& source, HTMLToken& token)
  {
      if (!m_current)
          createContainingTable();
  
      switch (token.type()) {
      case HTMLTokenTypes::Uninitialized:
          ASSERT_NOT_REACHED();
          break;
      case HTMLTokenTypes::DOCTYPE:
          processDoctypeToken(source, token);
          break;
      case HTMLTokenTypes::EndOfFile:
          if (!m_tbody->hasChildNodes())
              addLine(String());
          break;
      case HTMLTokenTypes::StartTag:
      case HTMLTokenTypes::EndTag:
          processTagToken(source, token);
          break;
      case HTMLTokenTypes::Comment:
          processCommentToken(source, token);
          break;
      case HTMLTokenTypes::Character:
          processCharacterToken(source, token);
          break;
      }
  }
  
  void HTMLViewSourceDocument::processDoctypeToken(const String& source, HTMLToken&)
  {
      if (!m_current)
          createContainingTable();
      m_current = addSpanWithClassName("webkit-html-doctype");
      addText(source, "webkit-html-doctype");
      m_current = m_td;
  }
  
  void HTMLViewSourceDocument::processTagToken(const String& source, HTMLToken& token)
  {
      m_current = addSpanWithClassName("webkit-html-tag");
  
      AtomicString tagName(token.name().data(), token.name().size());
  
      unsigned index = 0;
      HTMLToken::AttributeList::const_iterator iter = token.attributes().begin();
      while (index < source.length()) {
          if (iter == token.attributes().end()) {
              // We want to show the remaining characters in the token.
              index = addRange(source, index, source.length(), "");
              ASSERT(index == source.length());
              break;
          }
  
          AtomicString name(iter->m_name.data(), iter->m_name.size());
          String value(iter->m_value.data(), iter->m_value.size()); 
  
          index = addRange(source, index, iter->m_nameRange.m_start - token.startIndex(), "");
          index = addRange(source, index, iter->m_nameRange.m_end - token.startIndex(), "webkit-html-attribute-name");
  
          if (tagName == baseTag && name == hrefAttr)
              m_current = addBase(value);
  
          index = addRange(source, index, iter->m_valueRange.m_start - token.startIndex(), "");
  
          bool isLink = name == srcAttr || name == hrefAttr;
          index = addRange(source, index, iter->m_valueRange.m_end - token.startIndex(), "webkit-html-attribute-value", isLink, tagName == aTag);
  
          ++iter;
      }
      m_current = m_td;
  }
  
  void HTMLViewSourceDocument::processCommentToken(const String& source, HTMLToken&)
  {
      m_current = addSpanWithClassName("webkit-html-comment");
      addText(source, "webkit-html-comment");
      m_current = m_td;
  }
  
  void HTMLViewSourceDocument::processCharacterToken(const String& source, HTMLToken&)
  {
      addText(source, "");
  }
  
  PassRefPtr<Element> HTMLViewSourceDocument::addSpanWithClassName(const AtomicString& className)
  {
      if (m_current == m_tbody) {
          addLine(className);
          return m_current;
      }
  
      RefPtr<HTMLElement> span = HTMLElement::create(spanTag, this);
      span->setAttribute(classAttr, className);
      m_current->parserAddChild(span);
      span->attach();
      return span.release();
  }
  
  void HTMLViewSourceDocument::addLine(const AtomicString& className)
  {
      // Create a table row.
      RefPtr<HTMLTableRowElement> trow = HTMLTableRowElement::create(this);
      m_tbody->parserAddChild(trow);
      trow->attach();
  
      // Create a cell that will hold the line number (it is generated in the stylesheet using counters).
      RefPtr<HTMLTableCellElement> td = HTMLTableCellElement::create(tdTag, this);
      td->setAttribute(classAttr, "webkit-line-number");
      trow->parserAddChild(td);
      td->attach();
  
      // Create a second cell for the line contents
      td = HTMLTableCellElement::create(tdTag, this);
      td->setAttribute(classAttr, "webkit-line-content");
      trow->parserAddChild(td);
      td->attach();
      m_current = m_td = td;
  
  #ifdef DEBUG_LINE_NUMBERS
      RefPtr<Text> lineNumberText = Text::create(this, String::number(parser()->lineNumber() + 1) + " ");
      td->addChild(lineNumberText);
      lineNumberText->attach();
  #endif
  
      // Open up the needed spans.
      if (!className.isEmpty()) {
          if (className == "webkit-html-attribute-name" || className == "webkit-html-attribute-value")
              m_current = addSpanWithClassName("webkit-html-tag");
          m_current = addSpanWithClassName(className);
      }
  }
  
  void HTMLViewSourceDocument::finishLine()
  {
      if (!m_current->hasChildNodes()) {
          RefPtr<HTMLBRElement> br = HTMLBRElement::create(this);
          m_current->parserAddChild(br);
          br->attach();
      }
      m_current = m_tbody;
  }
  
  void HTMLViewSourceDocument::addText(const String& text, const AtomicString& className)
  {
      if (text.isEmpty())
          return;
  
      // Add in the content, splitting on newlines.
      Vector<String> lines;
      text.split('\n', true, lines);
      unsigned size = lines.size();
      for (unsigned i = 0; i < size; i++) {
          String substring = lines[i];
          if (m_current == m_tbody)
              addLine(className);
          if (substring.isEmpty()) {
              if (i == size - 1)
                  break;
              finishLine();
              continue;
          }
          RefPtr<Text> t = Text::create(this, substring);
          m_current->parserAddChild(t);
          t->attach();
          if (i < size - 1)
              finishLine();
      }
  }
  
  int HTMLViewSourceDocument::addRange(const String& source, int start, int end, const String& className, bool isLink, bool isAnchor)
  {
      ASSERT(start <= end);
      if (start == end)
          return start;
  
      String text = source.substring(start, end - start);
      if (!className.isEmpty()) {
          if (isLink)
              m_current = addLink(text, isAnchor);
          else
              m_current = addSpanWithClassName(className);
      }
      addText(text, className);
      if (!className.isEmpty() && m_current != m_tbody)
          m_current = static_cast<Element*>(m_current->parentNode());
      return end;
  }
  
  PassRefPtr<Element> HTMLViewSourceDocument::addBase(const AtomicString& href)
  {
      RefPtr<HTMLBaseElement> base = HTMLBaseElement::create(baseTag, this);
      base->setAttribute(hrefAttr, href);
      m_current->parserAddChild(base);
      base->attach();
      return base.release();
  }
  
  PassRefPtr<Element> HTMLViewSourceDocument::addLink(const AtomicString& url, bool isAnchor)
  {
      if (m_current == m_tbody)
          addLine("webkit-html-tag");
  
      // Now create a link for the attribute value instead of a span.
      RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(this);
      const char* classValue;
      if (isAnchor)
          classValue = "webkit-html-attribute-value webkit-html-external-link";
      else
          classValue = "webkit-html-attribute-value webkit-html-resource-link";
      anchor->setAttribute(classAttr, classValue);
      anchor->setAttribute(targetAttr, "_blank");
      anchor->setAttribute(hrefAttr, url);
      m_current->parserAddChild(anchor);
      anchor->attach();
      return anchor.release();
  }
  
  }
  