anchor.diff:          contents += ' ';
         }
       }
  -    return fence + contents + fence;
  -  }
  -
  -  String writeEmph(Emph emph, {String delimiter: "*"}) {
  -    return '${delimiter}${writeInlines(emph.contents, prevEmph: delimiter)}${delimiter}';
  -  }
  -
  -  String writeStrong(Strong strong, {String delimiter: "*"}) {
  +    write(fence);
  +    write(contents);
  +    write(fence);
  +  }
  +
  +  void writeEmph(Emph emph, {String delimiter: "*"}) {
  +    write(delimiter);
  +    writeInlines(emph.contents, prevEmph: delimiter);
  +    write(delimiter);
  +  }
  +
  +  void writeStrong(Strong strong, {String delimiter: "*"}) {
       String delimiterString = delimiter * 2;
  -    return '${delimiterString}${writeInlines(strong.contents, prevStrong: delimiter)}${delimiterString}';
  -  }
  -
  -  String writeLink(Link link) {
  -    String inlines = writeInlines(link.label);
  +    write(delimiterString);
  +    writeInlines(strong.contents);
  +    write(delimiterString);
  +  }
  +
  +  void writeLink(Link link) {
       if (link is InlineLink) {
  -      return '[${inlines}](${_writeTarget(link.target)})';
  -    }
  +      write('[');
  +      writeInlines(link.label);
  +      write('](');
  +      writeTarget(link.target);
  +      write(')');
  +      return;
  +    }
  +
       if (link is ReferenceLink) {
         _references[link.reference] = link.target;
  -      return '[${inlines}]' + (inlines.toUpperCase() != link.reference.toUpperCase() ? '[${link.reference}]' : '');
  +      _MarkdownBuilder builder = new _MarkdownBuilder(_references);
  +      builder.writeInlines(link.label);
  +      String inlines = builder.toString();
  +      write('[');
  +      write(inlines);
  +      write(']');
  +      if (inlines.toUpperCase() != link.reference.toUpperCase()) {
  +        write('[');
  +        write(link.reference);
  +        write(']');
  +      }
  +      return;
       }
   
       // Autolink
  +    write('<');
       if (link.label.length > 0 && link.label[0] is Str) {
  -      return '<' + link.label[0].contents + '>';
  -    }
  -    return '<' + link.target.link + '>';
  -  }
  -
  -  String writeImage(Image image) {
  -    return '![${writeInlines(image.label)}](${_writeTarget(image.target)})';
  -  }
  -
  -  String _writeReferences() {
  -    String result = "";
  +      write(link.label[0].contents);
  +    } else {
  +      write(link.target.link);
  +    }
  +    write('>');
  +  }
  +
  +  void writeImage(Image image) {
  +    // TODO reference images
  +    write('![');
  +    writeInlines(image.label);
  +    write('](');
  +    writeTarget(image.target);
  +    write(')');
  +  }
  +
  +  void writeTarget(Target target) {
  +    String result;
  +
  +    if (target.link.contains(' ') && !target.link.contains(r'[<>]')) {
  +      write('<' + target.link + '>');
  +    } else {
  +      write(escapeString(target.link));
  +    }
  +
  +    if (target.title != null) {
  +      write(' "${escapeString(target.title)}"');
  +    }
  +  }
  +
  +  void writeReferences() {
       _references.forEach((String ref, Target target) {
  -      result += '[$ref]: ${_writeTarget(target)}\n';
  +      write('[');
  +      write(ref);
  +      write(']: ');
  +      writeTarget(target);
  +      write('\n');
       });
  -
  -    return result;
  -  }
  -
  -  String _writeTarget(Target target) {
  -    String result;
  -
  -    if (target.link.contains(' ') && !target.link.contains(r'[<>]')) {
  -      result = '<' + target.link + '>';
  -    } else {
  -      result = escapeString(target.link);
  -    }
  -
  -    if (target.title != null) {
  -      result += ' "${escapeString(target.title)}"';
  -    }
  -    return result;
  -
  -  }
  -
  +  }
  +}
  +
  +
  +class MarkdownWriter {
  +  String write(Document document) {
  +    _MarkdownBuilder builder = new _MarkdownBuilder(<String, Target>{});
  +    builder.writeDocument(document);
  +
  +    return builder.toString();
  +  }
   
     static MarkdownWriter DEFAULT = new MarkdownWriter();
   }
  
