public XMLPageManifestLoader(String basePath, Collection<String> activeProfiles) {
    this.basePath = basePath;
    this.activeProfiles = activeProfiles;

    try {
      documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

      SERVICE_LOADER.forEach(resourcesReaders::add);

      xPath = XPathFactory.newInstance().newXPath();
      importsExpr = xPath.compile("/manifest/import");
      viewsExpr = xPath.compile("/manifest/view");
      resourcesExpr = xPath.compile("/manifest/resources");
      fragmentsExpr = xPath.compile("/manifest/fragment");
      selectorExpr = xPath.compile("/manifest/@selector");
    } catch (ParserConfigurationException | XPathExpressionException e) {
      throw new JumbleException("Fail to initialize manifest loader", e);
    }
  }
/** Created by Hatis on 15.09.2015. */
public class XMLPageManifestLoader implements PageManifestLoader {
  private static final ServiceLoader<ResourceReader> SERVICE_LOADER =
      ServiceLoader.load(ResourceReader.class);

  private Collection<ResourceReader> resourcesReaders = new ArrayList<>();

  private XPathExpression resourcesExpr;
  private XPathExpression viewsExpr;
  private XPathExpression importsExpr;
  private XPathExpression fragmentsExpr;
  private XPathExpression selectorExpr;

  private Collection<XPathExpression> fragmentRefExpressions = new ArrayList<>();
  private Collection<XPathExpression> resourcesRefExpressions = new ArrayList<>();

  private String basePath;
  private final Collection<String> activeProfiles;
  private Set<String> loadedFiles;
  private DocumentBuilder documentBuilder;
  private XPath xPath;

  public XMLPageManifestLoader(String basePath, Collection<String> activeProfiles) {
    this.basePath = basePath;
    this.activeProfiles = activeProfiles;

    try {
      documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

      SERVICE_LOADER.forEach(resourcesReaders::add);

      xPath = XPathFactory.newInstance().newXPath();
      importsExpr = xPath.compile("/manifest/import");
      viewsExpr = xPath.compile("/manifest/view");
      resourcesExpr = xPath.compile("/manifest/resources");
      fragmentsExpr = xPath.compile("/manifest/fragment");
      selectorExpr = xPath.compile("/manifest/@selector");
    } catch (ParserConfigurationException | XPathExpressionException e) {
      throw new JumbleException("Fail to initialize manifest loader", e);
    }
  }

  public XMLPageManifestLoader(Collection<String> activeProfiles) {
    this("classpath:", activeProfiles);
  }

  public XMLPageManifestLoader() {
    this("classpath:", null);
  }

  public void addResourceReader(ResourceReader reader) {
    resourcesReaders.add(reader);
  }

  public void addResourceReaders(Collection<ResourceReader> readers) {
    resourcesReaders.addAll(readers);
  }

  // load
  @Override
  public synchronized PageManifest load(String manifestPath, String... alsoLoadProfiles) {
    try {
      loadedFiles = new HashSet<>();
      createProfilesXPath(alsoLoadProfiles);
      PageManifestDef pmd = loadRootPageDefinition(manifestPath);
      return new PageManifestBuilder().buildManifest(pmd);
    } catch (ManifestNotFound e) {
      return null;
    } catch (ParserConfigurationException
        | SAXException
        | IOException
        | XPathExpressionException e) {
      throw new PageManifestLoadingException("Fail to load page manifest: " + manifestPath, e);
    }
  }

  private void createProfilesXPath(String... alsoLoadProfiles) throws XPathExpressionException {
    fragmentRefExpressions.add(xPath.compile("fragment"));
    resourcesRefExpressions.add(xPath.compile("resources"));

    for (String profile : activeProfiles) {
      fragmentRefExpressions.add(xPath.compile("profile[@name='" + profile + "']/fragment"));
      resourcesRefExpressions.add(xPath.compile("profile[@name='" + profile + "']/resources"));
    }

    for (String profile : alsoLoadProfiles) {
      fragmentRefExpressions.add(xPath.compile("profile[@name='" + profile + "']/fragment"));
      resourcesRefExpressions.add(xPath.compile("profile[@name='" + profile + "']/resources"));
    }
  }

  private PageManifestDef loadRootPageDefinition(String manifestPathOrPath)
      throws ParserConfigurationException, SAXException, XPathExpressionException, IOException {
    PageManifestDef pmd = new PageManifestDef();
    Document document = readPageDefinitionFile(manifestPathOrPath, pmd);
    pmd.setSelectorStrategy(selectorExpr.evaluate(document));
    return pmd;
  }

  private Document readPageDefinitionFile(String manifestPathOrPath, PageManifestDef pmd) {
    FileScope fileScope = new FileScope(basePath, manifestPathOrPath);

    // prevent cycle import
    if (loadedFiles.contains(fileScope.getFullPath())) return null;

    String filePath = fileScope.getFullPath();
    try {
      InputStream is = FileUtils.openStream(filePath);
      if (is == null)
        throw new JumbleException("Cant open file stream: " + fileScope.getFullPath());

      Document document = documentBuilder.parse(is);
      loadedFiles.add(fileScope.getFullPath());
      new PageManifestReader().read(fileScope, document, pmd);

      return document;
    } catch (FileNotFoundException e) {
      throw new ManifestNotFound("File not found: " + filePath);
    } catch (XPathExpressionException | IOException | SAXException e) {
      throw new PageManifestLoadingException("Cant read file: " + filePath, e);
    }
  }

