code: /* LanguageTool, a natural language style checker
   * Copyright (C) 2015 Daniel Naber (http://www.danielnaber.de)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library 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
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
   * USA
   */
  package org.languagetool.dev.archive;
  
  import com.google.common.base.Strings;
  import com.google.common.xml.XmlEscapers;
  import org.apache.commons.io.IOUtils;
  import org.languagetool.JLanguageTool;
  import org.languagetool.Language;
  import org.languagetool.Languages;
  import org.languagetool.rules.Rule;
  import org.languagetool.rules.patterns.PatternRule;
  import org.languagetool.rules.patterns.PatternToken;
  
  import java.io.File;
  import java.io.FileReader;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * Replaces the 'pattern' element in simple rules with the 'regexp' element.
   * WARNING: this is a hack, the rules it produces need to be checked and modified manually!
   */
  final class RuleSimplifier {
  
    private int touchedRulesCount;
  
    private void run(Language lang) throws IOException {
      File basePath = new File("/lt/git/languagetool/languagetool-language-modules");
      if (!basePath.exists()) {
        throw new RuntimeException("basePath does not exist: " + basePath);
      }
      String langCode = lang.getShortCode();
      File xml = new File(basePath, "/" + langCode + "/src/main/resources/org/languagetool/rules/" + langCode + "/grammar.xml");
      List<String> xmlLines = IOUtils.readLines(new FileReader(xml));
      JLanguageTool tool = new JLanguageTool(lang);
      int totalRules = 0;
      for (Rule rule : tool.getAllActiveRules()) {
        if (!(rule instanceof PatternRule)) {
          continue;
        }
        PatternRule patternRule = (PatternRule) rule;
        String id = patternRule.getFullId();
        if (isSimple((PatternRule)rule)) {
          System.err.println("Simplifying: " + id);
          simplify(patternRule, xmlLines);
        } else {
          System.err.println("Can't simplify: " + id);
        }
        totalRules++;
      }
      System.err.println("touchedRulesCount: " + touchedRulesCount + " out of " + totalRules);
      for (String xmlLine : xmlLines) {
        System.out.println(xmlLine);
      }
    }
  
    private boolean isSimple(PatternRule rule) {
      return rule.getPatternTokens().stream().allMatch(this::isSimple)
              && rule.getStartPositionCorrection() == 0
              && rule.getEndPositionCorrection() == 0;
    }
    
    private boolean isSimple(PatternToken t) {
      return !(t.getNegation() || t.getPOSNegation() || t.hasAndGroup() || t.hasExceptionList() ||
              t.hasNextException() || t.hasOrGroup() || t.isInflected() || t.isPOStagRegularExpression() ||
              t.getPOStag() != null || t.isReferenceElement() || t.isSentenceStart() ||
              t.getSkipNext() != 0);
    }
  
    private String getRegex(PatternRule rule) {
      StringBuilder sb = new StringBuilder();
      List<PatternToken> tokens = rule.getPatternTokens();
      boolean hasCSParts = tokens.stream().anyMatch(PatternToken::isCaseSensitive);
      boolean allCSParts = tokens.stream().allMatch(PatternToken::isCaseSensitive);
      for (PatternToken patternToken : rule.getPatternTokens()) {
        String str = patternToken.getString();
        boolean setAllParenthesis = containsBackRef(rule.getMessage()) || containsBackRef(rule.getSuggestionsOutMsg());
        if (hasCSParts && !allCSParts && !patternToken.isCaseSensitive()) {
          sb.append("(?i:");
          appendTokenString(sb, str, setAllParenthesis);
          sb.append(')');
        } else {
          appendTokenString(sb, str, setAllParenthesis);
        }
        sb.append(' ');
      }
      String escapedRegex = XmlEscapers.xmlContentEscaper().escape(sb.toString().trim());
      if (allCSParts) {
        return "<regexp case_sensitive='yes'>" + escapedRegex + "</regexp>";
      }
      return "<regexp>" + escapedRegex + "</regexp>";
    }
  
    private boolean containsBackRef(String str) {
      return str.matches(".*\\\\\\d+.*");
    }
    
    private void appendTokenString(StringBuilder sb, String str, boolean setAllParenthesis) {
      if (str.contains("|") || setAllParenthesis) {
        sb.append('(').append(str).append(')');
      } else {
        sb.append(str);
      }
    }
  
    // Note: this is a bad hack, we just iterate through the file's lines
    private void simplify(PatternRule rule, List<String> xmlLines) {
      List<Integer> linesToRemove = new ArrayList<>();
      String currentRuleId = null;
      Pattern pattern = Pattern.compile(".*id=[\"'](.*?)[\"'].*");
      String expectedSubId = rule.getSubId();
      int lineCount = 0;
      int subRuleCount = 0;
      int removedCount = 0;
      boolean inRuleGroup = false;
      String newRegex = null;
      boolean inAntiPattern = false;
      for (lineCount = 0; lineCount < xmlLines.size(); lineCount++) {
      //for (String xmlLine : xmlLines) {
        String xmlLine = xmlLines.get(lineCount);
        if (xmlLine.contains("<rulegroup")) {
          subRuleCount = 0;
          inRuleGroup = true;
        } else if (xmlLine.contains("</rulegroup>")) {
          subRuleCount = 0;
          inRuleGroup = false;
        } else if ((xmlLine.contains("<rule ")||xmlLine.contains("<rule>")) && inRuleGroup) {
          subRuleCount++;
        }
        Matcher m = pattern.matcher(xmlLine);
        if (m.matches()) {
          currentRuleId = m.group(1);
        }
        if (currentRuleId != null && !currentRuleId.equals(rule.getId())) {
          continue;
        }
        if (!inRuleGroup) {
          subRuleCount = 1;
        }
        if (!expectedSubId.equals("0") && !expectedSubId.equals(String.valueOf(subRuleCount))) {
          continue;
        }
        if (xmlLine.matches(".*<antipattern.*")) {
          inAntiPattern = true;
        }
        if (inAntiPattern) {
          continue;
        }
        if (xmlLine.matches(".*</antipattern.*")) {
          inAntiPattern = false;
          continue;
        }
        if (xmlLine.matches(".*<(token|pattern).*") || xmlLine.matches("\\s*</?marker>.*")) {
          linesToRemove.add(lineCount);
        }
        if (xmlLine.matches(".*</pattern.*")) {
          linesToRemove.add(lineCount);
          int lastTokenIndent = xmlLine.indexOf('<');
          newRegex = Strings.repeat(" ", lastTokenIndent) + getRegex(rule);
        }
      }
      Collections.reverse(linesToRemove); // start from end, as we need to remove items
      for (Integer s : linesToRemove) {
        xmlLines.remove(s.intValue());
        removedCount++;
      }
      if (removedCount == 0) {
        System.err.println("No line removed: " + rule + "[" + expectedSubId + "]");
      } else {
        xmlLines.add(linesToRemove.get(linesToRemove.size()-1), newRegex);
        touchedRulesCount++;
      }
    }
  
    public static void main(String[] args) throws IOException {
      RuleSimplifier prg = new RuleSimplifier();
      prg.run(Languages.getLanguageForShortCode("de"));
    }
  
  }
  