lyx_mirror/src/output_xhtml.cpp

1234 lines
31 KiB
C++
Raw Normal View History

/**
* \file output_xhtml.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Richard Heck
2015-05-17 15:27:12 +00:00
*
* This code is based upon output_docbook.cpp
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "output_xhtml.h"
#include "Buffer.h"
#include "buffer_funcs.h"
#include "BufferParams.h"
#include "Counters.h"
#include "Font.h"
#include "Layout.h"
#include "OutputParams.h"
#include "Paragraph.h"
#include "ParagraphList.h"
#include "ParagraphParameters.h"
#include "sgml.h"
#include "Text.h"
#include "TextClass.h"
#include "support/convert.h"
#include "support/debug.h"
#include "support/lassert.h"
#include "support/lstrings.h"
#include "support/textutils.h"
#include <vector>
2013-05-07 04:19:34 +00:00
// Uncomment to activate debugging code.
// #define XHTML_DEBUG
using namespace std;
using namespace lyx::support;
namespace lyx {
namespace html {
docstring escapeChar(char_type c, XHTMLStream::EscapeSettings e)
{
docstring str;
switch (e) {
case XHTMLStream::ESCAPE_NONE:
str += c;
break;
case XHTMLStream::ESCAPE_ALL:
if (c == '<') {
str += "&lt;";
break;
} else if (c == '>') {
str += "&gt;";
break;
}
// fall through
case XHTMLStream::ESCAPE_AND:
if (c == '&')
str += "&amp;";
else
str +=c ;
break;
}
return str;
}
// escape what needs escaping
docstring htmlize(docstring const & str, XHTMLStream::EscapeSettings e)
{
odocstringstream d;
docstring::const_iterator it = str.begin();
docstring::const_iterator en = str.end();
for (; it != en; ++it)
d << escapeChar(*it, e);
return d.str();
}
docstring escapeChar(char c, XHTMLStream::EscapeSettings e)
{
LATTEST(static_cast<unsigned char>(c) < 0x80);
return escapeChar(static_cast<char_type>(c), e);
}
docstring cleanAttr(docstring const & str)
{
docstring newname;
docstring::const_iterator it = str.begin();
docstring::const_iterator en = str.end();
for (; it != en; ++it) {
char_type const c = *it;
newname += isAlnumASCII(c) ? c : char_type('_');
}
2015-05-17 15:27:12 +00:00
return newname;
}
docstring StartTag::writeTag() const
{
docstring output = '<' + from_utf8(tag_);
if (!attr_.empty())
output += ' ' + html::htmlize(from_utf8(attr_), XHTMLStream::ESCAPE_NONE);
output += ">";
return output;
}
docstring StartTag::writeEndTag() const
{
string output = "</" + tag_ + ">";
return from_utf8(output);
}
bool StartTag::operator==(FontTag const & rhs) const
{
return rhs == *this;
}
docstring EndTag::writeEndTag() const
{
string output = "</" + tag_ + ">";
return from_utf8(output);
}
ParTag::ParTag(std::string const & tag, std::string attr,
std::string const & parid)
: StartTag(tag)
{
if (!parid.empty())
attr += " id='" + parid + "'";
attr_ = attr;
}
docstring CompTag::writeTag() const
{
docstring output = '<' + from_utf8(tag_);
if (!attr_.empty())
output += ' ' + html::htmlize(from_utf8(attr_), XHTMLStream::ESCAPE_NONE);
output += " />";
return output;
}
namespace {
string fontToTag(html::FontTypes type)
{
switch(type) {
case FT_EMPH:
return "em";
case FT_BOLD:
return "b";
case FT_NOUN:
return "dfn";
case FT_UBAR:
case FT_WAVE:
case FT_DBAR:
return "u";
case FT_SOUT:
case FT_XOUT:
return "del";
case FT_ITALIC:
return "i";
2013-05-10 20:58:38 +00:00
case FT_UPRIGHT:
case FT_SLANTED:
case FT_SMALLCAPS:
case FT_ROMAN:
case FT_SANS:
2013-05-10 20:58:38 +00:00
case FT_TYPE:
2013-05-13 16:22:41 +00:00
case FT_SIZE_TINY:
case FT_SIZE_SCRIPT:
case FT_SIZE_FOOTNOTE:
case FT_SIZE_SMALL:
case FT_SIZE_NORMAL:
case FT_SIZE_LARGE:
case FT_SIZE_LARGER:
case FT_SIZE_LARGEST:
case FT_SIZE_HUGE:
case FT_SIZE_HUGER:
case FT_SIZE_INCREASE:
case FT_SIZE_DECREASE:
return "span";
}
// kill warning
return "";
}
string fontToAttribute(html::FontTypes type)
{
switch(type) {
case FT_EMPH:
case FT_BOLD:
return "";
case FT_NOUN:
return "class='lyxnoun'";
case FT_UBAR:
return "";
case FT_DBAR:
return "class='dline'";
case FT_XOUT:
case FT_SOUT:
return "class='strikeout'";
case FT_WAVE:
return "class='wline'";
case FT_ITALIC:
return "";
2013-05-10 20:58:38 +00:00
case FT_UPRIGHT:
return "style='font-style:normal;'";
case FT_SLANTED:
return "style='font-style:oblique;'";
case FT_SMALLCAPS:
return "style='font-variant:small-caps;'";
case FT_ROMAN:
return "style='font-family:serif;'";
case FT_SANS:
return "style='font-family:sans-serif;'";
2013-05-10 20:58:38 +00:00
case FT_TYPE:
return "style='font-family:monospace;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_TINY:
case FT_SIZE_SCRIPT:
case FT_SIZE_FOOTNOTE:
return "style='font-size:x-small;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_SMALL:
return "style='font-size:small;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_NORMAL:
return "style='font-size:normal;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_LARGE:
return "style='font-size:large;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_LARGER:
case FT_SIZE_LARGEST:
return "style='font-size:x-large;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_HUGE:
case FT_SIZE_HUGER:
return "style='font-size:xx-large;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_INCREASE:
return "style='font-size:larger;'";
2013-05-13 16:22:41 +00:00
case FT_SIZE_DECREASE:
return "style='font-size:smaller;'";
}
// kill warning
return "";
}
} // end anonymous namespace
FontTag::FontTag(FontTypes type)
: StartTag(fontToTag(type), fontToAttribute(type)), font_type_(type)
{}
bool FontTag::operator==(StartTag const & tag) const
{
FontTag const * const ftag = tag.asFontTag();
if (!ftag)
return false;
return (font_type_ == ftag->font_type_);
}
EndFontTag::EndFontTag(FontTypes type)
: EndTag(fontToTag(type)), font_type_(type)
{}
} // namespace html
////////////////////////////////////////////////////////////////
///
/// XHTMLStream
///
////////////////////////////////////////////////////////////////
2013-05-07 04:19:34 +00:00
XHTMLStream::XHTMLStream(odocstream & os)
: os_(os), escape_(ESCAPE_ALL)
{}
2013-05-07 04:19:34 +00:00
#ifdef XHTML_DEBUG
2016-07-09 18:28:27 +00:00
void XHTMLStream::dumpTagStack(string const & msg)
{
2016-07-09 18:28:27 +00:00
*this << html::CR();
writeError(msg);
*this << html::CR();
writeError("Tag Stack");
TagDeque::const_reverse_iterator it = tag_stack_.rbegin();
TagDeque::const_reverse_iterator en = tag_stack_.rend();
for (; it != en; ++it) {
2016-07-09 18:28:27 +00:00
writeError(it->get()->tag_);
}
2016-07-09 18:28:27 +00:00
writeError("End Tag Stack");
*this << html::CR();
writeError("Pending Tags");
it = pending_tags_.rbegin();
en = pending_tags_.rend();
for (; it != en; ++it) {
2016-07-09 18:28:27 +00:00
writeError(it->get()->tag_);
}
2016-07-09 18:28:27 +00:00
writeError("End Pending Tags");
*this << html::CR();
}
#endif
void XHTMLStream::writeError(std::string const & s) const
{
LYXERR0(s);
2012-04-03 22:13:59 +00:00
os_ << from_utf8("<!-- Output Error: " + s + " -->\n");
}
namespace {
// an illegal tag for internal use
static html::StartTag const parsep_tag("&LyX_parsep_tag&");
Bulk cleanup/fix incorrect annotation at the end of namespaces. This commit does a bulk fix of incorrect annotations (comments) at the end of namespaces. The commit was generated by initially running clang-format, and then from the diff of the result extracting the hunks corresponding to fixes of namespace comments. The changes being applied and all the results have been manually reviewed. The source code successfully builds on macOS. Further details on the steps below, in case they're of interest to someone else in the future. 1. Checkout a fresh and up to date version of src/ git pull && git checkout -- src && git status src 2. Ensure there's a suitable .clang-format in place, i.e. with options to fix the comment at the end of namespaces, including: FixNamespaceComments: true SpacesBeforeTrailingComments: 1 and that clang-format is >= 5.0.0, by doing e.g.: clang-format -dump-config | grep Comments: clang-format --version 3. Apply clang-format to the source: clang-format -i $(find src -name "*.cpp" -or -name "*.h") 4. Create and filter out hunks related to fixing the namespace git diff -U0 src > tmp.patch grepdiff '^} // namespace' --output-matching=hunk tmp.patch > fix_namespace.patch 5. Filter out hunks corresponding to simple fixes into to a separate patch: pcregrep -M -e '^diff[^\n]+\nindex[^\n]+\n--- [^\n]+\n\+\+\+ [^\n]+\n' \ -e '^@@ -[0-9]+ \+[0-9]+ @@[^\n]*\n-\}[^\n]*\n\+\}[^\n]*\n' \ fix_namespace.patch > fix_namespace_simple.patch 6. Manually review the simple patch and then apply it, after first restoring the source. git checkout -- src patch -p1 < fix_namespace_simple.path 7. Manually review the (simple) changes and then stage the changes git diff src git add src 8. Again apply clang-format and filter out hunks related to any remaining fixes to the namespace, this time filter with more context. There will be fewer hunks as all the simple cases have already been handled: clang-format -i $(find src -name "*.cpp" -or -name "*.h") git diff src > tmp.patch grepdiff '^} // namespace' --output-matching=hunk tmp.patch > fix_namespace2.patch 9. Manually review/edit the resulting patch file to remove hunks for files which need to be dealt with manually, noting the file names and line numbers. Then restore files to as before applying clang-format and apply the patch: git checkout src patch -p1 < fix_namespace2.patch 10. Manually fix the files noted in the previous step. Stage files, review changes and commit.
2017-07-23 11:11:54 +00:00
} // namespace
bool XHTMLStream::closeFontTags()
{
if (isTagPending(parsep_tag))
// we haven't had any content
return true;
2016-08-05 02:00:24 +00:00
#ifdef XHTML_DEBUG
dumpTagStack("Beging Close Font Tags");
#endif
// this may be a useless check, since we ought at least to have
// the parsep_tag. but it can't hurt too much to be careful.
if (tag_stack_.empty())
return true;
// first, we close any open font tags we can close
2013-05-07 04:19:34 +00:00
TagPtr curtag = tag_stack_.back();
while (curtag->asFontTag()) {
os_ << curtag->writeEndTag();
tag_stack_.pop_back();
2013-05-07 04:19:34 +00:00
// this shouldn't happen, since then the font tags
// weren't in any other tag.
LASSERT(!tag_stack_.empty(), return true);
curtag = tag_stack_.back();
}
2015-05-17 15:27:12 +00:00
2016-08-05 02:00:24 +00:00
#ifdef XHTML_DEBUG
dumpTagStack("End Close Font Tags");
#endif
2017-07-03 17:53:14 +00:00
if (*curtag == parsep_tag)
return true;
// so we've hit a non-font tag.
writeError("Tags still open in closeFontTags(). Probably not a problem,\n"
"but you might want to check these tags:");
2013-05-07 04:19:34 +00:00
TagDeque::const_reverse_iterator it = tag_stack_.rbegin();
TagDeque::const_reverse_iterator const en = tag_stack_.rend();
for (; it != en; ++it) {
if (**it == parsep_tag)
break;
2013-05-07 04:19:34 +00:00
writeError((*it)->tag_);
}
return false;
}
2016-07-10 03:53:18 +00:00
void XHTMLStream::startDivision(bool keep_empty)
{
2013-05-07 04:19:34 +00:00
pending_tags_.push_back(makeTagPtr(html::StartTag(parsep_tag)));
if (keep_empty)
clearTagDeque();
2016-08-05 02:00:24 +00:00
#ifdef XHTML_DEBUG
dumpTagStack("StartDivision");
#endif
}
2016-07-10 03:53:18 +00:00
void XHTMLStream::endDivision()
{
if (isTagPending(parsep_tag)) {
// this case is normal. it just means we didn't have content,
// so the parsep_tag never got moved onto the tag stack.
while (!pending_tags_.empty()) {
// clear all pending tags up to and including the parsep tag.
// note that we work from the back, because we want to get rid
// of everything that hasn't been used.
2013-05-07 04:19:34 +00:00
TagPtr const cur_tag = pending_tags_.back();
pending_tags_.pop_back();
if (*cur_tag == parsep_tag)
break;
}
2016-08-05 02:00:24 +00:00
#ifdef XHTML_DEBUG
dumpTagStack("EndDivision");
#endif
2017-07-03 17:53:14 +00:00
return;
}
if (!isTagOpen(parsep_tag)) {
2016-07-10 03:53:18 +00:00
writeError("No division separation tag found in endDivision().");
return;
}
2015-05-17 15:27:12 +00:00
// this case is also normal, if the parsep tag is the last one
// on the stack. otherwise, it's an error.
while (!tag_stack_.empty()) {
2013-05-07 04:19:34 +00:00
TagPtr const cur_tag = tag_stack_.back();
tag_stack_.pop_back();
if (*cur_tag == parsep_tag)
break;
2013-05-07 04:19:34 +00:00
writeError("Tag `" + cur_tag->tag_ + "' still open at end of paragraph. Closing.");
os_ << cur_tag->writeEndTag();
}
2016-08-05 02:00:24 +00:00
#ifdef XHTML_DEBUG
dumpTagStack("EndDivision");
#endif
}
void XHTMLStream::clearTagDeque()
{
while (!pending_tags_.empty()) {
2013-05-07 04:19:34 +00:00
TagPtr const tag = pending_tags_.front();
if (*tag != parsep_tag)
// tabs?
os_ << tag->writeTag();
tag_stack_.push_back(tag);
pending_tags_.pop_front();
}
}
XHTMLStream & XHTMLStream::operator<<(docstring const & d)
{
clearTagDeque();
os_ << html::htmlize(d, escape_);
escape_ = ESCAPE_ALL;
return *this;
}
XHTMLStream & XHTMLStream::operator<<(const char * s)
{
clearTagDeque();
docstring const d = from_ascii(s);
os_ << html::htmlize(d, escape_);
escape_ = ESCAPE_ALL;
return *this;
}
XHTMLStream & XHTMLStream::operator<<(char_type c)
{
clearTagDeque();
os_ << html::escapeChar(c, escape_);
escape_ = ESCAPE_ALL;
return *this;
}
XHTMLStream & XHTMLStream::operator<<(char c)
{
clearTagDeque();
os_ << html::escapeChar(c, escape_);
escape_ = ESCAPE_ALL;
return *this;
}
XHTMLStream & XHTMLStream::operator<<(int i)
{
clearTagDeque();
os_ << i;
escape_ = ESCAPE_ALL;
return *this;
}
XHTMLStream & XHTMLStream::operator<<(EscapeSettings e)
2015-05-17 15:27:12 +00:00
{
escape_ = e;
return *this;
}
2015-05-17 15:27:12 +00:00
XHTMLStream & XHTMLStream::operator<<(html::StartTag const & tag)
{
if (tag.tag_.empty())
return *this;
2013-05-07 04:19:34 +00:00
pending_tags_.push_back(makeTagPtr(tag));
if (tag.keepempty_)
clearTagDeque();
return *this;
}
XHTMLStream & XHTMLStream::operator<<(html::ParTag const & tag)
{
if (tag.tag_.empty())
return *this;
pending_tags_.push_back(makeTagPtr(tag));
return *this;
}
2015-05-17 15:27:12 +00:00
XHTMLStream & XHTMLStream::operator<<(html::CompTag const & tag)
{
if (tag.tag_.empty())
return *this;
clearTagDeque();
os_ << tag.writeTag();
*this << html::CR();
return *this;
}
XHTMLStream & XHTMLStream::operator<<(html::FontTag const & tag)
{
if (tag.tag_.empty())
return *this;
pending_tags_.push_back(makeTagPtr(tag));
return *this;
}
XHTMLStream & XHTMLStream::operator<<(html::CR const &)
{
// tabs?
os_ << from_ascii("\n");
return *this;
}
bool XHTMLStream::isTagOpen(html::StartTag const & stag) const
{
TagDeque::const_iterator sit = tag_stack_.begin();
TagDeque::const_iterator const sen = tag_stack_.end();
for (; sit != sen; ++sit)
if (**sit == stag)
return true;
return false;
}
bool XHTMLStream::isTagOpen(html::EndTag const & etag) const
{
2013-05-07 04:19:34 +00:00
TagDeque::const_iterator sit = tag_stack_.begin();
TagDeque::const_iterator const sen = tag_stack_.end();
for (; sit != sen; ++sit)
if (etag == **sit)
return true;
return false;
}
bool XHTMLStream::isTagPending(html::StartTag const & stag) const
{
2013-05-07 04:19:34 +00:00
TagDeque::const_iterator sit = pending_tags_.begin();
TagDeque::const_iterator const sen = pending_tags_.end();
for (; sit != sen; ++sit)
if (**sit == stag)
return true;
return false;
}
// this is complicated, because we want to make sure that
2015-05-17 15:27:12 +00:00
// 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;
2013-05-07 04:19:34 +00:00
// if this tag is pending, we can simply discard it.
if (!pending_tags_.empty()) {
2013-05-07 04:19:34 +00:00
if (etag == *pending_tags_.back()) {
2015-05-17 15:27:12 +00:00
// we have <tag></tag>, so we discard it and remove it
// from the pending_tags_.
pending_tags_.pop_back();
return *this;
}
2013-05-07 04:19:34 +00:00
// there is a pending tag that isn't the one we are trying
2015-05-17 15:27:12 +00:00
// to close.
2013-05-07 04:19:34 +00:00
// is this tag itself pending?
// non-const iterators because we may call erase().
2013-05-07 04:19:34 +00:00
TagDeque::iterator dit = pending_tags_.begin();
TagDeque::iterator const den = pending_tags_.end();
for (; dit != den; ++dit) {
if (etag == **dit) {
// it was pending, so we just erase it
2015-05-17 15:27:12 +00:00
writeError("Tried to close pending tag `" + etag.tag_
+ "' when other tags were pending. Last pending tag is `"
2015-05-17 15:27:12 +00:00
+ to_utf8(pending_tags_.back()->writeTag())
2013-05-13 15:58:52 +00:00
+ "'. Tag discarded.");
pending_tags_.erase(dit);
return *this;
}
}
// so etag isn't itself pending. is it even open?
if (!isTagOpen(etag)) {
2015-05-17 15:27:12 +00:00
writeError("Tried to close `" + etag.tag_
+ "' when tag was not open. Tag discarded.");
return *this;
}
// ok, so etag is open.
2015-05-17 15:27:12 +00:00
// our strategy will be as below: we will do what we need to
// do to close this tag.
2015-05-17 15:27:12 +00:00
string estr = "Closing tag `" + etag.tag_
+ "' when other tags are pending. Discarded pending tags:\n";
for (dit = pending_tags_.begin(); dit != den; ++dit)
2013-05-13 15:58:52 +00:00
estr += to_utf8(html::htmlize((*dit)->writeTag(), XHTMLStream::ESCAPE_ALL)) + "\n";
writeError(estr);
// clear the pending tags...
pending_tags_.clear();
// ...and then just fall through.
}
2013-05-07 04:19:34 +00:00
// make sure there are tags to be closed
if (tag_stack_.empty()) {
writeError("Tried to close `" + etag.tag_
+ "' when no tags were open!");
2015-05-17 15:27:12 +00:00
return *this;
2013-05-07 04:19:34 +00:00
}
// is the tag we are closing the last one we opened?
if (etag == *tag_stack_.back()) {
// output it...
os_ << etag.writeEndTag();
// ...and forget about it
tag_stack_.pop_back();
return *this;
2015-05-17 15:27:12 +00:00
}
// 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)) {
2015-05-17 15:27:12 +00:00
writeError("Tried to close `" + etag.tag_
+ "' when tag was not open. Tag discarded.");
return *this;
}
2015-05-17 15:27:12 +00:00
// so the tag was opened, but other tags have been opened since
// and not yet closed.
// if it's a font tag, though...
if (etag.asFontTag()) {
// it won't be a problem if the other tags open since this one
// are also font tags.
2013-05-07 04:19:34 +00:00
TagDeque::const_reverse_iterator rit = tag_stack_.rbegin();
TagDeque::const_reverse_iterator ren = tag_stack_.rend();
for (; rit != ren; ++rit) {
if (etag == **rit)
break;
if (!(*rit)->asFontTag()) {
// we'll just leave it and, presumably, have to close it later.
2015-05-17 15:27:12 +00:00
writeError("Unable to close font tag `" + etag.tag_
2013-05-07 04:19:34 +00:00
+ "' due to open non-font tag `" + (*rit)->tag_ + "'.");
return *this;
}
}
2015-05-17 15:27:12 +00:00
// 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...
2013-05-07 04:19:34 +00:00
TagPtr curtag = tag_stack_.back();
// ...remembering them in a stack.
2013-05-07 04:19:34 +00:00
TagDeque fontstack;
while (etag != *curtag) {
os_ << curtag->writeEndTag();
fontstack.push_back(curtag);
tag_stack_.pop_back();
curtag = tag_stack_.back();
}
2015-05-17 15:27:12 +00:00
os_ << etag.writeEndTag();
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;
}
2015-05-17 15:27:12 +00:00
// it wasn't a font tag.
2015-05-17 15:27:12 +00:00
// 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.
2015-05-17 15:27:12 +00:00
writeError("Closing tag `" + etag.tag_
+ "' when other tags are open, namely:");
2013-05-07 04:19:34 +00:00
TagPtr curtag = tag_stack_.back();
while (etag != *curtag) {
2013-05-07 04:19:34 +00:00
writeError(curtag->tag_);
if (*curtag != parsep_tag)
os_ << curtag->writeEndTag();
tag_stack_.pop_back();
curtag = tag_stack_.back();
}
// curtag is now the one we actually want.
os_ << curtag->writeEndTag();
tag_stack_.pop_back();
return *this;
}
// End code for XHTMLStream
namespace {
// convenience functions
inline void openParTag(XHTMLStream & xs, Layout const & lay,
std::string parlabel)
{
xs << html::ParTag(lay.htmltag(), lay.htmlattr(), parlabel);
}
void openParTag(XHTMLStream & xs, Layout const & lay,
ParagraphParameters const & params,
std::string parlabel)
{
// FIXME Are there other things we should handle here?
string const align = alignmentToCSS(params.align());
if (align.empty()) {
openParTag(xs, lay, parlabel);
return;
}
string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
xs << html::ParTag(lay.htmltag(), attrs, parlabel);
}
inline void closeTag(XHTMLStream & xs, Layout const & lay)
{
xs << html::EndTag(lay.htmltag());
}
inline void openLabelTag(XHTMLStream & xs, Layout const & lay)
{
xs << html::StartTag(lay.htmllabeltag(), lay.htmllabelattr());
}
inline void closeLabelTag(XHTMLStream & xs, Layout const & lay)
{
xs << html::EndTag(lay.htmllabeltag());
}
inline void openItemTag(XHTMLStream & xs, Layout const & lay)
{
xs << html::StartTag(lay.htmlitemtag(), lay.htmlitemattr(), true);
}
2015-05-17 15:27:12 +00:00
void openItemTag(XHTMLStream & xs, Layout const & lay,
ParagraphParameters const & params)
{
// FIXME Are there other things we should handle here?
string const align = alignmentToCSS(params.align());
if (align.empty()) {
openItemTag(xs, lay);
return;
}
string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
xs << html::StartTag(lay.htmlitemtag(), attrs);
}
inline void closeItemTag(XHTMLStream & xs, Layout const & lay)
{
xs << html::EndTag(lay.htmlitemtag());
}
// end of convenience functions
ParagraphList::const_iterator findLastParagraph(
ParagraphList::const_iterator p,
ParagraphList::const_iterator const & pend)
{
for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p)
;
return p;
}
ParagraphList::const_iterator findEndOfEnvironment(
ParagraphList::const_iterator const & pstart,
ParagraphList::const_iterator const & pend)
{
ParagraphList::const_iterator p = pstart;
Layout const & bstyle = p->layout();
size_t const depth = p->params().depth();
for (++p; p != pend; ++p) {
Layout const & style = p->layout();
// It shouldn't happen that e.g. a section command occurs inside
// a quotation environment, at a higher depth, but as of 6/2009,
// it can happen. We pretend that it's just at lowest depth.
if (style.latextype == LATEX_COMMAND)
return p;
// If depth is down, we're done
if (p->params().depth() < depth)
return p;
// If depth is up, we're not done
if (p->params().depth() > depth)
continue;
// FIXME I am not sure about the first check.
// Surely we *could* have different layouts that count as
2015-05-17 15:27:12 +00:00
// LATEX_PARAGRAPH, right?
if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
return p;
}
return pend;
}
ParagraphList::const_iterator makeParagraphs(Buffer const & buf,
XHTMLStream & xs,
OutputParams const & runparams,
Text const & text,
ParagraphList::const_iterator const & pbegin,
ParagraphList::const_iterator const & pend)
{
ParagraphList::const_iterator const begin = text.paragraphs().begin();
ParagraphList::const_iterator par = pbegin;
for (; par != pend; ++par) {
Layout const & lay = par->layout();
if (!lay.counter.empty())
buf.masterBuffer()->params().
documentClass().counters().step(lay.counter, OutputUpdate);
// FIXME We should see if there's a label to be output and
// do something with it.
if (par != pbegin)
xs << html::CR();
// We want to open the paragraph tag if:
// (i) the current layout permits multiple paragraphs
// (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
// we are, but this is not the first paragraph
//
// But there is also a special case, and we first see whether we are in it.
// We do not want to open the paragraph tag if this paragraph contains
2017-07-03 17:53:14 +00:00
// only one item, and that item is "inline", i.e., not HTMLIsBlock (such
// as a branch). On the other hand, if that single item has a font change
// applied to it, then we still do need to open the paragraph.
//
// Obviously, this is very fragile. The main reason we need to do this is
// because of branches, e.g., a branch that contains an entire new section.
// We do not really want to wrap that whole thing in a <div>...</div>.
bool special_case = false;
2020-05-03 06:32:39 +00:00
Inset const * specinset = par->size() == 1 ? par->getInset(0) : nullptr;
if (specinset && !specinset->getLayout().htmlisblock()) {
Layout const & style = par->layout();
FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
style.labelfont : style.font;
FontInfo const our_font =
par->getFont(buf.masterBuffer()->params(), 0,
text.outerFont(distance(begin, par))).fontInfo();
if (first_font == our_font)
special_case = true;
}
2016-07-10 03:53:18 +00:00
bool const open_par = runparams.html_make_pars
&& (!runparams.html_in_par || par != pbegin)
&& !special_case;
// We want to issue the closing tag if either:
// (i) We opened it, and either html_in_par is false,
// or we're not in the last paragraph, anyway.
2015-05-17 15:27:12 +00:00
// (ii) We didn't open it and html_in_par is true,
// but we are in the first par, and there is a next par.
ParagraphList::const_iterator nextpar = par;
++nextpar;
2016-07-10 03:53:18 +00:00
bool const close_par =
(open_par && (!runparams.html_in_par || nextpar != pend))
|| (!open_par && runparams.html_in_par && par == pbegin && nextpar != pend);
2016-07-10 03:53:18 +00:00
if (open_par) {
2017-07-03 17:53:14 +00:00
// We do not issue the paragraph id if we are doing
// this for the TOC (or some similar purpose)
openParTag(xs, lay, par->params(),
runparams.for_toc ? "" : par->magicLabel());
}
2017-07-03 17:53:14 +00:00
docstring const deferred = par->simpleLyXHTMLOnePar(buf, xs,
runparams, text.outerFont(distance(begin, par)),
2016-07-10 03:53:18 +00:00
open_par, close_par);
2016-07-10 03:53:18 +00:00
if (close_par) {
closeTag(xs, lay);
xs << html::CR();
}
if (!deferred.empty()) {
xs << XHTMLStream::ESCAPE_NONE << deferred << html::CR();
}
}
return pend;
}
ParagraphList::const_iterator makeBibliography(Buffer const & buf,
XHTMLStream & xs,
OutputParams const & runparams,
Text const & text,
ParagraphList::const_iterator const & pbegin,
2015-05-17 15:27:12 +00:00
ParagraphList::const_iterator const & pend)
{
// FIXME XHTML
// Use TextClass::htmlTOCLayout() to figure out how we should look.
xs << html::StartTag("h2", "class='bibliography'")
<< pbegin->layout().labelstring(false)
<< html::EndTag("h2")
<< html::CR()
<< html::StartTag("div", "class='bibliography'")
<< html::CR();
makeParagraphs(buf, xs, runparams, text, pbegin, pend);
xs << html::EndTag("div");
return pend;
}
bool isNormalEnv(Layout const & lay)
{
return lay.latextype == LATEX_ENVIRONMENT
|| lay.latextype == LATEX_BIB_ENVIRONMENT;
}
2015-05-17 15:27:12 +00:00
2013-04-25 14:28:22 +00:00
ParagraphList::const_iterator makeEnvironment(Buffer const & buf,
XHTMLStream & xs,
OutputParams const & runparams,
Text const & text,
ParagraphList::const_iterator const & pbegin,
2015-05-17 15:27:12 +00:00
ParagraphList::const_iterator const & pend)
{
ParagraphList::const_iterator const begin = text.paragraphs().begin();
ParagraphList::const_iterator par = pbegin;
Layout const & bstyle = par->layout();
depth_type const origdepth = pbegin->params().depth();
// open tag for this environment
openParTag(xs, bstyle, pbegin->magicLabel());
xs << html::CR();
// we will on occasion need to remember a layout from before.
2020-05-03 06:32:39 +00:00
Layout const * lastlay = nullptr;
while (par != pend) {
Layout const & style = par->layout();
// the counter only gets stepped if we're in some kind of list,
// or if it's the first time through.
// note that enum, etc, are handled automatically.
// FIXME There may be a bug here about user defined enumeration
// types. If so, then we'll need to take the counter and add "i",
// "ii", etc, as with enum.
Counters & cnts = buf.masterBuffer()->params().documentClass().counters();
docstring const & cntr = style.counter;
2015-05-17 15:27:12 +00:00
if (!style.counter.empty()
&& (par == pbegin || !isNormalEnv(style))
&& cnts.hasCounter(cntr)
)
cnts.step(cntr, OutputUpdate);
ParagraphList::const_iterator send;
switch (style.latextype) {
case LATEX_ENVIRONMENT:
case LATEX_LIST_ENVIRONMENT:
case LATEX_ITEM_ENVIRONMENT: {
2015-05-17 15:27:12 +00:00
// There are two possiblities in this case.
// One is that we are still in the environment in which we
// started---which we will be if the depth is the same.
if (par->params().depth() == origdepth) {
LATTEST(bstyle == style);
2020-05-03 06:32:39 +00:00
if (lastlay != nullptr) {
closeItemTag(xs, *lastlay);
2020-05-03 06:32:39 +00:00
lastlay = nullptr;
}
2015-05-17 15:27:12 +00:00
// this will be positive, if we want to skip the
// initial word (if it's been taken for the label).
pos_type sep = 0;
bool const labelfirst = style.htmllabelfirst();
if (!labelfirst)
openItemTag(xs, style, par->params());
2015-05-17 15:27:12 +00:00
// label output
2015-05-17 15:27:12 +00:00
if (style.labeltype != LABEL_NO_LABEL &&
style.htmllabeltag() != "NONE") {
if (isNormalEnv(style)) {
2015-05-17 15:27:12 +00:00
// in this case, we print the label only for the first
// paragraph (as in a theorem).
if (par == pbegin) {
2015-05-17 15:27:12 +00:00
docstring const lbl =
pbegin->params().labelString();
if (!lbl.empty()) {
openLabelTag(xs, style);
xs << lbl;
closeLabelTag(xs, style);
}
xs << html::CR();
}
} else { // some kind of list
if (style.labeltype == LABEL_MANUAL) {
openLabelTag(xs, style);
sep = par->firstWordLyXHTML(xs, runparams);
closeLabelTag(xs, style);
xs << html::CR();
}
else {
openLabelTag(xs, style);
xs << par->params().labelString();
closeLabelTag(xs, style);
xs << html::CR();
}
}
} // end label output
if (labelfirst)
openItemTag(xs, style, par->params());
docstring deferred = par->simpleLyXHTMLOnePar(buf, xs, runparams,
text.outerFont(distance(begin, par)), true, true, sep);
xs << XHTMLStream::ESCAPE_NONE << deferred;
++par;
// We may not want to close the tag yet, in particular:
// If we're not at the end...
2015-05-17 15:27:12 +00:00
if (par != pend
// and are doing items...
&& !isNormalEnv(style)
// and if the depth has changed...
&& par->params().depth() != origdepth) {
// then we'll save this layout for later, and close it when
// we get another item.
lastlay = &style;
} else
closeItemTag(xs, style);
xs << html::CR();
}
// The other possibility is that the depth has increased, in which
// case we need to recurse.
else {
send = findEndOfEnvironment(par, pend);
2013-04-25 14:28:22 +00:00
par = makeEnvironment(buf, xs, runparams, text, par, send);
}
break;
}
case LATEX_PARAGRAPH:
send = findLastParagraph(par, pend);
par = makeParagraphs(buf, xs, runparams, text, par, send);
break;
// Shouldn't happen
case LATEX_BIB_ENVIRONMENT:
send = par;
++send;
par = makeParagraphs(buf, xs, runparams, text, par, send);
break;
// Shouldn't happen
case LATEX_COMMAND:
++par;
break;
}
}
2020-05-03 06:32:39 +00:00
if (lastlay != nullptr)
closeItemTag(xs, *lastlay);
closeTag(xs, bstyle);
xs << html::CR();
return pend;
}
void makeCommand(Buffer const & buf,
XHTMLStream & xs,
OutputParams const & runparams,
Text const & text,
ParagraphList::const_iterator const & pbegin)
{
Layout const & style = pbegin->layout();
if (!style.counter.empty())
buf.masterBuffer()->params().
documentClass().counters().step(style.counter, OutputUpdate);
bool const make_parid = !runparams.for_toc && runparams.html_make_pars;
2017-07-03 17:53:14 +00:00
openParTag(xs, style, pbegin->params(),
make_parid ? pbegin->magicLabel() : "");
// Label around sectioning number:
// FIXME Probably need to account for LABEL_MANUAL
// FIXME Probably also need now to account for labels ABOVE and CENTERED.
if (style.labeltype != LABEL_NO_LABEL) {
openLabelTag(xs, style);
xs << pbegin->params().labelString();
closeLabelTag(xs, style);
// Otherwise the label might run together with the text
xs << from_ascii(" ");
}
ParagraphList::const_iterator const begin = text.paragraphs().begin();
pbegin->simpleLyXHTMLOnePar(buf, xs, runparams,
text.outerFont(distance(begin, pbegin)));
closeTag(xs, style);
xs << html::CR();
}
} // end anonymous namespace
void xhtmlParagraphs(Text const & text,
Buffer const & buf,
XHTMLStream & xs,
OutputParams const & runparams)
{
ParagraphList const & paragraphs = text.paragraphs();
if (runparams.par_begin == runparams.par_end) {
runparams.par_begin = 0;
runparams.par_end = paragraphs.size();
}
pit_type bpit = runparams.par_begin;
pit_type const epit = runparams.par_end;
LASSERT(bpit < epit,
{ xs << XHTMLStream::ESCAPE_NONE << "<!-- XHTML output error! -->\n"; return; });
OutputParams ourparams = runparams;
ParagraphList::const_iterator const pend =
(epit == (int) paragraphs.size()) ?
paragraphs.end() : paragraphs.constIterator(epit);
while (bpit < epit) {
ParagraphList::const_iterator par = paragraphs.constIterator(bpit);
if (par->params().startOfAppendix()) {
// We want to reset the counter corresponding to toplevel sectioning
Layout const & lay =
buf.masterBuffer()->params().documentClass().getTOCLayout();
docstring const cnt = lay.counter;
if (!cnt.empty()) {
2012-07-22 09:45:39 +00:00
Counters & cnts =
buf.masterBuffer()->params().documentClass().counters();
cnts.reset(cnt);
}
}
Layout const & style = par->layout();
ParagraphList::const_iterator const lastpar = par;
ParagraphList::const_iterator send;
switch (style.latextype) {
case LATEX_COMMAND: {
// The files with which we are working never have more than
// one paragraph in a command structure.
2015-05-17 15:27:12 +00:00
// FIXME
// if (ourparams.html_in_par)
// fix it so we don't get sections inside standard, e.g.
// note that we may then need to make runparams not const, so we
// can communicate that back.
// FIXME Maybe this fix should be in the routines themselves, in case
// they are called from elsewhere.
makeCommand(buf, xs, ourparams, text, par);
++par;
break;
}
case LATEX_ENVIRONMENT:
case LATEX_LIST_ENVIRONMENT:
case LATEX_ITEM_ENVIRONMENT: {
// FIXME Same fix here.
send = findEndOfEnvironment(par, pend);
2013-04-25 14:28:22 +00:00
par = makeEnvironment(buf, xs, ourparams, text, par, send);
break;
}
case LATEX_BIB_ENVIRONMENT: {
// FIXME Same fix here.
send = findEndOfEnvironment(par, pend);
par = makeBibliography(buf, xs, ourparams, text, par, send);
break;
}
case LATEX_PARAGRAPH:
send = findLastParagraph(par, pend);
par = makeParagraphs(buf, xs, ourparams, text, par, send);
break;
}
bpit += distance(lastpar, par);
}
}
string alignmentToCSS(LyXAlignment align)
{
switch (align) {
case LYX_ALIGN_BLOCK:
// we are NOT going to use text-align: justify!!
case LYX_ALIGN_LEFT:
return "left";
case LYX_ALIGN_RIGHT:
return "right";
case LYX_ALIGN_CENTER:
return "center";
default:
break;
}
return "";
}
} // namespace lyx