diff --git a/src/driver_abbb23.cc b/src/driver_abbb23.cc index 42605d8..c5f2819 100644 --- a/src/driver_abbb23.cc +++ b/src/driver_abbb23.cc @@ -157,7 +157,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA600")), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xffffffffffffffff)) .set(DefaultMessage("OK")) )); @@ -170,7 +170,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA700")), Translate::Lookup() - .add(Translate::Rule("WARNING_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("WARNING_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xffffffffffffffff)) .set(DefaultMessage("OK")) )); @@ -183,7 +183,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA800")), Translate::Lookup() - .add(Translate::Rule("INFORMATION_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("INFORMATION_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xffffffffffffffff)) .set(DefaultMessage("")) )); @@ -196,7 +196,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA900")), Translate::Lookup() - .add(Translate::Rule("ALARM_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ALARM_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xfffffffffffffff)) .set(DefaultMessage("OK")) )); @@ -209,7 +209,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("01FFAD00")), Translate::Lookup() - .add(Translate::Rule("UNKNOWN", Translate::Type::BitToString) + .add(Translate::Rule("UNKNOWN", Translate::MapType::BitToString) .set(MaskBits(0xff)) .set(DefaultMessage("OK")) )); @@ -739,7 +739,7 @@ namespace .set(SubUnitNr(1),SubUnitNr(2)) .add(VIFCombinableRaw(0)), Translate::Lookup() - .add(Translate::Rule("OUTPUT", Translate::Type::BitToString) + .add(Translate::Rule("OUTPUT", Translate::MapType::BitToString) .set(MaskBits(0xff)) )); @@ -753,7 +753,7 @@ namespace .set(SubUnitNr(3),SubUnitNr(4)) .add(VIFCombinableRaw(0)), Translate::Lookup() - .add(Translate::Rule("INPUT", Translate::Type::BitToString) + .add(Translate::Rule("INPUT", Translate::MapType::BitToString) .set(MaskBits(0xff)) )); @@ -768,7 +768,7 @@ namespace .set(SubUnitNr(3),SubUnitNr(4)) .add(VIFCombinableRaw(0)), Translate::Lookup() - .add(Translate::Rule("INPUT", Translate::Type::BitToString) + .add(Translate::Rule("INPUT", Translate::MapType::BitToString) .set(MaskBits(0xff)) )); diff --git a/src/driver_aventieshca.cc b/src/driver_aventieshca.cc index ea63b7e..884319d 100644 --- a/src/driver_aventieshca.cc +++ b/src/driver_aventieshca.cc @@ -48,7 +48,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { @@ -75,7 +75,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "", { diff --git a/src/driver_aventieswm.cc b/src/driver_aventieswm.cc index 4f2c8d8..6ed7d49 100644 --- a/src/driver_aventieswm.cc +++ b/src/driver_aventieswm.cc @@ -49,7 +49,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { @@ -100,7 +100,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "", { diff --git a/src/driver_c5isf.cc b/src/driver_c5isf.cc index 54d175e..58807b7 100644 --- a/src/driver_c5isf.cc +++ b/src/driver_c5isf.cc @@ -101,7 +101,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::DecimalsToString, + Translate::MapType::DecimalsToString, AlwaysTrigger, MaskBits(9999), "OK", { diff --git a/src/driver_cma12w.cc b/src/driver_cma12w.cc index 8c4288d..579f288 100644 --- a/src/driver_cma12w.cc +++ b/src/driver_cma12w.cc @@ -77,7 +77,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::DigitalInput), Translate::Lookup() - .add(Translate::Rule("BATTERY", Translate::Type::BitToString) + .add(Translate::Rule("BATTERY", Translate::MapType::BitToString) .set(MaskBits(0xffff))) ); } diff --git a/src/driver_dme_07.cc b/src/driver_dme_07.cc index f00fb41..2828ecc 100644 --- a/src/driver_dme_07.cc +++ b/src/driver_dme_07.cc @@ -47,7 +47,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_dynamic.cc b/src/driver_dynamic.cc index 6061511..288c2fb 100644 --- a/src/driver_dynamic.cc +++ b/src/driver_dynamic.cc @@ -35,11 +35,16 @@ string get_translation(XMQDoc *doc, XMQNode *node, string name, string lang); string check_calculate(const char *formula, DriverDynamic *dd); Unit check_display_unit(const char *display_unit, DriverDynamic *dd); +bool checked_set_difvifkey(const char *difvifkey_s, FieldMatcher *fm, DriverDynamic *dd); void checked_set_measurement_type(const char *measurement_type_s, FieldMatcher *fm, DriverDynamic *dd); void checked_set_vif_range(const char *vif_range_s, FieldMatcher *fm, DriverDynamic *dd); void checked_set_storagenr_range(const char *storagenr_range_s, FieldMatcher *fm, DriverDynamic *dd); void checked_set_tariffnr_range(const char *tariffnr_range_s, FieldMatcher *fm, DriverDynamic *dd); void checked_set_subunitnr_range(const char *subunitnr_range_s, FieldMatcher *fm, DriverDynamic *dd); +Translate::MapType checked_map_type(const char *map_type_s, DriverDynamic *dd); +uint64_t checked_mask_bits(const char *mask_bits_s, DriverDynamic *dd); +uint64_t checked_value(const char *value_s, DriverDynamic *dd); +TestBit checked_test_type(const char *test_s, DriverDynamic *dd); void checked_add_vif_combinable(const char *vif_range_s, FieldMatcher *fm, DriverDynamic *dd); const char *line = "-------------------------------------------------------------------------------"; @@ -59,12 +64,12 @@ bool DriverDynamic::load(DriverInfo *di, const string &file_name, const char *co if (!content) { - ok = xmqParseFile(doc, file.c_str(), NULL); + ok = xmqParseFile(doc, file.c_str(), NULL, 0); } else { file = "builtin"; - ok = xmqParseBuffer(doc, content, content+strlen(content), NULL); + ok = xmqParseBuffer(doc, content, content+strlen(content), NULL, 0); } if (!ok) { @@ -283,6 +288,21 @@ XMQProceed DriverDynamic::add_field(XMQDoc *doc, XMQNode *field, DriverDynamic * // Check if there were any matches at all, if not, then disable the matcher. match.active = num_matches > 0; + // Now find all matchers. + Translate::Lookup lookup = Translate::Lookup(); + /* + .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .set(MaskBits(0x000f)) + .set(DefaultMessage("OK")) + .add(Translate::Map(0x01 ,"DRY", TestBit::Set)) + .add(Translate::Map(0x02 ,"REVERSE", TestBit::Set)) + .add(Translate::Map(0x04 ,"LEAK", TestBit::Set)) + .add(Translate::Map(0x08 ,"BURST", TestBit::Set)) + )); + */ + dd->tmp_lookup_ = &lookup; + int num_lookups = xmqForeach(doc, field, "lookup", (XMQNodeCallback)add_lookup, dd); + if (is_numeric) { if (calculate == "") @@ -326,12 +346,25 @@ XMQProceed DriverDynamic::add_field(XMQDoc *doc, XMQNode *field, DriverDynamic * } else { - dd->addStringFieldWithExtractor( - name, - info, - properties, - match - ); + if (num_lookups > 0) + { + dd->addStringFieldWithExtractorAndLookup( + name, + info, + properties, + match, + lookup + ); + } + else + { + dd->addStringFieldWithExtractor( + name, + info, + properties, + match + ); + } } return XMQ_CONTINUE; } @@ -340,6 +373,8 @@ XMQProceed DriverDynamic::add_match(XMQDoc *doc, XMQNode *match, DriverDynamic * { FieldMatcher *fm = dd->tmp_matcher_; + if (checked_set_difvifkey(xmqGetString(doc, match, "difvifkey"), fm, dd)) return XMQ_CONTINUE; + checked_set_measurement_type(xmqGetString(doc, match, "measurement_type"), fm, dd); checked_set_vif_range(xmqGetString(doc, match, "vif_range"), fm, dd); @@ -360,6 +395,61 @@ XMQProceed DriverDynamic::add_combinable(XMQDoc *doc, XMQNode *match, DriverDyna return XMQ_CONTINUE; } +/** + add_map: + Add a mapping from a value (bits,index,decimal) to a string name. + + map { + name = SURGE + info = 'Unexpected increase in pressure in relation to average pressure.' + value = 0x02 + test = set + } +*/ +XMQProceed DriverDynamic::add_map(XMQDoc *doc, XMQNode *map, DriverDynamic *dd) +{ + const char *name = xmqGetString(doc, map, "name"); + uint64_t value = checked_value(xmqGetString(doc, map, "value"), dd); + TestBit test_type = checked_test_type(xmqGetString(doc, map, "test"), dd); + + dd->tmp_rule_->add(Translate::Map(value, name, test_type)); + + return XMQ_CONTINUE; +} + +/** + add_lookup: + Add a lookup from bits,index or decimal to a sequence of string tokens. + Or fallback to the name (ERROR_FLAGS_8) suffixed by the untranslateable bits. + + lookup { + name = ERROR_FLAGS + map_type = BitToString + mask_bits = 0xffff + default_message = OK + map { } map {} + } +*/ +XMQProceed DriverDynamic::add_lookup(XMQDoc *doc, XMQNode *lookup, DriverDynamic *dd) +{ + const char *name = xmqGetString(doc, lookup, "name"); + Translate::MapType map_type = checked_map_type(xmqGetString(doc, lookup, "map_type"), dd); + uint64_t mask_bits = checked_mask_bits(xmqGetString(doc, lookup, "mask_bits"), dd); + const char *default_message = xmqGetString(doc, lookup, "default_message"); + + Translate::Rule rule = Translate::Rule(name, map_type); + dd->tmp_rule_ = &rule; + + rule.set(MaskBits(mask_bits)); + rule.set(DefaultMessage(default_message)); + + xmqForeach(doc, lookup, "map", (XMQNodeCallback)add_map, dd); + + dd->tmp_lookup_->add(rule); + + return XMQ_CONTINUE; +} + string check_driver_name(const char *name, string file) { if (!name) @@ -623,6 +713,31 @@ Unit check_display_unit(const char *display_unit_s, DriverDynamic *dd) return u; } +bool checked_set_difvifkey(const char *difvifkey_s, FieldMatcher *fm, DriverDynamic *dd) +{ + if (!difvifkey_s) return false; + + bool invalid_hex = false; + bool hex = isHexStringStrict(difvifkey_s, &invalid_hex); + + if (!hex || invalid_hex) + { + warning("(driver) error in %s, bad divfikey: %s\n" + "%s\n" + "Should be all hex.\n" + "%s\n", + dd->fileName().c_str(), + difvifkey_s, + line, + line); + throw 1; + } + + fm->set(DifVifKey(difvifkey_s)); + + return true; +} + void checked_set_measurement_type(const char *measurement_type_s, FieldMatcher *fm, DriverDynamic *dd) { if (!measurement_type_s) @@ -757,3 +872,118 @@ void checked_add_vif_combinable(const char *vif_combinable_s, FieldMatcher *fm, fm->add(vif_combinable); } + +Translate::MapType checked_map_type(const char *map_type_s, DriverDynamic *dd) +{ + if (!map_type_s) + { + warning("(driver) error in %s, cannot find: driver/field/lookup/map_type\n" + "%s\n" + "Remember to add for example: lookup { map_type = BitToString ... }\n" + "Available map types:\n" + "BitToString\n" + "IndexToString\n" + "DecimalsToString\n" + "%s\n", + dd->fileName().c_str(), + line, + line); + throw 1; + } + + Translate::MapType map_type = toMapType(map_type_s); + + if (map_type == Translate::MapType::Unknown) + { + warning("(driver) error in %s, bad map_type: %s\n" + "%s\n" + "Available map types:\n" + "BitToString\n" + "IndexToString\n" + "DecimalToString\n" + "%s\n", + dd->fileName().c_str(), + map_type_s, + line, + line); + throw 1; + } + + return map_type; +} + + +uint64_t checked_mask_bits(const char *mask_bits_s, DriverDynamic *dd) +{ + if (!mask_bits_s) + { + warning("(driver) error in %s, cannot find: driver/field/lookup/mask_bitse\n" + "%s\n" + "Remember to add for example: lookup { mask_bits = 0x00ff ... }\n" + "%s\n", + dd->fileName().c_str(), + line, + line); + throw 1; + } + + uint64_t mask = strtol(mask_bits_s, NULL, 16); + + return mask; +} + +uint64_t checked_value(const char *value_s, DriverDynamic *dd) +{ + if (!value_s) + { + warning("(driver) error in %s, cannot find: driver/field/lookup/map/value\n" + "%s\n" + "Remember to add for example: lookup { map { ... value = 0x01 ... }}\n" + "%s\n", + dd->fileName().c_str(), + line, + line); + throw 1; + } + + uint64_t value = strtol(value_s, NULL, 16); + + return value; +} + +TestBit checked_test_type(const char *test_s, DriverDynamic *dd) +{ + if (!test_s) + { + warning("(driver) error in %s, cannot find: driver/field/lookup/map/test\n" + "%s\n" + "Remember to add for example: lookup { map { test = Set } }\n" + "Available test types:\n" + "Set\n" + "NotSet\n" + "%s\n", + dd->fileName().c_str(), + line, + line); + throw 1; + } + + TestBit test_type = toTestBit(test_s); + + if (test_type == TestBit::Unknown) + { + warning("(driver) error in %s, bad test: %s\n" + "%s\n" + "Available test types:\n" + "Set\n" + "NotSet\n" + "%s\n", + dd->fileName().c_str(), + test_s, + line, + line); + throw 1; + } + + return test_type; +} diff --git a/src/driver_dynamic.h b/src/driver_dynamic.h index b0a9c1e..fb923e0 100644 --- a/src/driver_dynamic.h +++ b/src/driver_dynamic.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2023 Fredrik Öhrström (gpl-3.0-or-later) + Copyright (C) 2023-2024 Fredrik Öhrström (gpl-3.0-or-later) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,12 +31,17 @@ struct DriverDynamic : public virtual MeterCommonImplementation static XMQProceed add_match(XMQDoc *doc, XMQNode *match, DriverDynamic *dd); static XMQProceed add_combinable(XMQDoc *doc, XMQNode *match, DriverDynamic *dd); + static XMQProceed add_lookup(XMQDoc *doc, XMQNode *lookup, DriverDynamic *dd); + static XMQProceed add_map(XMQDoc *doc, XMQNode *map, DriverDynamic *dd); + const string &fileName() { return file_name_; } private: string file_name_; FieldMatcher *tmp_matcher_; + Translate::Lookup *tmp_lookup_; + Translate::Rule *tmp_rule_; }; #endif diff --git a/src/driver_ei6500.cc b/src/driver_ei6500.cc index 935d952..3b549f0 100644 --- a/src/driver_ei6500.cc +++ b/src/driver_ei6500.cc @@ -38,7 +38,7 @@ namespace { setMfctTPLStatusBits( Translate::Lookup() - .add(Translate::Rule("TPL_STS", Translate::Type::BitToString) + .add(Translate::Rule("TPL_STS", Translate::MapType::BitToString) .set(MaskBits(0xe0)) .set(DefaultMessage("OK")) .add(Translate::Map(0x04 ,"RTC_INVALID", TestBit::Set)))); @@ -54,7 +54,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { @@ -208,7 +208,7 @@ namespace { { "DUST", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x1f), "", { @@ -228,7 +228,7 @@ namespace { { "BATTERY_VOLTAGE", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x0f00), "", { @@ -267,7 +267,7 @@ namespace { { "OBSTACLE_DISTANCE", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x700000), "", { @@ -297,7 +297,7 @@ namespace { { "HEAD_STATUS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff8ff0e0), "OK", { diff --git a/src/driver_elf.cc b/src/driver_elf.cc index 7c1d70c..ea04a76 100644 --- a/src/driver_elf.cc +++ b/src/driver_elf.cc @@ -45,7 +45,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffffffff), "OK", { diff --git a/src/driver_em24.cc b/src/driver_em24.cc index f62abf1..dbf0fb5 100644 --- a/src/driver_em24.cc +++ b/src/driver_em24.cc @@ -53,7 +53,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "OK", { @@ -80,7 +80,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "", { diff --git a/src/driver_engelmann-faw.cc b/src/driver_engelmann-faw.cc index 0440410..67cc170 100644 --- a/src/driver_engelmann-faw.cc +++ b/src/driver_engelmann-faw.cc @@ -46,7 +46,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "OK", { diff --git a/src/driver_eurisii.cc b/src/driver_eurisii.cc index 06e0282..c905af9 100644 --- a/src/driver_eurisii.cc +++ b/src/driver_eurisii.cc @@ -50,7 +50,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { @@ -78,7 +78,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_evo868.cc b/src/driver_evo868.cc index b36d67a..8829e7b 100644 --- a/src/driver_evo868.cc +++ b/src/driver_evo868.cc @@ -49,7 +49,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_flowiq2200.cc b/src/driver_flowiq2200.cc index 8b95b3c..a07d88f 100644 --- a/src/driver_flowiq2200.cc +++ b/src/driver_flowiq2200.cc @@ -51,7 +51,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffffffff), "OK", { @@ -216,7 +216,7 @@ namespace { { "DRY", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x0070), "", { @@ -243,7 +243,7 @@ namespace { { "REVERSED", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x0380), "", { @@ -270,7 +270,7 @@ namespace { { "LEAKING", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x1c00), "", { @@ -297,7 +297,7 @@ namespace { { "BURSTING", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0xe000), "", { diff --git a/src/driver_gransystems.cc b/src/driver_gransystems.cc index 867a370..f868b86 100644 --- a/src/driver_gransystems.cc +++ b/src/driver_gransystems.cc @@ -70,7 +70,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, AutoMask, "OK", @@ -85,7 +85,7 @@ namespace }, { "ERROR_FLAGS_SINGLE_PHASE", - Translate::Type::BitToString, + Translate::MapType::BitToString, TriggerBits(0x01020000), AutoMask, "OK", @@ -99,7 +99,7 @@ namespace }, { "ERROR_FLAGS_THREE_PHASE", - Translate::Type::BitToString, + Translate::MapType::BitToString, TriggerBits(0x01010000), AutoMask, "OK", @@ -124,7 +124,7 @@ namespace { { "INFO_FLAGS", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, AutoMask, "", diff --git a/src/driver_hcae2.cc b/src/driver_hcae2.cc index 72936cf..3321291 100644 --- a/src/driver_hcae2.cc +++ b/src/driver_hcae2.cc @@ -49,7 +49,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_hydrocalm3.cc b/src/driver_hydrocalm3.cc index 5f7ed89..bb35a5a 100644 --- a/src/driver_hydrocalm3.cc +++ b/src/driver_hydrocalm3.cc @@ -39,7 +39,7 @@ namespace { setMfctTPLStatusBits( Translate::Lookup() - .add(Translate::Rule("TPL_STS", Translate::Type::BitToString) + .add(Translate::Rule("TPL_STS", Translate::MapType::BitToString) .set(MaskBits(0xe0)) .set(DefaultMessage("OK")) .add(Translate::Map(0x80 ,"SABOTAGE_ENCLOSURE", TestBit::Set)))); diff --git a/src/driver_iem3000.cc b/src/driver_iem3000.cc index d520a07..5dd899c 100644 --- a/src/driver_iem3000.cc +++ b/src/driver_iem3000.cc @@ -53,7 +53,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::ErrorFlags), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::IndexToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::IndexToString) .set(MaskBits(0xffffff)) .add(Translate::Map(0x000000, "CODE_101_EEPROM_ERROR", TestBit::Set)) .add(Translate::Map(0x000010, "CODE_102_NO_CALIBRATION_TABLE", TestBit::Set)) @@ -203,7 +203,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA600")), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xffffffffffffffff)) .set(DefaultMessage("OK")) )); @@ -216,7 +216,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA700")), Translate::Lookup() - .add(Translate::Rule("WARNING_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("WARNING_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xffffffffffffffff)) .set(DefaultMessage("OK")) )); @@ -229,7 +229,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA800")), Translate::Lookup() - .add(Translate::Rule("INFORMATION_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("INFORMATION_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xffffffffffffffff)) .set(DefaultMessage("")) )); @@ -242,7 +242,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("07FFA900")), Translate::Lookup() - .add(Translate::Rule("ALARM_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ALARM_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xfffffffffffffff)) .set(DefaultMessage("OK")) )); @@ -255,7 +255,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("01FFAD00")), Translate::Lookup() - .add(Translate::Rule("UNKNOWN", Translate::Type::BitToString) + .add(Translate::Rule("UNKNOWN", Translate::MapType::BitToString) .set(MaskBits(0xff)) .set(DefaultMessage("OK")) )); @@ -1407,7 +1407,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::DigitalInput), Translate::Lookup() - .add(Translate::Rule("INPUT", Translate::Type::BitToString) + .add(Translate::Rule("INPUT", Translate::MapType::BitToString) .set(MaskBits(0xffffff)) )); @@ -1419,7 +1419,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("02FF32")), Translate::Lookup() - .add(Translate::Rule("INPUT_STATUS", Translate::Type::BitToString) + .add(Translate::Rule("INPUT_STATUS", Translate::MapType::BitToString) .set(MaskBits(0xff)) )); @@ -1431,7 +1431,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::DigitalOutput), Translate::Lookup() - .add(Translate::Rule("OUTPUT", Translate::Type::BitToString) + .add(Translate::Rule("OUTPUT", Translate::MapType::BitToString) .set(MaskBits(0xffffff)) )); @@ -1443,7 +1443,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("03FF2D")), Translate::Lookup() - .add(Translate::Rule("OUTPUT_ASSOCIATION", Translate::Type::BitToString) + .add(Translate::Rule("OUTPUT_ASSOCIATION", Translate::MapType::BitToString) .set(MaskBits(0xffffff)) )); @@ -1455,7 +1455,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("03FF30")), Translate::Lookup() - .add(Translate::Rule("INPUT_ASSOCIATION", Translate::Type::BitToString) + .add(Translate::Rule("INPUT_ASSOCIATION", Translate::MapType::BitToString) .set(MaskBits(0xffffff)) )); @@ -1467,7 +1467,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey( "02FF36")), Translate::Lookup() - .add(Translate::Rule("OUTPUT_ASSOCIATION", Translate::Type::BitToString) + .add(Translate::Rule("OUTPUT_ASSOCIATION", Translate::MapType::BitToString) .set(MaskBits(0xffff)) )); @@ -1479,7 +1479,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("02FF34")), Translate::Lookup() - .add(Translate::Rule("OVERLOAD_ALARM", Translate::Type::BitToString) + .add(Translate::Rule("OVERLOAD_ALARM", Translate::MapType::BitToString) .set(MaskBits(0xff)) )); @@ -1500,7 +1500,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("02FF37")), Translate::Lookup() - .add(Translate::Rule("ACTIVATED_STATUS", Translate::Type::BitToString) + .add(Translate::Rule("ACTIVATED_STATUS", Translate::MapType::BitToString) .set(MaskBits(0xff)) )); @@ -1512,7 +1512,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("02FF38")), Translate::Lookup() - .add(Translate::Rule("UNACK_STATUS", Translate::Type::BitToString) + .add(Translate::Rule("UNACK_STATUS", Translate::MapType::BitToString) .set(MaskBits(0xff)) )); @@ -1643,7 +1643,7 @@ namespace .set(MeasurementType::Instantaneous) .set(DifVifKey("03FF23")), Translate::Lookup() - .add(Translate::Rule("POWER_SYS_CONFIG", Translate::Type::BitToString) + .add(Translate::Rule("POWER_SYS_CONFIG", Translate::MapType::BitToString) .set(MaskBits(0xffffff)) )); diff --git a/src/driver_itron.cc b/src/driver_itron.cc index 38cfc3b..46c308e 100644 --- a/src/driver_itron.cc +++ b/src/driver_itron.cc @@ -60,7 +60,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffffff), "OK", { @@ -102,7 +102,7 @@ namespace { { "WOOTA", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffffffff), "", { @@ -122,7 +122,7 @@ namespace { { "WOOTB", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "", { @@ -156,4 +156,3 @@ namespace // telegram=|3A4497269820362300167AF60020A52F2F_04132E100000066D03260DE12B007413FEFEFEFE426C1F01047F1600060C027F9A2A0E79187103002300| // {"enhanced_id": "002300037118", "id": "23362098", "media": "cold water", "meter": "itron", "meter_datetime": "2023-11-01 13:38:03", "name": "ColdWaterMeter", "status": "OK", "target_date": "2000-01-31", "timestamp": "1111-11-11T11:11:11Z", "total_m3": 4.142,"unknown_a": "WOOTA_C060016","unknown_b": "WOOTB_2A9A" } // |ColdWaterMeter;23362098;4.142;null;1111-11-11 11:11.11 - diff --git a/src/driver_kamheat.cc b/src/driver_kamheat.cc index 8c77357..f991f90 100644 --- a/src/driver_kamheat.cc +++ b/src/driver_kamheat.cc @@ -74,7 +74,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffffffff), "OK", { diff --git a/src/driver_kampress.cc b/src/driver_kampress.cc deleted file mode 100644 index d464ec3..0000000 --- a/src/driver_kampress.cc +++ /dev/null @@ -1,130 +0,0 @@ -/* - Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include"meters_common_implementation.h" - -namespace -{ - struct Driver : public virtual MeterCommonImplementation - { - Driver(MeterInfo &mi, DriverInfo &di); - }; - - static bool ok = registerDriver([](DriverInfo&di) - { - di.setName("kampress"); - di.setDefaultFields("name,id,status,pressure_bar,max_pressure_bar,min_pressure_bar,timestamp"); - di.setMeterType(MeterType::PressureSensor); - di.addLinkMode(LinkMode::C1); - di.addDetection(MANUFACTURER_KAM, 0x18, 0x01); - di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); }); - }); - - Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) - { - addStringFieldWithExtractorAndLookup( - "status", - "Status and error flags.", - DEFAULT_PRINT_PROPERTIES | INCLUDE_TPL_STATUS, - FieldMatcher::build() - .set(VIFRange::ErrorFlags), - { - { - { - "ERROR_FLAGS", - Translate::Type::BitToString, - AlwaysTrigger, MaskBits(0xffff), - "OK", - { - { 0x01, "DROP" }, // Unexpected drop in pressure in relation to average pressure. - { 0x02, "SURGE" }, // Unexpected increase in pressure in relation to average pressure. - { 0x04, "HIGH" }, // Average pressure has reached configurable limit. Default 15 bar - { 0x08, "LOW" }, // Average pressure has reached configurable limit. Default 1.5 bar - { 0x10, "TRANSIENT" }, // Pressure changes quickly over short timeperiods. Average is fluctuating. - { 0x20, "COMM_ERROR" } // Cannot measure properly or bad internal communication. - } - }, - }, - }); - - addNumericFieldWithExtractor( - "pressure", - "The measured pressure.", - DEFAULT_PRINT_PROPERTIES, - Quantity::Pressure, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Pressure) - ); - - addNumericFieldWithExtractor( - "max_pressure", - "The maximum pressure measured during ?.", - DEFAULT_PRINT_PROPERTIES, - Quantity::Pressure, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Maximum) - .set(VIFRange::Pressure) - ); - - addNumericFieldWithExtractor( - "min_pressure", - "The minimum pressure measured during ?.", - DEFAULT_PRINT_PROPERTIES, - Quantity::Pressure, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Minimum) - .set(VIFRange::Pressure) - ); - - addNumericFieldWithExtractor( - "alfa", - "We do not know what this is.", - DEFAULT_PRINT_PROPERTIES, - Quantity::Dimensionless, - VifScaling::None, - FieldMatcher::build() - .set(DifVifKey("05FF09")) - ); - - addNumericFieldWithExtractor( - "beta", - "We do not know what this is.", - DEFAULT_PRINT_PROPERTIES, - Quantity::Dimensionless, - VifScaling::None, - FieldMatcher::build() - .set(DifVifKey("05FF0A")) - ); - } -} - -// Test: Pressing kampress 77000317 NOKEY -// telegram=|32442D2C1703007701188D280080E39322DB8F78_22696600126967000269660005FF091954A33A05FF0A99BD823A02FD170800| -// {"media":"pressure","meter":"kampress","name":"Pressing","id":"77000317","status":"LOW","pressure_bar":1.02,"max_pressure_bar":1.03,"min_pressure_bar":1.02,"alfa_counter":0.001246,"beta_counter":0.000997,"timestamp":"1111-11-11T11:11:11Z"} -// |Pressing;77000317;LOW;1.02;1.03;1.02;1111-11-11 11:11.11 - -// telegram=|27442D2C1703007701188D280194E393226EC679DE735657_660067006600962B913A21B9423A0800| -// {"media":"pressure","meter":"kampress","name":"Pressing","id":"77000317","status":"LOW","pressure_bar":1.02,"max_pressure_bar":1.03,"min_pressure_bar":1.02,"alfa_counter":0.001108,"beta_counter":0.000743,"timestamp":"1111-11-11T11:11:11Z"} -// |Pressing;77000317;LOW;1.02;1.03;1.02;1111-11-11 11:11.11 - -// telegram=|27442D2C1703007701188D289554F295224ED579DE73188A_650066006600E80EA43A6B97A3BA0800| -// {"media":"pressure","meter":"kampress","name":"Pressing","id":"77000317","status":"LOW","pressure_bar":1.02,"max_pressure_bar":1.02,"min_pressure_bar":1.01,"alfa_counter":0.001252,"beta_counter":-0.001248,"timestamp":"1111-11-11T11:11:11Z"} -// |Pressing;77000317;LOW;1.02;1.02;1.01;1111-11-11 11:11.11 diff --git a/src/driver_lansendw.cc b/src/driver_lansendw.cc index 29cf411..453ec9c 100644 --- a/src/driver_lansendw.cc +++ b/src/driver_lansendw.cc @@ -44,7 +44,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::DigitalInput), Translate::Lookup() - .add(Translate::Rule("INPUT_BITS", Translate::Type::IndexToString) + .add(Translate::Rule("INPUT_BITS", Translate::MapType::IndexToString) .set(MaskBits(0xffff)) .add(Translate::Map(0x11 ,"CLOSED", TestBit::Set)) .add(Translate::Map(0x55 ,"OPEN", TestBit::Set)) @@ -58,7 +58,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::ErrorFlags), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xffff)) .set(DefaultMessage("OK")) )); diff --git a/src/driver_lansenpu.cc b/src/driver_lansenpu.cc index 9d7eb06..d51e536 100644 --- a/src/driver_lansenpu.cc +++ b/src/driver_lansenpu.cc @@ -39,7 +39,7 @@ namespace { setMfctTPLStatusBits( Translate::Lookup() - .add(Translate::Rule("TPL_STS", Translate::Type::BitToString) + .add(Translate::Rule("TPL_STS", Translate::MapType::BitToString) .set(MaskBits(0xe0)) .set(DefaultMessage("OK")) .add(Translate::Map(0x40 ,"SABOTAGE_ENCLOSURE", TestBit::Set)))); diff --git a/src/driver_lansenrp.cc b/src/driver_lansenrp.cc index 27e4543..296cf4f 100644 --- a/src/driver_lansenrp.cc +++ b/src/driver_lansenrp.cc @@ -40,7 +40,7 @@ namespace { setMfctTPLStatusBits( Translate::Lookup() - .add(Translate::Rule("TPL_STS", Translate::Type::BitToString) + .add(Translate::Rule("TPL_STS", Translate::MapType::BitToString) .set(MaskBits(0xe0)) .set(DefaultMessage("OK")) .add(Translate::Map(0x04 ,"LOW_BATTERY", TestBit::Set)))); @@ -92,7 +92,7 @@ namespace .set(VIFRange::Dimensionless) .set(SubUnitNr(2)), Translate::Lookup() - .add(Translate::Rule("INPUT_BITS", Translate::Type::IndexToString) + .add(Translate::Rule("INPUT_BITS", Translate::MapType::IndexToString) .set(MaskBits(0x01)) .add(Translate::Map(0x00, "NO", TestBit::Set)) .add(Translate::Map(0x01, "YES", TestBit::Set)) @@ -143,7 +143,7 @@ namespace .set(VIFRange::Dimensionless) .set(StorageNr(3)), Translate::Lookup() - .add(Translate::Rule("INPUT_BITS", Translate::Type::BitToString) + .add(Translate::Rule("INPUT_BITS", Translate::MapType::BitToString) .set(MaskBits(0xffff)) .add(Translate::Map(0x01 ,"SU", TestBit::Set)) .add(Translate::Map(0x02 ,"MO", TestBit::Set)) diff --git a/src/driver_lansensm.cc b/src/driver_lansensm.cc index dd7ad59..a47745e 100644 --- a/src/driver_lansensm.cc +++ b/src/driver_lansensm.cc @@ -49,7 +49,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_lansenth.cc b/src/driver_lansenth.cc index a14e38f..035152a 100644 --- a/src/driver_lansenth.cc +++ b/src/driver_lansenth.cc @@ -39,7 +39,7 @@ namespace addOptionalLibraryFields("on_time_h"); setMfctTPLStatusBits( Translate::Lookup() - .add(Translate::Rule("TPL_STS", Translate::Type::BitToString) + .add(Translate::Rule("TPL_STS", Translate::MapType::BitToString) .set(MaskBits(0xe0)) .set(DefaultMessage("OK")) .add(Translate::Map(0x40 ,"SABOTAGE_ENCLOSURE", TestBit::Set)))); diff --git a/src/driver_lse_07_17.cc b/src/driver_lse_07_17.cc index ae5c8d4..fc41cd6 100644 --- a/src/driver_lse_07_17.cc +++ b/src/driver_lse_07_17.cc @@ -105,7 +105,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_lse_08.cc b/src/driver_lse_08.cc index d2515f6..2a65c20 100644 --- a/src/driver_lse_08.cc +++ b/src/driver_lse_08.cc @@ -51,7 +51,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "OK", { diff --git a/src/driver_microclima.cc b/src/driver_microclima.cc index 070e00b..516bd89 100644 --- a/src/driver_microclima.cc +++ b/src/driver_microclima.cc @@ -52,7 +52,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_minomess.cc b/src/driver_minomess.cc index 6a73d1c..c71cec7 100644 --- a/src/driver_minomess.cc +++ b/src/driver_minomess.cc @@ -130,7 +130,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_multical21.cc b/src/driver_multical21.cc index cfee124..b175621 100644 --- a/src/driver_multical21.cc +++ b/src/driver_multical21.cc @@ -46,7 +46,7 @@ namespace FieldMatcher::build() .set(DifVifKey("02FF20")), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0x000f)) .set(DefaultMessage("OK")) .add(Translate::Map(0x01 ,"DRY", TestBit::Set)) @@ -136,7 +136,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0x000f), "", { @@ -159,7 +159,7 @@ namespace { { "DRY", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x0070), "", { @@ -186,7 +186,7 @@ namespace { { "REVERSED", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x0380), "", { @@ -213,7 +213,7 @@ namespace { { "LEAKING", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0x1c00), "", { @@ -240,7 +240,7 @@ namespace { { "BURSTING", - Translate::Type::IndexToString, + Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0xe000), "", { diff --git a/src/driver_munia.cc b/src/driver_munia.cc index 4c06725..41d53ed 100644 --- a/src/driver_munia.cc +++ b/src/driver_munia.cc @@ -47,7 +47,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_nemo.cc b/src/driver_nemo.cc index 30e68bc..5f301ab 100644 --- a/src/driver_nemo.cc +++ b/src/driver_nemo.cc @@ -174,7 +174,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::ErrorFlags), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0xff)) .set(DefaultMessage("OK")) )); diff --git a/src/driver_qcaloric.cc b/src/driver_qcaloric.cc index 6b7ebe0..d5f3cba 100644 --- a/src/driver_qcaloric.cc +++ b/src/driver_qcaloric.cc @@ -59,7 +59,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "OK", { diff --git a/src/driver_qheat.cc b/src/driver_qheat.cc index aac01e4..ebfc005 100644 --- a/src/driver_qheat.cc +++ b/src/driver_qheat.cc @@ -54,7 +54,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_qsmoke.cc b/src/driver_qsmoke.cc index 0f566be..4aef547 100644 --- a/src/driver_qsmoke.cc +++ b/src/driver_qsmoke.cc @@ -49,7 +49,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_qualcosonic.cc b/src/driver_qualcosonic.cc index 4df6228..be8398d 100644 --- a/src/driver_qualcosonic.cc +++ b/src/driver_qualcosonic.cc @@ -53,7 +53,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffffffff), "OK", { diff --git a/src/driver_sensostar.cc b/src/driver_sensostar.cc index 0b4b829..d7022e2 100644 --- a/src/driver_sensostar.cc +++ b/src/driver_sensostar.cc @@ -155,7 +155,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "OK", { diff --git a/src/driver_sharky.cc b/src/driver_sharky.cc index a61ff5b..e65ec2a 100644 --- a/src/driver_sharky.cc +++ b/src/driver_sharky.cc @@ -51,7 +51,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::ErrorFlags), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0x0000)) .set(DefaultMessage("OK")) )); diff --git a/src/driver_supercom587.cc b/src/driver_supercom587.cc index dfa29ef..f3575dd 100644 --- a/src/driver_supercom587.cc +++ b/src/driver_supercom587.cc @@ -48,7 +48,7 @@ namespace .set(MeasurementType::Instantaneous) .set(VIFRange::ErrorFlags), Translate::Lookup() - .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .add(Translate::Rule("ERROR_FLAGS", Translate::MapType::BitToString) .set(MaskBits(0x000f)) .set(DefaultMessage("OK")) )); diff --git a/src/driver_ultraheat.cc b/src/driver_ultraheat.cc index 676efc6..6dbdcdb 100644 --- a/src/driver_ultraheat.cc +++ b/src/driver_ultraheat.cc @@ -114,7 +114,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_ultrimis.cc b/src/driver_ultrimis.cc index 9c84821..0f9d53a 100644 --- a/src/driver_ultrimis.cc +++ b/src/driver_ultrimis.cc @@ -68,7 +68,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffffff), "OK", { diff --git a/src/driver_unismart.cc b/src/driver_unismart.cc index f378ded..0848585 100644 --- a/src/driver_unismart.cc +++ b/src/driver_unismart.cc @@ -49,7 +49,7 @@ namespace { { "STATUS_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { @@ -68,7 +68,7 @@ namespace { { "OTHER_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "", { diff --git a/src/driver_waterstarm.cc b/src/driver_waterstarm.cc index 51a5971..5948e14 100644 --- a/src/driver_waterstarm.cc +++ b/src/driver_waterstarm.cc @@ -52,7 +52,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { @@ -114,7 +114,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/driver_watertech.cc b/src/driver_watertech.cc index e5b4039..b1f45d5 100644 --- a/src/driver_watertech.cc +++ b/src/driver_watertech.cc @@ -48,7 +48,7 @@ namespace { { "ERROR_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xffff), "OK", { diff --git a/src/generated_database.cc b/src/generated_database.cc index 1faf253..43a1040 100644 --- a/src/generated_database.cc +++ b/src/generated_database.cc @@ -15,12 +15,14 @@ along with this program. If not, see . */ -// Generated 2024-02-12_01:46 +// Generated 2024-04-04_20:51 BuiltinDriver builtins_[] = { { "elster", "driver{name=elster meter_type=GasMeter default_fields=name,id,total_m3,timestamp detect{mvt=ELS,81,03}use=actuality_duration_s field{name=total quantity=Volume match{measurement_type=Instantaneous vif_range=Volume}about{de='Der Gesamtwasserverbrauch.'en='The total water consumption.'fr='''La consommation totale d'eau.'''sv='Den totala vattenförbrukningen.'}}}", false }, { "iperl", "driver{name=iperl meter_type=WaterMeter default_fields=name,id,total_m3,max_flow_m3h,timestamp detect{mvt=SEN,68,06 mvt=SEN,68,07 mvt=SEN,7c,07}field{name=total quantity=Volume match{measurement_type=Instantaneous vif_range=Volume}about{de='Der Gesamtwasserverbrauch.'en='The total water consumption.'fr='''La consommation totale d'eau.'''sv='Den totala vattenförbrukningen.'}}field{name=max_flow quantity=Flow match{measurement_type=Instantaneous vif_range=VolumeFlow}about{en='The maximum flow recorded during previous period.'}}}", false }, + { "kampress", "driver{name=kampress default_fields=name,id,status,pressure_bar,max_pressure_bar,min_pressure_bar,timestamp meter_type=PressureSensor detect{mvt=KAM,01,18}field{name=status quantity=Text info=status_and_error_flags match{measurement_type=Instantaneous vif_range=ErrorFlags}lookup{name=ERROR_FLAGS map_type=BitToString mask_bits=0xffff default_message=OK map{name=DROP info='Unexpected drop in pressure in relation to average pressure.'value=0x01 test=Set}map{name=SURGE info='Unexpected increase in pressure in relation to average pressure.'value=0x02 test=Set}map{name=HIGH info='Average pressure has reached configurable limit. Default 15 bar.'value=0x04 test=Set}map{name=LOW info='Average pressure has reached configurable limit. Default 1.5 bar.'value=0x08 test=Set}map{name=TRANSIENT info='Pressure changes quickly over short timeperiods. Average is fluctuating.'value=0x10 test=Set}map{name=COMM_ERROR info='Cannot measure properly or bad internal communication.'value=0x20 test=Set}}}field{name=pressure quantity=Pressure info='The measured pressure.'match{measurement_type=Instantaneous vif_range=Pressure}}field{name=max_pressure quantity=Pressure info='The maximum pressure measured during ?'match{measurement_type=Maximum vif_range=Pressure}}field{name=min_pressure quantity=Pressure info='The minimum pressure measured during ?'match{measurement_type=Minimum vif_range=Pressure}}field{name=alfa info='We do not know what this is.'quantity=Dimensionless vif_scaling=None match{difvifkey=05FF09}}field{name=beta info='We do not know what this is.'quantity=Dimensionless vif_scaling=None match{difvifkey=05FF0A}}}", false }, + { "werhlemodwm", "driver{name=werhlemodwm meter_type=WaterMeter default_fields=name,id,total_m3,timestamp detect{mvt=WZG,03,16}use=meter_datetime use=target_date use=target_m3 use=total_m3 use=fabrication_no field{name=next_target quantity=PointInTime display_unit=date match{measurement_type=Instantaneous vif_range=Date add_combinable=FutureValue storage_nr=1}}}", false }, }; MapToDriver builtins_mvts_[] = @@ -29,4 +31,6 @@ MapToDriver builtins_mvts_[] = { { MANUFACTURER_SEN,0x68,0x06 }, "iperl" }, { { MANUFACTURER_SEN,0x68,0x07 }, "iperl" }, { { MANUFACTURER_SEN,0x7c,0x07 }, "iperl" }, + { { MANUFACTURER_KAM,0x01,0x18 }, "kampress" }, + { { MANUFACTURER_WZG,0x03,0x16 }, "werhlemodwm" }, }; diff --git a/src/meters.cc b/src/meters.cc index a48f6bc..d2efca5 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -207,6 +207,7 @@ string loadDriver(const string &file, const char *content) { DriverInfo di; + debug("(meter) loading %s\n", file.c_str()); bool ok = DriverDynamic::load(&di, file, content); if (!ok) { @@ -217,8 +218,15 @@ string loadDriver(const string &file, const char *content) DriverInfo *old = lookupDriver(di.name().str()); if (old != NULL) { + debug("(meter) overriding %s\n", di.name().str().c_str()); if (old->getDynamicFileName() != "") { + if (di.getDynamicFileName() == old->getDynamicFileName()) + { + // Loading same file again, happens when using analyze. This is fine. + return di.name().str(); + } + // New file source registering the same driver name, nono. error("Newly loaded driver file %s tries to register the same name %s as driver file %s has already taken!\n", file.c_str(), di.name().str().c_str(), old->getDynamicFileName().c_str()); } diff --git a/src/testinternals.cc b/src/testinternals.cc index 07089cc..4279ad1 100644 --- a/src/testinternals.cc +++ b/src/testinternals.cc @@ -1383,13 +1383,13 @@ void test_translate() { Translate::Lookup lookup1 = Translate::Lookup() - .add(Translate::Rule("ACCESS_BITS", Translate::Type::BitToString) + .add(Translate::Rule("ACCESS_BITS", Translate::MapType::BitToString) .set(MaskBits(0xf0)) .add(Translate::Map(0x10, "NO_ACCESS", TestBit::Set)) .add(Translate::Map(0x20, "ALL_ACCESS", TestBit::Set)) .add(Translate::Map(0x40, "TEMP_ACCESS", TestBit::Set)) ) - .add(Translate::Rule("ACCESSOR_TYPE", Translate::Type::IndexToString) + .add(Translate::Rule("ACCESSOR_TYPE", Translate::MapType::IndexToString) .set(MaskBits(0x0f)) .add(Translate::Map(0x00, "ACCESSOR_RED", TestBit::Set)) .add(Translate::Map(0x07, "ACCESSOR_GREEN", TestBit::Set)) @@ -1401,7 +1401,7 @@ void test_translate() { { "FLOW_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0x3f), "OOOK", @@ -1420,7 +1420,7 @@ void test_translate() { { "NO_FLAGS", - Translate::Type::BitToString, + Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0x03), "OK", diff --git a/src/translatebits.cc b/src/translatebits.cc index 8a7ec9f..c0aad1e 100644 --- a/src/translatebits.cc +++ b/src/translatebits.cc @@ -19,6 +19,7 @@ #include"util.h" #include +#include using namespace Translate; using namespace std; @@ -210,15 +211,15 @@ void handleRule(Rule& rule, string &s, uint64_t bits) { switch (rule.type) { - case Type::BitToString: + case MapType::BitToString: handleBitToString(rule, s, bits); break; - case Type::IndexToString: + case MapType::IndexToString: handleIndexToString(rule, s, bits); break; - case Type::DecimalsToString: + case MapType::DecimalsToString: handleDecimalsToString(rule, s, bits); break; @@ -259,11 +260,19 @@ string Lookup::str() return x; } +Translate::MapType toMapType(const char *s) +{ + if (!strcmp(s, "BitToString")) return Translate::MapType::BitToString; + if (!strcmp(s, "IndexToString")) return Translate::MapType::IndexToString; + if (!strcmp(s, "DecimalsToString")) return Translate::MapType::DecimalsToString; + return Translate::MapType::Unknown; +} + Lookup NoLookup = {}; Map m = { 123, "howdy" }; vector vm = { { 123, "howdy" } }; -Rule r = { "name", Translate::Type::IndexToString, +Rule r = { "name", Translate::MapType::IndexToString, AlwaysTrigger, MaskBits(0xe000), "", { } }; diff --git a/src/translatebits.h b/src/translatebits.h index 77ddc80..6072411 100644 --- a/src/translatebits.h +++ b/src/translatebits.h @@ -66,8 +66,9 @@ private: namespace Translate { - enum class Type + enum class MapType { + Unknown, BitToString, // A bit translates to a text string. IndexToString, // A masked set of bits (a number) translates to a lookup index with text strings. DecimalsToString // Numbers are successively subtracted from input, each successfull subtraction translate into a text string. @@ -86,16 +87,16 @@ namespace Translate struct Rule { std::string name; - Type type; + MapType type; TriggerBits trigger; // Bits that must be set. MaskBits mask; // Bits to be used are set as 1. DefaultMessage default_message; // If no bits are set print this, typically "OK" or "". std::vector map; Rule() {}; - Rule(std::string n, Type t, TriggerBits tr, MaskBits mb, std::string dm, std::vector m) + Rule(std::string n, MapType t, TriggerBits tr, MaskBits mb, std::string dm, std::vector m) : name(n), type(t), trigger(tr), mask(mb), default_message(dm), map(m) {} - Rule(std::string n, Type t) : + Rule(std::string n, MapType t) : name(n), type(t), trigger(AlwaysTrigger), mask(AutoMask), default_message(DefaultMessage("")) {} Rule &set(TriggerBits t) { trigger = t; return *this; } Rule &set(MaskBits m) { mask = m; return *this; } @@ -116,6 +117,8 @@ namespace Translate }; }; +Translate::MapType toMapType(const char *s); + extern Translate::Lookup NoLookup; #endif diff --git a/src/util.cc b/src/util.cc index 087fa45..5803a8e 100644 --- a/src/util.cc +++ b/src/util.cc @@ -2227,3 +2227,10 @@ const std::string &language() return lang_; } + +TestBit toTestBit(const char *s) +{ + if (!strcmp(s, "Set")) return TestBit::Set; + if (!strcmp(s, "NotSet")) return TestBit::NotSet; + return TestBit::Unknown; +} diff --git a/src/util.h b/src/util.h index 957fcef..ddc1e5b 100644 --- a/src/util.h +++ b/src/util.h @@ -42,6 +42,7 @@ typedef unsigned char uchar; enum class TestBit { + Unknown, Set, NotSet }; @@ -305,6 +306,8 @@ bool is_lowercase_alnum_text(const char *text); // The language that the user expects driver and other messages in. const std::string &language(); +TestBit toTestBit(const char *s); + #ifndef FUZZING #define FUZZING false #endif diff --git a/src/xmq.c b/src/xmq.c index 76bb9e6..c6d33c3 100644 --- a/src/xmq.c +++ b/src/xmq.c @@ -1,4 +1,4 @@ -/* libxmq - Copyright (C) 2023 Fredrik Öhrström (spdx: MIT) +/* libxmq - Copyright (C) 2023-2024 Fredrik Öhrström (spdx: MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -102,7 +102,7 @@ typedef enum XMQColor { extern const char *color_names[13]; /** - XMQColorStrings: + XMQThemeStrings: @pre: string to inserted before the token @post: string to inserted after the token @@ -115,66 +115,82 @@ extern const char *color_names[13]; I.e. pre = "" post = "" I.e. pre = "{\color{red}" post = "}" */ -struct XMQColorStrings +struct XMQThemeStrings { const char *pre; const char *post; }; -typedef struct XMQColorStrings XMQColorStrings; +typedef struct XMQThemeStrings XMQThemeStrings; /** - XMQColoring: + XMQTheme - The coloring struct is used to prefix/postfix ANSI/HTML/TEX strings for + The theme struct is used to prefix/postfix ANSI/HTML/TEX strings for XMQ tokens to colorize the printed xmq output. */ -struct XMQColoring +struct XMQTheme { - XMQColorStrings document; // \documentclass{...}... etc - XMQColorStrings header; // .. - XMQColorStrings style; // Stylesheet content inside header (html) or color(tex) definitions. - XMQColorStrings body; // \begin{document}\end{document} - XMQColorStrings content; // Wrapper around rendered code. Like
, \textt{...}
+    const char *indentation_space;
+    const char *explicit_space;
+    const char *explicit_nl;
+    const char *explicit_tab;
+    const char *explicit_cr;
 
-    XMQColorStrings whitespace; // The normal whitespaces: space=32. Normally not colored.
-    XMQColorStrings tab_whitespace; // The tab, colored with red background.
-    XMQColorStrings unicode_whitespace; // The remaining unicode whitespaces, like: nbsp=160 color as red underline.
-    XMQColorStrings indentation_whitespace; // The xmq generated indentation spaces. Normally not colored.
-    XMQColorStrings equals; // The key = value equal sign.
-    XMQColorStrings brace_left; // Left brace starting a list of childs.
-    XMQColorStrings brace_right; // Right brace ending a list of childs.
-    XMQColorStrings apar_left; // Left parentheses surrounding attributes. foo(x=1)
-    XMQColorStrings apar_right; // Right parentheses surrounding attributes.
-    XMQColorStrings cpar_left; // Left parentheses surrounding a compound value. foo = (
' x '
)
-    XMQColorStrings cpar_right; // Right parentheses surrounding a compound value.
-    XMQColorStrings quote; // A quote 'x y z' can be single or multiline.
-    XMQColorStrings entity; // A entity 

-    XMQColorStrings comment; // A comment // foo or /* foo */
-    XMQColorStrings comment_continuation; // A comment containing newlines /* Hello */* there */
-    XMQColorStrings ns_colon; // The color of the colon separating a namespace from a name.
-    XMQColorStrings element_ns; // The namespace part of an element tag, i.e. the text before colon in foo:alfa.
-    XMQColorStrings element_name; // When an element tag has multiple children or attributes it is rendered using this color.
-    XMQColorStrings element_key; // When an element tag is suitable to be presented as a key value, this color is used.
-    XMQColorStrings element_value_text; // When an element is presented as a key and the value is presented as text, use this color.
-    XMQColorStrings element_value_quote; // When the value is a single quote, use this color.
-    XMQColorStrings element_value_entity; // When the value is a single entity, use this color.
-    XMQColorStrings element_value_compound_quote; // When the value is compounded and this is a quote in the compound.
-    XMQColorStrings element_value_compound_entity; // When the value is compounded and this is an entity in the compound.
-    XMQColorStrings attr_ns; // The namespace part of an attribute name, i.e. the text before colon in bar:speed.
-    XMQColorStrings attr_key; // The color of the attribute name, i.e. the key.
-    XMQColorStrings attr_value_text; // When the attribute value is text, use this color.
-    XMQColorStrings attr_value_quote; // When the attribute value is a quote, use this color.
-    XMQColorStrings attr_value_entity; // When the attribute value is an entity, use this color.
-    XMQColorStrings attr_value_compound_quote; // When the attribute value is a compound and this is a quote in the compound.
-    XMQColorStrings attr_value_compound_entity; // When the attribute value is a compound and this is an entity in the compound.
-    XMQColorStrings ns_declaration; // The xmlns part of an attribute namespace declaration.
+    XMQThemeStrings document; //   \documentclass{...}... etc
+    XMQThemeStrings header; // ..
+    XMQThemeStrings style;  // Stylesheet content inside header (html) or color(tex) definitions.
+    XMQThemeStrings body; //  \begin{document}\end{document}
+    XMQThemeStrings content; // Wrapper around rendered code. Like 
, \textt{...}
+
+    XMQThemeStrings whitespace; // The normal whitespaces: space=32. Normally not colored.
+    XMQThemeStrings tab_whitespace; // The tab, colored with red background.
+    XMQThemeStrings unicode_whitespace; // The remaining unicode whitespaces, like: nbsp=160 color as red underline.
+    XMQThemeStrings indentation_whitespace; // The xmq generated indentation spaces. Normally not colored.
+    XMQThemeStrings equals; // The key = value equal sign.
+    XMQThemeStrings brace_left; // Left brace starting a list of childs.
+    XMQThemeStrings brace_right; // Right brace ending a list of childs.
+    XMQThemeStrings apar_left; // Left parentheses surrounding attributes. foo(x=1)
+    XMQThemeStrings apar_right; // Right parentheses surrounding attributes.
+    XMQThemeStrings cpar_left; // Left parentheses surrounding a compound value. foo = (
' x '
)
+    XMQThemeStrings cpar_right; // Right parentheses surrounding a compound value.
+    XMQThemeStrings quote; // A quote 'x y z' can be single or multiline.
+    XMQThemeStrings entity; // A entity 

+    XMQThemeStrings comment; // A comment // foo or /* foo */
+    XMQThemeStrings comment_continuation; // A comment containing newlines /* Hello */* there */
+    XMQThemeStrings ns_colon; // The color of the colon separating a namespace from a name.
+    XMQThemeStrings element_ns; // The namespace part of an element tag, i.e. the text before colon in foo:alfa.
+    XMQThemeStrings element_name; // When an element tag has multiple children or attributes it is rendered using this color.
+    XMQThemeStrings element_key; // When an element tag is suitable to be presented as a key value, this color is used.
+    XMQThemeStrings element_value_text; // When an element is presented as a key and the value is presented as text, use this color.
+    XMQThemeStrings element_value_quote; // When the value is a single quote, use this color.
+    XMQThemeStrings element_value_entity; // When the value is a single entity, use this color.
+    XMQThemeStrings element_value_compound_quote; // When the value is compounded and this is a quote in the compound.
+    XMQThemeStrings element_value_compound_entity; // When the value is compounded and this is an entity in the compound.
+    XMQThemeStrings attr_ns; // The namespace part of an attribute name, i.e. the text before colon in bar:speed.
+    XMQThemeStrings attr_key; // The color of the attribute name, i.e. the key.
+    XMQThemeStrings attr_value_text; // When the attribute value is text, use this color.
+    XMQThemeStrings attr_value_quote; // When the attribute value is a quote, use this color.
+    XMQThemeStrings attr_value_entity; // When the attribute value is an entity, use this color.
+    XMQThemeStrings attr_value_compound_quote; // When the attribute value is a compound and this is a quote in the compound.
+    XMQThemeStrings attr_value_compound_entity; // When the attribute value is a compound and this is an entity in the compound.
+    XMQThemeStrings ns_declaration; // The xmlns part of an attribute namespace declaration.
 };
-typedef struct XMQColoring XMQColoring;
+typedef struct XMQTheme XMQTheme;
 
-void getColor(XMQOutputSettings *os, XMQColor c, const char **pre, const char **post);
+void getThemeStrings(XMQOutputSettings *os, XMQColor c, const char **pre, const char **post);
 
 #define COLORS_MODULE
 
+// PARTS ENTITIES ////////////////////////////////////////
+
+/**
+    toHtmlEntity:
+    @uc: Unicode code point.
+*/
+const char *toHtmlEntity(int uc);
+
+#define ENTITIES_MODULE
+
 // PARTS UTF8 ////////////////////////////////////////
 
 #ifndef BUILDING_XMQ
@@ -306,6 +322,7 @@ struct XMQOutputSettings
     int  add_indent;
     bool compact;
     bool use_color;
+    bool bg_dark_mode;
     bool escape_newlines;
     bool escape_non_7bit;
 
@@ -313,7 +330,7 @@ struct XMQOutputSettings
     XMQRenderFormat render_to;
     bool render_raw;
     bool only_style;
-    const char *render_style;
+    const char *render_theme;
 
     XMQWriter content;
     XMQWriter error;
@@ -334,8 +351,8 @@ struct XMQOutputSettings
     const char *use_id; // If non-NULL inser this id in the pre tag.
     const char *use_class; // If non-NULL insert this class in the pre tag.
 
-    XMQColoring *default_coloring; // Shortcut to the no namespace coloring inside colorings.
-    HashMap *colorings; // Map namespaces to unique colorings.
+    XMQTheme *default_theme; // Shortcut to the no namespace theme inside themes.
+    HashMap *themes; // Map namespaces to unique colorings.
     void *free_me;
 };
 typedef struct XMQOutputSettings XMQOutputSettings;
@@ -362,6 +379,7 @@ struct XMQParseState
     void *element_last; // Last added sibling to stack top node.
     bool parsing_doctype; // True when parsing a doctype.
     bool parsing_pi; // True when parsing a processing instruction, pi.
+    bool merge_text; // Merge text nodes and character entities.
     const char *pi_name; // Name of the pi node just started.
     XMQOutputSettings *output_settings; // Used when coloring existing text using the tokenizer.
     int magic_cookie; // Used to check that the state has been properly initialized.
@@ -508,9 +526,9 @@ void xmqFreeParseState(XMQParseState *state);
 bool xmqTokenizeBuffer(XMQParseState *state, const char *start, const char *stop);
 bool xmqTokenizeFile(XMQParseState *state, const char *file);
 
-void setup_terminal_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw);
-void setup_html_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw);
-void setup_tex_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw);
+void setup_terminal_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw);
+void setup_html_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw);
+void setup_tex_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw);
 
 // XMQ tokenizer functions ///////////////////////////////////////////////////////////
 