  // read
  class PageManifestReader {
    private FileScope fileScope;

    public PageManifestDef read(FileScope fileScope, Document document, PageManifestDef pmd)
        throws XPathExpressionException {
      this.fileScope = fileScope;
      XmlUtils.iterateSubElements(
          document, resourcesExpr, e -> pmd.addResourceSet(readResourcesDefinition(e)));
      XmlUtils.iterateSubElements(
          document, fragmentsExpr, e -> pmd.addFragments(readFragmentDefinition(e)));
      XmlUtils.iterateSubElements(document, viewsExpr, e -> pmd.addView(readViewDefinition(e)));

      List<String> imports =
          XmlUtils.getSingleAttrNodes(
              document, importsExpr, "file", "import tag must contain file attribute");

      imports
          .stream()
          .forEach(
              i ->
                  XMLPageManifestLoader.this.readPageDefinitionFile(
                      fileScope.getRelativePath(i), pmd));

      return pmd;
    }

    private ViewDefinition readViewDefinition(Element element) {
      String name = element.getAttribute("name");
      Assert.ifEmpty(name, "can`t read view without name");

      String extend = element.getAttribute("extend");
      String file = element.getAttribute("file");

      if (isNoneEmpty(file)) file = fileScope.getRelativePath(file);

      ViewDefinition viewDef = new ViewDefinition(name, file);
      viewDef.setExtend(extend);

      fragmentRefExpressions.forEach(
          x -> {
            XmlUtils.iterateSubElements(
                element, x, e -> viewDef.addFragment(readFragmentReference(e)));
          });

      resourcesRefExpressions.forEach(
          x -> {
            XmlUtils.iterateSubElements(
                element, x, e -> viewDef.addResources(readResourcesReference(e)));
          });

      return viewDef;
    }

    private FragmentDef readFragmentDefinition(Element element) {
      String name = element.getAttribute("name");
      Assert.ifEmpty(name, "can`t read resources bundle without name");

      String file = element.getAttribute("file");
      if (isNoneEmpty(file)) file = fileScope.getRelativePath(file);

      Assert.ifEmpty(file, "fragment definition must contain file attribute");

      FragmentDef fragmentDef = new FragmentDef(name, file);

      readFragmentInnerContent(element, fragmentDef);

      return fragmentDef;
    }

    private FragmentReferenceDef readFragmentReference(Element element) {
      String name = element.getAttribute("name");
      Assert.ifEmpty(name, "can`t read fragment without name");

      String file = element.getAttribute("file");
      if (isNoneEmpty(file)) file = fileScope.getRelativePath(file);

      FragmentReferenceDef fragmentDef = new FragmentReferenceDef(name, file);

      String layout = element.getAttribute("layout");
      fragmentDef.setLayout(layout);

      readFragmentInnerContent(element, fragmentDef);

      return fragmentDef;
    }

    private void readFragmentInnerContent(Element element, FragmentDef fragmentDef) {
      fragmentRefExpressions.forEach(
          x -> {
            XmlUtils.iterateSubElements(
                element, x, e -> fragmentDef.addInnerFragment(readFragmentReference(e)));
          });

      resourcesRefExpressions.forEach(
          x -> {
            XmlUtils.iterateSubElements(
                element, x, e -> fragmentDef.addResources(readResourcesReference(e)));
          });
    }

    private ResourcesDef readResourcesReference(Element resourcesElement) {
      String name = resourcesElement.getAttribute("name");
      ResourcesDef resDef = new ResourcesDef(name);
      readResources(resourcesElement, resDef);
      return resDef;
    }

    private ResourcesDef readResourcesDefinition(Element resourcesElement) {
      String name = resourcesElement.getAttribute("name");
      Assert.ifEmpty(name, "can`t read resources bundle without name");

      ResourcesDef resDef = new ResourcesDef(name);

      String depends = resourcesElement.getAttribute("depends");
      if (isNoneEmpty(depends)) {
        resDef.setDepends(
            Arrays.stream(StringUtils.split(depends, ","))
                .map(StringUtils::trim)
                .collect(Collectors.toList()));
      }

      readResources(resourcesElement, resDef);

      return resDef;
    }

    private void readResources(Element resourcesElement, ResourcesDef resDef) {
      XmlUtils.iterateSubElements(
          resourcesElement,
          element -> {
            XmlManifestResourceElement resourceElement = new XmlManifestResourceElement(element);

            Optional<ResourceReader> optional =
                resourcesReaders
                    .stream()
                    .filter(reader -> reader.accept(resourceElement))
                    .findFirst();

            try {
              if (optional.isPresent())
                resDef.addResource(optional.get().read(resourceElement, fileScope));
            } catch (Exception e) {
              throw new JumbleException("Fail to load resource: " + element.getTagName(), e);
            }
          });
    }
  }
}