Repository: rdkit/rdkit — pull request #4282 (cpp)
Title: Fixes #4281

Made the pyridine _N_-oxide pattern more specific and added a test.

Resolved issues:
title: The normalization pattern for pyridine N-oxide is not specific enough
body: Reported by JP Ebejer:

```
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize

m = Chem.MolFromSmiles('Cn1c(=O)c2nc[nH][n+](=O)c2n(C)c1=O')
m
```
![image](https://user-images.githubusercontent.com/5244385/123306106-29642c80-d521-11eb-8ec5-fb897a8b308b.png)
```
rdMolStandardize.Cleanup(m)
RDKit ERROR: [19:20:42] Can't kekulize mol.  Unkekulized atoms: 0 3 4 6 7
```

The molecule is not actually a pyridine _N_-oxide, rather the oxy tautomer of a _N_-hydroxypyridinium ion, but it hits the pyridine _N_-oxide normalization pattern because the latter is not sufficiently specific.

Hints:

Fix patch:
diff --git a/Code/GraphMol/MolStandardize/TransformCatalog/normalizations.in b/Code/GraphMol/MolStandardize/TransformCatalog/normalizations.in
index 6b452219452..8aeb6b44d3e 100644
--- a/Code/GraphMol/MolStandardize/TransformCatalog/normalizations.in
+++ b/Code/GraphMol/MolStandardize/TransformCatalog/normalizations.in
@@ -16,7 +16,7 @@ std::vector<std::pair<std::string, std::string>> defaultNormalizations = {
      "[N,P,As,Sb;X3:1](=[O,S,Se,Te:2])=[O,S,Se,Te:3]>>[*+1:1]([*-1:2])=[*:3]"},
     {"Sulfone to S(=O)(=O)",
      "[S+2:1]([O-:2])([O-:3])>>[S+0:1](=[O-0:2])(=[O-0:3])"},
-    {"Pyridine oxide to n+O-", "[n:1]=[O:2]>>[n+:1][O-:2]"},
+    {"Pyridine oxide to n+O-", "[nH0+0:1]=[OH0+0:2]>>[n+:1][O-:2]"},
     {"Azide to N=N+=N-", "[*:1][N:2]=[N:3]#[N:4]>>[*:1][N:2]=[N+:3]=[N-:4]"},
     {"Diazo/azo to =N+=N-", "[*:1]=[N:2]#[N:3]>>[*:1]=[N+:2]=[N-:3]"},
     {"Sulfoxide to -S+(O-)-",


Test patch:
diff --git a/Code/GraphMol/MolStandardize/testNormalize.cpp b/Code/GraphMol/MolStandardize/testNormalize.cpp
index ae31a9583a3..d65045f43ff 100644
--- a/Code/GraphMol/MolStandardize/testNormalize.cpp
+++ b/Code/GraphMol/MolStandardize/testNormalize.cpp
@@ -501,6 +501,21 @@ void testEmptyMol() {
   TEST_ASSERT(!normalized->getNumAtoms());
 }
 
+void testGithub4281() {
+  BOOST_LOG(rdInfoLog)
+      << "-----------------------\n Testing Github #3460: "
+         "Normalization rule incorrectly matches [N+]=O rather than n=O"
+      << std::endl;
+  auto mol = "Cn1c(=O)c2nc[nH][n+](=O)c2n(C)c1=O"_smiles;
+  std::stringstream captureLog;
+  rdInfoLog->SetTee(captureLog);
+  Normalizer nn;
+  std::unique_ptr<ROMol> normalized(nn.normalize(*mol));
+  rdInfoLog->ClearTee();
+  auto logged = captureLog.str();
+  TEST_ASSERT(logged.find("FAILED sanitizeMol") == std::string::npos);
+}
+
 int main() {
   RDLog::InitLogs();
 #if 1
@@ -511,5 +526,6 @@ int main() {
   testNormalizeMultipleAltSmarts();
   testGithub3460();
   testEmptyMol();
+  testGithub4281();
   return 0;
 }