@@ -580,8 +598,8 @@ bool is_comment_node(const xmlNode *node);
 bool is_pi_node(const xmlNode *node);
 bool is_leaf_node(xmlNode *node);
 bool is_key_value_node(xmlNode *node);
-void trim_node(xmlNode *node, XMQTrimType tt);
-void trim_text_node(xmlNode *node, XMQTrimType tt);
+void trim_node(xmlNode *node, int flags);
+void trim_text_node(xmlNode *node, int flags);
 
 // Output buffer functions ////////////////////////////////////////////////////////
 
@@ -750,7 +768,6 @@ typedef enum XMQColor XMQColor;
 size_t print_utf8_char(XMQPrintState *ps, const char *start, const char *stop);
 size_t print_utf8_internal(XMQPrintState *ps, const char *start, const char *stop);
 size_t print_utf8(XMQPrintState *ps, XMQColor c, size_t num_pairs, ...);
-size_t to_utf8(int uc, char buf[4]);
 
 #define UTF8_MODULE
 
@@ -877,16 +894,6 @@ char *potentially_add_leading_ending_space(const char *start, const char *stop);
 
 #define TEXT_MODULE
 
-// PARTS ENTITIES ////////////////////////////////////////
-
-/**
-    toHtmlEntity:
-    @uc: Unicode code point.
-*/
-const char *toHtmlEntity(int uc);
-
-#define ENTITIES_MODULE
-
 // PARTS XML ////////////////////////////////////////
 
 #include
