/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* This file is part of the libabw project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <cassert>
#include <locale>
#include <memory>
#include <sstream>
#include <boost/spirit/include/qi.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>
#include <librevenge/librevenge.h>
#include "ABWContentCollector.h"
#include "libabw_internal.h"
#define ABW_EPSILON 1.0E-06
#define MAX_LIST_LEVEL 64 // a safeguard against damaged files
using boost::optional;
namespace libabw
{
namespace
{
static std::string getColor(const std::string &s)
{
if (s.empty())
return s;
if (s[0] == '#')
{
if (s.length() != 7)
return std::string();
else
return s;
}
else if (s.length() != 6)
return std::string();
std::string out = ("#");
out.append(s);
return out;
}
static void separateTabsAndInsertText(ABWOutputElements &outputElements, const librevenge::RVNGString &text)
{
if (text.empty())
return;
librevenge::RVNGString tmpText;
librevenge::RVNGString::Iter i(text);
for (i.rewind(); i.next();)
{
if (*(i()) == '\t')
{
if (!tmpText.empty())
{
outputElements.addInsertText(tmpText);
tmpText.clear();
}
outputElements.addInsertTab();
}
else if (*(i()) == '\n'|| *(i()) == (char)0x0a)
{
if (!tmpText.empty())
{
outputElements.addInsertText(tmpText);
tmpText.clear();
}
outputElements.addInsertLineBreak();
}
else
{
tmpText.append(i());
}
}
if (!tmpText.empty())
outputElements.addInsertText(tmpText);
}
static void separateSpacesAndInsertText(ABWOutputElements &outputElements, const librevenge::RVNGString &text)
{
if (text.empty())
{
outputElements.addInsertText(text);
return;
}
librevenge::RVNGString tmpText;
int numConsecutiveSpaces = 0;
librevenge::RVNGString::Iter i(text);
for (i.rewind(); i.next();)
{
if (*(i()) == ' ')
numConsecutiveSpaces++;
else
numConsecutiveSpaces = 0;
if (numConsecutiveSpaces > 1)
{
if (!tmpText.empty())
{
separateTabsAndInsertText(outputElements, tmpText);
tmpText.clear();
}
outputElements.addInsertSpace();
}
else
tmpText.append(i());
}
separateTabsAndInsertText(outputElements, tmpText);
}
void parseTableColumns(const std::string &str, librevenge::RVNGPropertyListVector &columns)
{
if (str.empty())
return;
std::string propString(boost::trim_copy_if(str, boost::is_any_of("/ ")));
std::vector<std::string> strVec;
boost::algorithm::split(strVec, propString, boost::is_any_of("/"), boost::token_compress_on);
for (auto &i : strVec)
{
ABWUnit unit(ABW_NONE);
double value(0.0);
boost::algorithm::trim(i);
if (findDouble(i, value, unit) || ABW_IN != unit)
{
librevenge::RVNGPropertyList propList;
propList.insert("style:column-width", value);
columns.append(propList);
}
}
}
bool parseTabStop(const std::string &str, librevenge::RVNGPropertyList &tabStop)
{
if (str.empty())
return false;
std::string sTabStop(boost::trim_copy_if(str, boost::is_any_of("/ ")));
std::vector<std::string> strVec;
boost::algorithm::split(strVec, sTabStop, boost::is_any_of("/"), boost::token_compress_on);
if (strVec.size() < 2)
return false;
boost::algorithm::trim(strVec[0]);
ABWUnit unit(ABW_NONE);
double value(0.0);
if (!findDouble(strVec[0], value, unit) || ABW_IN != unit)
return false;
tabStop.insert("style:position", value);
boost::algorithm::trim(strVec[1]);
if (!strVec[1].empty())
{
switch (strVec[1][0])
{
case 'L':
tabStop.insert("style:type", "left");
break;
case 'C':
tabStop.insert("style:type", "center");
break;
case 'D':
tabStop.insert("style:type", "char");
break;
case 'R':
tabStop.insert("style:type", "right");
break;
default:
tabStop.insert("style:type", "left");
break;
}
}
if (strVec[1].size() > 1)
{
switch (strVec[1][1])
{
case '3':
tabStop.insert("style:leader-text", "_");
break;
case '2':
tabStop.insert("style:leader-text", "-");
break;
case '1':
tabStop.insert("style:leader-text", ".");
break;
default:
break;
}
}
return true;
}
void parseTabStops(const std::string &str, librevenge::RVNGPropertyListVector &tabStops)
{
if (str.empty())
return;
std::string sTabStops(boost::trim_copy_if(str, boost::is_any_of(", ")));
std::vector<std::string> strVec;
boost::algorithm::split(strVec, sTabStops, boost::is_any_of(","), boost::token_compress_on);
for (auto &i : strVec)
{
boost::trim(i);
librevenge::RVNGPropertyList tabStop;
if (parseTabStop(i, tabStop))
tabStops.append(tabStop);
}
}
void parseLang(const std::string &langStr, optional<std::string> &lang, optional<std::string> &country, optional<std::string> &script)
{
std::vector<std::string> tags;
tags.reserve(3);
boost::split(tags, langStr, boost::is_any_of("-_"), boost::token_compress_off);
if ((tags.size() > 0) && boost::all(tags[0], boost::is_lower()) && (2 <= tags[0].size()) && (3 >= tags[0].size()))
{
lang = tags[0];
if (tags.size() > 1)
{
if (boost::all(tags[1], boost::is_upper()) && (2 == tags[1].size()))
country = tags[1];
else
script = tags[1];
}
if ((tags.size() > 2) && bool(script))
{
if (boost::all(tags[2], boost::is_upper()) && (2 == tags[2].size()))
country = tags[2];
}
}
}
static std::string decodeUrl(const std::string &str)
{
using namespace boost::spirit::qi;
if (str.empty())
return str;
// look for a hexadecimal number of 2 digits
uint_parser<char,16,2,2> urlhex;
std::string decoded_string;
auto it = str.cbegin();
if (parse(it, str.cend(),
+(
(lit('%') >> (char_('%') | urlhex))
| (!lit('%') >> char_)
),
decoded_string)
&& it == str.cend())
return decoded_string;
return str;
}
std::string findProperty(const ABWPropertyMap &propMap, const char *const name)
{
if (!name)
return std::string();
auto iter = propMap.find(name);
if (iter != propMap.end())
return iter->second;
return std::string();
}
} // anonymous namespace
} // namespace libabw
libabw::ABWContentTableState::ABWContentTableState() :
m_currentTableProperties(),
m_currentCellProperties(),
m_currentTableCol(-1),
m_currentTableRow(-1),
m_currentTableCellNumberInRow(-1),
m_currentTableId(-1),
m_isTableRowOpened(false),
m_isTableColumnOpened(false),
m_isTableCellOpened(false),
m_isCellWithoutParagraph(false),
m_isRowWithoutCell(false)
{
}
libabw::ABWContentTableState::ABWContentTableState(const ABWContentTableState &ts) :
m_currentTableProperties(ts.m_currentTableProperties),
m_currentCellProperties(ts.m_currentCellProperties),
m_currentTableCol(ts.m_currentTableCol),
m_currentTableRow(ts.m_currentTableRow),
m_currentTableCellNumberInRow(ts.m_currentTableCellNumberInRow),
m_currentTableId(ts.m_currentTableId),
m_isTableRowOpened(ts.m_isTableRowOpened),
m_isTableColumnOpened(ts.m_isTableColumnOpened),
m_isTableCellOpened(ts.m_isTableCellOpened),
m_isCellWithoutParagraph(ts.m_isCellWithoutParagraph),
m_isRowWithoutCell(ts.m_isRowWithoutCell)
{
}
libabw::ABWContentTableState::~ABWContentTableState()
{
}
libabw::ABWContentParsingState::ABWContentParsingState() :
m_isDocumentStarted(false),
m_isPageSpanOpened(false),
m_isSectionOpened(false),
m_isHeaderOpened(false),
m_isFooterOpened(false),
m_isPageFrame(false),
m_isSpanOpened(false),
m_isParagraphOpened(false),
m_isListElementOpened(false),
m_inParagraphOrListElement(false),
m_currentSectionStyle(),
m_currentParagraphStyle(),
m_currentCharacterStyle(),
m_pageWidth(0.0),
m_pageHeight(0.0),
m_pageMarginTop(0.0),
m_pageMarginBottom(0.0),
m_pageMarginLeft(0.0),
m_pageMarginRight(0.0),
m_footerId(-1),
m_footerLeftId(-1),
m_footerFirstId(-1),
m_footerLastId(-1),
m_headerId(-1),
m_headerLeftId(-1),
m_headerFirstId(-1),
m_headerLastId(-1),
m_currentHeaderFooterId(-1),
m_currentHeaderFooterOccurrence(),
m_parsingContext(ABW_SECTION),
m_deferredPageBreak(false),
m_deferredColumnBreak(false),
m_isNote(false),
m_currentListLevel(0),
m_currentListId(0),
m_isFirstTextInListElement(false),
m_tableStates(),
m_listLevels()
{
}
libabw::ABWContentParsingState::ABWContentParsingState(const ABWContentParsingState &ps) :
m_isDocumentStarted(ps.m_isDocumentStarted),
m_isPageSpanOpened(ps.m_isPageSpanOpened),
m_isSectionOpened(ps.m_isSectionOpened),
m_isHeaderOpened(ps.m_isHeaderOpened),
m_isFooterOpened(ps.m_isFooterOpened),
m_isPageFrame(ps.m_isPageFrame),
m_isSpanOpened(ps.m_isSpanOpened),
m_isParagraphOpened(ps.m_isParagraphOpened),
m_isListElementOpened(ps.m_isListElementOpened),
m_inParagraphOrListElement(ps.m_inParagraphOrListElement),
m_currentSectionStyle(ps.m_currentSectionStyle),
m_currentParagraphStyle(ps.m_currentParagraphStyle),
m_currentCharacterStyle(ps.m_currentCharacterStyle),
m_pageWidth(ps.m_pageWidth),
m_pageHeight(ps.m_pageHeight),
m_pageMarginTop(ps.m_pageMarginTop),
m_pageMarginBottom(ps.m_pageMarginBottom),
m_pageMarginLeft(ps.m_pageMarginLeft),
m_pageMarginRight(ps.m_pageMarginRight),
m_footerId(ps.m_footerId),
m_footerLeftId(ps.m_footerLeftId),
m_footerFirstId(ps.m_footerFirstId),
m_footerLastId(ps.m_footerLastId),
m_headerId(ps.m_headerId),
m_headerLeftId(ps.m_headerLeftId),
m_headerFirstId(ps.m_headerFirstId),
m_headerLastId(ps.m_headerLastId),
m_currentHeaderFooterId(ps.m_currentHeaderFooterId),
m_currentHeaderFooterOccurrence(ps.m_currentHeaderFooterOccurrence),
m_parsingContext(ps.m_parsingContext),
m_deferredPageBreak(ps.m_deferredPageBreak),
m_deferredColumnBreak(ps.m_deferredColumnBreak),
m_isNote(ps.m_isNote),
m_currentListLevel(ps.m_currentListLevel),
m_currentListId(ps.m_currentListId),
m_isFirstTextInListElement(ps.m_isFirstTextInListElement),
m_tableStates(ps.m_tableStates),
m_listLevels(ps.m_listLevels)
{
}
libabw::ABWContentParsingState::~ABWContentParsingState()
{
}
libabw::ABWContentCollector::ABWContentCollector(librevenge::RVNGTextInterface *iface, const std::map<int, int> &tableSizes,
const std::map<std::string, ABWData> &data,
const std::map<int, std::shared_ptr<ABWListElement>> &listElements) :
m_ps(new ABWContentParsingState),
m_iface(iface),
m_parsingStates(),
m_dontLoop(),
m_textStyles(),
m_documentStyle(),
m_metadata(),
m_data(data),
m_tableSizes(tableSizes),
m_tableCounter(0),
m_outputElements(),
m_pageOutputElements(),
m_listElements(listElements),
m_dummyListElements()
{
}
libabw::ABWContentCollector::~ABWContentCollector()
{
}
void libabw::ABWContentCollector::collectTextStyle(const char *name, const char *basedon, const char *followedby, const char *props)
{
ABWStyle style;
style.basedon = basedon ? basedon : std::string();
style.followedby = followedby ? followedby : std::string();
if (props)
parsePropString(props, style.properties);
if (name)
m_textStyles[name] = style;
}
void libabw::ABWContentCollector::_recurseTextProperties(const char *name, ABWPropertyMap &styleProps)
{
if (name)
{
m_dontLoop.insert(name);
std::map<std::string, ABWStyle>::const_iterator iter = m_textStyles.find(name);
if (iter != m_textStyles.end() && !(iter->second.basedon.empty()) && !m_dontLoop.count(iter->second.basedon))
_recurseTextProperties(iter->second.basedon.c_str(), styleProps);
if (iter != m_textStyles.end())
{
for (const auto &propertie : iter->second.properties)
styleProps[propertie.first] = propertie.second;
}
// Styles based on "Heading X" style are recognized as headings.
if (boost::starts_with(name, "Heading "))
{
int level = 0;
const std::string levelStr = std::string(name).substr(8);
if (findInt(levelStr, level))
{
// Abiword only has 4 levels of headings, but allow some more
if ((0 < level) && (10 > level))
styleProps["libabw:outline-level"] = levelStr;
}
}
}
if (!m_dontLoop.empty())
m_dontLoop.clear();
}
std::string libabw::ABWContentCollector::_findDocumentProperty(const char *const name)
{
return findProperty(m_documentStyle, name);
}
std::string libabw::ABWContentCollector::_findParagraphProperty(const char *name)
{
return findProperty(m_ps->m_currentParagraphStyle, name);
}
std::string libabw::ABWContentCollector::_findTableProperty(const char *name)
{
assert(!m_ps->m_tableStates.empty());
return findProperty(m_ps->m_tableStates.top().m_currentTableProperties, name);
}
std::string libabw::ABWContentCollector::_findCellProperty(const char *name)
{
assert(!m_ps->m_tableStates.empty());
return findProperty(m_ps->m_tableStates.top().m_currentCellProperties, name);
}
std::string libabw::ABWContentCollector::_findSectionProperty(const char *name)
{
return findProperty(m_ps->m_currentSectionStyle, name);
}
std::string libabw::ABWContentCollector::_findCharacterProperty(const char *name)
{
std::string prop = findProperty(m_ps->m_currentCharacterStyle, name);
if (prop.empty())
prop = findProperty(m_ps->m_currentParagraphStyle, name);
return prop;
}
std::string libabw::ABWContentCollector::_findMetadataEntry(const char *const name)
{
return findProperty(m_metadata, name);
}
void libabw::ABWContentCollector::collectDocumentProperties(const char *const props)
{
if (props)
parsePropString(props, m_documentStyle);
}
void libabw::ABWContentCollector::_addBorderProperties(const std::map<std::string, std::string> &map, librevenge::RVNGPropertyList &propList, const std::string &defaultUndefBorderProp)
{
int setBorders=0;
static char const *odtWh[4]= {"fo:border-left", "fo:border-right", "fo:border-top", "fo:border-bottom"};
for (int i=0, depl=1; i<4; ++i, depl*=2)
{
static char const *wh[4]= {"left", "right", "top", "bot"};
std::string whString(wh[i]);
std::string tmp=whString+"-color";
if (map.find(tmp)==map.end()) continue;
std::string color=getColor(map.find(tmp)->second);
if (color.empty())
continue;
int style;
tmp=whString+"-style";
if (map.find(tmp)==map.end() || !findInt(map.find(tmp)->second, style))
style=1;
else if (style<=0 || style>=4)
{
if (style==0) setBorders |= depl;
continue;
}
ABWUnit unit(ABW_NONE);
double width(0.0);
tmp=whString+"-thickness";
if (map.find(tmp)==map.end() || !findDouble(map.find(tmp)->second, width, unit))
width=0.01;
else if (width<=0 || unit != ABW_IN)
continue;
std::stringstream s;
s.imbue(std::locale("C")); // be sure that we use standart double
s << width << "in ";
if (style==2) s << "dotted ";
else if (style==3) s << "dashed ";
else s << "solid ";
s << color;
propList.insert(odtWh[i], s.str().c_str());
setBorders|=depl;
}
if (defaultUndefBorderProp.empty()) return;
for (int i=0, depl=1; i<4; ++i, depl*=2)
{
if (setBorders&depl) continue;
propList.insert(odtWh[i], defaultUndefBorderProp.c_str());
}
}
void libabw::ABWContentCollector::collectParagraphProperties(const char *level, const char *listid, const char * /*parentid*/, const char *style, const char *props)
{
_closeBlock();
if (!level || !findInt(level, m_ps->m_currentListLevel) || m_ps->m_currentListLevel < 1)
m_ps->m_currentListLevel = 0;
if (m_ps->m_currentListLevel > MAX_LIST_LEVEL)
m_ps->m_currentListLevel = MAX_LIST_LEVEL;
if (!listid || !findInt(listid, m_ps->m_currentListId) || m_ps->m_currentListId < 0)
m_ps->m_currentListId = 0;
m_ps->m_currentParagraphStyle.clear();
if (style)
_recurseTextProperties(style, m_ps->m_currentParagraphStyle);
else
_recurseTextProperties("Normal", m_ps->m_currentParagraphStyle);
ABWPropertyMap tmpProps;
if (props)
parsePropString(props, tmpProps);
for (ABWPropertyMap::const_iterator iter = tmpProps.begin(); iter != tmpProps.end(); ++iter)
m_ps->m_currentParagraphStyle[iter->first] = iter->second;
m_ps->m_inParagraphOrListElement = true;
}
void libabw::ABWContentCollector::collectCharacterProperties(const char *style, const char *props)
{
// We started a new span without closing the last one. That can actually happen, because <p>
// allows mixed content, so there can be text spans not enclosed in <c>.
if (m_ps->m_isSpanOpened)
_closeSpan();
m_ps->m_currentCharacterStyle.clear();
if (style)
_recurseTextProperties(style, m_ps->m_currentCharacterStyle);
ABWPropertyMap tmpProps;
if (props)
parsePropString(props, tmpProps);
for (ABWPropertyMap::const_iterator iter = tmpProps.begin(); iter != tmpProps.end(); ++iter)
m_ps->m_currentCharacterStyle[iter->first] = iter->second;
}
void libabw::ABWContentCollector::collectSectionProperties(const char *footer, const char *footerLeft, const char *footerFirst, const char *footerLast,
const char *header, const char *headerLeft, const char *headerFirst, const char *headerLast,
const char *props)
{
_closeHeader();
_closeFooter();
_closeSection();
double pageMarginLeft = m_ps->m_pageMarginLeft;
double pageMarginRight = m_ps->m_pageMarginRight;
double pageMarginTop = m_ps->m_pageMarginTop;
double pageMarginBottom = m_ps->m_pageMarginBottom;
int headerId = m_ps->m_headerId;
int headerLeftId = m_ps->m_headerLeftId;
int headerFirstId = m_ps->m_headerFirstId;
int headerLastId = m_ps->m_headerLastId;
int footerId = m_ps->m_footerId;
int footerLeftId = m_ps->m_footerLeftId;
int footerFirstId = m_ps->m_footerFirstId;
int footerLastId = m_ps->m_footerLastId;
m_ps->m_currentSectionStyle.clear();
ABWPropertyMap tmpProps;
if (props)
parsePropString(props, tmpProps);
ABWUnit unit(ABW_NONE);
double value(0.0);
for (ABWPropertyMap::const_iterator iter = tmpProps.begin(); iter != tmpProps.end(); ++iter)
{
if (iter->first == "page-margin-right" && !iter->second.empty() && fabs(m_ps->m_pageMarginRight) < ABW_EPSILON)
{
if (findDouble(iter->second, value, unit) && unit == ABW_IN && value > 0.0 && fabs(value) > ABW_EPSILON)
m_ps->m_pageMarginRight = value;
}
else if (iter->first == "page-margin-left" && !iter->second.empty() && fabs(m_ps->m_pageMarginLeft) < ABW_EPSILON)
{
if (findDouble(iter->second, value, unit) && unit == ABW_IN && value > 0.0 && fabs(value) > ABW_EPSILON)
m_ps->m_pageMarginLeft = value;
}
else if (iter->first == "page-margin-top" && !iter->second.empty() && fabs(m_ps->m_pageMarginTop) < ABW_EPSILON)
{
if (findDouble(iter->second, value, unit) && unit == ABW_IN && value > 0.0 && fabs(value) > ABW_EPSILON)
m_ps->m_pageMarginTop = value;
}
else if (iter->first == "page-margin-bottom" && !iter->second.empty() && fabs(m_ps->m_pageMarginBottom) < ABW_EPSILON)
{
if (findDouble(iter->second, value, unit) && unit == ABW_IN && value > 0.0 && fabs(value) > ABW_EPSILON)
m_ps->m_pageMarginBottom = value;
}
m_ps->m_currentSectionStyle[iter->first] = iter->second;
}
int intValue(0);
if (footer && findInt(footer, intValue) && intValue >= 0)
m_ps->m_footerId = intValue;
else
m_ps->m_footerId = -1;
if (footerLeft && findInt(footerLeft, intValue) && intValue >= 0)
m_ps->m_footerLeftId = intValue;
else
m_ps->m_footerLeftId = -1;
if (footerFirst && findInt(footerFirst, intValue) && intValue >= 0)
m_ps->m_footerFirstId = intValue;
else
m_ps->m_footerFirstId = -1;
if (footerLast && findInt(footerLast, intValue) && intValue >= 0)
m_ps->m_footerLastId = intValue;
else
m_ps->m_footerLastId = -1;
if (header && findInt(header, intValue) && intValue >= 0)
m_ps->m_headerId = intValue;
else
m_ps->m_headerId = -1;
if (headerLeft && findInt(headerLeft, intValue) && intValue >= 0)
m_ps->m_headerLeftId = intValue;
else
m_ps->m_headerLeftId = -1;
if (headerFirst && findInt(headerFirst, intValue) && intValue >= 0)
m_ps->m_headerFirstId = intValue;
else
m_ps->m_headerFirstId = -1;
if (headerLast && findInt(headerLast, intValue) && intValue >= 0)
m_ps->m_headerLastId = intValue;
else
m_ps->m_headerLastId = -1;
if (fabs(m_ps->m_pageMarginRight) < ABW_EPSILON)
m_ps->m_pageMarginRight = 1.0;
if (fabs(m_ps->m_pageMarginLeft) < ABW_EPSILON)
m_ps->m_pageMarginLeft = 1.0;
if (fabs(m_ps->m_pageMarginTop) < ABW_EPSILON)
m_ps->m_pageMarginTop = 1.0;
if (fabs(m_ps->m_pageMarginBottom) < ABW_EPSILON)
m_ps->m_pageMarginBottom = 1.0;
if (fabs(pageMarginLeft-m_ps->m_pageMarginLeft) > ABW_EPSILON ||
fabs(pageMarginRight-m_ps->m_pageMarginRight) > ABW_EPSILON ||
fabs(pageMarginTop-m_ps->m_pageMarginTop) > ABW_EPSILON ||
fabs(pageMarginBottom-m_ps->m_pageMarginBottom) > ABW_EPSILON ||
footerId != m_ps->m_footerId || footerLeftId != m_ps->m_footerLeftId ||
footerFirstId != m_ps->m_footerFirstId || footerLastId != m_ps->m_footerLastId ||
headerId != m_ps->m_headerId || headerLeftId != m_ps->m_headerLeftId ||
headerFirstId != m_ps->m_headerFirstId || headerLastId != m_ps->m_headerLastId)
{
_closePageSpan();
}
}
void libabw::ABWContentCollector::collectHeaderFooter(const char *id, const char *type)
{
if (!id || !findInt(id, m_ps->m_currentHeaderFooterId))
m_ps->m_currentHeaderFooterId = -1;
if (!type)
m_ps->m_currentHeaderFooterId = -1;
std::string sType(type ? type : "");
boost::trim(sType);
std::vector<std::string> strVec;
boost::algorithm::split(strVec, sType, boost::is_any_of("-"), boost::token_compress_on);
if (strVec.size() >= 2)
m_ps->m_currentHeaderFooterOccurrence = strVec[1].c_str();
else
m_ps->m_currentHeaderFooterOccurrence = "all";
if (!strVec.empty())
{
if (strVec[0] == "header")
m_ps->m_parsingContext = ABW_HEADER;
else if (strVec[0] == "footer")
m_ps->m_parsingContext = ABW_FOOTER;
else
m_ps->m_parsingContext = ABW_SECTION;
}
else
m_ps->m_parsingContext = ABW_SECTION;
}
void libabw::ABWContentCollector::collectPageSize(const char *width, const char *height, const char *units, const char * /* pageScale */)
{
std::string widthStr(width ? width : "");
std::string heightStr(height ? height : "");
if (units)
{
widthStr.append(units);
heightStr.append(units);
}
ABWUnit unit;
double value;
if (findDouble(widthStr, value, unit) && unit == ABW_IN)
m_ps->m_pageWidth = value;
if (findDouble(heightStr, value, unit) && unit == ABW_IN)
m_ps->m_pageHeight = value;
}
void libabw::ABWContentCollector::startDocument()
{
if (!m_ps->m_isNote && m_ps->m_tableStates.empty())
{
if (m_iface && !m_ps->m_isDocumentStarted)
{
m_iface->startDocument(librevenge::RVNGPropertyList());
_setMetadata();
}
m_ps->m_isDocumentStarted = true;
}
}
void libabw::ABWContentCollector::endDocument()
{
if (!m_ps->m_isNote)
{
if (!m_ps->m_isPageSpanOpened)
_openPageSpan();
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList(); // flush the list
// close the document nice and tight
_closeSection();
_closeHeader();
_closeFooter();
_closePageSpan();
if (m_iface)
{
m_pageOutputElements.write(m_iface);
m_outputElements.write(m_iface);
m_iface->endDocument();
}
}
}
void libabw::ABWContentCollector::_setMetadata()
{
librevenge::RVNGPropertyList propList;
const std::string dcKeys[] = { "language", "publisher", "source", "subject", "title", "type" };
for (std::size_t i = 0; i != ABW_NUM_ELEMENTS(dcKeys); ++i)
{
const std::string abwKey = "dc." + dcKeys[i];
const std::string rvngKey = "dc:" + dcKeys[i];
const std::string prop = _findMetadataEntry(abwKey.c_str());
if (!prop.empty())
propList.insert(rvngKey.c_str(), prop.c_str());
}
std::string prop = _findMetadataEntry("abiword.keywords");
if (!prop.empty())
propList.insert("meta:keyword", prop.c_str());
prop = _findMetadataEntry("dc.creator");
if (!prop.empty())
propList.insert("meta:initial-creator", prop.c_str());
#ifdef VERSION
const std::string version(VERSION);
#else
const std::string version("unknown");
#endif
std::string generator = "libabw/" + version;
propList.insert("meta:generator", generator.c_str());
if (m_iface)
m_iface->setDocumentMetaData(propList);
}
void libabw::ABWContentCollector::endSection()
{
m_ps->m_currentListLevel = 0;
_changeList(); // flush the list exterior
_closeHeader();
_closeFooter();
_closeSection();
}
void libabw::ABWContentCollector::closeParagraphOrListElement()
{
// we have an empty paragraph, insert it
if (!m_ps->m_isParagraphOpened && !m_ps->m_isListElementOpened)
_openSpan();
_closeBlock();
m_ps->m_currentParagraphStyle.clear();
m_ps->m_inParagraphOrListElement = false;
}
void libabw::ABWContentCollector::openLink(const char *href)
{
if (m_ps->m_isSpanOpened)
_closeSpan();
_openBlock();
librevenge::RVNGPropertyList propList;
if (href)
propList.insert("xlink:href", decodeUrl(href).c_str());
m_outputElements.addOpenLink(propList);
if (!m_ps->m_isSpanOpened)
_openSpan();
}
void libabw::ABWContentCollector::closeLink()
{
if (m_ps->m_isSpanOpened)
_closeSpan();
m_outputElements.addCloseLink();
}
void libabw::ABWContentCollector::closeSpan()
{
_closeSpan();
m_ps->m_currentCharacterStyle.clear();
}
void libabw::ABWContentCollector::insertLineBreak()
{
if (!m_ps->m_isSpanOpened)
_openSpan();
m_outputElements.addInsertLineBreak();
}
void libabw::ABWContentCollector::insertColumnBreak()
{
_closeBlock();
m_ps->m_deferredColumnBreak = true;
}
void libabw::ABWContentCollector::insertPageBreak()
{
_closeBlock();
m_ps->m_deferredPageBreak = true;
}
void libabw::ABWContentCollector::insertText(const char *text)
{
if (!m_ps->m_inParagraphOrListElement)
return;
if (m_ps->m_isFirstTextInListElement && text && text[0]==' ' && text[1]==0)
return;
if (!m_ps->m_isSpanOpened)
_openSpan();
if (!text)
return;
if (m_ps->m_isFirstTextInListElement && text[0] == '\t')
separateSpacesAndInsertText(m_outputElements, text+1);
else
separateSpacesAndInsertText(m_outputElements, text);
m_ps->m_isFirstTextInListElement = false;
}
void libabw::ABWContentCollector::_openPageSpan()
{
if (!m_ps->m_isPageSpanOpened && !m_ps->m_isNote && m_ps->m_tableStates.empty())
{
if (!m_ps->m_isDocumentStarted)
startDocument();
librevenge::RVNGPropertyList propList;
// assume default page size is A4
propList.insert("fo:page-width", m_ps->m_pageWidth>0 ? m_ps->m_pageWidth : 8.27);
propList.insert("fo:page-height", m_ps->m_pageHeight>0 ? m_ps->m_pageHeight : 11.7);
propList.insert("fo:margin-left", m_ps->m_pageMarginLeft);
propList.insert("fo:margin-right", m_ps->m_pageMarginRight);
propList.insert("fo:margin-top", m_ps->m_pageMarginTop);
propList.insert("fo:margin-bottom", m_ps->m_pageMarginBottom);
if (!m_ps->m_isPageSpanOpened)
m_outputElements.addOpenPageSpan(propList,
m_ps->m_footerId, m_ps->m_footerLeftId,
m_ps->m_footerFirstId, m_ps->m_footerLastId,
m_ps->m_headerId, m_ps->m_headerLeftId,
m_ps->m_headerFirstId, m_ps->m_headerLastId);
}
m_ps->m_isPageSpanOpened = true;
}
void libabw::ABWContentCollector::_closePageSpan()
{
if (m_ps->m_isPageSpanOpened)
{
_closeHeader();
_closeFooter();
_closeSection();
m_outputElements.addClosePageSpan();
}
m_ps->m_isPageSpanOpened = false;
}
void libabw::ABWContentCollector::_openSection()
{
if (!m_ps->m_isSectionOpened && !m_ps->m_isNote && m_ps->m_tableStates.empty())
{
if (!m_ps->m_isPageSpanOpened)
_openPageSpan();
librevenge::RVNGPropertyList propList;
ABWUnit unit(ABW_NONE);
double value(0.0);
if (findDouble(_findSectionProperty("page-margin-right"), value, unit) && unit == ABW_IN)
propList.insert("fo:margin-right", value - m_ps->m_pageMarginRight);
if (findDouble(_findSectionProperty("page-margin-left"), value, unit) && unit == ABW_IN)
propList.insert("fo:margin-left", value - m_ps->m_pageMarginLeft);
if (findDouble(_findSectionProperty("section-space-after"), value, unit) && unit == ABW_IN)
propList.insert("librevenge:margin-bottom", value);
std::string sValue = _findSectionProperty("dom-dir");
if (sValue.empty()) // try document default
sValue = _findDocumentProperty("dom-dir");
if (sValue == "ltr")
propList.insert("style:writing-mode", "lr-tb");
else if (sValue == "rtl")
propList.insert("style:writing-mode", "rl-tb");
int intValue(0);
if (findInt(_findSectionProperty("columns"), intValue) && intValue > 1)
{
librevenge::RVNGPropertyListVector columns;
for (int i = 0; i < intValue; ++i)
{
librevenge::RVNGPropertyList column;
column.insert("style:rel-width", 1.0 / (double)intValue, librevenge::RVNG_PERCENT);
columns.append(column);
}
if (columns.count())
{
propList.insert("style:columns", columns);
propList.insert("text:dont-balance-text-columns", true);
}
}
m_outputElements.addOpenSection(propList);
}
m_ps->m_isSectionOpened = true;
}
void libabw::ABWContentCollector::_openFooter()
{
if (!m_ps->m_isFooterOpened && !m_ps->m_isNote && m_ps->m_tableStates.empty())
{
librevenge::RVNGPropertyList propList;
propList.insert("librevenge:occurrence", m_ps->m_currentHeaderFooterOccurrence);
m_outputElements.addOpenFooter(propList, m_ps->m_currentHeaderFooterId);
}
m_ps->m_isFooterOpened = true;
}
void libabw::ABWContentCollector::_openHeader()
{
if (!m_ps->m_isHeaderOpened && !m_ps->m_isNote && m_ps->m_tableStates.empty())
{
librevenge::RVNGPropertyList propList;
propList.insert("librevenge:occurrence", m_ps->m_currentHeaderFooterOccurrence);
m_outputElements.addOpenHeader(propList, m_ps->m_currentHeaderFooterId);
}
m_ps->m_isHeaderOpened = true;
}
void libabw::ABWContentCollector::_fillParagraphProperties(librevenge::RVNGPropertyList &propList,
bool isListElement)
{
ABWUnit unit(ABW_NONE);
double value(0.0);
int intValue(0);
std::string sValue;
if (findDouble(_findParagraphProperty("margin-right"), value, unit) && unit == ABW_IN)
propList.insert("fo:margin-right", value);
if (findDouble(_findParagraphProperty("margin-top"), value, unit) && unit == ABW_IN)
propList.insert("fo:margin-top", value);
if (findDouble(_findParagraphProperty("margin-bottom"), value, unit) && unit == ABW_IN)
propList.insert("fo:margin-bottom", value);
if (!isListElement)
{
if (findDouble(_findParagraphProperty("margin-left"), value, unit) && unit == ABW_IN)
propList.insert("fo:margin-left", value);
if (findDouble(_findParagraphProperty("text-indent"), value, unit) && unit == ABW_IN)
propList.insert("fo:text-indent", value);
// TODO: Numbered headings should probably not be handled as lists.
// Just do not make them headings for now.
sValue = _findParagraphProperty("libabw:outline-level");
if (!sValue.empty())
propList.insert("text:outline-level", sValue.c_str());
}
sValue = _findParagraphProperty("text-align");
if (!sValue.empty())
{
if (sValue == "left")
propList.insert("fo:text-align", "start");
else if (sValue == "right")
propList.insert("fo:text-align", "end");
else
propList.insert("fo:text-align", sValue.c_str());
}
sValue = _findParagraphProperty("line-height");
if (!sValue.empty())
{
std::string propName("fo:line-height");
size_t position = sValue.find_last_of('+');
if (position && position != std::string::npos)
{
propName = "style:line-height-at-least";
sValue.erase(position);
}
if (findDouble(sValue, value, unit))
{
if (ABW_IN == unit)
propList.insert(propName.c_str(), value);
else if (ABW_PERCENT == unit)
propList.insert(propName.c_str(), value, librevenge::RVNG_PERCENT);
}
}
if (findInt(_findParagraphProperty("orphans"), intValue))
propList.insert("fo:orphans", intValue);
if (findInt(_findParagraphProperty("widows"), intValue))
propList.insert("fo:widows", intValue);
librevenge::RVNGPropertyListVector tabStops;
parseTabStops(_findParagraphProperty("tabstops"), tabStops);
if (tabStops.count())
propList.insert("style:tab-stops", tabStops);
sValue = _findParagraphProperty("dom-dir");
if (sValue == "ltr")
propList.insert("style:writing-mode", "lr-tb");
else if (sValue == "rtl")
propList.insert("style:writing-mode", "rl-tb");
if (m_ps->m_deferredPageBreak)
propList.insert("fo:break-before", "page");
else if (m_ps->m_deferredColumnBreak)
propList.insert("fo:break-before", "column");
_addBorderProperties(m_ps->m_currentParagraphStyle, propList);
m_ps->m_deferredPageBreak = false;
m_ps->m_deferredColumnBreak = false;
}
void libabw::ABWContentCollector::_openBlock()
{
if (m_ps->m_isParagraphOpened || m_ps->m_isListElementOpened)
return;
if (m_ps->m_currentListLevel == 0)
_openParagraph();
else
_openListElement();
}
void libabw::ABWContentCollector::_closeBlock()
{
if (!m_ps->m_isParagraphOpened && !m_ps->m_isListElementOpened)
return;
if (m_ps->m_isSpanOpened)
_closeSpan();
if (m_ps->m_isParagraphOpened)
_closeParagraph();
if (m_ps->m_isListElementOpened)
_closeListElement();
}
void libabw::ABWContentCollector::_openParagraph()
{
if (!m_ps->m_isParagraphOpened)
{
switch (m_ps->m_parsingContext)
{
case ABW_HEADER:
if (!m_ps->m_isHeaderOpened)
_openHeader();
break;
case ABW_FOOTER:
if (!m_ps->m_isFooterOpened)
_openFooter();
break;
case ABW_SECTION:
default:
if (!m_ps->m_isSectionOpened)
_openSection();
break;
case ABW_FRAME_IMAGE:
ABW_DEBUG_MSG(("libabw::ABWContentCollector::_openParagraph: can not open a paragraph\n"));
m_ps->m_parsingContext=ABW_FRAME_UNKNOWN;
break;
case ABW_FRAME_TEXTBOX:
case ABW_FRAME_UNKNOWN:
break;
}
if (!m_ps->m_tableStates.empty() && !m_ps->m_tableStates.top().m_isTableCellOpened)
_openTableCell();
_changeList();
librevenge::RVNGPropertyList propList;
_fillParagraphProperties(propList, false);
m_ps->m_deferredPageBreak = false;
m_ps->m_deferredColumnBreak = false;
m_outputElements.addOpenParagraph(propList);
m_ps->m_isParagraphOpened = true;
if (!m_ps->m_tableStates.empty())
m_ps->m_tableStates.top().m_isCellWithoutParagraph = false;
}
}
void libabw::ABWContentCollector::_openListElement()
{
if (!m_ps->m_isListElementOpened)
{
switch (m_ps->m_parsingContext)
{
case ABW_HEADER:
if (!m_ps->m_isHeaderOpened)
_openHeader();
break;
case ABW_FOOTER:
if (!m_ps->m_isFooterOpened)
_openFooter();
break;
case ABW_SECTION:
default:
if (!m_ps->m_isSectionOpened)
_openSection();
break;
case ABW_FRAME_IMAGE:
ABW_DEBUG_MSG(("libabw::ABWContentCollector::_openListElement: can not open a list\n"));
m_ps->m_parsingContext=ABW_FRAME_UNKNOWN;
break;
case ABW_FRAME_TEXTBOX:
case ABW_FRAME_UNKNOWN:
break;
}
if (!m_ps->m_tableStates.empty() && !m_ps->m_tableStates.top().m_isTableCellOpened)
_openTableCell();
_changeList();
librevenge::RVNGPropertyList propList;
_fillParagraphProperties(propList, true);
m_outputElements.addOpenListElement(propList);
m_ps->m_isListElementOpened = true;
if (!m_ps->m_tableStates.empty())
m_ps->m_tableStates.top().m_isCellWithoutParagraph = false;
m_ps->m_isFirstTextInListElement = true;
}
}
void libabw::ABWContentCollector::_openSpan()
{
if (!m_ps->m_isSpanOpened)
{
_openBlock();
librevenge::RVNGPropertyList propList;
ABWUnit unit(ABW_NONE);
double value(0.0);
if (findDouble(_findCharacterProperty("font-size"), value, unit) && unit == ABW_IN)
propList.insert("fo:font-size", value);
std::string sValue = _findCharacterProperty("font-family");
if (!sValue.empty())
propList.insert("style:font-name", sValue.c_str());
sValue = _findCharacterProperty("font-style");
if (!sValue.empty() && sValue != "normal")
propList.insert("fo:font-style", sValue.c_str());
sValue = _findCharacterProperty("font-weight");
if (!sValue.empty() && sValue != "normal")
propList.insert("fo:font-weight", sValue.c_str());
sValue = _findCharacterProperty("display");
if (!sValue.empty() && sValue == "none")
propList.insert("text:display", "none");
sValue = _findCharacterProperty("dir-override");
if (!sValue.empty() && sValue == "rtl")
propList.insert("style:writing-mode", "rl-tb");
sValue = _findCharacterProperty("text-decoration");
std::vector<std::string> listDecorations;
boost::split(listDecorations, sValue, boost::is_any_of(" "), boost::token_compress_on);
for (const auto &decoration : listDecorations)
{
if (decoration == "underline")
{
propList.insert("style:text-underline-type", "single");
propList.insert("style:text-underline-style", "solid");
}
else if (decoration == "line-through")
{
propList.insert("style:text-line-through-type", "single");
propList.insert("style:text-line-through-style", "solid");
}
else if (decoration == "overline")
{
propList.insert("style:text-overline-type", "single");
propList.insert("style:text-overline-style", "solid");
}
}
sValue = getColor(_findCharacterProperty("color"));
if (!sValue.empty())
propList.insert("fo:color", sValue.c_str());
sValue = getColor(_findCharacterProperty("bgcolor"));
if (!sValue.empty())
propList.insert("fo:background-color", sValue.c_str());
sValue = _findCharacterProperty("text-position");
if (sValue == "subscript")
propList.insert("style:text-position", "sub");
else if (sValue == "superscript")
propList.insert("style:text-position", "super");
sValue = _findCharacterProperty("lang");
if (sValue.empty()) // try document default
sValue = _findDocumentProperty("lang");
if (!sValue.empty())
{
optional<std::string> lang;
optional<std::string> country;
optional<std::string> script;
parseLang(sValue, lang, country, script);
if (bool(lang))
propList.insert("fo:language", get(lang).c_str());
if (bool(country))
propList.insert("fo:country", get(country).c_str());
if (bool(script))
propList.insert("fo:script", get(script).c_str());
}
// do we need to check "font-stretch" here or it is always equal to normal ?
m_outputElements.addOpenSpan(propList);
}
m_ps->m_isSpanOpened = true;
}
void libabw::ABWContentCollector::_closeSection()
{
if (m_ps->m_isSectionOpened)
{
while (!m_ps->m_tableStates.empty())
_closeTable();
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
m_outputElements.addCloseSection();
m_ps->m_isSectionOpened = false;
}
}
void libabw::ABWContentCollector::_closeHeader()
{
if (m_ps->m_isHeaderOpened)
{
while (!m_ps->m_tableStates.empty())
_closeTable();
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
m_outputElements.addCloseHeader();
m_ps->m_isHeaderOpened = false;
}
m_ps->m_currentHeaderFooterId = -1;
m_ps->m_currentHeaderFooterOccurrence.clear();
}
void libabw::ABWContentCollector::_closeFooter()
{
if (m_ps->m_isFooterOpened)
{
while (!m_ps->m_tableStates.empty())
_closeTable();
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
m_outputElements.addCloseFooter();
m_ps->m_isFooterOpened = false;
}
m_ps->m_currentHeaderFooterId = -1;
m_ps->m_currentHeaderFooterOccurrence.clear();
}
void libabw::ABWContentCollector::_closeParagraph()
{
if (m_ps->m_isParagraphOpened)
{
if (m_ps->m_isSpanOpened)
_closeSpan();
m_outputElements.addCloseParagraph();
}
m_ps->m_isParagraphOpened = false;
}
void libabw::ABWContentCollector::_closeSpan()
{
if (m_ps->m_isSpanOpened)
m_outputElements.addCloseSpan();
m_ps->m_isSpanOpened = false;
}
void libabw::ABWContentCollector::_openTable()
{
switch (m_ps->m_parsingContext)
{
case ABW_HEADER:
if (!m_ps->m_isHeaderOpened)
_openHeader();
break;
case ABW_FOOTER:
if (!m_ps->m_isFooterOpened)
_openFooter();
break;
case ABW_SECTION:
default:
if (!m_ps->m_isSectionOpened)
_openSection();
break;
case ABW_FRAME_IMAGE:
case ABW_FRAME_TEXTBOX:
case ABW_FRAME_UNKNOWN:
break;
}
librevenge::RVNGPropertyList propList;
if (m_ps->m_deferredPageBreak)
propList.insert("fo:break-before", "page");
else if (m_ps->m_deferredColumnBreak)
propList.insert("fo:break-before", "column");
m_ps->m_deferredPageBreak = false;
m_ps->m_deferredColumnBreak = false;
librevenge::RVNGPropertyListVector tmpColumns;
parseTableColumns(_findTableProperty("table-column-props"), tmpColumns);
auto numColumns = unsigned(tmpColumns.count());
auto iter = m_tableSizes.find(m_ps->m_tableStates.top().m_currentTableId);
if (iter != m_tableSizes.end())
numColumns = unsigned(iter->second);
librevenge::RVNGPropertyListVector columns;
for (unsigned j = 0; j < numColumns; ++j)
{
if (j < tmpColumns.count())
columns.append(tmpColumns[j]);
else
columns.append(librevenge::RVNGPropertyList());
}
if (columns.count())
propList.insert("librevenge:table-columns", columns);
ABWUnit unit(ABW_NONE);
double value(0.0);
if (findDouble(_findTableProperty("table-column-leftpos"), value, unit) && unit == ABW_IN)
{
propList.insert("fo:margin-left", value);
propList.insert("table:align", "margins");
}
else
propList.insert("table:align", "left");
m_outputElements.addOpenTable(propList);
m_ps->m_tableStates.top().m_currentTableRow = (-1);
m_ps->m_tableStates.top().m_currentTableCol = (-1);
m_ps->m_tableStates.top().m_currentTableCellNumberInRow = (-1);
}
void libabw::ABWContentCollector::_closeTable()
{
if (!m_ps->m_tableStates.empty())
{
if (m_ps->m_tableStates.top().m_isTableRowOpened)
_closeTableRow();
m_outputElements.addCloseTable();
m_ps->m_tableStates.pop();
}
}
void libabw::ABWContentCollector::_openTableRow()
{
if (m_ps->m_tableStates.top().m_isTableRowOpened)
_closeTableRow();
m_ps->m_tableStates.top().m_currentTableCol = 0;
m_ps->m_tableStates.top().m_currentTableCellNumberInRow = 0;
m_outputElements.addOpenTableRow(librevenge::RVNGPropertyList());
m_ps->m_tableStates.top().m_isTableRowOpened = true;
m_ps->m_tableStates.top().m_isRowWithoutCell = true;
m_ps->m_tableStates.top().m_currentTableRow++;
}
void libabw::ABWContentCollector::_closeTableRow()
{
if (m_ps->m_tableStates.top().m_isTableRowOpened)
{
if (m_ps->m_tableStates.top().m_isTableCellOpened)
_closeTableCell();
if (m_ps->m_tableStates.top().m_isRowWithoutCell)
{
m_ps->m_tableStates.top().m_isRowWithoutCell = false;
m_outputElements.addInsertCoveredTableCell(librevenge::RVNGPropertyList());
}
m_outputElements.addCloseTableRow();
}
m_ps->m_tableStates.top().m_isTableRowOpened = false;
}
void libabw::ABWContentCollector::_openTableCell()
{
librevenge::RVNGPropertyList propList;
propList.insert("librevenge:column", m_ps->m_tableStates.top().m_currentTableCol);
propList.insert("librevenge:row", m_ps->m_tableStates.top().m_currentTableRow);
int rightAttach(0);
if (findInt(_findCellProperty("right-attach"), rightAttach))
propList.insert("table:number-columns-spanned", rightAttach - m_ps->m_tableStates.top().m_currentTableCol);
int botAttach(0);
if (findInt(_findCellProperty("bot-attach"), botAttach))
propList.insert("table:number-rows-spanned", botAttach - m_ps->m_tableStates.top().m_currentTableRow);
std::string bgColor = getColor(_findCellProperty("background-color"));
if (!bgColor.empty())
propList.insert("fo:background-color", bgColor.c_str());
// by default, table cells have a small border
_addBorderProperties(m_ps->m_tableStates.top().m_currentCellProperties, propList,"0.01in solid #000000");
m_outputElements.addOpenTableCell(propList);
m_ps->m_tableStates.top().m_currentTableCellNumberInRow++;
m_ps->m_tableStates.top().m_isTableCellOpened = true;
m_ps->m_tableStates.top().m_isCellWithoutParagraph = true;
m_ps->m_tableStates.top().m_isRowWithoutCell = false;
}
void libabw::ABWContentCollector::_closeTableCell()
{
if (m_ps->m_tableStates.top().m_isTableCellOpened)
{
if (m_ps->m_tableStates.top().m_isCellWithoutParagraph)
_openSpan();
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
m_outputElements.addCloseTableCell();
}
m_ps->m_tableStates.top().m_isTableCellOpened = false;
}
void libabw::ABWContentCollector::openFoot(const char *id)
{
if (!m_ps->m_isParagraphOpened && !m_ps->m_isListElementOpened)
_openSpan();
_closeSpan();
librevenge::RVNGPropertyList propList;
if (id)
propList.insert("librevenge:number", id);
m_outputElements.addOpenFootnote(propList);
m_parsingStates.push(m_ps);
m_ps = std::make_shared<ABWContentParsingState>();
m_ps->m_isNote = true;
}
void libabw::ABWContentCollector::closeFoot()
{
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
m_outputElements.addCloseFootnote();
if (!m_parsingStates.empty())
{
m_ps = m_parsingStates.top();
m_parsingStates.pop();
}
}
void libabw::ABWContentCollector::openEndnote(const char *id)
{
if (!m_ps->m_isParagraphOpened && !m_ps->m_isListElementOpened)
_openSpan();
_closeSpan();
librevenge::RVNGPropertyList propList;
if (id)
propList.insert("librevenge:number", id);
m_outputElements.addOpenEndnote(propList);
m_parsingStates.push(m_ps);
m_ps = std::make_shared<ABWContentParsingState>();
m_ps->m_isNote = true;
}
void libabw::ABWContentCollector::closeEndnote()
{
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
m_outputElements.addCloseEndnote();
if (!m_parsingStates.empty())
{
m_ps = m_parsingStates.top();
m_parsingStates.pop();
}
}
void libabw::ABWContentCollector::openField(const char *type, const char * /*id*/)
{
if (!type || type[0]==0)
{
ABW_DEBUG_MSG(("libabw::ABWContentCollector::openField: called without type\n"));
return;
}
if (!m_ps->m_inParagraphOrListElement)
return;
librevenge::RVNGPropertyList propList;
std::string typ(type);
size_t len=typ.length();
// see fp_Fields.h
switch (typ[0])
{
case 'a':
if (len>4 && typ.substr(0,4)=="app_")
{
// app_ver, app_compiledate, app_compiletime, app_id, app_target, app_options
return;
}
break;
case 'c':
if (len==10 && typ=="char_count")
propList.insert("librevenge:field-type", "text:character-count");
break;
case 'd':
if (len==4 && typ=="date")
{
propList.insert("librevenge:field-type", "text:date");
propList.insert("number:automatic-order", "true");
librevenge::RVNGPropertyListVector pVect;
_convertFieldDTFormat("%A, %B %d,%Y",pVect);
propList.insert("librevenge:value-type", "date");
propList.insert("librevenge:format", pVect);
break;
}
if (len>5 && typ.substr(0,5)=="date_")
{
propList.insert("librevenge:field-type", "text:date");
propList.insert("number:automatic-order", "true");
librevenge::RVNGPropertyListVector pVect;
if (len==10 && typ=="date_ntdlf") // default
;
else if (len==11 && typ=="date_mmddyy")
_convertFieldDTFormat("%m/%d/%y",pVect);
else if (len==11 && typ=="date_ddmmyy")
_convertFieldDTFormat("%d/%m/%y",pVect);
else if (len==8 && typ=="date_mdy")
_convertFieldDTFormat("%B %d,%Y",pVect);
else if (len==10 && typ=="date_mthdy")
_convertFieldDTFormat("%b %d,%Y",pVect);
else if (len==8 && typ=="date_dfl")
_convertFieldDTFormat("%a %b %d %H:%M:%S %Y",pVect);
else if (len==10 && typ=="date_wkday")
_convertFieldDTFormat("%A",pVect);
else if (len==8 && typ=="date_doy") // normally this is the day of the year
_convertFieldDTFormat("%d",pVect);
if (!pVect.empty())
{
propList.insert("librevenge:value-type", "date");
propList.insert("librevenge:format", pVect);
}
break;
}
if (len==15 && typ=="datetime_custom") // TODO add format
{
propList.insert("librevenge:field-type", "text:date");
propList.insert("number:automatic-order", "true");
librevenge::RVNGPropertyListVector pVect;
_convertFieldDTFormat("%d/%m/%y %H:%M:%S",pVect);
propList.insert("librevenge:value-type", "date");
propList.insert("librevenge:format", pVect);
break;
}
break;
case 'e':
if (len==12 && typ=="endnote_anch")
return;
if (len==11 && typ=="endnote_ref")
return;
break;
case 'f':
if (len==9 && typ=="file_name")
{
propList.insert("librevenge:field-type", "text:file-name");
propList.insert("text:display", "full");
break;
}
if (len==13 && typ=="footnote_anch")
return;
if (len==12 && typ=="footnote_ref")
return;
break;
case 'l':
if (len==10 && typ=="list_label")
return;
// line_count
break;
case 'm':
if (len>5 && typ.substr(0,5)=="meta_")
{
if (len==10 && typ=="meta_title")
propList.insert("librevenge:field-type", "text:title");
else if (len==12 && typ=="meta_subject")
propList.insert("librevenge:field-type", "text:subject");
else if (len==12 && typ=="meta_creator")
propList.insert("librevenge:field-type", "text:creator");
else if (len==14 && typ=="meta_publisher")
propList.insert("librevenge:field-type", "text:printed-by");
//else if (len==16 && typ=="meta_contributor")
// else if (len==9 && typ=="meta_type")
else if (len==13 && typ=="meta_keywords")
propList.insert("librevenge:field-type", "text:keywords");
//else if (len==13 && typ=="meta_language")
else if (len==16 && typ=="meta_description")
propList.insert("librevenge:field-type", "text:description");
//else if (len==13 && typ=="meta_coverage")
//else if (len==11 && typ=="meta_rights")
else if (len==9 && typ=="meta_date")
propList.insert("librevenge:field-type", "text:creation-date");
else if (len==22 && typ=="meta_date_last_changed")
propList.insert("librevenge:field-type", "text:modification-date");
break;
}
if (len==10 && typ=="mail_merge") // a datafield?
return;
break;
case 'n':
// nbsp_count
break;
case 'p':
if (len>5 && typ.substr(0,5)=="page_")
{
if (len==11 && typ=="page_number")
propList.insert("librevenge:field-type", "text:page-number");
else if (len==10 && typ=="page_count")
propList.insert("librevenge:field-type", "text:page-count");
// page_ref ?
break;
}
if (len>5 && typ.substr(0,5)=="para_")
{
if (len==10 && typ=="para_count")
propList.insert("librevenge:field-type", "text:paragraph-count");
break;
}
break;
case 's':
if (len==15 && typ=="short_file_name")
{
propList.insert("librevenge:field-type", "text:file-name");
propList.insert("text:display", "full"); // checkme
break;
}
if (len>4 && typ.substr(0,4)=="sum_")
{
// sum_cols, sum_rows
break;
}
break;
case 't':
if (len==4 && typ=="time")
{
propList.insert("librevenge:field-type", "text:time");
propList.insert("number:automatic-order", "true");
break;
}
if (len>5 && typ.substr(0,5)=="time_") // TODO add format
{
propList.insert("librevenge:field-type", "text:time");
propList.insert("number:automatic-order", "true");
librevenge::RVNGPropertyListVector pVect;
if (len==9 && typ=="time_ampm")
_convertFieldDTFormat("%I:%M:%S %p",pVect);
if (len==9 && typ=="time_zone") // CEST, ...
return;
// if (len==10 && typ=="time_epoch") //second since ...
if (len==12 && typ=="time_miltime") // ""
return;
if (!pVect.empty())
{
propList.insert("librevenge:value-type", "time");
propList.insert("librevenge:format", pVect);
}
break;
}
if (len>4 && typ.substr(0,4)=="toc_")
{
if (len==14 && typ=="toc_list_label")
return;
// toc_page_number
break;
}
break;
case 'w':
if (len==11 && typ=="word_count")
propList.insert("librevenge:field-type", "text:word-count");
break;
default:
break;
}
if (propList.empty())
{
ABW_DEBUG_MSG(("libabw::ABWContentCollector::openField: sorry, unknown type=%s\n", type));
}
else
{
if (!m_ps->m_isSpanOpened)
_openSpan();
m_outputElements.addInsertField(propList);
m_ps->m_isFirstTextInListElement = false;
}
}
bool libabw::ABWContentCollector::_convertFieldDTFormat(std::string const &dtFormat, librevenge::RVNGPropertyListVector &propVect)
{
propVect.clear();
size_t len=dtFormat.size();
std::string text("");
librevenge::RVNGPropertyList list;
for (size_t c=0; c < len; ++c)
{
if (dtFormat[c]!='%' || c+1==len)
{
text+=dtFormat[c];
continue;
}
char ch=dtFormat[++c];
if (ch=='%')
{
text += '%';
continue;
}
if (!text.empty())
{
list.clear();
list.insert("librevenge:value-type", "text");
list.insert("librevenge:text", text.c_str());
propVect.append(list);
text.clear();
}
list.clear();
switch (ch)
{
case 'Y':
list.insert("number:style", "long");
/* FALLTHRU */
case 'y':
list.insert("librevenge:value-type", "year");
propVect.append(list);
break;
case 'B':
list.insert("number:style", "long");
/* FALLTHRU */
case 'b':
case 'h':
list.insert("librevenge:value-type", "month");
list.insert("number:textual", true);
propVect.append(list);
break;
case 'm':
list.insert("librevenge:value-type", "month");
propVect.append(list);
break;
case 'e':
list.insert("number:style", "long");
/* FALLTHRU */
case 'd':
list.insert("librevenge:value-type", "day");
propVect.append(list);
break;
case 'A':
list.insert("number:style", "long");
/* FALLTHRU */
case 'a':
list.insert("librevenge:value-type", "day-of-week");
propVect.append(list);
break;
case 'H':
list.insert("number:style", "long");
/* FALLTHRU */
case 'I':
list.insert("librevenge:value-type", "hours");
propVect.append(list);
break;
case 'M':
list.insert("librevenge:value-type", "minutes");
list.insert("number:style", "long");
propVect.append(list);
break;
case 'S':
list.insert("librevenge:value-type", "seconds");
list.insert("number:style", "long");
propVect.append(list);
break;
case 'p':
list.clear();
list.insert("librevenge:value-type", "am-pm");
propVect.append(list);
break;
#if !defined(__clang__)
default:
ABW_DEBUG_MSG(("libabw::ABWContentCollector::_convertFieldDTFormat: find unimplement command %c(ignored)\n", ch));
#endif
}
}
if (!text.empty())
{
list.clear();
list.insert("librevenge:value-type", "text");
list.insert("librevenge:text", text.c_str());
propVect.append(list);
}
return propVect.count()!=0;
}
void libabw::ABWContentCollector::closeField()
{
}
void libabw::ABWContentCollector::openTable(const char *props)
{
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
if (m_ps->m_tableStates.empty())
{
switch (m_ps->m_parsingContext)
{
case ABW_HEADER:
if (!m_ps->m_isHeaderOpened)
_openHeader();
break;
case ABW_FOOTER:
if (!m_ps->m_isFooterOpened)
_openFooter();
break;
case ABW_SECTION:
default:
if (!m_ps->m_isSectionOpened)
_openSection();
break;
case ABW_FRAME_IMAGE:
case ABW_FRAME_TEXTBOX:
case ABW_FRAME_UNKNOWN:
break;
}
}
m_ps->m_tableStates.push(ABWContentTableState());
m_ps->m_tableStates.top().m_currentTableId = m_tableCounter++;
if (props)
parsePropString(props, m_ps->m_tableStates.top().m_currentTableProperties);
_openTable();
}
void libabw::ABWContentCollector::closeTable()
{
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList();
_closeTable();
}
void libabw::ABWContentCollector::openCell(const char *props)
{
if (!m_ps->m_tableStates.empty())
{
if (props)
parsePropString(props, m_ps->m_tableStates.top().m_currentCellProperties);
const int currentRow(getCellPos("top-attach", "bottom-attach", m_ps->m_tableStates.top().m_currentTableRow + 1));
while (m_ps->m_tableStates.top().m_currentTableRow < currentRow)
{
if (m_ps->m_tableStates.top().m_currentTableRow >= 0)
_closeTableRow();
_openTableRow();
}
m_ps->m_tableStates.top().m_currentTableCol =
getCellPos("left-attach", "right-attach", m_ps->m_tableStates.top().m_currentTableCol + 1);
}
}
int libabw::ABWContentCollector::getCellPos(const char *startProp, const char *endProp, int defStart)
{
int startAttach(0);
const bool haveStart(findInt(_findCellProperty(startProp), startAttach));
int endAttach(0);
const bool haveEnd(findInt(_findCellProperty(endProp), endAttach));
int newStartAttach(startAttach);
if (haveStart && haveEnd)
{
if (endAttach <= startAttach && endAttach > 0)
newStartAttach = endAttach - 1;
}
else if (haveStart && !haveEnd)
{
if (startAttach / 1000 > defStart) // likely a damaged input
newStartAttach = defStart;
}
else if (!haveStart && haveEnd)
{
if (endAttach <= 0 || endAttach / 1000 > defStart) // likely a damaged input
newStartAttach = defStart;
else
newStartAttach = endAttach - 1;
}
else
{
newStartAttach = defStart;
}
return newStartAttach;
}
void libabw::ABWContentCollector::closeCell()
{
if (!m_ps->m_tableStates.empty())
{
_closeTableCell();
m_ps->m_tableStates.top().m_currentCellProperties.clear();
}
}
void libabw::ABWContentCollector::openFrame(const char *props, const char *imageId, const char */*title*/, const char */*alt*/)
{
ABWPropertyMap propMap;
if (props)
parsePropString(props, propMap);
ABWPropertyMap::const_iterator iter;
librevenge::RVNGPropertyList propList;
ABWUnit unit(ABW_NONE);
double value(0.0);
// size
iter = propMap.find("frame-height");
if (iter != propMap.end() && findDouble(iter->second, value, unit) && ABW_IN == unit)
propList.insert("svg:height", value);
iter = propMap.find("frame-width");
if (iter != propMap.end() && findDouble(iter->second, value, unit) && ABW_IN == unit)
propList.insert("svg:width", value);
// position
bool isParagraph=true;
iter = propMap.find("position-to");
if (iter != propMap.end())
{
if (iter->second=="page-above-text")
isParagraph=false;
else if (iter->second=="column-above-text")
/* unsure how to retrieve that, so check if the page positions
are defined, if yes, use a page anchor. */
isParagraph=(propMap.find("frame-page-ypos")==propMap.end());
else if (iter->second!="block-above-text")
{
ABW_DEBUG_MSG(("libabw::ABWContentCollector::openFrame: sorry, unknown pos: %s asume paragraph\n", iter->second.c_str()));
}
}
iter = propMap.find(isParagraph ? "xpos" : "frame-page-xpos");
if (iter != propMap.end() && findDouble(iter->second, value, unit) && ABW_IN == unit)
propList.insert("svg:x", value);
iter = propMap.find(isParagraph ? "ypos" : "frame-page-ypos");
if (iter != propMap.end() && findDouble(iter->second, value, unit) && ABW_IN == unit)
propList.insert("svg:y", value);
if (!isParagraph)
{
propList.insert("style:vertical-rel", "page");
propList.insert("style:horizontal-rel", "page");
}
if (!isParagraph)
{
iter = propMap.find("frame-pref-page");
int page=0;
if (iter != propMap.end() && findInt(iter->second, page))
propList.insert("text:anchor-page-number", page+1);
}
// style
int intValue;
iter = propMap.find("bg-style"); // 0: none, 1: color=background-color
if (iter != propMap.end() && findInt(iter->second, intValue) && intValue==1)
{
iter = propMap.find("background-color");
if (iter != propMap.end())
{
std::string color("#");
color+=iter->second;
propList.insert("fo:background-color", color.c_str());
}
}
propList.insert("text:anchor-type", isParagraph ? "paragraph" : "page");
iter = propMap.find("wrap-mode");
if (iter != propMap.end())
{
if (iter->second=="wrapped-to-left")
propList.insert("style:wrap", "left");
else if (iter->second=="wrapped-to-right")
propList.insert("style:wrap", "right");
else if (iter->second=="wrapped-to-both")
propList.insert("style:wrap", "parallel");
else if (iter->second=="above-text")
{
propList.insert("style:wrap", "dynamic");
propList.insert("style:run-through", "foreground");
}
else if (iter->second=="below-text")
{
propList.insert("style:wrap", "dynamic");
propList.insert("style:run-through", "background");
}
else
{
ABW_DEBUG_MSG(("libabw::ABWContentCollector::openFrame: sorry, unknown wrap mode: %s\n", iter->second.c_str()));
}
}
m_ps->m_isPageFrame=!isParagraph;
m_outputElements.addOpenFrame(propList);
iter = propMap.find("frame-type");
if (iter==propMap.end())
{
ABW_DEBUG_MSG(("libabw::ABWContentCollector::openFrame: can not find the frame type\n"));
}
else if (iter->second=="image")
{
m_ps->m_parsingContext=ABW_FRAME_IMAGE;
auto imIter = m_data.end();
if (imageId) imIter= m_data.find(imageId);
if (imIter==m_data.end())
{
ABW_DEBUG_MSG(("libabw::ABWContentCollector::openFrame: can not find the image\n"));
return;
}
propList.clear();
propList.insert("librevenge:mime-type", imIter->second.m_mimeType);
propList.insert("office:binary-data", imIter->second.m_binaryData);
m_outputElements.addInsertBinaryObject(propList);
return;
}
else if (iter->second=="textbox")
{
m_ps->m_parsingContext=ABW_FRAME_TEXTBOX;
propList.clear();
m_outputElements.addOpenTextBox(propList);
return;
}
m_ps->m_parsingContext=ABW_FRAME_UNKNOWN;
ABW_DEBUG_MSG(("libabw::ABWContentCollector::openFrame: sorry, unknown frame type: %s\n", iter->second.c_str()));
}
void libabw::ABWContentCollector::closeFrame(libabw::ABWOutputElements *(&elements), bool &pageFrame)
{
elements=nullptr;
pageFrame=false;
if (m_ps->m_isNote)
{
ABW_DEBUG_MSG(("libabw::ABWContentCollector::closeFrame: sorry, oops, sorry, a note is not closed\n"));
return;
}
if (m_ps->m_parsingContext!=ABW_FRAME_IMAGE && m_ps->m_parsingContext!=ABW_FRAME_TEXTBOX)
return;
while (!m_ps->m_tableStates.empty())
_closeTable();
_closeBlock();
m_ps->m_currentListLevel = 0;
_changeList(); // flush the list
if (m_ps->m_parsingContext==ABW_FRAME_TEXTBOX)
m_outputElements.addCloseTextBox();
m_outputElements.addCloseFrame();
elements=&m_outputElements;
pageFrame=m_ps->m_isPageFrame;
}
void libabw::ABWContentCollector::addFrameElements(ABWOutputElements &elements, bool pageFrame)
{
if (pageFrame)
m_pageOutputElements.splice(elements);
else
{
_openBlock();
m_outputElements.splice(elements);
}
}
void libabw::ABWContentCollector::collectData(const char *, const char *, const librevenge::RVNGBinaryData &)
{
}
void libabw::ABWContentCollector::insertImage(const char *dataid, const char *props)
{
if (!m_ps->m_isSpanOpened)
_openSpan();
ABWPropertyMap properties;
if (props)
parsePropString(props, properties);
if (dataid)
{
auto iter = m_data.find(dataid);
if (iter != m_data.end())
{
librevenge::RVNGPropertyList propList;
ABWUnit unit(ABW_NONE);
double value(0.0);
ABWPropertyMap::const_iterator i = properties.find("height");
if (i != properties.end() && findDouble(i->second, value, unit) && ABW_IN == unit)
propList.insert("svg:height", value);
else
propList.insert("fo:min-height", 1.0);
i = properties.find("width");
if (i != properties.end() && findDouble(i->second, value, unit) && ABW_IN == unit)
propList.insert("svg:width", value);
else
propList.insert("fo:min-width", 1.0);
propList.insert("text:anchor-type", "as-char");
m_outputElements.addOpenFrame(propList);
propList.clear();
propList.insert("librevenge:mime-type", iter->second.m_mimeType);
propList.insert("office:binary-data", iter->second.m_binaryData);
m_outputElements.addInsertBinaryObject(propList);
m_outputElements.addCloseFrame();
}
}
}
void libabw::ABWContentCollector::_handleListChange()
{
int oldListLevel;
if (m_ps->m_listLevels.empty())
oldListLevel = 0;
else
oldListLevel = m_ps->m_listLevels.top().first;
if (m_ps->m_currentListLevel > oldListLevel)
{
if (!m_ps->m_isSectionOpened)
_openSection();
_recurseListLevels(oldListLevel, m_ps->m_currentListLevel, m_ps->m_currentListId);
}
else if (m_ps->m_currentListLevel < oldListLevel)
{
while (!m_ps->m_listLevels.empty() && m_ps->m_listLevels.top().first > m_ps->m_currentListLevel)
{
if (!m_ps->m_listLevels.top().second || m_ps->m_listLevels.top().second->getType() == ABW_UNORDERED)
m_outputElements.addCloseUnorderedListLevel();
else
m_outputElements.addCloseOrderedListLevel();
ABW_DEBUG_MSG(("Popped level %i off the list level stack\n", m_ps->m_listLevels.top().first));
m_ps->m_listLevels.pop();
}
}
}
void libabw::ABWContentCollector::_writeOutDummyListLevels(int oldLevel, int newLevel)
{
if (oldLevel < newLevel)
{
_writeOutDummyListLevels(oldLevel, newLevel-1);
m_dummyListElements.push_back(std::make_shared<ABWUnorderedListElement>());
m_dummyListElements.back()->m_listLevel = newLevel;
m_ps->m_listLevels.push(std::make_pair(newLevel, m_dummyListElements.back()));
librevenge::RVNGPropertyList propList;
m_dummyListElements.back()->writeOut(propList);
m_outputElements.addOpenUnorderedListLevel(propList);
}
}
void libabw::ABWContentCollector::_recurseListLevels(int oldLevel, int newLevel, int newListId)
{
if (oldLevel >= newLevel)
return;
const auto iter = m_listElements.find(newListId);
if (iter != m_listElements.end() && iter->second)
{
if (iter->second->m_parentId)
_recurseListLevels(oldLevel, newLevel-1, iter->second->m_parentId);
else
_writeOutDummyListLevels(oldLevel, newLevel-1);
m_ps->m_listLevels.push(std::make_pair(newLevel, iter->second));
librevenge::RVNGPropertyList propList;
iter->second->writeOut(propList);
// osnola: use the element list id if set, if not use newListId
propList.insert("librevenge:list-id",
iter->second->m_listId ? iter->second->m_listId : newListId);
if (iter->second->getType() == ABW_UNORDERED)
m_outputElements.addOpenUnorderedListLevel(propList);
else
m_outputElements.addOpenOrderedListLevel(propList);
}
}
void libabw::ABWContentCollector::_changeList()
{
_closeBlock();
_handleListChange();
}
void libabw::ABWContentCollector::_closeListElement()
{
if (m_ps->m_isListElementOpened)
{
if (m_ps->m_isSpanOpened)
_closeSpan();
m_outputElements.addCloseListElement();
}
m_ps->m_isListElementOpened = false;
m_ps->m_isFirstTextInListElement = false;
}
void libabw::ABWContentCollector::addMetadataEntry(const char *const key, const char *const value)
{
assert(key);
assert(value);
m_metadata[key] = value;
}
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */