+XHTMLStream & XHTMLStream::operator<<(html::StartTag const & tag)
+{
+ if (tag.tag_.empty())
+ return *this;
+ pending_tags_.push_back(tag);
+ if (tag.keepempty_)
+ clearTagDeque();
+ return *this;
+}
+
+
+XHTMLStream & XHTMLStream::operator<<(html::CompTag const & tag)
+{
+ if (tag.tag_.empty())
+ return *this;
+ clearTagDeque();
+ // tabs?
+ os_ << tag.asTag();
+ cr();
+ return *this;
+}
+
+
+bool XHTMLStream::isTagOpen(string const & stag)
+{
+ TagStack::const_iterator sit = tag_stack_.begin();
+ TagStack::const_iterator const sen = tag_stack_.end();
+ for (; sit != sen; ++sit)
+ if (sit->tag_ == stag)
+ return true;
+ return false;
+}
+
+
+// this is complicated, because we want to make sure that
+// everything is properly nested. the code ought to make
+// sure of that, but we won't assert (yet) if we run into
+// a problem. we'll just output error messages and try our
+// best to make things work.
+XHTMLStream & XHTMLStream::operator<<(html::EndTag const & etag)
+{
+ if (etag.tag_.empty())
+ return *this;
+
+ // make sure there are tags to be closed
+ if (tag_stack_.empty()) {
+ writeError("Tried to close `" + etag.tag_
+ + "' when no tags were open!");
+ return *this;
+ }
+
+ // first make sure we're not closing an empty tag
+ if (!pending_tags_.empty()) {
+ html::StartTag const & stag = pending_tags_.back();
+ if (etag.tag_ == stag.tag_) {
+ // we have <tag></tag>, so we discard it and remove it
+ // from the pending_tags_.
+ pending_tags_.pop_back();
+ return *this;
+ }
+ // there is a pending tag that isn't the one we are trying
+ // to close.
+ // is this tag itself pending?
+ // non-const iterators because we may call erase().
+ TagDeque::iterator dit = pending_tags_.begin();
+ TagDeque::iterator const den = pending_tags_.end();
+ for (; dit != den; ++dit) {
+ if (dit->tag_ == etag.tag_) {
+ // it was pending, so we just erase it
+ writeError("Tried to close pending tag `" + etag.tag_
+ + "' when other tags were pending. Last pending tag is `"
+ + pending_tags_.back().tag_ + "'. Tag discarded.");
+ pending_tags_.erase(dit);
+ return *this;
+ }
+ }
+ // so etag isn't itself pending. is it even open?
+ if (!isTagOpen(etag.tag_)) {
+ writeError("Tried to close `" + etag.tag_
+ + "' when tag was not open. Tag discarded.");
+ return *this;
+ }
+ // ok, so etag is open.
+ // our strategy will be as below: we will do what we need to
+ // do to close this tag.
+ string estr = "Closing tag `" + etag.tag_
+ + "' when other tags are pending. Discarded pending tags:\n";
+ for (dit = pending_tags_.begin(); dit != den; ++dit)
+ estr += dit->tag_ + "\n";
+ writeError(estr);
+ // clear the pending tags...
+ pending_tags_.clear();
+ // ...and then just fall through.
+ }
+
+ // is the tag we are closing the last one we opened?
+ if (etag.tag_ == tag_stack_.back().tag_) {
+ // output it...
+ os_ << etag.asEndTag();
+ // ...and forget about it
+ tag_stack_.pop_back();
+ return *this;
+ }
+
+ // we are trying to close a tag other than the one last opened.
+ // let's first see if this particular tag is still open somehow.
+ if (!isTagOpen(etag.tag_)) {
+ writeError("Tried to close `" + etag.tag_
+ + "' when tag was not open. Tag discarded.");
+ return *this;
+ }
+
+ // so the tag was opened, but other tags have been opened since
+ // and not yet closed.
+ // if it's a font tag, though...
+ if (html::isFontTag(etag.tag_)) {
+ // it won't be a problem if the other tags open since this one
+ // are also font tags.
+ TagStack::const_reverse_iterator rit = tag_stack_.rbegin();
+ TagStack::const_reverse_iterator ren = tag_stack_.rend();
+ for (; rit != ren; ++rit) {
+ if (rit->tag_ == etag.tag_)
+ break;
+ if (!html::isFontTag(rit->tag_)) {
+ // we'll just leave it and, presumably, have to close it later.
+ writeError("Unable to close font tag `" + etag.tag_
+ + "' due to open non-font tag `" + rit->tag_ + "'.");
+ return *this;
+ }
+ }
+
+ // so we have e.g.:
+ // <em>this is <strong>bold
+ // and are being asked to closed em. we want:
+ // <em>this is <strong>bold</strong></em><strong>
+ // first, we close the intervening tags...
+ html::StartTag curtag = tag_stack_.back();
+ // ...remembering them in a stack.
+ TagStack fontstack;
+ while (curtag.tag_ != etag.tag_) {
+ os_ << curtag.asEndTag();
+ fontstack.push_back(curtag);
+ tag_stack_.pop_back();
+ curtag = tag_stack_.back();
+ }
+ // now close our tag...
+ os_ << etag.asEndTag();
+ tag_stack_.pop_back();
+
+ // ...and restore the other tags.
+ rit = fontstack.rbegin();
+ ren = fontstack.rend();
+ for (; rit != ren; ++rit)
+ pending_tags_.push_back(*rit);
+ return *this;
+ }
+
+ // it wasn't a font tag.
+ // so other tags were opened before this one and not properly closed.
+ // so we'll close them, too. that may cause other issues later, but it
+ // at least guarantees proper nesting.
+ writeError("Closing tag `" + etag.tag_
+ + "' when other tags are open, namely:");
+ html::StartTag curtag = tag_stack_.back();
+ while (curtag.tag_ != etag.tag_) {
+ writeError(curtag.tag_);
+ os_ << curtag.asEndTag();
+ tag_stack_.pop_back();
+ curtag = tag_stack_.back();
+ }
+ // curtag is now the one we actually want.
+ os_ << curtag.asEndTag();
+ tag_stack_.pop_back();
+
+ return *this;
+}