@@ -1145,6 +1152,7 @@ struct XMQOutputSettings
     int  add_indent;
     bool compact;
     bool use_color;
+    bool bg_dark_mode;
     bool escape_newlines;
     bool escape_non_7bit;
 
@@ -1152,7 +1160,7 @@ struct XMQOutputSettings
     XMQRenderFormat render_to;
     bool render_raw;
     bool only_style;
-    const char *render_style;
+    const char *render_theme;
 
     XMQWriter content;
     XMQWriter error;
@@ -1173,8 +1181,8 @@ struct XMQOutputSettings
     const char *use_id; // If non-NULL inser this id in the pre tag.
     const char *use_class; // If non-NULL insert this class in the pre tag.
 
-    XMQColoring *default_coloring; // Shortcut to the no namespace coloring inside colorings.
-    HashMap *colorings; // Map namespaces to unique colorings.
+    XMQTheme *default_theme; // Shortcut to the no namespace theme inside themes.
+    HashMap *themes; // Map namespaces to unique colorings.
     void *free_me;
 };
 typedef struct XMQOutputSettings XMQOutputSettings;
@@ -1201,6 +1209,7 @@ struct XMQParseState
     void *element_last; // Last added sibling to stack top node.
     bool parsing_doctype; // True when parsing a doctype.
     bool parsing_pi; // True when parsing a processing instruction, pi.
