In primo luogo, mi rendo conto che il titolo della domanda è tanto terribile quanto il codice di esempio che inserirò di seguito, quindi ti prego di sopportare me mentre spiego il problema più chiaramente, e se hai un'idea migliore per il titolo - sii il mio ospite e modificarlo.
Immagina un lungo testo in chiaro. Consiste di parole separate da segni di punteggiatura e / o spazi. Quello che devo fare è convertirlo in una lista di parole + segni di punteggiatura che separano questa parola da quella successiva. E la svolta è che ho anche bisogno di determinare quale segno di punteggiatura è (o qual è l'ultimo se ce n'è più di uno di seguito). Quindi, ho bisogno di trasformare il testo in una collezione di strutture:
{
wordFollowedByPunctuation: String;
punctuationMark: PunctuationType; // E. g. {Point, Comma, Colon, Space, ...}
}
Se tutti i segni di punteggiatura fossero caratteri singoli, sarebbe facile poiché potremmo utilizzare l'analisi del carattere a passaggio singolo. Ho un prototipo funzionante, anche se terribile, in C ++ (usando Qt
- QString
e QChar
- per supporto Unicode).
Ecco la struttura TextFragment
: convertirò il testo in una raccolta di questi elementi:
struct TextFragment
{
enum Delimiter {
Space,
Comma,
Point,
ExclamationMark,
QuestionMark,
Dash,
Colon,
Semicolon,
Ellipsis,
Bracket,
Newline
};
inline TextFragment(const QString& text, Delimiter delimiter) : _text(text), _delimitier(delimiter) {}
const QString _text;
const Delimiter _delimitier;
};
Ed ecco l'analisi vera e propria:
const QString text = readText(device);
struct Delimiter {
QChar delimiterCharacter;
TextFragment::Delimiter delimiterType;
inline bool operator< (const Delimiter& other) const {
return delimiterCharacter < other.delimiterCharacter;
}
};
static const std::set<Delimiter> delimiters {
{' ', TextFragment::Space},
{'.', TextFragment::Point},
{':', TextFragment::Colon},
{';', TextFragment::Semicolon},
{',', TextFragment::Comma},
// TODO: dash should be ignored unless it has an adjacent space!
{'-', TextFragment::Dash},
// TODO:
// {"...", TextFragment::Ellipsis},
{'⋯', TextFragment::Ellipsis},
{'…', TextFragment::Ellipsis},
{'!', TextFragment::ExclamationMark},
{'\n', TextFragment::Newline},
{'?', TextFragment::QuestionMark},
{')', TextFragment::Bracket},
{'(', TextFragment::Bracket},
{'[', TextFragment::Bracket},
{']', TextFragment::Bracket},
{'{', TextFragment::Bracket},
{'}', TextFragment::Bracket}
};
std::vector<TextFragment> fragments;
QString buffer;
bool wordEnded = false;
TextFragment::Delimiter lastDelimiter = TextFragment::Space;
for (QChar ch: text)
{
if (ch == '\r')
continue;
const auto it = delimiters.find({ch, TextFragment::Space});
if (it == delimiters.end()) // Not a delimiter
{
if (wordEnded) // This is the first letter of a new word
{
fragments.emplace_back(buffer, lastDelimiter);
wordEnded = false;
buffer = ch;
}
else
buffer += ch;
}
else // This is a delimiter. Append it to the current word.
{
lastDelimiter = it->delimiterType;
wordEnded = true;
buffer += ch;
}
}
return fragments;
Qui abbiamo una macchina a stati che si sforza di non somigliarne uno, ed è peggio per quello. Funziona. Ma ha un problema più grande dello stile di codifica: i alcuni delimitatori sono multi-carattere, e semplicemente non possono gestirli. Un esempio è l'ellissi costituita da 3 punti: "..." Voglio distinguerlo da un singolo punto. Un altro esempio è che voglio distinguere tra un trattino e un trattino. Un trattino separa parti di una parola composta, e. g. "up-to-date", e per quanto mi riguarda non è un segno di punteggiatura. Un trattino, d'altra parte, è: "Joe - e il suo fidato bastardo - era sempre il benvenuto." Ora, è un carattere di trattino speciale, ma in semplici testi non Unicode entrambi sono comunemente rappresentati da un trattino ("-"). Quindi l'unico modo che vedo per distinguerli è solo "spazio + trattino + spazio" o "spazio + trattino + altro segno di punteggiatura" come trattino.
Esempio: la frase
If only he had tried... well, it doesn't matter now.
dovrebbe risultare
{"If ", Space},
{"only ", Space},
{"he ", Space},
{"had ", Space},
{"tried... ", Ellipsis},
{"well, ", Comma},
{"it ", Space},
{"doesn't ", Space},
{"matter ", Space},
{"now", Point}
Per come la vedo io, ho bisogno di una sorta di parsing esaustivo al posto del mio avido prototipo (e, naturalmente, i separatori stessi dovrebbero essere rappresentati da stringhe, non da personaggi). Qual è il modo più semplice per farlo? Posso usare espressioni regolari per questo (sono terribile con loro)?