positive.code:           return pad + str;
          }
          return str;
        });
      });
  
      return result + items.join(list.tight ? "" : "\n");
    }
  
    String writeInlines(Iterable<Inline> inlines, {String prevEmph, String prevStrong}) {
      if (inlines.length == 1 && prevEmph != null) {
        if (inlines.first is Emph) {
          return writeEmph(inlines.first, delimiter: prevEmph == "*" ? "_" : "*");
        }
        if (inlines.first is Strong) {
          return writeStrong(inlines.first, delimiter: prevEmph == "*" ? "_" : "*");
        }
      }
      StringBuffer result = new StringBuffer();
      Iterator<Inline> it = inlines.iterator;
      int i = 0;
      int last = inlines.length - 1;
      while(it.moveNext()) {
        Inline inline = it.current;
        if (inline is Str) {
          result.write(escapeString(inline.contents));
        } else if (inline is Space) {
          result.write(' ');
        } else if (inline is NonBreakableSpace) {
          result.write('&nbsp;');
        } else if (inline is LineBreak) {
          result.write('\\\n');
        } else if (inline is Emph) {
          result.write(writeEmph(inline));
        } else if (inline is Strong) {
          result.write(writeStrong(inline));
        } else if (inline is Link) {
          result.write(writeLink(inline));
        } else if (inline is Image) {
          result.write(writeImage(inline));
        } else if (inline is Code) {
          result.write(writeCodeInline(inline));
        } else if (inline is RawInline) {
          result.write(inline.contents);
        } else {
          throw new UnimplementedError(inline.toString());
        }
  
        ++i;
      }
  
      return result.toString();
    }
  
    RegExp escapedChars = new RegExp(r'[!\"#\$%&' r"'()*+,-./:;<=>?@\[\\\]^_`{|}~]");
    String escapeString(String str) => str.replaceAllMapped(escapedChars, (Match m) => r"\" + m.group(0));
  
    String writeCodeInline(Code code) {
      String fence = '`' * code.fenceSize;
      String contents = code.contents;
      if (contents == '') {
  
negative.code:       prevBlock = block;
  
        if (block is Para) {
          writePara(block);
        } else if (block is Header) {
          writeHeader(block);
        } else if (block is HorizontalRule) {
          writeHorizontalRule(block, unorderedListChar);
        } else if (block is CodeBlock) {
          if (_b is ListBlock) {
            write('\n');
          }
          writeCodeBlock(block);
        } else if (block is Blockquote) {
          writeBlockquote(block);
        } else if (block is RawBlock) {
          write(block.contents);
          write("\n");
        } else if (block is UnorderedList) {
          writeUnorderedList(block, prevBlock: _b);
        } else if (block is OrderedList) {
          writeOrderedList(block, prevBlock: _b);
        } else {
          throw new UnimplementedError(block.toString());
        }
      }
    }
  
  
    void writeHorizontalRule(HorizontalRule hRule, [String unorderedListChar = "*"]) {
      if (unorderedListChar == "-") {
        write('*' * 10);
      } else {
        write('-' * 10);
      }
    }
  
  
    void writePara(Para para) {
      writeInlines(para.contents);
      write("\n");
    }
  
  
    void writeBlockquote(Blockquote blockquote) {
      _MarkdownBuilder inner = new _MarkdownBuilder(_references);
      inner.writeBlocks(blockquote.contents);
      String contents = inner.toString();
      if (contents.endsWith('\n')) {
        contents = contents.substring(0, contents.length - 1);
      }
      write(contents.splitMapJoin("\n", onNonMatch: (String str) => "> $str") + "\n");
    }
  
  
    void writeHeader(Header header) {
      if (header is SetextHeader && header.level == 2) {
        _MarkdownBuilder inner = new _MarkdownBuilder(_references);
        inner.writeInlines(header.contents);
        String inlines = inner.toString();
        write(inlines);
        write("\n");
        write((header.level == 1 ? '=' : '-') * inlines.length + "\n");
        return;
      }
      write("#" * header.level + " ");
      writeInlines(header.contents);
      write("\n");
    }
  
  
    void writeCodeBlock(CodeBlock codeBlock) {
      if (codeBlock is FencedCodeBlock) {
        String fence = (codeBlock.fenceType == FenceType.BacktickFence ? '`' : '~') * codeBlock.fenceSize;
        write(fence);
        if (codeBlock.attributes is InfoString) {
          write(' ${codeBlock.attributes.language}');
        }
        write("\n");
        write(codeBlock.contents);
        write(fence);
        write('\n');
        return;
      }
  
      // Indented code block
      write(codeBlock.contents.splitMapJoin("\n", onNonMatch: (str) => str == "" ? str : "    " + str));
    }
  
  
    void writeUnorderedList(UnorderedList list, {Block prevBlock: null}) {
      if (prevBlock is UnorderedList && prevBlock.bulletType == list.bulletType) {
        write("\n");
      }
  
      Iterator<ListItem> it = list.items.iterator;
      bool first = true;
      bool tight = list.tight;
      while (it.moveNext()) {
        if (first) {
          first = false;
        } else if (!tight) {
          write("\n");
        }
  
        ListItem listItem = it.current;
        _MarkdownBuilder builder = new _MarkdownBuilder(_references);
        builder.writeBlocks(listItem.contents, tight: list.tight, unorderedListChar: list.bulletType.char);
        String contents = builder.toString();
        String marker = list.bulletType.char + " ";
        String pad;
  
        write(marker);
  
        write(contents.splitMapJoin("\n", onNonMatch: (String str) {
          if (pad == null) { // First
            pad = " " * marker.length;
            return str;
          } else if (str != "") {
            return pad + str;
          }
          return str;
        }));
      }
    }
  
  
    void writeOrderedList(OrderedList list, {Block prevBlock: null}) {
      if (prevBlock is OrderedList && prevBlock.indexSeparator == list.indexSeparator) {
        write("\n");
      }
      int index = list.startIndex;
      Iterator<ListItem> it = list.items.iterator;
      bool first = true;
      bool tight = list.tight;
      while (it.moveNext()) {
        if (first) {
          first = false;
        } else if (!tight) {
          write("\n");
        }
  
        ListItem listItem = it.current;
        _MarkdownBuilder builder = new _MarkdownBuilder(_references);
        builder.writeBlocks(listItem.contents, tight: list.tight);
        String contents = builder.toString();
        String pad;
  
        write(contents.splitMapJoin("\n", onNonMatch: (String str) {
          if (pad == null) {
            String marker = index.toString() + list.indexSeparator.char + " ";
            pad = " " * marker.length;
  