+    bool merge_text; // Merge text nodes and character entities.
     const char *pi_name; // Name of the pi node just started.
     XMQOutputSettings *output_settings; // Used when coloring existing text using the tokenizer.
     int magic_cookie; // Used to check that the state has been properly initialized.
@@ -1347,9 +1356,9 @@ void xmqFreeParseState(XMQParseState *state);
 bool xmqTokenizeBuffer(XMQParseState *state, const char *start, const char *stop);
 bool xmqTokenizeFile(XMQParseState *state, const char *file);
 
-void setup_terminal_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw);
-void setup_html_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw);
-void setup_tex_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw);
+void setup_terminal_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw);
+void setup_html_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw);
+void setup_tex_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw);
 
 // XMQ tokenizer functions ///////////////////////////////////////////////////////////
 
@@ -1419,8 +1428,8 @@ bool is_comment_node(const xmlNode *node);
 bool is_pi_node(const xmlNode *node);
 bool is_leaf_node(xmlNode *node);
 bool is_key_value_node(xmlNode *node);
-void trim_node(xmlNode *node, XMQTrimType tt);
-void trim_text_node(xmlNode *node, XMQTrimType tt);
+void trim_node(xmlNode *node, int flags);
+void trim_text_node(xmlNode *node, int flags);
 
 // Output buffer functions ////////////////////////////////////////////////////////
 
