diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/base/byte_buffer.cc | 3 | ||||
-rw-r--r-- | src/base/byte_pointer_data_destination.cc | 32 | ||||
-rw-r--r-- | src/base/data_context.cc | 2 | ||||
-rw-r--r-- | src/base/data_line_map.cc | 2 | ||||
-rw-r--r-- | src/base/data_scanner.cc | 45 | ||||
-rw-r--r-- | src/base/data_segment.cc | 1 | ||||
-rw-r--r-- | src/base/istream_ref_data_source.cc | 2 | ||||
-rw-r--r-- | src/base/string_ref_data_source.cc | 25 | ||||
-rw-r--r-- | src/xml/xml_attribute_rule.cc | 32 | ||||
-rw-r--r-- | src/xml/xml_cdata_and_comment_rules.cc | 83 | ||||
-rw-r--r-- | src/xml/xml_element_rules.cc | 182 | ||||
-rw-r--r-- | src/xml/xml_handler.cc | 39 | ||||
-rw-r--r-- | src/xml/xml_pi_rule.cc | 28 | ||||
-rw-r--r-- | src/xml/xml_reader.cc | 189 | ||||
-rw-r--r-- | src/xml/xml_rule.cc | 187 | ||||
-rw-r--r-- | src/xml/xml_token_context.cc | 119 | ||||
-rw-r--r-- | src/xml/xml_writer.cc | 141 |
17 files changed, 1097 insertions, 15 deletions
diff --git a/src/base/byte_buffer.cc b/src/base/byte_buffer.cc index 55fbc2f..ac4d387 100644 --- a/src/base/byte_buffer.cc +++ b/src/base/byte_buffer.cc @@ -1,5 +1,6 @@ #include "image_io/base/byte_buffer.h" +#include <cstring> #include <utility> namespace photos_editing_formats { @@ -24,7 +25,7 @@ static size_t WriteBytes(const ByteData& byte_data, Byte* pos) { } } } else { - memcpy(pos, byte_data.GetValue().c_str(), byte_count); + std::memcpy(pos, byte_data.GetValue().c_str(), byte_count); } return byte_count; } diff --git a/src/base/byte_pointer_data_destination.cc b/src/base/byte_pointer_data_destination.cc new file mode 100644 index 0000000..9c8e572 --- /dev/null +++ b/src/base/byte_pointer_data_destination.cc @@ -0,0 +1,32 @@ +#include "image_io/base/byte_pointer_data_destination.h" + +#include <algorithm> +#include <cstring> + +#include "image_io/base/data_range.h" +#include "image_io/base/data_segment.h" + +namespace photos_editing_formats { +namespace image_io { + +void BytePointerDataDestination::StartTransfer() {} + +DataDestination::TransferStatus BytePointerDataDestination::Transfer( + const DataRange& transfer_range, const DataSegment& data_segment) { + if (transfer_range.IsValid()) { + size_t size_remaining = size_ - bytes_transferred_; + size_t bytes_to_copy = std::min(size_remaining, transfer_range.GetLength()); + const Byte* buffer = data_segment.GetBuffer(transfer_range.GetBegin()); + if (buffer) { + std::memcpy(bytes_ + bytes_transferred_, buffer, bytes_to_copy); + bytes_transferred_ += bytes_to_copy; + return bytes_transferred_ == size_ ? kTransferDone : kTransferOk; + } + } + return kTransferError; +} + +void BytePointerDataDestination::FinishTransfer() {} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/base/data_context.cc b/src/base/data_context.cc index 3d58cd2..fac3533 100644 --- a/src/base/data_context.cc +++ b/src/base/data_context.cc @@ -1,8 +1,10 @@ #include "image_io/base/data_context.h" +#include <algorithm> #include <cctype> #include <iomanip> #include <sstream> +#include <string> #include "image_io/base/byte_data.h" diff --git a/src/base/data_line_map.cc b/src/base/data_line_map.cc index 06ecfd9..2b3abe6 100644 --- a/src/base/data_line_map.cc +++ b/src/base/data_line_map.cc @@ -5,8 +5,6 @@ namespace photos_editing_formats { namespace image_io { -size_t DataLineMap::GetDataLineCount() const { return data_lines_.size(); } - DataLine DataLineMap::GetDataLine(size_t location) const { if (data_lines_.empty()) { return DataLine(); diff --git a/src/base/data_scanner.cc b/src/base/data_scanner.cc index e6677a5..36128af 100644 --- a/src/base/data_scanner.cc +++ b/src/base/data_scanner.cc @@ -1,11 +1,18 @@ #include "image_io/base/data_scanner.h" +#include <algorithm> + namespace photos_editing_formats { namespace image_io { +using std::string; + namespace { const char kWhitespaceChars[] = " \t\n\r"; +const char kBase64PadChar = '='; +const char kBase64Chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /// This function is like strspn but does not assume a null-terminated string. size_t memspn(const char* s, size_t slen, const char* accept) { @@ -49,9 +56,17 @@ size_t ScanWhitespaceChars(const char* s, size_t slen) { } // namespace -std::string DataScanner::GetWhitespaceChars() { return kWhitespaceChars; } +string DataScanner::GetWhitespaceChars() { return kWhitespaceChars; } + +string DataScanner::GetBase64Chars(bool include_pad_char) { + string chars(kBase64Chars); + if (include_pad_char) chars += kBase64PadChar; + return chars; +} + +string DataScanner::GetBase64PadChar() { return string(1, kBase64PadChar); } -DataScanner DataScanner::CreateLiteralScanner(const std::string& literal) { +DataScanner DataScanner::CreateLiteralScanner(const string& literal) { return DataScanner(DataScanner::kLiteral, literal); } @@ -63,12 +78,11 @@ DataScanner DataScanner::CreateQuotedStringScanner() { return DataScanner(DataScanner::kQuotedString); } -DataScanner DataScanner::CreateSentinelScanner(const std::string& sentinels) { +DataScanner DataScanner::CreateSentinelScanner(const string& sentinels) { return DataScanner(DataScanner::kSentinel, sentinels); } -DataScanner DataScanner::CreateThroughLiteralScanner( - const std::string& literal) { +DataScanner DataScanner::CreateThroughLiteralScanner(const string& literal) { return DataScanner(DataScanner::kThroughLiteral, literal); } @@ -80,6 +94,10 @@ DataScanner DataScanner::CreateOptionalWhitespaceScanner() { return DataScanner(DataScanner::kOptionalWhitespace); } +size_t DataScanner::ScanChars(const char* s, size_t slen, const char* scanset) { + return memspn(s, slen, scanset); +} + size_t DataScanner::ExtendTokenLength(size_t delta_length) { token_range_ = DataRange(token_range_.GetBegin(), token_range_.GetEnd() + delta_length); @@ -87,7 +105,7 @@ size_t DataScanner::ExtendTokenLength(size_t delta_length) { } void DataScanner::SetInternalError(const DataContext& context, - const std::string& error_description, + const string& error_description, DataMatchResult* result) { result->SetType(DataMatchResult::kError); result->SetMessage( @@ -96,7 +114,7 @@ void DataScanner::SetInternalError(const DataContext& context, } void DataScanner::SetSyntaxError(const DataContext& context, - const std::string& error_description, + const string& error_description, DataMatchResult* result) { result->SetType(DataMatchResult::kError); result->SetMessage(Message::kSyntaxError, @@ -215,7 +233,7 @@ DataMatchResult DataScanner::ScanSentinel(const char* cbytes, } } if (result.GetBytesConsumed() == 0) { - SetSyntaxError(context, "Expected sentinal character", &result); + SetSyntaxError(context, "Unexpected character encountered", &result); } return result; } @@ -341,8 +359,11 @@ void DataScanner::Reset() { ResetTokenRange(); } -std::string DataScanner::GetDescription() const { - std::string description; +string DataScanner::GetDescription() const { + if (!description_.empty()) { + return description_; + } + string description; switch (type_) { case kLiteral: description = "Literal:'"; @@ -375,12 +396,12 @@ std::string DataScanner::GetDescription() const { return description; } -std::string DataScanner::GetLiteral() const { +string DataScanner::GetLiteral() const { return type_ == kLiteral || type_ == kThroughLiteral ? literal_or_sentinels_ : ""; } -std::string DataScanner::GetSentenels() const { +string DataScanner::GetSentenels() const { return type_ == kSentinel ? literal_or_sentinels_ : ""; } diff --git a/src/base/data_segment.cc b/src/base/data_segment.cc index 95b4cc7..a3e4a9a 100644 --- a/src/base/data_segment.cc +++ b/src/base/data_segment.cc @@ -1,5 +1,6 @@ #include "image_io/base/data_segment.h" +#include <algorithm> #include <cstring> namespace photos_editing_formats { diff --git a/src/base/istream_ref_data_source.cc b/src/base/istream_ref_data_source.cc index 5e3d126..6bd298c 100644 --- a/src/base/istream_ref_data_source.cc +++ b/src/base/istream_ref_data_source.cc @@ -1,5 +1,7 @@ #include "image_io/base/istream_ref_data_source.h" +#include <algorithm> + #include "image_io/base/data_destination.h" #include "image_io/base/data_segment.h" diff --git a/src/base/string_ref_data_source.cc b/src/base/string_ref_data_source.cc new file mode 100644 index 0000000..cdf7bf4 --- /dev/null +++ b/src/base/string_ref_data_source.cc @@ -0,0 +1,25 @@ +#include "image_io/base/string_ref_data_source.h" + +#include <string> + +namespace photos_editing_formats { +namespace image_io { + +namespace { + +/// @param str The string from which to create a DataSegment. +/// @return A DataSegment the byte pointer of which is taken from the str. +std::shared_ptr<DataSegment> CreateDataSegment(const std::string &str) { + Byte *bytes = reinterpret_cast<Byte *>(const_cast<char *>(str.c_str())); + return DataSegment::Create(DataRange(0, str.length()), bytes, + DataSegment::BufferDispositionPolicy::kDontDelete); +} + +} // namespace + +StringRefDataSource::StringRefDataSource(const std::string &string_ref) + : DataSegmentDataSource(CreateDataSegment(string_ref)), + string_ref_(string_ref) {} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_attribute_rule.cc b/src/xml/xml_attribute_rule.cc new file mode 100644 index 0000000..955e60c --- /dev/null +++ b/src/xml/xml_attribute_rule.cc @@ -0,0 +1,32 @@ +#include "image_io/xml/xml_attribute_rule.h" + +#include "image_io/xml/xml_handler.h" +#include "image_io/xml/xml_token_context.h" + +namespace photos_editing_formats { +namespace image_io { + +XmlAttributeRule::XmlAttributeRule() : XmlRule("Attribute") { + // S? Name S? = S? 'Value' + AddOptionalWhitespaceTerminal(); + AddNameTerminal().WithAction( + [&](const XmlActionContext& context) { return HandleName(context); }); + AddOptionalWhitespaceTerminal(); + AddLiteralTerminal("="); + AddOptionalWhitespaceTerminal(); + AddQuotedStringTerminal().WithAction( + [&](const XmlActionContext& context) { return HandleValue(context); }); +} + +DataMatchResult XmlAttributeRule::HandleName(const XmlActionContext& context) { + XmlTokenContext token_context(context); + return context.GetHandler()->AttributeName(token_context); +} + +DataMatchResult XmlAttributeRule::HandleValue(const XmlActionContext& context) { + XmlTokenContext token_context(context); + return context.GetHandler()->AttributeValue(token_context); +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_cdata_and_comment_rules.cc b/src/xml/xml_cdata_and_comment_rules.cc new file mode 100644 index 0000000..d3a4d50 --- /dev/null +++ b/src/xml/xml_cdata_and_comment_rules.cc @@ -0,0 +1,83 @@ +#include "image_io/xml/xml_cdata_and_comment_rules.h" + +#include <utility> + +#include "image_io/xml/xml_handler.h" +#include "image_io/xml/xml_token_context.h" + +namespace photos_editing_formats { +namespace image_io { + +XmlCdataRule::XmlCdataRule() : XmlCdataRule(kFirstStartPoint) {} + +XmlCdataRule::XmlCdataRule(StartPoint start_point) : XmlRule("CDATA") { + // <![CDATA[ ... ]]> + AddLiteralTerminal("<!"); + AddLiteralTerminal("[CDATA["); + AddThroughLiteralTerminal("]]>").WithAction( + [&](const XmlActionContext& context) { + return HandleCdataValue(context); + }); + if (start_point == kSecondStartPoint) { + SetTerminalIndex(1); + } +} + +DataMatchResult XmlCdataRule::HandleCdataValue( + const XmlActionContext& context) { + XmlTokenContext token_context(context); + return context.GetHandler()->Cdata(token_context); +} + +XmlCommentRule::XmlCommentRule() : XmlCommentRule(kFirstStartPoint) {} + +XmlCommentRule::XmlCommentRule(StartPoint start_point) : XmlRule("Comment") { + // <!-- ... --> + AddLiteralTerminal("<!"); + AddLiteralTerminal("--"); + AddThroughLiteralTerminal("-->").WithAction( + [&](const XmlActionContext& context) { + return HandleCommentValue(context); + }); + if (start_point == kSecondStartPoint) { + SetTerminalIndex(1); + } +} + +DataMatchResult XmlCommentRule::HandleCommentValue( + const XmlActionContext& context) { + XmlTokenContext token_context(context); + return context.GetHandler()->Comment(token_context); +} + +XmlCdataOrCommentRule::XmlCdataOrCommentRule() + : XmlCdataOrCommentRule(kFirstStartPoint) {} + +XmlCdataOrCommentRule::XmlCdataOrCommentRule(StartPoint start_point) + : XmlRule("CdataOrComment") { + // <![CDATA[ ... ]]> or <!-- ... --> + // So after the initial "<!" literal can come a "[" or a "-". + AddLiteralTerminal("<!"); + AddSentinelTerminal("[-").WithAction([&](const XmlActionContext& context) { + return HandlePostBangChar(context); + }); + if (start_point == kSecondStartPoint) { + SetTerminalIndex(1); + } +} + +DataMatchResult XmlCdataOrCommentRule::HandlePostBangChar( + const XmlActionContext& context) { + char sentinel = context.GetTerminal()->GetScanner()->GetSentinel(); + if (sentinel == '[') { + std::unique_ptr<XmlRule> rule(new XmlCdataRule(kSecondStartPoint)); + SetNextRule(std::move(rule)); + } else if (sentinel == '-') { + std::unique_ptr<XmlRule> rule(new XmlCommentRule(kSecondStartPoint)); + SetNextRule(std::move(rule)); + } + return context.GetResultWithBytesConsumed(0); +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_element_rules.cc b/src/xml/xml_element_rules.cc new file mode 100644 index 0000000..53feb87 --- /dev/null +++ b/src/xml/xml_element_rules.cc @@ -0,0 +1,182 @@ +#include "image_io/xml/xml_element_rules.h" + +#include <utility> + +#include "image_io/xml/xml_attribute_rule.h" +#include "image_io/xml/xml_cdata_and_comment_rules.h" +#include "image_io/xml/xml_handler.h" +#include "image_io/xml/xml_pi_rule.h" +#include "image_io/xml/xml_token_context.h" + +namespace photos_editing_formats { +namespace image_io { + +namespace { + +/// Some names of terminals used by these rules. +const char kWhitespace[] = "Whitespace"; +const char kEmptyElementEnd[] = "EmptyElementEnd"; +const char kElementEnd[] = "ElementEnd"; +const char kElementSentinalDescription[] = + "The start of an attribute name or the end of the element ('>' or '/>')"; + +/// A shortcut for referring to all XmlPortion bits. +const XmlPortion kAllPortions = + XmlPortion::kBegin | XmlPortion::kMiddle | XmlPortion::kEnd; + +/// @param context The action context passed to an action handler. +/// @param token_range The token range to use when building the token context. +/// @param portion The token portion to use when building the token context. +/// @param A token context for use in calling an XmlHandler function. +XmlTokenContext GetTokenContext(const XmlActionContext& context, + const DataRange& token_range, + XmlPortion portion) { + return XmlTokenContext(context.GetLocation(), context.GetRange(), + context.GetSegment(), context.GetDataLineMap(), + context.GetResult(), token_range, portion); +} + +} // namespace + +XmlElementRule::XmlElementRule() : XmlElementRule(kFirstStartPoint) {} + +XmlElementRule::XmlElementRule(XmlRule::StartPoint start_point) + : XmlRule("Element") { + AddLiteralTerminal("<"); + AddNameTerminal().WithAction( + [&](const XmlActionContext& context) { return HandleName(context); }); + AddOptionalWhitespaceTerminal().WithName(kWhitespace); + AddSentinelTerminal("~/>") + .WithDescription(kElementSentinalDescription) + .WithAction([&](const XmlActionContext& context) { + return HandlePostWhitespaceChar(context); + }); + AddLiteralTerminal("/>") + .WithName(kEmptyElementEnd) + .WithAction([&](const XmlActionContext& context) { + return HandleEmptyElemTagEnd(context); + }); + AddLiteralTerminal(">") + .WithName(kElementEnd) + .WithAction([&](const XmlActionContext& context) { + return HandleSTagEnd(context); + }); + if (start_point == kSecondStartPoint) { + SetTerminalIndex(1); + } +} + +DataMatchResult XmlElementRule::HandleName(const XmlActionContext& context) { + XmlTokenContext token_context(context); + return context.GetHandler()->StartElement(token_context); +} + +DataMatchResult XmlElementRule::HandlePostWhitespaceChar( + const XmlActionContext& context) { + DataMatchResult result = context.GetResultWithBytesConsumed(0); + char sentinel = context.GetTerminal()->GetScanner()->GetSentinel(); + if (sentinel == '/') { + size_t index = GetTerminalIndexFromName(kEmptyElementEnd); + SetTerminalIndex(index); + } else if (sentinel == '>') { + size_t index = GetTerminalIndexFromName(kElementEnd); + SetTerminalIndex(index); + } else if (sentinel == '~') { + std::unique_ptr<XmlRule> rule(new XmlAttributeRule); + SetNextRule(std::move(rule)); + ResetTerminalScanners(); + size_t index = GetTerminalIndexFromName(kWhitespace); + SetTerminalIndex(index); + result.SetType(DataMatchResult::kPartial); + } + return result; +} + +DataMatchResult XmlElementRule::HandleEmptyElemTagEnd( + const XmlActionContext& context) { + SetTerminalIndex(GetTerminalCount()); + return context.GetHandler()->FinishElement( + GetTokenContext(context, DataRange(), XmlPortion::kNone)); +} + +DataMatchResult XmlElementRule::HandleSTagEnd(const XmlActionContext& context) { + DataMatchResult result = context.GetResult(); + std::unique_ptr<XmlRule> rule(new XmlElementContentRule); + SetNextRule(std::move(rule)); + return result; +} + +XmlElementContentRule::XmlElementContentRule() : XmlRule("ElementContent") { + // ElementContent until + // <N... Element + // <?N ... ?> PI + // <!-- ... --> Comment + // <![CDATA[ ... ]]> CDATA + // </Nws> Element Etag + // &...; EntityRef or CharRef (Don't care about this) + AddThroughLiteralTerminal("<").WithAction( + [&](const XmlActionContext& context) { return HandleContent(context); }); + AddSentinelTerminal("~?!/").WithAction([&](const XmlActionContext& context) { + return HandlePostOpenChar(context); + }); + AddNameTerminal().WithAction( + [&](const XmlActionContext& context) { return HandleEndTag(context); }); + AddLiteralTerminal(">"); +} + +DataMatchResult XmlElementContentRule::HandleContent( + const XmlActionContext& context) { + const auto& range = context.GetTerminal()->GetScanner()->GetTokenRange(); + if (range.IsValid()) { + size_t end = context.GetResult().GetType() == DataMatchResult::kFull + ? range.GetEnd() - 1 + : range.GetEnd(); + DataRange token_range(range.GetBegin(), end); + if (token_range.GetLength() > 0) { + XmlTokenContext token_context = + GetTokenContext(context, token_range, kAllPortions); + DataMatchResult result = + context.GetHandler()->ElementContent(token_context); + context.GetTerminal()->GetScanner()->ResetTokenRange(); + return result; + } + } + context.GetTerminal()->GetScanner()->ResetTokenRange(); + return context.GetResult(); +} + +DataMatchResult XmlElementContentRule::HandlePostOpenChar( + const XmlActionContext& context) { + DataMatchResult result = context.GetResult(); + char sentinel = context.GetTerminal()->GetScanner()->GetSentinel(); + if (sentinel == '~') { + result.SetBytesConsumed(0); + result.SetType(DataMatchResult::kPartial); + std::unique_ptr<XmlRule> rule(new XmlElementRule(kSecondStartPoint)); + SetNextRule(std::move(rule)); + } else if (sentinel == '?') { + result.SetType(DataMatchResult::kPartial); + std::unique_ptr<XmlRule> rule(new XmlPiRule(kSecondStartPoint)); + SetNextRule(std::move(rule)); + } else if (sentinel == '!') { + result.SetType(DataMatchResult::kPartial); + std::unique_ptr<XmlRule> rule(new XmlCdataOrCommentRule(kSecondStartPoint)); + SetNextRule(std::move(rule)); + } else if (sentinel == '/') { + // Do nothing so that the next terminals (the 'name>' part of '</name>') + // will be activated and scanned. + return context.GetResult(); + } + ResetTerminalScanners(); + SetTerminalIndex(0); + return result; +} + +DataMatchResult XmlElementContentRule::HandleEndTag( + const XmlActionContext& context) { + XmlTokenContext token_context(context); + return context.GetHandler()->FinishElement(token_context); +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_handler.cc b/src/xml/xml_handler.cc new file mode 100644 index 0000000..591d43c --- /dev/null +++ b/src/xml/xml_handler.cc @@ -0,0 +1,39 @@ +#include "image_io/xml/xml_handler.h" + +namespace photos_editing_formats { +namespace image_io { + +DataMatchResult XmlHandler::AttributeName(const XmlTokenContext& context) { + return context.GetResult(); +} + +DataMatchResult XmlHandler::AttributeValue(const XmlTokenContext& context) { + return context.GetResult(); +} + +DataMatchResult XmlHandler::StartElement(const XmlTokenContext& context) { + return context.GetResult(); +} + +DataMatchResult XmlHandler::FinishElement(const XmlTokenContext& context) { + return context.GetResult(); +} + +DataMatchResult XmlHandler::ElementContent(const XmlTokenContext& context) { + return context.GetResult(); +} + +DataMatchResult XmlHandler::Comment(const XmlTokenContext& context) { + return context.GetResult(); +} + +DataMatchResult XmlHandler::Cdata(const XmlTokenContext& context) { + return context.GetResult(); +} + +DataMatchResult XmlHandler::Pi(const XmlTokenContext& context) { + return context.GetResult(); +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_pi_rule.cc b/src/xml/xml_pi_rule.cc new file mode 100644 index 0000000..071b8fd --- /dev/null +++ b/src/xml/xml_pi_rule.cc @@ -0,0 +1,28 @@ +#include "image_io/xml/xml_pi_rule.h" + +#include "image_io/xml/xml_handler.h" +#include "image_io/xml/xml_token_context.h" + +namespace photos_editing_formats { +namespace image_io { + +XmlPiRule::XmlPiRule() : XmlPiRule(kFirstStartPoint) {} + +XmlPiRule::XmlPiRule(XmlRule::StartPoint start_point) : XmlRule("PI") { + // <? ... ?> + AddLiteralTerminal("<?"); + AddThroughLiteralTerminal("?>").WithAction( + [&](const XmlActionContext& context) { return HandlePiValue(context); }); + if (start_point == kSecondStartPoint) { + SetTerminalIndex(1); + } +} + +DataMatchResult XmlPiRule::HandlePiValue(const XmlActionContext& context) { + XmlTokenContext token_context(context); + DataMatchResult result = context.GetHandler()->Pi(token_context); + return result; +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_reader.cc b/src/xml/xml_reader.cc new file mode 100644 index 0000000..467cb82 --- /dev/null +++ b/src/xml/xml_reader.cc @@ -0,0 +1,189 @@ +#include "image_io/xml/xml_reader.h" + +#include <iomanip> +#include <sstream> +#include <string> +#include <utility> + +#include "image_io/base/message.h" +#include "image_io/base/message_handler.h" + +namespace photos_editing_formats { +namespace image_io { + +namespace { + +/// The reader name used for error messages. +const char kReaderName[] = "XmlReader"; + +} // namespace + +bool XmlReader::StartParse(std::unique_ptr<XmlRule> rule) { + bytes_parsed_ = 0; + rule_stack_.clear(); + if (!rule) { + std::string text = std::string(kReaderName) + ":StartParse:NoTopLevelRule"; + Message message(Message::kInternalError, 0, text); + ReportError(message); + return false; + } + rule_stack_.push_back(std::move(rule)); + has_internal_or_syntax_error_ = false; + has_errors_ = false; + return true; +} + +bool XmlReader::FinishParse() { + if (has_internal_or_syntax_error_) { + return false; + } + std::string error_text; + if (rule_stack_.empty() || + (rule_stack_.size() == 1 && + rule_stack_.back()->IsPermissibleToFinish(&error_text))) { + return true; + } + std::stringstream ss; + ss << kReaderName << ":"; + if (error_text.empty()) { + ss << "While parsing text with rule:"; + ss << rule_stack_.back()->GetName(); + XmlTerminal* terminal = rule_stack_.back()->GetCurrentTerminal(); + if (terminal) { + if (!terminal->GetName().empty()) { + ss << ":" << terminal->GetName(); + } + ss << ":" << terminal->GetScanner()->GetDescription(); + } + } else { + ss << error_text; + } + Message message(Message::kPrematureEndOfDataError, 0, ss.str()); + has_internal_or_syntax_error_ = true; + ReportError(message); + return false; +} + +bool XmlReader::Parse(const std::string& value) { + size_t location = GetBytesParsed(); + DataRange range(location, location + value.length()); + const Byte* bytes = reinterpret_cast<const Byte*>(value.c_str()); + auto segment = DataSegment::Create(range, bytes, DataSegment::kDontDelete); + return Parse(location, range, *segment); +} + +bool XmlReader::Parse(size_t start_location, const DataRange& range, + const DataSegment& segment) { + if (has_internal_or_syntax_error_) { + return false; + } + XmlHandlerContext context(start_location, range, segment, *data_line_map_, + handler_); + InitializeContextNameList(&context); + if (!context.IsValidLocationAndRange()) { + DataMatchResult result; + result.SetMessage(Message::kInternalError, + context.GetInvalidLocationAndRangeErrorText()); + ReportError(result, context); + return false; + } + if (rule_stack_.empty()) { + DataMatchResult result; + result.SetMessage(Message::kInternalError, "NoActiveRule"); + ReportError(result, context); + return false; + } + if (data_line_map_ == &internal_data_line_map_) { + internal_data_line_map_.FindDataLines(range, segment); + } + size_t bytes_remaining = range.GetEnd() - start_location; + while (bytes_remaining > 0 && !rule_stack_.empty() && + !has_internal_or_syntax_error_) { + auto& rule = rule_stack_.back(); + InitializeContextNameList(&context); + DataMatchResult result = rule->Parse(context); + switch (result.GetType()) { + case DataMatchResult::kError: + case DataMatchResult::kNone: + ReportError(result, context); + break; + case DataMatchResult::kPartial: + ReportMessageIfNeeded(result); + bytes_parsed_ += result.GetBytesConsumed(); + bytes_remaining -= result.GetBytesConsumed(); + context.IncrementLocation(result.GetBytesConsumed()); + if (rule->HasNextRule()) { + // Delegation by child rule: push the next. + rule_stack_.push_back(rule->ReleaseNextRule()); + } + break; + case DataMatchResult::kPartialOutOfData: + ReportMessageIfNeeded(result); + bytes_parsed_ += result.GetBytesConsumed(); + return true; + case DataMatchResult::kFull: + ReportMessageIfNeeded(result); + bytes_parsed_ += result.GetBytesConsumed(); + bytes_remaining -= result.GetBytesConsumed(); + context.IncrementLocation(result.GetBytesConsumed()); + if (rule->HasNextRule()) { + // Delegation by chaining: pop the current rule and push the next. + auto next_rule = rule->ReleaseNextRule(); + rule_stack_.pop_back(); + rule_stack_.push_back(std::move(next_rule)); + } else { + rule_stack_.pop_back(); + } + break; + } + } + if (bytes_remaining > 0 && rule_stack_.empty()) { + InitializeContextNameList(&context); + std::string text = context.GetErrorText("NoActiveRule", ""); + Message message(Message::kSyntaxError, 0, text); + ReportError(message); + return false; + } + return !has_internal_or_syntax_error_; +} + +void XmlReader::InitializeContextNameList(XmlHandlerContext* context) { + auto name_list = context->GetNameList(); + name_list.clear(); + name_list.push_back(kReaderName); + if (!rule_stack_.empty()) { + name_list.push_back(rule_stack_.back()->GetName()); + } +} + +void XmlReader::ReportMessageIfNeeded(const DataMatchResult& result) { + if (result.HasMessage()) { + ReportError(result.GetMessage()); + } +} + +void XmlReader::ReportError(const DataMatchResult& result, + const DataContext& context) { + if (!result.HasMessage()) { + Message message(Message::kInternalError, 0, + context.GetErrorText("Rule had error but no message", "")); + ReportError(message); + } + ReportError(result.GetMessage()); +} + +void XmlReader::ReportError(const Message& message) { + if (message_handler_) { + message_handler_->ReportMessage(message); + } + if (message.GetType() == Message::kInternalError || + message.GetType() == Message::kSyntaxError) { + has_internal_or_syntax_error_ = true; + } + if (message.IsError()) { + has_errors_ = true; + } +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_rule.cc b/src/xml/xml_rule.cc new file mode 100644 index 0000000..793381c --- /dev/null +++ b/src/xml/xml_rule.cc @@ -0,0 +1,187 @@ +#include "image_io/xml/xml_rule.h" + +#include <string> +#include <utility> + +#include "image_io/base/data_scanner.h" + +namespace photos_editing_formats { +namespace image_io { + +using std::string; +using std::unique_ptr; + +namespace { + +/// A scanner is reentrant if it ran out of data. In these cases, the next data +/// segment sent into the rule for parsing may be non-contiguous with the +/// previous one. If that is the case, update the scanner's token length to +/// account for the missing bytes. (Scanner token ranges represent a bounding +/// box around the token value - in these cases the actual token value is really +/// a vector of ranges. Client handlers are responsible for dealing with that +/// reality, not the scanner or rule). +/// @param scanner The current possibly reentrant scanner. +/// @param context_range The new data range that is to be parsed. +void MaybeUpdateTokenLengthForReentrantScanner(DataScanner* scanner, + const DataRange& context_range) { + const auto& token_range = scanner->GetTokenRange(); + if (scanner->GetScanCallCount() > 0 && token_range.IsValid() && + context_range.GetBegin() > token_range.GetEnd()) { + size_t skipped_byte_count = context_range.GetBegin() - token_range.GetEnd(); + scanner->ExtendTokenLength(skipped_byte_count); + } +} + +} // namespace + +XmlRule::XmlRule(const std::string& name) : name_(name), terminal_index_(0) {} + +XmlTerminal& XmlRule::AddLiteralTerminal(const std::string& literal) { + terminals_.emplace_back(DataScanner::CreateLiteralScanner(literal)); + return terminals_.back(); +} + +XmlTerminal& XmlRule::AddNameTerminal() { + terminals_.emplace_back(DataScanner::CreateNameScanner()); + return terminals_.back(); +} + +XmlTerminal& XmlRule::AddQuotedStringTerminal() { + terminals_.emplace_back(DataScanner::CreateQuotedStringScanner()); + return terminals_.back(); +} + +XmlTerminal& XmlRule::AddSentinelTerminal(const std::string& sentinels) { + terminals_.emplace_back(DataScanner::CreateSentinelScanner(sentinels)); + return terminals_.back(); +} + +XmlTerminal& XmlRule::AddThroughLiteralTerminal(const std::string& literal) { + terminals_.emplace_back(DataScanner::CreateThroughLiteralScanner(literal)); + return terminals_.back(); +} + +XmlTerminal& XmlRule::AddWhitespaceTerminal() { + terminals_.emplace_back(DataScanner::CreateWhitespaceScanner()); + return terminals_.back(); +} + +XmlTerminal& XmlRule::AddOptionalWhitespaceTerminal() { + terminals_.emplace_back(DataScanner::CreateOptionalWhitespaceScanner()); + return terminals_.back(); +} + +size_t XmlRule::GetTerminalIndexFromName(const std::string name) const { + if (!name.empty()) { + for (size_t index = 0; index < terminals_.size(); ++index) { + if (terminals_[index].GetName() == name) { + return index; + } + } + } + return terminals_.size(); +} + +void XmlRule::SetTerminalIndex(size_t terminal_index) { + terminal_index_ = terminal_index; +} + +XmlTerminal* XmlRule::GetCurrentTerminal() { + return terminal_index_ < terminals_.size() ? &terminals_[terminal_index_] + : nullptr; +} + +XmlTerminal* XmlRule::GetTerminal(size_t index) { + return index < terminals_.size() ? &terminals_[index] : nullptr; +} + +void XmlRule::ResetTerminalScanners() { + for (auto& terminal : terminals_) { + terminal.GetScanner()->Reset(); + } +} + +bool XmlRule::IsPermissibleToFinish(std::string*) const { + return false; +} + +DataMatchResult XmlRule::Parse(XmlHandlerContext context) { + DataMatchResult result; + if (!context.IsValidLocationAndRange()) { + result.SetType(DataMatchResult::kError); + result.SetMessage(Message::kInternalError, + context.GetInvalidLocationAndRangeErrorText()); + return result; + } + bool force_parse_return = false; + size_t bytes_available = context.GetBytesAvailable(); + size_t current_terminal_index = GetTerminalIndex(); + if (current_terminal_index < terminals_.size()) { + MaybeUpdateTokenLengthForReentrantScanner( + terminals_[current_terminal_index].GetScanner(), context.GetRange()); + } + while (!force_parse_return && current_terminal_index < terminals_.size() && + bytes_available > 0) { + SetTerminalIndex(current_terminal_index); + auto& terminal = terminals_[current_terminal_index]; + DataMatchResult scanner_result = terminal.GetScanner()->Scan(context); + if (terminal.GetAction() && + (scanner_result.GetType() == DataMatchResult::kFull || + scanner_result.GetType() == DataMatchResult::kPartialOutOfData)) { + XmlActionContext action_context(context, &terminal, scanner_result); + scanner_result = terminal.GetAction()(action_context); + } + result.SetType(scanner_result.GetType()); + result.IncrementBytesConsumed(scanner_result.GetBytesConsumed()); + context.IncrementLocation(scanner_result.GetBytesConsumed()); + bytes_available -= scanner_result.GetBytesConsumed(); + switch (scanner_result.GetType()) { + case DataMatchResult::kError: + result.SetMessage(scanner_result.GetMessage()); + force_parse_return = true; + break; + case DataMatchResult::kNone: + result.SetType(DataMatchResult::kError); + result.SetMessage( + Message::kInternalError, + context.GetErrorText("Invalid scanner match result", + terminal.GetScanner()->GetDescription())); + force_parse_return = true; + break; + case DataMatchResult::kPartial: + case DataMatchResult::kPartialOutOfData: + if (scanner_result.HasMessage()) { + result.SetMessage(scanner_result.GetMessage()); + } + force_parse_return = true; + break; + case DataMatchResult::kFull: + if (scanner_result.HasMessage() && !result.HasMessage()) { + result.SetMessage(scanner_result.GetMessage()); + } + current_terminal_index = current_terminal_index == GetTerminalIndex() + ? current_terminal_index + 1 + : GetTerminalIndex(); + SetTerminalIndex(current_terminal_index); + if (current_terminal_index < GetTerminalCount()) { + result.SetType(DataMatchResult::kPartial); + } + force_parse_return = HasNextRule(); + break; + } + } + return result; +} + +bool XmlRule::HasNextRule() const { return next_rule_ != nullptr; } + +std::unique_ptr<XmlRule> XmlRule::ReleaseNextRule() { + return std::move(next_rule_); +} + +void XmlRule::SetNextRule(std::unique_ptr<XmlRule> next_rule) { + next_rule_ = std::move(next_rule); +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_token_context.cc b/src/xml/xml_token_context.cc new file mode 100644 index 0000000..4ffea3f --- /dev/null +++ b/src/xml/xml_token_context.cc @@ -0,0 +1,119 @@ +#include "image_io/xml/xml_token_context.h" + +#include <string> + +#include "image_io/xml/xml_action.h" +#include "image_io/xml/xml_terminal.h" + +namespace photos_editing_formats { +namespace image_io { + +using std::vector; + +namespace { + +const XmlPortion kAllPortions = + XmlPortion::kBegin | XmlPortion::kMiddle | XmlPortion::kEnd; + +XmlPortion GetPortion(const XmlActionContext& context) { + return XmlTokenContext::ComputeTokenPortion( + context.GetTerminal()->GetScanner()->GetScanCallCount(), + context.GetResult().GetType()); +} + +} // namespace + +XmlTokenContext::XmlTokenContext(const XmlActionContext& context) + : DataContext(context), + result_(context.GetResult()), + token_range_(context.GetTerminal()->GetScanner()->GetTokenRange()), + token_portion_(GetPortion(context)) {} + +XmlTokenContext::XmlTokenContext(size_t location, const DataRange& range, + const DataSegment& segment, + const DataLineMap& data_line_map, + const DataMatchResult& result, + const DataRange& token_range, + const XmlPortion& token_portion) + : DataContext(location, range, segment, data_line_map), + result_(result), + token_range_(token_range), + token_portion_(token_portion) {} + +bool XmlTokenContext::BuildTokenValue(std::string* value, + bool trim_first_and_last_chars) const { + bool contains_end = ContainsAny(token_portion_, XmlPortion::kEnd); + size_t end_delta = trim_first_and_last_chars && contains_end ? 1 : 0; + size_t begin_delta = 0; + if (ContainsAny(token_portion_, XmlPortion::kBegin)) { + begin_delta = trim_first_and_last_chars ? 1 : 0; + value->clear(); + } + if (ContainsAny(token_portion_, kAllPortions)) { + const auto& segment = GetSegment(); + DataRange range_with_deltas(token_range_.GetBegin() + begin_delta, + token_range_.GetEnd() - end_delta); + auto clipped_range = GetRange().GetIntersection(range_with_deltas); + if (clipped_range.IsValid()) { + const char* cbytes = reinterpret_cast<const char*>( + segment.GetBuffer(clipped_range.GetBegin())); + value->append(cbytes, clipped_range.GetLength()); + } + } + return contains_end; +} + +bool XmlTokenContext::BuildTokenValueRanges( + vector<DataRange>* value_ranges, bool trim_first_and_last_chars) const { + size_t delta = trim_first_and_last_chars ? 1 : 0; + auto clipped_range = GetRange().GetIntersection(token_range_); + if (ContainsAny(token_portion_, XmlPortion::kBegin)) { + value_ranges->clear(); + if (clipped_range.IsValid()) { + value_ranges->push_back( + DataRange(clipped_range.GetBegin() + delta, clipped_range.GetEnd())); + } + + } else if (ContainsAny(token_portion_, kAllPortions)) { + if (clipped_range.IsValid()) { + if (!value_ranges->empty() && + value_ranges->back().GetEnd() == clipped_range.GetBegin()) { + value_ranges->back() = + DataRange(value_ranges->back().GetBegin(), clipped_range.GetEnd()); + } else { + value_ranges->push_back(clipped_range); + } + } + } + bool has_end = ContainsAny(token_portion_, XmlPortion::kEnd); + if (has_end && !value_ranges->empty() && clipped_range.IsValid() && + trim_first_and_last_chars) { + auto& back_range = value_ranges->back(); + back_range = DataRange(back_range.GetBegin(), back_range.GetEnd() - delta); + } + return has_end; +} + +XmlPortion XmlTokenContext::ComputeTokenPortion( + size_t token_scan_count, DataMatchResult::Type result_type) { + const bool first_scan = token_scan_count == 1; + const bool subsequent_scan = token_scan_count > 1; + const bool full_match = result_type == DataMatchResult::kFull; + const bool partial_match = + result_type == DataMatchResult::kPartialOutOfData || + result_type == DataMatchResult::kPartial; + XmlPortion portion = XmlPortion::kNone; + if (first_scan && full_match) { + portion = kAllPortions; + } else if (first_scan && partial_match) { + portion = XmlPortion::kBegin | XmlPortion::kMiddle; + } else if (subsequent_scan && full_match) { + portion = XmlPortion::kMiddle | XmlPortion::kEnd; + } else if (subsequent_scan && partial_match) { + portion = XmlPortion::kMiddle; + } + return portion; +} + +} // namespace image_io +} // namespace photos_editing_formats diff --git a/src/xml/xml_writer.cc b/src/xml/xml_writer.cc new file mode 100644 index 0000000..e280b66 --- /dev/null +++ b/src/xml/xml_writer.cc @@ -0,0 +1,141 @@ +#include "image_io/xml/xml_writer.h" + +#include <iomanip> +#include <string> + +namespace photos_editing_formats { +namespace image_io { + +using std::ostream; +using std::string; +using std::vector; + +namespace { + +const char kXmlnsColon[] = "xmlns:"; + +} // namespace + +XmlWriter::XmlWriter(std::ostream& os) + : os_(os), element_count_(0), quote_mark_('"') {} + +void XmlWriter::WriteXmlns(const string& prefix, const string& uri) { + string name = string(kXmlnsColon) + prefix; + WriteAttributeNameAndValue(name, uri, true); +} + +size_t XmlWriter::StartWritingElement(const string& element_name) { + MaybeWriteCloseBracket(true); + size_t current_depth = element_data_.size(); + if (current_depth > 0) { + element_data_.back().has_children = true; + } + element_data_.emplace_back(element_name); + os_ << indent_ << "<" << element_name; + indent_ += " "; + element_count_ += 1; + return current_depth; +} + +void XmlWriter::FinishWritingElement() { + if (!element_data_.empty()) { + if (indent_.size() >= 2) { + indent_.resize(indent_.size() - 2); + } + auto& data = element_data_.back(); + if (!data.has_content && !data.has_children) { + if (!data.has_attributes || data.has_children) { + os_ << indent_; + } + os_ << "/>" << std::endl; + } else { + if (!data.has_content) { + os_ << indent_; + } + os_ << "</" << data.name << ">" << std::endl; + } + element_data_.pop_back(); + } +} + +void XmlWriter::FinishWritingElementsToDepth(size_t depth) { + if (!element_data_.empty()) { + for (size_t index = element_data_.size(); index > depth; --index) { + FinishWritingElement(); + } + } +} + +size_t XmlWriter::StartWritingElements(const vector<string>& element_names) { + size_t current_depth = element_data_.size(); + for (const auto& element_name : element_names) { + StartWritingElement(element_name); + } + return current_depth; +} + +void XmlWriter::WriteElementAndContent(const string& element_name, + const string& content) { + StartWritingElement(element_name); + WriteContent(content); + FinishWritingElement(); +} + +void XmlWriter::WriteContent(const string& content) { + MaybeWriteCloseBracket(false); + if (!element_data_.empty()) { + auto& data = element_data_.back(); + data.has_content = true; + os_ << content; + } +} + +void XmlWriter::WriteAttributeNameAndValue(const string& name, + const string& value, + bool add_quote_marks) { + WriteAttributeName(name); + WriteAttributeValue(add_quote_marks, value, add_quote_marks); +} + +void XmlWriter::WriteAttributeName(const string& name) { + if (!element_data_.empty()) { + os_ << std::endl << indent_ << name << "="; + element_data_.back().has_attributes = true; + } +} + +void XmlWriter::WriteAttributeValue(bool add_leading_quote_mark, + const string& value, + bool add_trailing_quote_mark) { + if (!element_data_.empty()) { + if (add_leading_quote_mark) os_ << quote_mark_; + os_ << value; + if (add_trailing_quote_mark) os_ << quote_mark_; + } +} + +void XmlWriter::WriteComment(const std::string& comment) { + MaybeWriteCloseBracket(true); + os_ << indent_ << "<!-- " << comment << " -->" << std::endl; + if (!element_data_.empty()) { + auto& data = element_data_.back(); + data.has_children = true; + } +} + +bool XmlWriter::MaybeWriteCloseBracket(bool with_trailing_newline) { + if (!element_data_.empty()) { + auto& data = element_data_.back(); + if (!data.has_content && !data.has_children) { + os_ << ">"; + if (with_trailing_newline) { + os_ << std::endl; + } + return true; + } + } + return false; +} + +} // namespace image_io +} // namespace photos_editing_formats |