diff --git a/format.sh b/format.sh index b18272b2..913d258e 100755 --- a/format.sh +++ b/format.sh @@ -8,7 +8,6 @@ src/elements/audio_track_format_id.cpp src/elements/audio_block_format_id.cpp src/elements/audio_block_format_hoa.cpp src/document.cpp -src/private/xml_parser.cpp include/adm/elements/audio_block_format_hoa.hpp include/adm/elements/audio_pack_format.hpp include/adm/elements/audio_channel_format.hpp diff --git a/include/adm/private/rapidxml_formatter.hpp b/include/adm/private/rapidxml_formatter.hpp index d14012c7..734f045b 100644 --- a/include/adm/private/rapidxml_formatter.hpp +++ b/include/adm/private/rapidxml_formatter.hpp @@ -7,8 +7,6 @@ namespace adm { class XmlNode; - void formatAudioProgramme( - XmlNode &node, const std::shared_ptr programme); void formatLabel(XmlNode &node, const Label &label); void formatAudioComplementaryObjectGroupLabel( XmlNode &node, const AudioComplementaryObjectGroupLabel &label); diff --git a/include/adm/private/xml_parser.hpp b/include/adm/private/xml_parser.hpp index dd5fdf53..336612c2 100644 --- a/include/adm/private/xml_parser.hpp +++ b/include/adm/private/xml_parser.hpp @@ -44,13 +44,10 @@ namespace adm { AudioProgrammeReferenceScreen parseAudioProgrammeReferenceScreen( NodePtr node); Label parseLabel(NodePtr node); - AudioBlockFormatObjects parseAudioBlockFormatObjects(NodePtr node); Gain parseGain(NodePtr node); ChannelLock parseChannelLock(NodePtr node); ObjectDivergence parseObjectDivergence(NodePtr node); JumpPosition parseJumpPosition(NodePtr node); - AudioBlockFormatDirectSpeakers parseAudioBlockFormatDirectSpeakers( - NodePtr node); SphericalSpeakerPosition parseSphericalSpeakerPosition( const std::vector>& sphericalCoordinates); @@ -61,36 +58,45 @@ namespace adm { SpeakerPosition parseSpeakerPosition(std::vector node); SpeakerLabel parseSpeakerLabel(NodePtr node); HeadphoneVirtualise parseHeadphoneVirtualise(NodePtr node); - AudioBlockFormatHoa parseAudioBlockFormatHoa(NodePtr node); - AudioBlockFormatBinaural parseAudioBlockFormatBinaural(NodePtr node); NodePtr findAudioFormatExtendedNodeEbuCore(NodePtr root); NodePtr findAudioFormatExtendedNodeFullRecursive(NodePtr root); class XmlParser { public: - explicit XmlParser( - const std::string& filename, - ParserOptions options = ParserOptions::none, - std::shared_ptr destDocument = Document::create()); - explicit XmlParser( - std::istream& stream, ParserOptions options = ParserOptions::none, - std::shared_ptr destDocument = Document::create()); + explicit XmlParser(std::shared_ptr destDocument, + ParserOptions options = ParserOptions::none); - std::shared_ptr parse(); + void parseFile(const std::string& filename); + void parseStream(std::istream& stream); + void parseString(const std::string& xmlString); + void parseXmlFile(rapidxml::file<>& xmlFile); + void parseXml(const rapidxml::xml_document<>& xmlDocument); bool hasUnresolvedReferences(); - private: - std::shared_ptr parseAudioProgramme(NodePtr node); - std::shared_ptr parseAudioContent(NodePtr node); - std::shared_ptr parseAudioObject(NodePtr node); - std::shared_ptr parseAudioTrackFormat(NodePtr node); - std::shared_ptr parseAudioStreamFormat(NodePtr node); - std::shared_ptr parseAudioPackFormat(NodePtr node); - std::shared_ptr parseAudioTrackUid(NodePtr node); - std::shared_ptr parseAudioChannelFormat(NodePtr node); + protected: + virtual std::shared_ptr parseAudioProgramme(NodePtr node); + virtual std::shared_ptr parseAudioContent(NodePtr node); + virtual std::shared_ptr parseAudioObject(NodePtr node); + virtual std::shared_ptr parseAudioTrackFormat( + NodePtr node); + virtual std::shared_ptr parseAudioStreamFormat( + NodePtr node); + virtual std::shared_ptr parseAudioPackFormat( + NodePtr node); + virtual std::shared_ptr parseAudioTrackUid(NodePtr node); + virtual std::shared_ptr parseAudioChannelFormat( + NodePtr node); - rapidxml::file<> xmlFile_; + virtual AudioBlockFormatObjects parseAudioBlockFormatObjects( + NodePtr node); + virtual AudioBlockFormatDirectSpeakers + parseAudioBlockFormatDirectSpeakers(NodePtr node); + virtual AudioBlockFormatHoa parseAudioBlockFormatHoa(NodePtr node); + virtual AudioBlockFormatBinaural parseAudioBlockFormatBinaural( + NodePtr node); + + private: ParserOptions options_; std::shared_ptr document_; diff --git a/include/adm/private/xml_parser_helper.hpp b/include/adm/private/xml_parser_helper.hpp index bed80dd0..96d61489 100644 --- a/include/adm/private/xml_parser_helper.hpp +++ b/include/adm/private/xml_parser_helper.hpp @@ -63,7 +63,7 @@ namespace adm { * @returns NodePtr to first element or a nullptr if no element could * not be found. */ - NodePtr findElement(NodePtr node, const std::string& name) { + inline NodePtr findElement(NodePtr node, const std::string& name) { for (NodePtr elementNode = node->first_node(); elementNode; elementNode = elementNode->next_sibling()) { if (std::string(elementNode->name()) == name) { @@ -79,7 +79,8 @@ namespace adm { * @returns a vector of NodePtr or an empty vector if no element could * not be found. */ - std::vector findElements(NodePtr node, const std::string& name) { + inline std::vector findElements(NodePtr node, + const std::string& name) { std::vector elements; for (NodePtr elementNode = node->first_node(); elementNode; elementNode = elementNode->next_sibling()) { @@ -342,7 +343,7 @@ namespace adm { } } - FormatDescriptor checkFormat( + inline FormatDescriptor checkFormat( boost::optional formatLabel, boost::optional formatDefinition) { if (formatLabel != boost::none && formatDefinition != boost::none) { diff --git a/include/adm/private/xml_writer.hpp b/include/adm/private/xml_writer.hpp index 438d16ab..0d046e7d 100644 --- a/include/adm/private/xml_writer.hpp +++ b/include/adm/private/xml_writer.hpp @@ -1,10 +1,15 @@ #pragma once #include "adm/write.hpp" +#include namespace adm { class Document; + class AudioProgramme; + namespace xml { + class XmlNode; + class XmlWriter { public: explicit XmlWriter(WriterOptions options = WriterOptions::none); @@ -12,6 +17,10 @@ namespace adm { std::ostream& write(std::shared_ptr document, std::ostream& stream); + protected: + virtual void formatAudioProgramme( + XmlNode& node, const std::shared_ptr programme); + private: WriterOptions options_; }; diff --git a/src/common_definitions.cpp b/src/common_definitions.cpp index cd894639..9176a897 100644 --- a/src/common_definitions.cpp +++ b/src/common_definitions.cpp @@ -158,9 +158,14 @@ namespace adm { std::shared_ptr getCommonDefinitions() { std::stringstream commonDefinitions; getEmbeddedFile("common_definitions.xml", commonDefinitions); - xml::XmlParser parser(commonDefinitions, + + std::shared_ptr destDocument = Document::create(); + + xml::XmlParser parser(destDocument, xml::ParserOptions::recursive_node_search); - return parser.parse(); + parser.parseStream(commonDefinitions); + + return destDocument; } void addCommonDefinitionsTo(std::shared_ptr document) { diff --git a/src/parse.cpp b/src/parse.cpp index fa67b49c..788cb37c 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -8,15 +8,21 @@ namespace adm { std::shared_ptr parseXml(const std::string& filename, xml::ParserOptions options) { - auto commonDefinitions = getCommonDefinitions(); - xml::XmlParser parser(filename, options, commonDefinitions); - return parser.parse(); + std::shared_ptr doc = getCommonDefinitions(); + + xml::XmlParser parser(doc, options); + parser.parseFile(filename); + + return doc; } std::shared_ptr parseXml(std::istream& stream, xml::ParserOptions options) { - auto commonDefinitions = getCommonDefinitions(); - xml::XmlParser parser(stream, options, commonDefinitions); - return parser.parse(); + std::shared_ptr doc = getCommonDefinitions(); + + xml::XmlParser parser(doc, options); + parser.parseStream(stream); + + return doc; } } // namespace adm diff --git a/src/private/rapidxml_formatter.cpp b/src/private/rapidxml_formatter.cpp index c1931a70..368c4848 100644 --- a/src/private/rapidxml_formatter.cpp +++ b/src/private/rapidxml_formatter.cpp @@ -46,21 +46,6 @@ namespace adm { } // namespace detail - void formatAudioProgramme( - XmlNode &node, const std::shared_ptr programme) { - // clang-format off - node.addAttribute(programme, "audioProgrammeID"); - node.addOptionalAttribute(programme, "audioProgrammeName"); - node.addOptionalAttribute(programme, "audioProgrammeLanguage"); - node.addOptionalAttribute(programme, "start"); - node.addOptionalAttribute(programme, "end"); - node.addOptionalAttribute(programme, "maxDuckingDepth"); - node.addReferences(programme, "audioContentIDRef"); - node.addVectorElements(programme, "loudnessMetadata", &formatLoudnessMetadata); - node.addVectorElements(programme, "audioProgrammeLabel", &formatLabel); - // clang-format on - } - void formatLoudnessMetadata(XmlNode &node, const LoudnessMetadata loudnessMetadata) { node.addOptionalAttribute(&loudnessMetadata, diff --git a/src/private/xml_parser.cpp b/src/private/xml_parser.cpp index 2ae28a3e..ef6fcfc9 100644 --- a/src/private/xml_parser.cpp +++ b/src/private/xml_parser.cpp @@ -17,20 +17,37 @@ namespace adm { return static_cast(options & flag); } - XmlParser::XmlParser(const std::string& filename, ParserOptions options, - std::shared_ptr destDocument) - : xmlFile_(filename.c_str()), - options_(options), - document_(destDocument) {} + XmlParser::XmlParser(std::shared_ptr destDocument, + ParserOptions options) + : options_(options), document_(std::move(destDocument)) {} - XmlParser::XmlParser(std::istream& stream, ParserOptions options, - std::shared_ptr destDocument) - : xmlFile_(stream), options_(options), document_(destDocument) {} + void XmlParser::parseFile(const std::string& filename) { + rapidxml::file<> xmlFile(filename.c_str()); + parseXmlFile(xmlFile); + } + + void XmlParser::parseStream(std::istream& stream) { + rapidxml::file<> xmlFile(stream); + parseXmlFile(xmlFile); + } + + void XmlParser::parseString(const std::string& xmlString) { + // null-terminated copy to be modified by rapidxml (so can't use c_str) + std::vector vec(xmlString.begin(), xmlString.end()); + vec.push_back('\0'); - std::shared_ptr XmlParser::parse() { rapidxml::xml_document<> xmlDocument; - xmlDocument.parse<0>(xmlFile_.data()); + xmlDocument.parse<0>(vec.data()); + parseXml(xmlDocument); + } + void XmlParser::parseXmlFile(rapidxml::file<>& xmlFile) { + rapidxml::xml_document<> xmlDocument; + xmlDocument.parse<0>(xmlFile.data()); + parseXml(xmlDocument); + } + + void XmlParser::parseXml(const rapidxml::xml_document<>& xmlDocument) { if (!xmlDocument.first_node()) throw error::XmlParsingError("xml document is empty"); @@ -80,7 +97,6 @@ namespace adm { } else { throw error::XmlParsingError("audioFormatExtended node not found"); } - return document_; } // namespace xml /** @@ -430,8 +446,8 @@ namespace adm { return audioTrackUid; } - AudioBlockFormatDirectSpeakers parseAudioBlockFormatDirectSpeakers( - NodePtr node) { + AudioBlockFormatDirectSpeakers + XmlParser::parseAudioBlockFormatDirectSpeakers(NodePtr node) { AudioBlockFormatDirectSpeakers audioBlockFormat; // clang-format off setOptionalAttribute(node, "audioBlockFormatID", audioBlockFormat, &parseAudioBlockFormatId); @@ -600,7 +616,8 @@ namespace adm { return headphoneVirtualise; } - AudioBlockFormatObjects parseAudioBlockFormatObjects(NodePtr node) { + AudioBlockFormatObjects XmlParser::parseAudioBlockFormatObjects( + NodePtr node) { AudioBlockFormatObjects audioBlockFormat{SphericalPosition()}; // clang-format off setOptionalAttribute(node, "audioBlockFormatID", audioBlockFormat, &parseAudioBlockFormatId); @@ -826,7 +843,8 @@ namespace adm { return ContentKind( parseAttribute(node, "mixedContentKind")); } else { - throw error::XmlParsingError("unknown dialogue id", getDocumentLine(node)); + throw error::XmlParsingError("unknown dialogue id", + getDocumentLine(node)); } } @@ -835,7 +853,7 @@ namespace adm { return AudioProgrammeReferenceScreen(); } - AudioBlockFormatHoa parseAudioBlockFormatHoa(NodePtr node) { + AudioBlockFormatHoa XmlParser::parseAudioBlockFormatHoa(NodePtr node) { AudioBlockFormatHoa audioBlockFormat{Order(), Degree()}; // clang-format off setOptionalAttribute(node, "audioBlockFormatID", audioBlockFormat, &parseAudioBlockFormatId); @@ -855,11 +873,14 @@ namespace adm { return audioBlockFormat; } - AudioBlockFormatBinaural parseAudioBlockFormatBinaural(NodePtr node) { + AudioBlockFormatBinaural XmlParser::parseAudioBlockFormatBinaural( + NodePtr node) { AudioBlockFormatBinaural audioBlockFormat; - setOptionalAttribute(node, "rtime", audioBlockFormat, &parseTimecode); - setOptionalAttribute(node, "duration", audioBlockFormat, &parseTimecode); + setOptionalAttribute(node, "rtime", audioBlockFormat, + &parseTimecode); + setOptionalAttribute(node, "duration", audioBlockFormat, + &parseTimecode); setOptionalElement(node, "gain", audioBlockFormat, &parseGain); setOptionalElement(node, "importance", audioBlockFormat); diff --git a/src/private/xml_writer.cpp b/src/private/xml_writer.cpp index 8d3c00f9..caa1ee3d 100644 --- a/src/private/xml_writer.cpp +++ b/src/private/xml_writer.cpp @@ -37,8 +37,13 @@ namespace adm { } else { root = xmlDocument.addEbuStructure(); } + + root.addBaseElements( + document, "audioProgramme", [this](XmlNode& node, auto programme) { + formatAudioProgramme(node, programme); + }); + // clang-format off - root.addBaseElements(document, "audioProgramme", &formatAudioProgramme); root.addBaseElements(document, "audioContent", &formatAudioContent); root.addBaseElements(document, "audioObject", &formatAudioObject); root.addBaseElements(document, "audioPackFormat", &formatAudioPackFormat); @@ -50,5 +55,20 @@ namespace adm { return stream << xmlDocument; } + void XmlWriter::formatAudioProgramme( + XmlNode& node, const std::shared_ptr programme) { + // clang-format off + node.addAttribute(programme, "audioProgrammeID"); + node.addOptionalAttribute(programme, "audioProgrammeName"); + node.addOptionalAttribute(programme, "audioProgrammeLanguage"); + node.addOptionalAttribute(programme, "start"); + node.addOptionalAttribute(programme, "end"); + node.addOptionalAttribute(programme, "maxDuckingDepth"); + node.addReferences(programme, "audioContentIDRef"); + node.addVectorElements(programme, "loudnessMetadata", &formatLoudnessMetadata); + node.addVectorElements(programme, "audioProgrammeLabel", &formatLabel); + // clang-format on + } + } // namespace xml } // namespace adm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ad63e7f..6d07867e 100755 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -94,3 +94,8 @@ add_adm_test("xml_writer_audio_content_tests") add_adm_test("xml_writer_objects_creation_tests") add_adm_test("xml_writer_label_tests") add_adm_test("xml_writer_tests") + +# tests which use non-exported symbols +if ((NOT BUILD_SHARED_LIBS) OR (NOT ADM_HIDE_INTERNAL_SYMBOLS)) + add_adm_test("xml_parser_override_tests") +endif () diff --git a/tests/xml_parser_override_tests.cpp b/tests/xml_parser_override_tests.cpp new file mode 100644 index 00000000..7b6bb638 --- /dev/null +++ b/tests/xml_parser_override_tests.cpp @@ -0,0 +1,51 @@ +#include +#include "adm/private/xml_parser.hpp" +#include "adm/private/xml_parser_helper.hpp" + +using namespace adm; +using namespace adm::xml; + +// example of overriding the block format parser to fix broken xml +class XmlParserDiffuseFix : public XmlParser { + public: + using XmlParser::XmlParser; + + protected: + AudioBlockFormatObjects parseAudioBlockFormatObjects(NodePtr node) override { + NodePtr diffuse = xml::detail::findElement(node, "diffuse"); + if (diffuse != nullptr) { + if (std::string(diffuse->value()) == "true") + diffuse->value("1.0"); + else if (std::string(diffuse->value()) == "false") + diffuse->value("0.0"); + } + + return XmlParser::parseAudioBlockFormatObjects(node); + } +}; + +TEST_CASE("test override") { + std::string xml = R"( + + + + 30.0 + 0.0 + 1.0 + true + + + + )"; + + std::shared_ptr doc = Document::create(); + XmlParserDiffuseFix parser(doc, ParserOptions::recursive_node_search); + parser.parseString(xml); + + auto acf = doc->lookup(parseAudioChannelFormatId("AC_00031001")); + auto abf = acf->getElements()[0]; + CHECK(abf.get().get() == 1.0f); +}