@@ -1606,7 +1615,7 @@ char *copy_lines(int num_prefix_spaces, const char *start, const char *stop, int
 void copy_quote_settings_from_output_settings(XMQQuoteSettings *qs, XMQOutputSettings *os);
 xmlNodePtr create_entity(XMQParseState *state, size_t l, size_t c, const char *cstart, const char *cstop, const char*stop, xmlNodePtr parent);
 void create_node(XMQParseState *state, const char *start, const char *stop);
-void update_namespace_href(xmlNsPtr ns, const char *start, const char *stop);
+void update_namespace_href(XMQParseState *state, xmlNsPtr ns, const char *start, const char *stop);
 xmlNodePtr create_quote(XMQParseState *state, size_t l, size_t col, const char *start, const char *stop, const char *suffix,  xmlNodePtr parent);
 void debug_content_comment(XMQParseState *state, size_t line, size_t start_col, const char *start, const char *stop, const char *suffix);
 void debug_content_value(XMQParseState *state, size_t line, size_t start_col, const char *start, const char *stop, const char *suffix);
@@ -1655,7 +1664,7 @@ size_t num_utf8_bytes(char c);
 void print_explicit_spaces(XMQPrintState *ps, XMQColor c, int num);
 void reset_ansi(XMQParseState *state);
 void reset_ansi_nl(XMQParseState *state);
-void setup_htmq_coloring(XMQColoring *c, bool dark_mode, bool use_color, bool render_raw);
+void setup_htmq_coloring(XMQTheme *c, bool dark_mode, bool use_color, bool render_raw);
 const char *skip_any_potential_bom(const char *start, const char *stop);
 void text_print_node(XMQPrintState *ps, xmlNode *node);
 void text_print_nodes(XMQPrintState *ps, xmlNode *from);
@@ -1665,8 +1674,8 @@ void write_safe_html(XMQWrite write, void *writer_state, const char *start, cons
 void write_safe_tex(XMQWrite write, void *writer_state, const char *start, const char *stop);
 bool xmqVerbose();
 void xmqSetupParseCallbacksNoop(XMQParseCallbacks *callbacks);
-bool xmq_parse_buffer_html(XMQDoc *doq, const char *start, const char *stop, XMQTrimType tt);
-bool xmq_parse_buffer_xml(XMQDoc *doq, const char *start, const char *stop, XMQTrimType tt);
+bool xmq_parse_buffer_html(XMQDoc *doq, const char *start, const char *stop, int flags);
+bool xmq_parse_buffer_xml(XMQDoc *doq, const char *start, const char *stop, int flags);
 bool xmq_parse_buffer_text(XMQDoc *doq, const char *start, const char *stop, const char *implicit_root);
 void xmq_print_html(XMQDoc *doq, XMQOutputSettings *output_settings);
 void xmq_print_xml(XMQDoc *doq, XMQOutputSettings *output_settings);
@@ -1680,6 +1689,9 @@ const char *xml_element_type_to_string(xmlElementType type);
 const char *indent_depth(int i);
 void free_indent_depths();
 
+xmlNode *merge_surrounding_text_nodes(xmlNode *node);
+xmlNode *merge_hex_chars_node(xmlNode *node);
+
 // Declare tokenize_whitespace tokenize_name functions etc...
 #define X(TYPE) void tokenize_##TYPE(XMQParseState*state, size_t line, size_t col,const char *start, const char *stop, const char *suffix);
 LIST_OF_XMQ_TOKENS
@@ -1691,25 +1703,43 @@ LIST_OF_XMQ_TOKENS
 #undef X
 
 //////////////////////////////////////////////////////////////////////////////////
+
 char ansi_reset_color[] = "\033[0m";
 
-void xmqSetupDefaultColors(XMQOutputSettings *os, bool dark_mode)
+void xmqSetupDefaultColors(XMQOutputSettings *os)
 {
-    const char *style = os->render_style;
-    if (!style) style = "";
-    XMQColoring *c = (XMQColoring*)hashmap_get(os->colorings, style);
-    if (!c)
+    bool dark_mode = os->bg_dark_mode;
+    XMQTheme *theme = (XMQTheme*)hashmap_get(os->themes, "");
+    if (os->render_theme == NULL)
     {
-        fprintf(stderr, "xmq: No such render style \"%s\"\n", style);
+        os->render_theme = dark_mode?"darkbg":"lightbg";
+    }
+    else
+    {
+        if (!strcmp(os->render_theme, "darkbg"))
+        {
+            dark_mode = true;
+        }
+        else if (!strcmp(os->render_theme, "lightbg"))
+        {
+            dark_mode = false;
+        }
+        else
+        {
+            theme = (XMQTheme*)hashmap_get(os->themes, os->render_theme);
+        }
+    }
+    if (!theme)
+    {
+        fprintf(stderr, "xmq: No such render theme \"%s\"\n", os->render_theme);
         exit(1);
     }
-    assert(c);
-    memset(c, 0, sizeof(XMQColoring));
-    os->indentation_space = " ";
-    os->explicit_space = " ";
-    os->explicit_nl = "\n";
-    os->explicit_tab = "\t";
-    os->explicit_cr = "\r";
+    assert(theme);
+    os->indentation_space = theme->indentation_space; // " ";
+    os->explicit_space = theme->explicit_space; // " ";
+    os->explicit_nl = theme->explicit_nl; // "\n";
+    os->explicit_tab = theme->explicit_tab; // "\t";
+    os->explicit_cr = theme->explicit_cr; // "\r";
 
     if (os->render_to == XMQ_RENDER_PLAIN)
     {
@@ -1717,26 +1747,26 @@ void xmqSetupDefaultColors(XMQOutputSettings *os, bool dark_mode)
     else
     if (os->render_to == XMQ_RENDER_TERMINAL)
     {
-        setup_terminal_coloring(os, c, dark_mode, os->use_color, os->render_raw);
+        setup_terminal_coloring(os, theme, dark_mode, os->use_color, os->render_raw);
     }
     else if (os->render_to == XMQ_RENDER_HTML)
     {
-        setup_html_coloring(os, c, dark_mode, os->use_color, os->render_raw);
+        setup_html_coloring(os, theme, dark_mode, os->use_color, os->render_raw);
     }
     else if (os->render_to == XMQ_RENDER_TEX)
     {
-        setup_tex_coloring(os, c, dark_mode, os->use_color, os->render_raw);
+        setup_tex_coloring(os, theme, dark_mode, os->use_color, os->render_raw);
     }
 
     if (os->only_style)
     {
-        printf("%s\n", c->style.pre);
+        printf("%s\n", theme->style.pre);
         exit(0);
     }
 
 }
 
-void setup_terminal_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw)
+void setup_terminal_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw)
 {
     if (!use_color) return;
     if (dark_mode)
@@ -1809,7 +1839,7 @@ void setup_terminal_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mo
     }
 }
 
-void setup_html_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw)
+void setup_html_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw)
 {
     os->indentation_space = " ";
     os->explicit_nl = "\n";
@@ -1827,7 +1857,7 @@ void setup_html_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode,
         c->style.pre = "@media screen and (orientation: portrait) { pre { font-size: 2vw; } }"
             "@media screen and (orientation: landscape) { pre { max-width: 98%; } }"
             "pre.xmq_dark {white-space:pre-wrap;word-break:break-all;border-radius:2px;background-color:#263338;border:solid 1px #555555;display:inline-block;padding:1em;color:white;}\n"
-            "pre.xmq_light{white-space:pre-wrap;word-break:break-all;border-radius:2px;background-color:#ffffff;border:solid 1px #888888;display:inline-block;padding:1em;color:black;}\n"
+            "pre.xmq_light{white-space:pre-wrap;word-break:break-all;border-radius:2px;background-color:#ffffcc;border:solid 1px #888888;display:inline-block;padding:1em;color:black;}\n"
             "body.xmq_dark {background-color:black;}\n"
             "body.xmq_light {}\n"
             "xmqC{color:#2aa1b3;}\n"
@@ -1955,11 +1985,11 @@ void setup_html_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode,
     c->ns_colon.pre = NULL;
 }
 
-void setup_htmq_coloring(XMQColoring *c, bool dark_mode, bool use_color, bool render_raw)
+void setup_htmq_coloring(XMQTheme *c, bool dark_mode, bool use_color, bool render_raw)
 {
 }
 
