class BitextPatternRuleHandler extends PatternRuleHandler {

  private static final String SOURCE = "source";
  private static final String TARGET = "target";
  private static final String SRC_EXAMPLE = "srcExample";
  private static final String TRG_EXAMPLE = "trgExample";

  private final List<BitextPatternRule> rules = new ArrayList<>();

  private PatternRule srcRule;
  private PatternRule trgRule;

  private IncorrectExample trgExample;
  private IncorrectExample srcExample;

  private Language srcLang;

  private List<StringPair> correctExamples = new ArrayList<>();
  private List<IncorrectBitextExample> incorrectExamples = new ArrayList<>();

  List<BitextPatternRule> getBitextRules() {
    return rules;
  }

  // ===========================================================
  // SAX DocumentHandler methods
  // ===========================================================

  @Override
  public void startElement(String namespaceURI, String lName,
      String qName, Attributes attrs) throws SAXException {
    switch (qName) {
      case RULES:
        String languageStr = attrs.getValue("targetLang");
        language = Languages.getLanguageForShortCode(languageStr);
        break;
      case RULE:
        super.startElement(namespaceURI, lName, qName, attrs);
        correctExamples = new ArrayList<>();
        incorrectExamples = new ArrayList<>();
        break;
      case TARGET:
        startPattern(attrs);
        break;
      case SOURCE:
        srcLang = Languages.getLanguageForShortCode(attrs.getValue("lang"));
        break;
      default:
        super.startElement(namespaceURI, lName, qName, attrs);
        break;
    }
  }

  @Override
  public void endElement(String namespaceURI, String sName,
      String qName) throws SAXException {
    switch (qName) {
      case RULE:
        trgRule.setMessage(message.toString());
        for (Match m : suggestionMatches) {
          trgRule.addSuggestionMatch(m);
        }
        if (phrasePatternTokens.size() <= 1) {
          suggestionMatches.clear();
        }
        BitextPatternRule bRule = new BitextPatternRule(srcRule, trgRule);
        bRule.setCorrectBitextExamples(correctExamples);
        bRule.setIncorrectBitextExamples(incorrectExamples);
        bRule.setSourceLanguage(srcLang);
        rules.add(bRule);
        break;
      case SRC_EXAMPLE:
        srcExample = setExample();
        break;
      case TRG_EXAMPLE:
        trgExample = setExample();
        break;
      case SOURCE:
        srcRule = finalizeRule();
        break;
      case TARGET:
        trgRule = finalizeRule();
        break;
      case EXAMPLE:
        if (inCorrectExample) {
          correctExamples.add(new StringPair(srcExample.getExample(), trgExample.getExample()));
        } else if (inIncorrectExample) {
          StringPair examplePair = new StringPair(srcExample.getExample(), trgExample.getExample());
          if (trgExample.getCorrections().isEmpty()) {
            incorrectExamples.add(new IncorrectBitextExample(examplePair));
          } else {
            List<String> corrections = trgExample.getCorrections();
            incorrectExamples.add(new IncorrectBitextExample(examplePair, corrections));
          }
        }
        inCorrectExample = false;
        inIncorrectExample = false;
        inErrorTriggerExample = false;
        break;
      default:
        super.endElement(namespaceURI, sName, qName);
        break;
    }

  }

  private IncorrectExample setExample() {<FILL_FUNCTION_BODY>}

  private PatternRule finalizeRule() {
    PatternRule rule = null;
    if (phrasePatternTokens.isEmpty()) {
      rule = new PatternRule(id, language, patternTokens,
          name, "", shortMessage.toString());
      prepareRule(rule);
    } else {
      if (!patternTokens.isEmpty()) {
        for (List<PatternToken> ph : phrasePatternTokens) {
          ph.addAll(new ArrayList<>(patternTokens));
        }
      }
      for (List<PatternToken> phrasePatternToken : phrasePatternTokens) {
        processElement(phrasePatternToken);
        rule = new PatternRule(id, language, phrasePatternToken,
            name, message.toString(), shortMessage.toString(), "",
            phrasePatternTokens.size() > 1);
        prepareRule(rule);
      }
    }
    patternTokens.clear();
    if (phrasePatternTokens != null) {
      phrasePatternTokens.clear();
    }
    startPositionCorrection = 0;
    endPositionCorrection = 0;
    return rule;
  }

}

    IncorrectExample example = null;
    if (inCorrectExample) {
      example = new IncorrectExample(correctExample.toString());
    } else if (inIncorrectExample) {
      if (exampleCorrection != null) {
        List<String> corrections = new ArrayList<>(Arrays.asList(exampleCorrection.toString().split("\\|")));
        if (exampleCorrection.toString().endsWith("|")) {  // suggestions plus an empty suggestion (split() will ignore trailing empty items)
          corrections.add("");
        }
        example = new IncorrectExample(incorrectExample.toString(), corrections);
      } else {
        example = new IncorrectExample(incorrectExample.toString());
      }
    } else if (inErrorTriggerExample) {
      throw new RuntimeException("'triggers_error' is not supported for bitext XML");
    }
    correctExample = new StringBuilder();
    incorrectExample = new StringBuilder();
    exampleCorrection = null;
    return example;
  
<methods>public void <init>() ,public void <init>(java.lang.String) ,public void <init>(java.lang.String, org.languagetool.Language) ,public void characters(char[], int, int) ,public void endElement(java.lang.String, java.lang.String, java.lang.String) throws org.xml.sax.SAXException,public org.xml.sax.InputSource resolveEntity(java.lang.String, java.lang.String) throws java.io.IOException, org.xml.sax.SAXException,public void startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) throws org.xml.sax.SAXException<variables>private static final java.lang.String EXTERNAL,static final java.lang.String MARKER_TAG,static final java.lang.String PLEASE_SPELL_ME,static final java.lang.String RAW_TAG,public static final java.lang.String TYPE,private int antiPatternCounter,protected org.languagetool.rules.Category category,protected java.lang.String categoryIssueType,private boolean defaultOff,private boolean defaultTempOff,private int distanceTokens,private int endPos,protected java.lang.String filterArgs,protected java.lang.String filterClassName,private java.lang.String idPrefix,private boolean inAntiPattern,private boolean inRule,private final Map<java.lang.String,java.net.URL> internedUrls,private boolean interpretPosTagsPreDisambiguation,private boolean isRuleSuppressMisspelled,private boolean isSuggestionSuppressMisspelled,private int minPrevMatches,protected java.lang.String name,private boolean relaxedMode,private final List<org.languagetool.tagging.disambiguation.rules.DisambiguationPatternRule> ruleAntiPatterns,private boolean ruleGroupDefaultOff,private boolean ruleGroupDefaultTempOff,private java.lang.String ruleGroupDescription,private int ruleGroupDistanceTokens,protected java.lang.String ruleGroupIssueType,private int ruleGroupMinPrevMatches,protected java.lang.String ruleIssueType,private final List<org.languagetool.tagging.disambiguation.rules.DisambiguationPatternRule> rulegroupAntiPatterns,protected final non-sealed java.lang.String sourceFile,private int startPos,private int subId,private int tokenCountForMarker