Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docraft/include/docraft/craft/parser/docraft_parser_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,18 @@ namespace docraft::craft::parser::detail {
DocraftColor parse_docraft_color(const std::string &color_str);

/**
* @brief Parses the universal attributes (name/x/y/width/height/padding/weight/
* z_index/visible/position) shared by every Craft-language tag into a
* `DocraftCommonAttributes`. Generic and tag-agnostic -- called once per element by
* `DocraftCraftLanguageParser`, not by individual `IDocraftParser` implementations.
* @brief Parses the common node attributes into a `DocraftCommonAttributes`.
* Attribute support is validated per tag before this generic, tag-agnostic parser is
* called; not every tag supports every common attribute (in particular, content- or
* coordinate-sized nodes reject explicit width/height).
* @param craft_language_source The XML node to read attributes from.
* @return The parsed common attributes.
*/
DocraftCommonAttributes parse_common_node_attributes(const pugi::xml_node& craft_language_source);

/**
* @brief Rejects any attribute on `craft_language_source` that is neither a common
* node attribute nor listed in `accepted`.
* @brief Rejects any attribute on `craft_language_source` that is neither a supported
* common node attribute for this tag nor listed in `accepted`.
*
* Unknown *tags* have always been hard errors; this makes unknown attributes behave
* the same way, so a name the parser would silently drop is reported instead of
Expand Down
19 changes: 19 additions & 0 deletions docraft/src/docraft/craft/parser/docraft_parser_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,28 @@
basic::attribute::kMarginBottom, basic::attribute::kMarginLeft, basic::attribute::kWeight,
basic::attribute::kPosition, basic::attribute::kZIndex, basic::attribute::kVisible};

// These nodes derive their extent from their contents or coordinates. Although
// width/height are common syntactically, their current layout implementations
// have no explicit-size semantics, so accepting them would be a silent no-op.
static constexpr std::array<std::string_view, 5> kContentOrCoordinateSized = {
elements::kList, elements::kParagraph, elements::kLine,
elements::kCurveLine, elements::kTable};

for (const auto& attribute : craft_language_source.attributes())
{
const std::string_view name = attribute.name();
const bool is_explicit_size =
name == basic::attribute::kWidth || name == basic::attribute::kHeight;
if (is_explicit_size &&
std::ranges::find(kContentOrCoordinateSized, tag_name) != kContentOrCoordinateSized.end())

Check warning on line 116 in docraft/src/docraft/craft/parser/docraft_parser_helpers.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "is_explicit_size" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=Cadons_Docraft&issues=AaAGnha5XIT1OSYeOz3z&open=AaAGnha5XIT1OSYeOz3z&pullRequest=78
{
throw docraft::exception::InvalidInputException(std::format(
"Attribute '{}' is not supported on <{}>; that element's size is determined by {}",
name, tag_name,
tag_name == elements::kLine || tag_name == elements::kCurveLine
? "its coordinates"
: "its content"));
}
// Table sub-elements (Row/Cell/HTitle/VTitle) are not laid out as nodes of
// their own, so the common positioning attributes mean nothing on them and
// are not silently tolerated either.
Expand Down
26 changes: 25 additions & 1 deletion docraft/test/docraft/craft/docraft_craft_language_parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,30 @@ TEST(DocraftCraftLanguageParserTest, AcceptsTagSpecificAndCommonAttributes)
position="absolute" z_index="2" visible="true"/>)XML"));
}

TEST(DocraftCraftLanguageParserTest, RejectsExplicitSizeOnNodesWithoutExplicitSizeSemantics)
{
for (const std::string_view tag : {"List", "Paragraph", "Line", "CurveLine", "Table"})
{
for (const std::string_view attribute : {"width", "height"})
{
SCOPED_TRACE(std::string(tag) + " " + std::string(attribute));
const std::string xml = "<" + std::string(tag) + " " + std::string(attribute) + "=\"400\"/>";
try
{
parse_craft(xml.c_str());
FAIL() << "expected unsupported explicit sizing to be rejected";
}
catch (const docraft::exception::InvalidInputException& e)
{
const std::string message = e.what();
EXPECT_NE(message.find(attribute), std::string::npos);
EXPECT_NE(message.find(tag), std::string::npos);
EXPECT_NE(message.find("not supported"), std::string::npos);
}
}
}
}

TEST(DocraftCraftLanguageParserTest, TagSpecificAttributesDoNotLeakBetweenElements)
{
// `points` belongs to Polygon/Triangle, `radius` to Circle: each element accepts only
Expand Down Expand Up @@ -357,4 +381,4 @@ TEST(DocraftCraftLanguageParserTest, TableTitlesAcceptOnlyTheirOwnAttributes)
<TBody><Row><Cell><Text>v</Text></Cell></Row></TBody>
</Table>)XML";
EXPECT_THROW(parse_craft(unknown), docraft::exception::InvalidInputException);
}
}
Loading