-void setup_tex_coloring(XMQOutputSettings *os, XMQColoring *c, bool dark_mode, bool use_color, bool render_raw)
+void setup_tex_coloring(XMQOutputSettings *os, XMQTheme *c, bool dark_mode, bool use_color, bool render_raw)
 {
     os->indentation_space = "\\xmqI ";
     os->explicit_space = " ";
@@ -2081,13 +2111,13 @@ void xmqRenderHtmlSettings(XMQOutputSettings *settings,
 
 void xmqOverrideColor(XMQOutputSettings *os, const char *render_style, XMQSyntax sy, const char *pre, const char *post, const char *ns)
 {
-    if (!os->colorings)
+    if (!os->themes)
     {
         fprintf(stderr, "Internal error: you have to invoke xmqSetupDefaultColors first before overriding.\n");
         exit(1);
     }
     if (!ns) ns = "";
-    XMQColoring *cols = (XMQColoring*)hashmap_get(os->colorings, ns);
+    XMQTheme *cols = (XMQTheme*)hashmap_get(os->themes, ns);
     assert(cols);
 }
 
@@ -2100,7 +2130,7 @@ int xmqStateErrno(XMQParseState *state)
     void tokenize_##TYPE(XMQParseState*state, size_t line, size_t col,const char *start,const char *stop,const char *suffix) { \
         if (!state->simulated) { \
             const char *pre, *post;  \
-            getColor(state->output_settings, COLOR_##TYPE, &pre, &post); \
+            getThemeStrings(state->output_settings, COLOR_##TYPE, &pre, &post); \
             if (pre) state->output_settings->content.write(state->output_settings->content.writer_state, pre, NULL); \
             if (state->output_settings->render_to == XMQ_RENDER_TERMINAL) { \
                 state->output_settings->content.write(state->output_settings->content.writer_state, start, stop); \
@@ -2142,24 +2172,33 @@ void add_nl(XMQParseState *state)
     state->output_settings->content.write(state->output_settings->content.writer_state, "\n", NULL);
 }
 
+XMQTheme *prepareSolarizedTheme(XMQTheme *parent);
+
+XMQTheme *prepareSolarizedTheme(XMQTheme *parent)
+{
+    return NULL;
+}
+
 XMQOutputSettings *xmqNewOutputSettings()
 {
     XMQOutputSettings *os = (XMQOutputSettings*)malloc(sizeof(XMQOutputSettings));
     memset(os, 0, sizeof(XMQOutputSettings));
-    os->colorings = hashmap_create(11);
-    XMQColoring *c = (XMQColoring*)malloc(sizeof(XMQColoring));
-    memset(c, 0, sizeof(XMQColoring));
-    hashmap_put(os->colorings, "", c);
-    os->default_coloring = c;
+    os->themes = hashmap_create(11);
+    XMQTheme *theme = (XMQTheme*)malloc(sizeof(XMQTheme));
+    memset(theme, 0, sizeof(XMQTheme));
+    hashmap_put(os->themes, "", theme);
+    os->default_theme = theme;
 
-    os->indentation_space = " ";
-    os->explicit_space = " ";
-    os->explicit_nl = "\n";
-    os->explicit_tab = "\t";
-    os->explicit_cr = "\r";
+    os->indentation_space = theme->indentation_space = " ";
+    os->explicit_space = theme->explicit_space = " ";
+    os->explicit_nl = theme->explicit_nl = "\n";
+    os->explicit_tab = theme->explicit_tab = "\t";
+    os->explicit_cr = theme->explicit_cr = "\r";
     os->add_indent = 4;
     os->use_color = false;
 
+    //hashmap_put(os->themes, "solarized", prepareSolarizedTheme(theme));
+
     return os;
 }
 
@@ -2170,8 +2209,8 @@ void xmqFreeOutputSettings(XMQOutputSettings *os)
         free(os->free_me);
         os->free_me = NULL;
     }
-    hashmap_free_and_values(os->colorings);
-    os->colorings = NULL;
+    hashmap_free_and_values(os->themes);
+    os->themes = NULL;
     free(os);
 }
 
@@ -2190,6 +2229,11 @@ void xmqSetUseColor(XMQOutputSettings *os, bool use_color)
     os->use_color = use_color;
 }
 
+void xmqSetBackgroundMode(XMQOutputSettings *os, bool bg_dark_mode)
+{
+    os->bg_dark_mode = bg_dark_mode;
+}
+
 void xmqSetEscapeNewlines(XMQOutputSettings *os, bool escape_newlines)
 {
     os->escape_newlines = escape_newlines;
@@ -2205,11 +2249,6 @@ void xmqSetOutputFormat(XMQOutputSettings *os, XMQContentType output_format)
     os->output_format = output_format;
 }
 
-/*void xmqSetColoring(XMQOutputSettings *os, XMQColoring coloring)
-{
-    os->coloring = coloring;
-    }*/
-
 void xmqSetRenderFormat(XMQOutputSettings *os, XMQRenderFormat render_to)
 {
     os->render_to = render_to;
@@ -2220,16 +2259,16 @@ void xmqSetRenderRaw(XMQOutputSettings *os, bool render_raw)
     os->render_raw = render_raw;
 }
 
+void xmqSetRenderTheme(XMQOutputSettings *os, const char *theme_name)
+{
+    os->render_theme = theme_name;
+}
+
 void xmqSetRenderOnlyStyle(XMQOutputSettings *os, bool only_style)
 {
     os->only_style = only_style;
 }
 
-void xmqSetRenderStyle(XMQOutputSettings *os, const char *render_style)
-{
-    os->render_style = render_style;
-}
-
 void xmqSetWriterContent(XMQOutputSettings *os, XMQWriter content)
 {
     os->content = content;
@@ -2385,8 +2424,8 @@ bool xmqTokenizeBuffer(XMQParseState *state, const char *start, const char *stop
     XMQWrite write = output_settings->content.write;
     void *writer_state = output_settings->content.writer_state;
 
-    const char *pre = output_settings->default_coloring->content.pre;
-    const char *post = output_settings->default_coloring->content.post;
+    const char *pre = output_settings->default_theme->content.pre;
+    const char *post = output_settings->default_theme->content.post;
     if (pre) write(writer_state, pre, NULL);
 
     if (!setjmp(state->error_handler))
@@ -2732,7 +2771,6 @@ char *xmq_un_comment(size_t indent, char space, const char *start, const char *s
     assert(start < stop);
     assert(*start == '/');
 
-    // PRUTT printf("indent=%d text>%.*s<\n", (int)indent, (int)(stop-start), start);
     const char *i = start;
     while (i < stop && *i == '/') i++;
 
@@ -2787,7 +2825,6 @@ char *xmq_un_comment(size_t indent, char space, const char *start, const char *s
 
     assert(start <= stop);
     char *foo = xmq_trim_quote(indent, space, start, stop);
-    // PRUTT printf("uncommented text>%s<\n", foo);
     return foo;
 }
 
@@ -2970,9 +3007,6 @@ char *xmq_trim_quote(size_t indent, char space, const char *start, const char *s
     return buf;
 }
 
-
-
-
 void xmqSetupParseCallbacksNoop(XMQParseCallbacks *callbacks)
 {
     memset(callbacks, 0, sizeof(*callbacks));
@@ -3079,7 +3113,7 @@ void xmqSetupParseCallbacksDebugContent(XMQParseCallbacks *callbacks)
     callbacks->magic_cookie = MAGIC_COOKIE;
 }
 
-void xmqSetupParseCallbacksColorizeTokens(XMQParseCallbacks *callbacks, XMQRenderFormat render_format, bool dark_mode)
+void xmqSetupParseCallbacksColorizeTokens(XMQParseCallbacks *callbacks, XMQRenderFormat render_format)
 {
     memset(callbacks, 0, sizeof(*callbacks));
 
@@ -3211,7 +3245,7 @@ const char *skip_any_potential_bom(const char *start, const char *stop)
     return start;
 }
 
-bool xmqParseBuffer(XMQDoc *doq, const char *start, const char *stop, const char *implicit_root)
+bool xmqParseBuffer(XMQDoc *doq, const char *start, const char *stop, const char *implicit_root, int flags)
 {
     bool rc = true;
     XMQOutputSettings *output_settings = xmqNewOutputSettings();
@@ -3220,6 +3254,7 @@ bool xmqParseBuffer(XMQDoc *doq, const char *start, const char *stop, const char
     xmq_setup_parse_callbacks(parse);
 
     XMQParseState *state = xmqNewParseState(parse, output_settings);
+    state->merge_text = !(flags & XMQ_FLAG_NOMERGE);
     state->doq = doq;
     xmqSetStateSourceName(state, doq->source_name_);
 
@@ -3249,7 +3284,7 @@ bool xmqParseBuffer(XMQDoc *doq, const char *start, const char *stop, const char
     return rc;
 }
 
-bool xmqParseFile(XMQDoc *doq, const char *file, const char *implicit_root)
+bool xmqParseFile(XMQDoc *doq, const char *file, const char *implicit_root, int flags)
 {
     bool ok = true;
     char *buffer = NULL;
@@ -3311,7 +3346,7 @@ bool xmqParseFile(XMQDoc *doq, const char *file, const char *implicit_root)
         goto exit;
     }
 
-    ok = xmqParseBuffer(doq, buffer, buffer+fsize, implicit_root);
+    ok = xmqParseBuffer(doq, buffer, buffer+fsize, implicit_root, flags);
 
     exit:
 
@@ -3345,7 +3380,29 @@ xmlNodePtr create_quote(XMQParseState *state,
     size_t indent = col - 1;
     char *trimmed = xmq_un_quote(indent, ' ', start, stop, true);
     xmlNodePtr n = xmlNewDocText(state->doq->docptr_.xml, (const xmlChar *)trimmed);
-    xmlAddChild(parent, n);
+    if (state->merge_text)
+    {
+        n = xmlAddChild(parent, n);
+    }
+    else
+    {
+        // I want to prevent merging of this new text node with previous text nodes....
+        // Alas there is no such setting in libxml2 so I perform the addition explicit here.
+        // Check the source for xmlAddChild.
+        n->parent = parent;
+        if (parent->children == NULL)
+        {
+            parent->children = n;
+            parent->last = n;
+        }
+        else
+        {
+            xmlNodePtr prev = parent->last;
+	    prev->next = n;
+            n->prev = prev;
+            parent->last = n;
+        }
+    }
     free(trimmed);
     return n;
 }
@@ -3373,13 +3430,32 @@ xmlNodePtr create_entity(XMQParseState *state,
     xmlNodePtr n = NULL;
     if (tmp[1] == '#')
     {
-        n = xmlNewCharRef(state->doq->docptr_.xml, (const xmlChar *)tmp);
+        // Character entity.
+        if (!state->merge_text)
+        {
+            // Do not merge with surrounding text.
+            n = xmlNewCharRef(state->doq->docptr_.xml, (const xmlChar *)tmp);
+        }
+        else
+        {
+            // Make inte text that will be merged.
+            UTF8Char uni;
+            int uc = 0;
+            if (tmp[2] == 'x') uc = strtol(tmp+3, NULL, 16);
+            else uc = strtol(tmp+2, NULL, 10);
+            size_t len = encode_utf8(uc, &uni);
+            char buf[len+1];
+            memcpy(buf, uni.bytes, len);
+            buf[len] = 0;
+            n = xmlNewDocText(state->doq->docptr_.xml, (xmlChar*)buf);
+        }
     }
     else
     {
+        // Named references are kept as is.
         n = xmlNewReference(state->doq->docptr_.xml, (const xmlChar *)tmp);
     }
-    xmlAddChild(parent, n);
+    n = xmlAddChild(parent, n);
     free(tmp);
 
     return n;
@@ -3599,6 +3675,7 @@ void do_ns_declaration(XMQParseState *state,
         ns = xmlNewNs(element,
                       NULL,
                       NULL);
+        debug("[XMQ] create default namespace in element %s\n", element->name);
         if (!ns)
         {
             // Oups, this element already has a default namespace.
@@ -3615,6 +3692,11 @@ void do_ns_declaration(XMQParseState *state,
             }
             free(list);
         }
+        if (element->ns == NULL)
+        {
+            debug("[XMQ] set default namespace in element %s prefix=%s href=%s\n", element->name, ns->prefix, ns->href);
+            xmlSetNs(element, ns);
+        }
         state->default_namespace = ns;
     }
     else
@@ -3702,13 +3784,25 @@ void do_attr_key(XMQParseState *state,
     free(key);
 }
 
-void update_namespace_href(xmlNsPtr ns,
+void update_namespace_href(XMQParseState *state,
+                           xmlNsPtr ns,
                            const char *start,
                            const char *stop)
 {
     if (!stop) stop = start+strlen(start);
+
     char *href = strndup(start, stop-start);
     ns->href = (const xmlChar*)href;
+    debug("[XMQ] update namespace prefix=%s with href=%s\n", ns->prefix, href);
+
+    if (start[0] == 0 && ns == state->default_namespace)
+    {
+        xmlNodePtr element = (xmlNode*)state->element_stack->top->data;
+        debug("[XMQ] remove default namespace in element %s\n", element->name);
+        xmlSetNs(element, NULL);
+        state->default_namespace = NULL;
+        return;
+    }
 }
 
 void do_attr_value_text(XMQParseState *state,
@@ -3722,7 +3816,7 @@ void do_attr_value_text(XMQParseState *state,
     {
         assert(state->declaring_xmlns_namespace);
 
-        update_namespace_href((xmlNsPtr)state->declaring_xmlns_namespace, start, stop);
+        update_namespace_href(state, (xmlNsPtr)state->declaring_xmlns_namespace, start, stop);
         state->declaring_xmlns = false;
         state->declaring_xmlns_namespace = NULL;
         return;
@@ -3741,7 +3835,7 @@ void do_attr_value_quote(XMQParseState*state,
     if (state->declaring_xmlns)
     {
         char *trimmed = xmq_un_quote(col, ' ', start, stop, true);
-        update_namespace_href((xmlNsPtr)state->declaring_xmlns_namespace, trimmed, NULL);
+        update_namespace_href(state, (xmlNsPtr)state->declaring_xmlns_namespace, trimmed, NULL);
         state->declaring_xmlns = false;
         state->declaring_xmlns_namespace = NULL;
         free(trimmed);
@@ -3822,6 +3916,7 @@ void create_node(XMQParseState *state, const char *start, const char *stop)
 
         if (state->element_namespace)
         {
+            // Have a namespace before the element name, eg abc:work
             xmlNsPtr ns = xmlSearchNs(state->doq->docptr_.xml,
                                       new_node,
                                       (const xmlChar *)state->element_namespace);
@@ -3832,11 +3927,21 @@ void create_node(XMQParseState *state, const char *start, const char *stop)
                 ns = xmlNewNs(new_node,
                               NULL,
                               (const xmlChar *)state->element_namespace);
+                debug("[XMQ] created namespace prefix=%s in element %s\n", state->element_namespace, name);
             }
+            debug("[XMQ] setting namespace prefix=%s for element %s\n", state->element_namespace, name);
             xmlSetNs(new_node, ns);
             free(state->element_namespace);
             state->element_namespace = NULL;
         }
+        else if (state->default_namespace)
+        {
+            // We have a default namespace.
+            xmlNsPtr ns = (xmlNsPtr)state->default_namespace;
+            assert(ns->prefix == NULL);
+            debug("[XMQ] set default namespace with href=%s for element %s\n", ns->href, name);
+            xmlSetNs(new_node, ns);
+        }
 
         state->element_last = new_node;
     }
@@ -4101,7 +4206,7 @@ void xmq_print_xmq(XMQDoc *doq, XMQOutputSettings *os)
 
     XMQWrite write = os->content.write;
     void *writer_state = os->content.writer_state;
-    XMQColoring *c = os->default_coloring;
+    XMQTheme *c = os->default_theme;
 
     if (c->document.pre) write(writer_state, c->document.pre, NULL);
     if (c->header.pre) write(writer_state, c->header.pre, NULL);
@@ -4153,8 +4258,11 @@ void xmqPrint(XMQDoc *doq, XMQOutputSettings *output_settings)
     }
 }
 
-void trim_text_node(xmlNode *node, XMQTrimType tt)
+void trim_text_node(xmlNode *node, int flags)
 {
+    // If node has whitespace preserve set, then do not trim.
+    // if (xmlNodeGetSpacePreserve (node)) return;
+
     const char *content = xml_element_content(node);
     // We remove any all whitespace node.
     // This ought to have been removed with XML_NOBLANKS alas that does not always happen.
@@ -4189,19 +4297,19 @@ void trim_text_node(xmlNode *node, XMQTrimType tt)
     free(trimmed);
 }
 
-void trim_node(xmlNode *node, XMQTrimType tt)
+void trim_node(xmlNode *node, int flags)
 {
     debug("[XMQ] trim %s\n", xml_element_type_to_string(node->type));
 
     if (is_content_node(node))
     {
-        trim_text_node(node, tt);
+        trim_text_node(node, flags);
         return;
     }
 
     if (is_comment_node(node))
     {
-        trim_text_node(node, tt);
+        trim_text_node(node, flags);
         return;
     }
 
@@ -4212,12 +4320,12 @@ void trim_node(xmlNode *node, XMQTrimType tt)
     while (i)
     {
         xmlNode *next = xml_next_sibling(i); // i might be freed in trim.
-        trim_node(i, tt);
+        trim_node(i, flags);
         i = next;
     }
 }
 
-void xmqTrimWhitespace(XMQDoc *doq, XMQTrimType tt)
+void xmqTrimWhitespace(XMQDoc *doq, int flags)
 {
     xmlNodePtr i = doq->docptr_.xml->children;
     if (!doq || !i) return;
@@ -4225,11 +4333,79 @@ void xmqTrimWhitespace(XMQDoc *doq, XMQTrimType tt)
     while (i)
     {
         xmlNode *next = xml_next_sibling(i); // i might be freed in trim.
-        trim_node(i, tt);
+        trim_node(i, flags);
         i = next;
     }
 }
 
+/*
+xmlNode *merge_surrounding_text_nodes(xmlNode *node)
+{
+    const char *val = (const char *)node->name;
+    // Not a hex entity.
+    if (val[0] != '#' || val[1] != 'x') return node->next;
+
+    debug("[XMQ] merge hex %s chars %s\n", val, xml_element_type_to_string(node->type));
+
+    UTF8Char uni;
+    int uc = strtol(val+2, NULL, 16);
+    size_t len = encode_utf8(uc, &uni);
+    char buf[len+1];
+    memcpy(buf, uni.bytes, len);
+    buf[len] = 0;
+
+    xmlNodePtr prev = node->prev;
+    xmlNodePtr next = node->next;
+    if (prev && prev->type == XML_TEXT_NODE)
+    {
+        xmlNodeAddContentLen(prev, (xmlChar*)buf, len);
+        xmlUnlinkNode(node);
+        xmlFreeNode(node);
+        debug("[XMQ] merge left\n");
+    }
+    if (next && next->type == XML_TEXT_NODE)
+    {
+        xmlNodeAddContent(prev, next->content);
+        xmlNode *n = next->next;
+        xmlUnlinkNode(next);
+        xmlFreeNode(next);
+        next = n;
+        debug("[XMQ] merge right\n");
+    }
+
+    return next;
+}
+
+xmlNode *merge_hex_chars_node(xmlNode *node)
+{
+    if (node->type == XML_ENTITY_REF_NODE)
+    {
+        return merge_surrounding_text_nodes(node);
+    }
+
+    // Do not recurse into these
+    if (node->type == XML_ENTITY_DECL) return node->next;
+
+    xmlNodePtr i = xml_first_child(node);
+    while (i)
+    {
+        i = merge_hex_chars_node(i);
+    }
+    return node->next;
+}
+
+void xmqMergeHexCharEntities(XMQDoc *doq)
+{
+    xmlNodePtr i = doq->docptr_.xml->children;
+    if (!doq || !i) return;
+
+    while (i)
+    {
+        i = merge_hex_chars_node(i);
+    }
+}
+*/
+
 char *escape_xml_comment(const char *comment)
 {
     // The escape char is ␐ which is utf8 0xe2 0x90 0x90
@@ -4964,17 +5140,20 @@ double xmqGetDouble(XMQDoc *doq, XMQNode *node, const char *xpath)
     return atof(content);
 }
 
-bool xmq_parse_buffer_xml(XMQDoc *doq, const char *start, const char *stop, XMQTrimType tt)
+bool xmq_parse_buffer_xml(XMQDoc *doq, const char *start, const char *stop, int flags)
 {
-    xmlDocPtr doc;
-
     /* Macro to check API for match with the DLL we are using */
     LIBXML_TEST_VERSION ;
 
     int parse_options = XML_PARSE_NOCDATA | XML_PARSE_NONET;
-    if (tt != XMQ_TRIM_NONE) parse_options |= XML_PARSE_NOBLANKS;
+    bool should_trim = false;
+    if (flags & XMQ_FLAG_TRIM_HEURISTIC ||
+        flags & XMQ_FLAG_TRIM_EXACT) should_trim = true;
+    if (flags & XMQ_FLAG_TRIM_NONE) should_trim = false;
 
-    doc = xmlReadMemory(start, stop-start, doq->source_name_, NULL, parse_options);
+    if (should_trim) parse_options |= XML_PARSE_NOBLANKS;
+
+    xmlDocPtr doc = xmlReadMemory(start, stop-start, doq->source_name_, NULL, parse_options);
     if (doc == NULL)
     {
         doq->errno_ = XMQ_ERROR_PARSING_XML;
@@ -4987,15 +5166,15 @@ bool xmq_parse_buffer_xml(XMQDoc *doq, const char *start, const char *stop, XMQT
     {
         xmlFreeDoc(doq->docptr_.xml);
     }
+
     doq->docptr_.xml = doc;
-    xmlCleanupParser();
 
     xmq_fixup_comments_after_readin(doq);
 
     return true;
 }
 
-bool xmq_parse_buffer_html(XMQDoc *doq, const char *start, const char *stop, XMQTrimType tt)
+bool xmq_parse_buffer_html(XMQDoc *doq, const char *start, const char *stop, int flags)
 {
     htmlDocPtr doc;
     xmlNode *roo_element = NULL;
@@ -5004,7 +5183,13 @@ bool xmq_parse_buffer_html(XMQDoc *doq, const char *start, const char *stop, XMQ
     LIBXML_TEST_VERSION
 
     int parse_options = HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_NONET;
-    if (tt != XMQ_TRIM_NONE) parse_options |= HTML_PARSE_NOBLANKS;
+
+    bool should_trim = false;
+    if (flags & XMQ_FLAG_TRIM_HEURISTIC ||
+        flags & XMQ_FLAG_TRIM_EXACT) should_trim = true;
+    if (flags & XMQ_FLAG_TRIM_NONE) should_trim = false;
+
+    if (should_trim) parse_options |= HTML_PARSE_NOBLANKS;
 
     doc = htmlReadMemory(start, stop-start, "foof", NULL, parse_options);
 
@@ -5022,7 +5207,6 @@ bool xmq_parse_buffer_html(XMQDoc *doq, const char *start, const char *stop, XMQ
     {
         PRINT_ERROR("empty document\n");
         xmlFreeDoc(doc);
-        xmlCleanupParser();
         return 0;
     }
 
@@ -5031,7 +5215,6 @@ bool xmq_parse_buffer_html(XMQDoc *doq, const char *start, const char *stop, XMQ
         xmlFreeDoc(doq->docptr_.html);
     }
     doq->docptr_.html = doc;
-    xmlCleanupParser();
 
     xmq_fixup_comments_after_readin(doq);
 
@@ -5065,7 +5248,7 @@ bool xmqParseBufferWithType(XMQDoc *doq,
                             const char *stop,
                             const char *implicit_root,
                             XMQContentType ct,
-                            XMQTrimType tt)
+                            int flags)
 {
     bool ok = true;
 
@@ -5108,10 +5291,10 @@ bool xmqParseBufferWithType(XMQDoc *doq,
 
     switch (ct)
     {
-    case XMQ_CONTENT_XMQ: ok = xmqParseBuffer(doq, start, stop, implicit_root); break;
-    case XMQ_CONTENT_HTMQ: ok = xmqParseBuffer(doq, start, stop, implicit_root); break;
-    case XMQ_CONTENT_XML: ok = xmq_parse_buffer_xml(doq, start, stop, tt); break;
-    case XMQ_CONTENT_HTML: ok = xmq_parse_buffer_html(doq, start, stop, tt); break;
+    case XMQ_CONTENT_XMQ: ok = xmqParseBuffer(doq, start, stop, implicit_root, flags); break;
+    case XMQ_CONTENT_HTMQ: ok = xmqParseBuffer(doq, start, stop, implicit_root, flags); break;
+    case XMQ_CONTENT_XML: ok = xmq_parse_buffer_xml(doq, start, stop, flags); break;
+    case XMQ_CONTENT_HTML: ok = xmq_parse_buffer_html(doq, start, stop, flags); break;
     case XMQ_CONTENT_JSON: ok = xmq_parse_buffer_json(doq, start, stop, implicit_root); break;
     case XMQ_CONTENT_TEXT: ok = xmq_parse_buffer_text(doq, start, stop, implicit_root); break;
     default: break;
@@ -5121,13 +5304,19 @@ exit:
 
     if (ok)
     {
-        if (tt == XMQ_TRIM_HEURISTIC ||
-            (tt == XMQ_TRIM_DEFAULT && (
-                ct == XMQ_CONTENT_XML ||
-                ct == XMQ_CONTENT_HTML)))
+        bool should_trim = false;
+
+        if (flags & XMQ_FLAG_TRIM_HEURISTIC ||
+            flags & XMQ_FLAG_TRIM_EXACT) should_trim = true;
+
+        if (!(flags & XMQ_FLAG_TRIM_NONE) &&
+            (ct == XMQ_CONTENT_XML ||
+             ct == XMQ_CONTENT_HTML))
         {
-            xmqTrimWhitespace(doq, tt);
+            should_trim = true;
         }
+
+        if (should_trim) xmqTrimWhitespace(doq, flags);
     }
 
     return ok;
@@ -5229,7 +5418,7 @@ bool xmqParseFileWithType(XMQDoc *doq,
                           const char *file,
                           const char *implicit_root,
                           XMQContentType ct,
-                          XMQTrimType tt)
+                          int flags)
 {
     bool rc = true;
     size_t fsize;
@@ -5247,7 +5436,7 @@ bool xmqParseFileWithType(XMQDoc *doq,
     }
     if (!rc) return false;
 
-    rc = xmqParseBufferWithType(doq, buffer, buffer+fsize, implicit_root, ct, tt);
+    rc = xmqParseBufferWithType(doq, buffer, buffer+fsize, implicit_root, ct, flags);
 
     free((void*)buffer);
 
@@ -5400,18 +5589,18 @@ char *strndup(const char *s, size_t l)
    pre: Store a pointer to the start color string here.
    post: Store a pointer to the end color string here.
 */
-void getColor(XMQOutputSettings *os, XMQColor color, const char **pre, const char **post)
+void getThemeStrings(XMQOutputSettings *os, XMQColor color, const char **pre, const char **post)
 {
-    XMQColoring *coloring = os->default_coloring;
+    XMQTheme *theme = os->default_theme;
     switch(color)
     {
 
-#define X(TYPE) case COLOR_##TYPE: *pre = coloring->TYPE.pre; *post = coloring->TYPE.post; return;
+#define X(TYPE) case COLOR_##TYPE: *pre = theme->TYPE.pre; *post = theme->TYPE.post; return;
 LIST_OF_XMQ_TOKENS
 #undef X
 
-    case COLOR_unicode_whitespace: *pre = coloring->unicode_whitespace.pre; *post = coloring->unicode_whitespace.post; return;
-    case COLOR_indentation_whitespace: *pre = coloring->indentation_whitespace.pre; *post = coloring->indentation_whitespace.post; return;
+    case COLOR_unicode_whitespace: *pre = theme->unicode_whitespace.pre; *post = theme->unicode_whitespace.post; return;
+    case COLOR_indentation_whitespace: *pre = theme->indentation_whitespace.pre; *post = theme->indentation_whitespace.post; return;
     default:
         *pre = NULL;
         *post = NULL;
@@ -6757,10 +6946,11 @@ void json_print_array_with_children(XMQPrintState *ps,
                                     xmlNode *container,
                                     xmlNode *node)
 {
+    json_check_comma(ps);
+
     if (container)
     {
         // We have a containing node, then we can print this using "name" : [ ... ]
-        json_check_comma(ps);
         json_print_element_name(ps, container, node, 1, 0);
         print_utf8(ps, COLOR_none, 1, ":", NULL);
     }
@@ -7080,6 +7270,8 @@ void fixup_json(XMQDoc *doq, xmlNode *node)
                 xmlFreeNode(i);
                 i = next;
             }
+            assert(node);
+            assert(new_child);
             xmlAddChild(node, new_child);
             free(new_content);
             return;
@@ -7785,7 +7977,7 @@ size_t print_utf8(XMQPrintState *ps, XMQColor color, size_t num_pairs, ...)
     void *writer_state = os->content.writer_state;
 
     const char *pre, *post;
-    getColor(os, color, &pre, &post);
+    getThemeStrings(os, color, &pre, &post);
     const char *previous_color = NULL;
 
     if (pre)
@@ -7880,11 +8072,14 @@ bool xml_non_empty_namespace(xmlNs *ns)
 bool xml_has_non_empty_namespace_defs(xmlNode *node)
 {
     xmlNs *ns = node->nsDef;
+    if (ns) return true;
+    /*!= NULL)
     while (ns)
     {
-        if (xml_non_empty_namespace(ns)) return true;
+        //if (xml_non_empty_namespace(ns)) return true;
         ns = xml_next_namespace_def(ns);
     }
+    */
     return false;
 }
 
@@ -8418,7 +8613,7 @@ void print_color_pre(XMQPrintState *ps, XMQColor color)
     XMQOutputSettings *os = ps->output_settings;
     const char *pre = NULL;
     const char *post = NULL;
-    getColor(os, color, &pre, &post);
+    getThemeStrings(os, color, &pre, &post);
 
     if (pre)
     {
@@ -8433,7 +8628,7 @@ void print_color_post(XMQPrintState *ps, XMQColor color)
     XMQOutputSettings *os = ps->output_settings;
     const char *pre = NULL;
     const char *post = NULL;
-    getColor(os, color, &pre, &post);
+    getThemeStrings(os, color, &pre, &post);
 
     XMQWrite write = os->content.write;
     void *writer_state = os->content.writer_state;
@@ -8467,7 +8662,7 @@ const char *xmqParseErrorToString(XMQParseError e)
     case XMQ_ERROR_QUOTE_CLOSED_WITH_TOO_MANY_QUOTES: return "quote closed with too many quotes";
     case XMQ_ERROR_UNEXPECTED_CLOSING_BRACE: return "unexpected closing brace";
     case XMQ_ERROR_EXPECTED_CONTENT_AFTER_EQUALS: return "expected content after equals";
-    case XMQ_ERROR_UNEXPECTED_TAB: return "unexpected tab character (remember tabs must be quoted)";
+    case XMQ_ERROR_UNEXPECTED_TAB: return "invalid tab character found, remember that tab is not allowed as a field separator, to store tab as content it must be quoted";
     case XMQ_ERROR_INVALID_CHAR: return "unexpected character";
     case XMQ_ERROR_BAD_DOCTYPE: return "doctype could not be parsed";
     case XMQ_ERROR_CANNOT_HANDLE_XML: return "cannot handle xml use libxmq-all for this!";
@@ -10237,7 +10432,7 @@ void print_node(XMQPrintState *ps, xmlNode *node, size_t align)
 void print_white_spaces(XMQPrintState *ps, int num)
 {
     XMQOutputSettings *os = ps->output_settings;
-    XMQColoring *c = os->default_coloring;
+    XMQTheme *c = os->default_theme;
     XMQWrite write = os->content.write;
     void *writer_state = os->content.writer_state;
     if (c->whitespace.pre) write(writer_state, c->whitespace.pre, NULL);
@@ -10280,7 +10475,7 @@ void print_explicit_spaces(XMQPrintState *ps, XMQColor c, int num)
 
     const char *pre = NULL;
     const char *post = NULL;
-    getColor(os, c, &pre, &post);
+    getThemeStrings(os, c, &pre, &post);
 
     write(writer_state, pre, NULL);
     for (int i=0; ioutput_settings;
-    XMQColoring *c = os->default_coloring;
+    XMQTheme *c = os->default_theme;
     XMQWrite write = os->content.write;
     void *writer_state = os->content.writer_state;
 
@@ -10318,7 +10513,7 @@ void print_quotes(XMQPrintState *ps, size_t num, XMQColor color)
 
     const char *pre = NULL;
     const char *post = NULL;
-    getColor(os, color, &pre, &post);
+    getThemeStrings(os, color, &pre, &post);
 
     if (pre) write(writer_state, pre, NULL);
     for (size_t i=0; icontent.write;
     void *writer_state = os->content.writer_state;
     const char *pre, *post;
-    getColor(os, color, &pre, &post);
+    getThemeStrings(os, color, &pre, &post);
 
     int uc = 0;
     size_t bytes = 0;
@@ -10415,7 +10610,7 @@ void print_slashes(XMQPrintState *ps, const char *pre, const char *post, size_t
     void *writer_state = os->content.writer_state;
     const char *cpre = NULL;
     const char *cpost = NULL;
-    getColor(os, COLOR_comment, &cpre, &cpost);
+    getThemeStrings(os, COLOR_comment, &cpre, &cpost);
 
     if (cpre) write(writer_state, cpre, NULL);
     if (pre) write(writer_state, pre, NULL);
@@ -10623,7 +10818,7 @@ void print_attribute(XMQPrintState *ps, xmlAttr *a, size_t align)
 
 void print_namespace_declaration(XMQPrintState *ps, xmlNs *ns, size_t align)
 {
-    if (!xml_non_empty_namespace(ns)) return;
+    //if (!xml_non_empty_namespace(ns)) return;
 
     check_space_before_attribute(ps);
 
@@ -10650,7 +10845,7 @@ void print_namespace_declaration(XMQPrintState *ps, xmlNs *ns, size_t align)
 
         if (!ps->output_settings->compact) print_white_spaces(ps, 1);
 
-        print_utf8(ps, COLOR_attr_value_text, 1, v, NULL);
+        print_value_internal_text(ps, v, NULL, LEVEL_ATTR_VALUE);
     }
 }
 
@@ -10713,7 +10908,7 @@ void print_quote_lines_and_color_uwhitespace(XMQPrintState *ps,
     void *writer_state = os->content.writer_state;
 
     const char *pre, *post;
-    getColor(os, color, &pre, &post);
+    getThemeStrings(os, color, &pre, &post);
 
     if (pre) write(writer_state, pre, NULL);
 
@@ -10883,7 +11078,16 @@ const char *find_next_char_that_needs_escape(XMQPrintState *ps, const char *star
         i++;
     }
 
-    return i;
+    // Now move backwards, perhaps there was newlines before this problematic character...
+    // Then we have to escape those as well since they are ending the previous quote.
+    const char *j = i-1;
+    while (j > start)
+    {
+        int c = (int)((unsigned char)*j);
+        if (c != '\n') break;
+        j--;
+    }
+    return j+1;
 }
 
 void print_value_internal_text(XMQPrintState *ps, const char *start, const char *stop, Level level)
@@ -10961,30 +11165,35 @@ void print_value_internal_text(XMQPrintState *ps, const char *start, const char
     // Ok, normal content to be quoted. However we might need to split the content
     // at chars that need to be replaced with character entities. Normally no
     // chars need to be replaced. But in compact mode, the \n newlines are replaced with 

+    // If content contains CR LF its replaced with 

     // Also one can replace all non-ascii chars with their entities if so desired.
-    bool compact = ps->output_settings->compact;
     for (const char *from = start; from < stop; )
     {
         const char *to = find_next_char_that_needs_escape(ps, from, stop);
         if (from == to)
         {
-            char c = *from;
             check_space_before_entity_node(ps);
             to += print_char_entity(ps, level_to_entity_color(level), from, stop);
-            if (c == '\n' && !compact)
+            while (from+1 < stop && *(from+1) == '\n')
             {
-                print_nl_and_indent(ps, NULL, NULL);
+                // Special case, we have escaped something right before newline(s).
+                // Escape the newline(s) as well. This is important for CR LF.
+                // If not then we have to loop around detecting that the newline
+                // is leading a quote and needs to be escaped. Escape it here already.
+                from++;
+                check_space_before_entity_node(ps);
+                to += print_char_entity(ps, level_to_entity_color(level), from, stop);
             }
         }
         else
         {
-            check_space_before_quote(ps, level);
             bool add_nls = false;
             bool add_compound = false;
             bool compact = ps->output_settings->compact;
             count_necessary_quotes(from, to, false, &add_nls, &add_compound);
             if (!add_compound && (!add_nls || !compact))
             {
+                check_space_before_quote(ps, level);
                 print_safe_leaf_quote(ps, level_to_quote_color(level), from, to);
             }
             else
diff --git a/src/xmq.h b/src/xmq.h
index c85bc05..faa5da3 100644
--- a/src/xmq.h
+++ b/src/xmq.h
@@ -1,4 +1,4 @@
-/* libxmq - Copyright (C) 2023 Fredrik Öhrström (spdx: MIT)
+/* libxmq - Copyright (C) 2023-2024 Fredrik Öhrström (spdx: MIT)
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
@@ -124,25 +124,34 @@ typedef enum
 } XMQRenderFormat;
 
 /**
-    XMQTrimType:
-    @XMQ_TRIM_DEFAULT: Use the default, ie no-trim for xmq/json, normal-trim for xml/html.
-    @XMQ_TRIM_NONE: Do not trim at all. Keep unnecessary xml/html indentation and newlines.
-    @XMQ_TRIM_HEURISTIC: Normal trim heuristic. Remove leading/ending whitespace, remove incidental indentation.
-    @XMQ_TRIM_EXTRA: Like normal but remove all indentation (not just incidental) and collapse whitespace.
-    @XMQ_TRIM_RESHUFFLE: Like extra but also reflow all content at word boundaries to limit line lengths.
+    XMQFlagBits:
+    @XMQ_FLAG_TRIM_NONE: Do not trim any whitespace.
+    @XMQ_FLAG_TRIM_HEURISTIC: Remove leading/ending whitespace, but try to keep significant, remove incidental indentation.
+    @XMQ_FLAG_TRIM_EXACT: Trim exactly according to XML rules. Depends on your XSD,space:preserve and more and is COMPLICATED!
+    @XMQ_FLAG_NOMERGE: Do not merge text and character entities.
+
+    If a 0 is provided as the flags to the parse functions, then it will parse using the these default settings:
+    When loading xml/html:
+        trim the whitespace from the input to generate the most likely desired xmq output.
+        merge character entities
+    When loading xmq/htmq:
+        no trimming but
+        merge character entities such as 
 and consecutive text quotes
 
-    When loading xml/html trim the whitespace from the input to generate the most likely desired xmq output.
-    When loading xmq/htmq, the whitespace is never trimmed since xmq explicitly encodes all important whitespace.
     If you load xml with XMQ_TRIM_NONE (--trim=none) there will be a lot of unnecessary whitespace stored in
     the xmq, like  	
 etc.
     You can then view the xmq with XMQ_TRIM_HEURISTIC (--trim=heuristic) to drop the whitespace.
+
+    If you load xmq with --nomerge then character entities and separate text blocks will be kept as is.
+    The --nomerge currently does not work for XML/HTML since libxml2 does not have a setting for merge.
 */
 typedef enum
 {
-    XMQ_TRIM_DEFAULT = 0,
-    XMQ_TRIM_NONE = 1,
-    XMQ_TRIM_HEURISTIC = 2,
-} XMQTrimType;
+    XMQ_FLAG_TRIM_NONE = 1,
+    XMQ_FLAG_TRIM_HEURISTIC = 2,
+    XMQ_FLAG_TRIM_EXACT = 4,
+    XMQ_FLAG_NOMERGE = 8,
+} XMQFlagBits;
 
 /**
     XMQSyntax:
@@ -351,7 +360,7 @@ void xmqFreeParseCallbacks(XMQParseCallbacks *cb);
 
     Used to colorize xmq input, without building a parse tree.
 */
-void xmqSetupParseCallbacksColorizeTokens(XMQParseCallbacks *state, XMQRenderFormat render_format, bool dark_mode);
+void xmqSetupParseCallbacksColorizeTokens(XMQParseCallbacks *state, XMQRenderFormat render_format);
 
 /**
     xmqSetupParseCallbacksDebugTokens:
@@ -486,7 +495,7 @@ void xmqFreeDoc(XMQDoc *doc);
 
     Parse a file, or if file is NULL, read from stdin.
 */
-bool xmqParseFile(XMQDoc *doc, const char *file, const char *implicit_root);
+bool xmqParseFile(XMQDoc *doc, const char *file, const char *implicit_root, int flags);
 
 /**
     xmqParseBuffer:
@@ -498,7 +507,7 @@ bool xmqParseFile(XMQDoc *doc, const char *file, const char *implicit_root);
     Parse a buffer or a file and create a document.
     The xmq format permits multiple root nodes if an implicit root is supplied.
 */
-bool xmqParseBuffer(XMQDoc *doc, const char *start, const char *stop, const char *implicit_root);
+bool xmqParseBuffer(XMQDoc *doc, const char *start, const char *stop, const char *implicit_root, int flags);
 
 /**
     xmqParseReader:
@@ -509,7 +518,7 @@ bool xmqParseBuffer(XMQDoc *doc, const char *start, const char *stop, const char
     Parse data fetched with a reader and create a document.
     The xmq format permits multiple root nodes if an implicit root is supplied.
 */
-bool xmqParseReader(XMQDoc *doc, XMQReader *reader, const char *implicit_root);
+bool xmqParseReader(XMQDoc *doc, XMQReader *reader, const char *implicit_root, int flags);
 
 /** Allocate the print settings structure and zero it. */
 XMQOutputSettings *xmqNewOutputSettings();
@@ -520,13 +529,14 @@ void xmqFreeOutputSettings(XMQOutputSettings *os);
 void xmqSetAddIndent(XMQOutputSettings *os, int add_indent);
 void xmqSetCompact(XMQOutputSettings *os, bool compact);
 void xmqSetUseColor(XMQOutputSettings *os, bool use_color);
+void xmqSetBackgroundMode(XMQOutputSettings *os, bool bg_dark_mode);
 void xmqSetEscapeNewlines(XMQOutputSettings *os, bool escape_newlines);
 void xmqSetEscapeNon7bit(XMQOutputSettings *os, bool escape_non_7bit);
 void xmqSetOutputFormat(XMQOutputSettings *os, XMQContentType output_format);
 void xmqSetRenderFormat(XMQOutputSettings *os, XMQRenderFormat render_to);
+void xmqSetRenderTheme(XMQOutputSettings *os, const char *theme_name);
 void xmqSetRenderRaw(XMQOutputSettings *os, bool render_raw);
 void xmqSetRenderOnlyStyle(XMQOutputSettings *os, bool only_style);
-void xmqSetRenderStyle(XMQOutputSettings *os, const char *render_style);
 void xmqSetWriterContent(XMQOutputSettings *os, XMQWriter content);
 void xmqSetWriterError(XMQOutputSettings *os, XMQWriter error);
 
@@ -546,7 +556,7 @@ void xmqSetupPrintMemory(XMQOutputSettings *ps, char **start, char **stop);
 void xmqPrint(XMQDoc *doc, XMQOutputSettings *settings);
 
 /** Trim xml whitespace. */
-void xmqTrimWhitespace(XMQDoc *doc, XMQTrimType tt);
+void xmqTrimWhitespace(XMQDoc *doc, int flags);
 
 /** A parsing error will be described here! */
 const char *xmqDocError(XMQDoc *doc);
@@ -661,7 +671,7 @@ bool xmqParseBufferWithType(XMQDoc *doc,
                             const char *stop,
                             const char *implicit_root,
                             XMQContentType ct,
-                            XMQTrimType tt);
+                            int flags);
 
 /**
     xmqParseFileWithType:
@@ -672,14 +682,14 @@ bool xmqParseFileWithType(XMQDoc *doc,
                           const char *file,
                           const char *implicit_root,
                           XMQContentType ct,
-                          XMQTrimType tt);
+                          int flags);
 
 /**
    xmqSetupDefaultColors:
 
-   Set the default colors for settings based on the background color.
+   Set the default colors for settings based on the theme and background color.
 */
-void xmqSetupDefaultColors(XMQOutputSettings *settings, bool dark_mode);
+void xmqSetupDefaultColors(XMQOutputSettings *settings);
 
 /**
    xmqOverrideSetting: Change the default strings for spaces etc.
diff --git a/tests/generated_tests.xmq b/tests/generated_tests.xmq
index 27a0639..ab93984 100644
--- a/tests/generated_tests.xmq
+++ b/tests/generated_tests.xmq
@@ -1,4 +1,4 @@
-// Generated 2024-02-12_08:58
+// Generated 2024-04-04_20:51
 test {
     args     = 'Gas elster 05105025 NOKEY'
     telegram = 3644A511640010253837722550100593158103E70020052F2F_0374E602000C137034220302FD74EE0F2F2F2F2F2F2F2F2F2F2F2F2F2F2F
@@ -19,3 +19,23 @@ test {
     json     = '{"media":"water","meter":"iperl","name":"MoreWater","id":"12345699","total_m3":7.704,"max_flow_m3h":0,"timestamp":"1111-11-11T11:11:11Z"}'
     fields   = 'MoreWater;12345699;7.704;0;1111-11-11 11:11.11'
 }
+test {
+    args     = 'Pressing kampress 77000317 NOKEY'
+    telegram = '32442D2C1703007701188D280080E39322DB8F78_22696600126967000269660005FF091954A33A05FF0A99BD823A02FD170800
+                27442D2C1703007701188D289554F295224ED579DE73188A_650066006600E80EA43A6B97A3BA0800'
+    json     = '{"media":"pressure","meter":"kampress","name":"Pressing","id":"77000317","status":"LOW","pressure_bar":1.02,"max_pressure_bar":1.02,"min_pressure_bar":1.01,"alfa_counter":0.001252,"beta_counter":-0.001248,"timestamp":"1111-11-11T11:11:11Z"}'
+    fields   = 'Pressing;77000317;LOW;1.02;1.02;1.01;1111-11-11 11:11.11'
+}
+test {
+    args     = 'Pressing kampress 77000317 NOKEY'
+    telegram = '32442D2C1703007701188D280080E39322DB8F78_22696600126967000269660005FF091954A33A05FF0A99BD823A02FD170800
+                27442D2C1703007701188D280194E393226EC679DE735657_660067006600962B913A21B9423A0800'
+    json     = '{"media":"pressure","meter":"kampress","name":"Pressing","id":"77000317","status":"LOW","pressure_bar":1.02,"max_pressure_bar":1.03,"min_pressure_bar":1.02,"alfa_counter":0.001108,"beta_counter":0.000743,"timestamp":"1111-11-11T11:11:11Z"}'
+    fields   = 'Pressing;77000317;LOW;1.02;1.03;1.02;1111-11-11 11:11.11'
+}
+test {
+    args     = 'Pressing kampress 77000317 NOKEY'
+    telegram = 32442D2C1703007701188D280080E39322DB8F78_22696600126967000269660005FF091954A33A05FF0A99BD823A02FD170800
+    json     = '{"media":"pressure","meter":"kampress","name":"Pressing","id":"77000317","status":"LOW","pressure_bar":1.02,"max_pressure_bar":1.03,"min_pressure_bar":1.02,"alfa_counter":0.001246,"beta_counter":0.000997,"timestamp":"1111-11-11T11:11:11Z"}'
+    fields   = 'Pressing;77000317;LOW;1.02;1.03;1.02;1111-11-11 11:11.11'
+}
diff --git a/tests/test_bad_driver.sh b/tests/test_bad_driver.sh
index b24970f..ebe842e 100755
--- a/tests/test_bad_driver.sh
+++ b/tests/test_bad_driver.sh
@@ -497,3 +497,22 @@ EOF
 $PROG --format=hr --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NOKEY > $TEST/test_output.txt 2>&1 || true
 
 performCheck
+
+TESTNAME="Test lookup of bits"
+TESTRESULT="ERROR"
+cat > $TEST/driver.xmq < $TEST/test_expected.txt < $TEST/test_output.txt 2>&1 || true
+
+performCheck
diff --git a/tests/testit.sh b/tests/testit.sh
index 175a695..36f8224 100755
--- a/tests/testit.sh
+++ b/tests/testit.sh
@@ -11,9 +11,12 @@ FIELDS="$5"
 
 OK=true
 
-rm -f $TEST/test_output.txt $TEST/test_expected.txt
+rm -f $TEST/test_output.txt $TEST/test_expected.txt $TEST/simulation_tmp.txt
 
-$PROG --format=json $HEX $ARGS \
+echo "$HEX" | sed 's/^/telegram=/g' > $TEST/simulation_tmp.txt
+
+$PROG --format=json $TEST/simulation_tmp.txt $ARGS \
+    | tail -n 1 \
     | jq . --sort-keys \
     | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' \
     > $TEST/test_output.txt
@@ -31,7 +34,8 @@ fi
 
 rm -f $TEST/test_output.txt $TEST/test_expected.txt
 
-$PROG --format=fields $HEX $ARGS \
+$PROG --format=fields $TEST/simulation_tmp.txt $ARGS \
+    | tail -n 1 \
     | sed 's/....-..-.. ..:..:../1111-11-11 11:11.11/' \
     > $TEST/test_output.txt