Генерация XML только до определенной глубины и предельных списков - PullRequest
0 голосов
/ 27 октября 2018

Для проекта, который генерирует XML-файлы на основе XSD-файла, я хочу автоматически генерировать документацию. *

В этой документации я перечисляю различные элементы, определенные в XSD. И для каждого элемента я хочу показать пример этого элемента. Проблема в том, что XML-пример может быть довольно длинным и содержать много дочерних элементов. Поэтому я хочу сократить пример на:

  • ограничение показанной глубины
  • ограничение количества элементов в списке

Для корневого элемента этот пример может выглядеть следующим образом:

<root>
  <elements>
    <element>...<element>
    <element>...<element>
    <element>...<element>
    ...
  </elements>
</root>

Мой подход:

Для генерации классов из XSD, а также для генерации и проверки XML-файлов я использую JAXB. Но я не мог понять, как упорядочить нерутинный элемент. Поэтому я создаю свои примеры с помощью XStream.

Чтобы ограничить XML-пример, я пытаюсь украсить PrettyPrintWriter, но это кажется довольно громоздким. Два декоратора можно увидеть в моем ответе. Я просто не ожидал, что позаботится о внутренней части библиотеки для такой (распространенной?) Функции.

Есть ли более простой способ сделать это? (Я также могу использовать другую библиотеку, кроме XStream, или вообще ни одной.)

* На мой подход влияют Spring Auto Rest Docs

1 Ответ

0 голосов
/ 29 октября 2018

Чтобы ограничить показанную глубину, я создал следующий XStream WriterWrapper.Класс может обернуть, например, PrettyPrintWriter и гарантирует, что обернутый писатель получает только узлы выше заданного порога глубины.

public class RestrictedPrettyPrintWriter extends WriterWrapper {

 private final ConverterLookup converterLookup;
 private final int maximalDepth;
 private int depth;

 public RestrictedPrettyPrintWriter(HierarchicalStreamWriter sw, ConverterLookup converterLookup, int maximalDepth) {
  super(sw);
  this.converterLookup = converterLookup;
  this.maximalDepth = maximalDepth;
 }

 @Override public void startNode(String name, Class clazz) {
  Converter converter = this.converterLookup.lookupConverterForType(clazz);
  boolean isSimpleType = converter instanceof SingleValueConverter;
  _startNode(name, !isSimpleType);
 }

 @Override public void startNode(String name) {
  _startNode(name, false);
 }

 @Override public void endNode() {
  if (_isLessDeepThanMaximalDepth() || _isMaximalDepthReached()) {
   super.endNode();
  }
  depth--;
 }

 @Override public void addAttribute(String key, String value) {
  if (_isLessDeepThanMaximalDepth() || _isMaximalDepthReached()) {
   super.addAttribute(key, value);
  }
 }

 @Override public void setValue(String text) {
  if (_isLessDeepThanMaximalDepth() || _isMaximalDepthReached()) {
   super.setValue(text);
  }
 }

 /**
  * @param name name of the new node
  * @param isComplexType indicates if the element is complex or contains a single value
  */
 private void _startNode(String name, boolean isComplexType) {
  depth++;
  if (_isLessDeepThanMaximalDepth()) {
   super.startNode(name);
  } else if (_isMaximalDepthReached()) {
   super.startNode(name);
   /* 
    * set the placeholder value now
    * setValue() will never be called for complex types 
    */
   if (isComplexType) {
    super.setValue("...");
   }
  }
 }

 private boolean _isMaximalDepthReached() {
  return depth == maximalDepth;
 }

 private boolean _isLessDeepThanMaximalDepth() {
  return depth < maximalDepth;
 }

}

Чтобы ограничить списки, я попытался, в первой попытке, изменитьXStream CollectionConverter.Но этот подход не был достаточно общим, потому что неявные списки не используют этот конвертер.

Поэтому я создал еще один WriterWrapper, который подсчитывает последовательные вхождения элементов с одинаковыми именами.

public class RestrictedCollectionWriter extends WriterWrapper {

 private final int maxConsecutiveOccurences;

 private int depth;
 /** Contains one element per depth.
  *  More precisely: the current element and its parents.
  */
 private Map < Integer, Elements > elements = new HashMap < > ();

 public RestrictedCollectionWriter(HierarchicalStreamWriter sw, int maxConsecutiveOccurences) {
  super(sw);
  this.maxConsecutiveOccurences = maxConsecutiveOccurences;
 }

 @Override public void startNode(String name, Class clazz) {
  _startNode(name);
 }

 @Override public void startNode(String name) {
  _startNode(name);
 }

 @Override public void endNode() {
  if (_isCurrentElementPrintable()) {
   super.endNode();
  }
  depth--;
 }

 @Override public void addAttribute(String key, String value) {
  if (_isCurrentElementPrintable()) {
   super.addAttribute(key, value);
  }
 }

 @Override public void setValue(String text) {
  if (_isCurrentElementPrintable()) {
   super.setValue(text);
  }
 }

 /**
  * @param name name of the new node
  */
 private void _startNode(String name) {
  depth++;
  Elements currentElement = this.elements.getOrDefault(depth, new Elements());
  this.elements.put(depth, currentElement);

  Elements parent = this.elements.get(depth - 1);
  boolean parentPrintable = parent == null ? true : parent.isPrintable();
  currentElement.setName(name, parentPrintable);

  if (currentElement.isPrintable()) {
   super.startNode(name);
  }
 }

 private boolean _isCurrentElementPrintable() {
  Elements currentElement = this.elements.get(depth);
  return currentElement.isPrintable();
 }

 /**
  * Evaluates if an element is printable or not.
  * This is based on the concurrent occurences of the element's name
  * and if the parent element is printable or not.
  */
 private class Elements {
  private String name = "";
  private int concurrentOccurences = 0;
  private boolean parentPrintable;

  public void setName(String name, boolean parentPrintable) {
   if (this.name.equals(name)) {
    concurrentOccurences++;
   } else {
    concurrentOccurences = 1;
   }
   this.name = name;
   this.parentPrintable = parentPrintable;
  }

  public boolean isPrintable() {
   return parentPrintable && concurrentOccurences <= maxConsecutiveOccurences;
  }
 }
}

В следующем листинге показано, как можно использовать два класса.

XStream xstream = new XStream(new StaxDriver());
StringWriter sw = new StringWriter();
PrettyPrintWriter pw = new PrettyPrintWriter(sw);
RestrictedCollectionWriter cw = new RestrictedCollectionWriter(pw, 3);
xstream.marshal(objectToMarshal, new RestrictedPrettyPrintWriter(cw, xstream.getConverterLookup(), 3));
...